diff --git a/lib/build/recipe/accountlinking/types.d.ts b/lib/build/recipe/accountlinking/types.d.ts index 70529eddc..d1659e6e0 100644 --- a/lib/build/recipe/accountlinking/types.d.ts +++ b/lib/build/recipe/accountlinking/types.d.ts @@ -63,9 +63,10 @@ export declare type RecipeInterface = { } | { status: - | "PRIMARY_USER_ALREADY_EXISTS_FOR_RECIPE_USER_ID_ERROR" - | "PRIMARY_USER_ALREADY_EXISTS_FOR_ACCOUNT_INFO_ERROR"; + | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" + | "ACCOUNT_INFO_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; primaryUserId: string; + description: string; } >; createPrimaryUser: (input: { @@ -78,9 +79,10 @@ export declare type RecipeInterface = { } | { status: - | "PRIMARY_USER_ALREADY_EXISTS_FOR_RECIPE_USER_ID_ERROR" - | "PRIMARY_USER_ALREADY_EXISTS_FOR_ACCOUNT_INFO_ERROR"; + | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" + | "ACCOUNT_INFO_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; primaryUserId: string; + description: string; } >; canLinkAccounts: (input: { @@ -93,14 +95,17 @@ export declare type RecipeInterface = { } | { status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + description: string; primaryUserId: string; } | { status: "ACCOUNTS_ALREADY_LINKED_ERROR"; + description: string; } | { status: "ACCOUNT_INFO_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; primaryUserId: string; + description: string; } >; linkAccounts: (input: { @@ -114,13 +119,16 @@ export declare type RecipeInterface = { | { status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; primaryUserId: string; + description: string; } | { status: "ACCOUNTS_ALREADY_LINKED_ERROR"; + description: string; } | { status: "ACCOUNT_INFO_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; primaryUserId: string; + description: string; } >; unlinkAccounts: (input: { diff --git a/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.d.ts b/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.d.ts index d9381e6b6..83b8371fe 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.d.ts +++ b/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.d.ts @@ -4,6 +4,9 @@ declare type Response = | { status: "OK"; } + | { + status: "PROVIDE_RECIPE_USER_ID_AS_USER_ID_ERROR"; + } | { status: "INVALID_PASSWORD_ERROR"; error: string; diff --git a/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.js b/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.js index 632c83edc..f64e1d86c 100644 --- a/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.js +++ b/lib/build/recipe/dashboard/api/userdetails/userPasswordPut.js @@ -41,6 +41,7 @@ exports.userPasswordPut = (_, options) => __awaiter(void 0, void 0, void 0, function* () { const requestBody = yield options.req.getJSONBody(); const userId = requestBody.userId; + const email = requestBody.email; const newPassword = requestBody.newPassword; if (userId === undefined || typeof userId !== "string") { throw new error_1.default({ @@ -80,7 +81,7 @@ exports.userPasswordPut = (_, options) => error: passwordValidationError, }; } - const passwordResetToken = yield emailpassword_1.default.createResetPasswordToken(userId); + const passwordResetToken = yield emailpassword_1.default.createResetPasswordToken(userId, email); if (passwordResetToken.status === "UNKNOWN_USER_ID_ERROR") { // Techincally it can but its an edge case so we assume that it wont throw new Error("Should never come here"); @@ -106,7 +107,7 @@ exports.userPasswordPut = (_, options) => error: passwordValidationError, }; } - const passwordResetToken = yield thirdpartyemailpassword_1.default.createResetPasswordToken(userId); + const passwordResetToken = yield thirdpartyemailpassword_1.default.createResetPasswordToken(userId, email); if (passwordResetToken.status === "UNKNOWN_USER_ID_ERROR") { // Techincally it can but its an edge case so we assume that it wont throw new Error("Should never come here"); diff --git a/lib/build/recipe/emailpassword/api/implementation.js b/lib/build/recipe/emailpassword/api/implementation.js index 7af1e93c9..dd82895e8 100644 --- a/lib/build/recipe/emailpassword/api/implementation.js +++ b/lib/build/recipe/emailpassword/api/implementation.js @@ -35,6 +35,15 @@ const logger_1 = require("../../../logger"); const session_1 = require("../../session"); function getAPIImplementation() { return { + linkAccountToExistingAccountPOST: function (_input) { + return __awaiter(this, void 0, void 0, function* () { + return { + status: "ACCOUNT_NOT_VERIFIED_ERROR", + isNotVerifiedAccountFromInputSession: false, + description: "", + }; + }); + }, emailExistsGET: function ({ email, options, userContext }) { return __awaiter(this, void 0, void 0, function* () { let user = yield options.recipeImplementation.getUserByEmail({ email, userContext }); @@ -54,7 +63,8 @@ function getAPIImplementation() { }; } let response = yield options.recipeImplementation.createResetPasswordToken({ - userId: user.id, + userId: user.recipeUserId, + email: user.email, userContext, }); if (response.status === "UNKNOWN_USER_ID_ERROR") { @@ -138,6 +148,8 @@ function getAPIImplementation() { status: "OK", session, user, + createdNewUser: true, + createdNewRecipeUser: true, }; }); }, diff --git a/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.js b/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.js index aff225848..69a1f3f81 100644 --- a/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.js +++ b/lib/build/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.js @@ -37,7 +37,7 @@ class BackwardCompatibilityService { this.sendEmail = (input) => __awaiter(this, void 0, void 0, function* () { let user = yield this.recipeInterfaceImpl.getUserById({ - userId: input.user.id, + userId: input.user.recipeUserId, userContext: input.userContext, }); if (user === undefined) { diff --git a/lib/build/recipe/emailpassword/index.d.ts b/lib/build/recipe/emailpassword/index.d.ts index a23064ac6..b56deadee 100644 --- a/lib/build/recipe/emailpassword/index.d.ts +++ b/lib/build/recipe/emailpassword/index.d.ts @@ -33,8 +33,20 @@ export default class Wrapper { >; static getUserById(userId: string, userContext?: any): Promise; static getUserByEmail(email: string, userContext?: any): Promise; + /** + * We do not make email optional here cause we want to + * allow passing in primaryUserId. If we make email optional, + * and if the user provides a primaryUserId, then it may result in two problems: + * - there is no recipeUserId = input primaryUserId, in this case, + * this function will throw an error + * - There is a recipe userId = input primaryUserId, but that recipe has no email, + * or has wrong email compared to what the user wanted to generate a reset token for. + * + * And we want to allow primaryUserId being passed in. + */ static createResetPasswordToken( userId: string, + email: string, userContext?: any ): Promise< | { @@ -52,7 +64,8 @@ export default class Wrapper { ): Promise< | { status: "OK"; - userId?: string | undefined; + email: string; + userId: string; } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; diff --git a/lib/build/recipe/emailpassword/index.js b/lib/build/recipe/emailpassword/index.js index c40451791..c430e0567 100644 --- a/lib/build/recipe/emailpassword/index.js +++ b/lib/build/recipe/emailpassword/index.js @@ -74,9 +74,21 @@ class Wrapper { userContext: userContext === undefined ? {} : userContext, }); } - static createResetPasswordToken(userId, userContext) { + /** + * We do not make email optional here cause we want to + * allow passing in primaryUserId. If we make email optional, + * and if the user provides a primaryUserId, then it may result in two problems: + * - there is no recipeUserId = input primaryUserId, in this case, + * this function will throw an error + * - There is a recipe userId = input primaryUserId, but that recipe has no email, + * or has wrong email compared to what the user wanted to generate a reset token for. + * + * And we want to allow primaryUserId being passed in. + */ + static createResetPasswordToken(userId, email, userContext) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createResetPasswordToken({ userId, + email, userContext: userContext === undefined ? {} : userContext, }); } diff --git a/lib/build/recipe/emailpassword/types.d.ts b/lib/build/recipe/emailpassword/types.d.ts index cb0c9b24a..f4c3d4ef5 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -104,8 +104,20 @@ export declare type RecipeInterface = { >; getUserById(input: { userId: string; userContext: any }): Promise; getUserByEmail(input: { email: string; userContext: any }): Promise; + /** + * We do not make email optional here cause we want to + * allow passing in primaryUserId. If we make email optional, + * and if the user provides a primaryUserId, then it may result in two problems: + * - there is no recipeUserId = input primaryUserId, in this case, + * this function will throw an error + * - There is a recipe userId = input primaryUserId, but that recipe has no email, + * or has wrong email compared to what the user wanted to generate a reset token for. + * + * And we want to allow primaryUserId being passed in. + */ createResetPasswordToken(input: { userId: string; + email: string; userContext: any; }): Promise< | { @@ -123,11 +135,8 @@ export declare type RecipeInterface = { }): Promise< | { status: "OK"; - /** - * The id of the user whose password was reset. - * Defined for Core versions 3.9 or later - */ - userId?: string; + email: string; + userId: string; } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; @@ -179,6 +188,10 @@ export declare type APIInterface = { | { status: "OK"; } + | { + status: "PASSWORD_RESET_NOT_ALLOWED"; + reason: string; + } | GeneralErrorResponse >); passwordResetPOST: @@ -194,7 +207,8 @@ export declare type APIInterface = { }) => Promise< | { status: "OK"; - userId?: string; + email: string; + userId: string; } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; @@ -234,11 +248,55 @@ export declare type APIInterface = { | { status: "OK"; user: User; + createdNewUser: boolean; session: SessionContainerInterface; } | { status: "EMAIL_ALREADY_EXISTS_ERROR"; } + | { + status: "SIGNUP_NOT_ALLOWED"; + reason: string; + } + | GeneralErrorResponse + >); + linkAccountToExistingAccountPOST: + | undefined + | ((input: { + formFields: { + id: string; + value: string; + }[]; + session: SessionContainerInterface; + options: APIOptions; + userContext: any; + }) => Promise< + | { + status: "OK"; + user: User; + createdNewRecipeUser: boolean; + session: SessionContainerInterface; + wereAccountsAlreadyLinked: boolean; + } + | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + | { + status: "ACCOUNT_INFO_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + | { + status: "ACCOUNT_LINKING_NOT_ALLOWED_ERROR"; + description: string; + } + | { + status: "ACCOUNT_NOT_VERIFIED_ERROR"; + isNotVerifiedAccountFromInputSession: boolean; + description: string; + } | GeneralErrorResponse >); }; @@ -246,6 +304,7 @@ export declare type TypeEmailPasswordPasswordResetEmailDeliveryInput = { type: "PASSWORD_RESET"; user: { id: string; + recipeUserId: string; email: string; }; passwordResetLink: string; diff --git a/lib/build/recipe/thirdpartyemailpassword/api/emailPasswordAPIImplementation.js b/lib/build/recipe/thirdpartyemailpassword/api/emailPasswordAPIImplementation.js index c8d9d033a..ecdb8e01a 100644 --- a/lib/build/recipe/thirdpartyemailpassword/api/emailPasswordAPIImplementation.js +++ b/lib/build/recipe/thirdpartyemailpassword/api/emailPasswordAPIImplementation.js @@ -1,7 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); function getIterfaceImpl(apiImplmentation) { - var _a, _b, _c, _d, _e; + var _a, _b, _c, _d, _e, _f; return { emailExistsGET: (_a = apiImplmentation.emailPasswordEmailExistsGET) === null || _a === void 0 @@ -21,6 +21,10 @@ function getIterfaceImpl(apiImplmentation) { (_e = apiImplmentation.emailPasswordSignUpPOST) === null || _e === void 0 ? void 0 : _e.bind(apiImplmentation), + linkAccountToExistingAccountPOST: + (_f = apiImplmentation.linkEmailPasswordAccountToExistingAccountPOST) === null || _f === void 0 + ? void 0 + : _f.bind(apiImplmentation), }; } exports.default = getIterfaceImpl; diff --git a/lib/build/recipe/thirdpartyemailpassword/api/implementation.js b/lib/build/recipe/thirdpartyemailpassword/api/implementation.js index f9420149f..e84d6e291 100644 --- a/lib/build/recipe/thirdpartyemailpassword/api/implementation.js +++ b/lib/build/recipe/thirdpartyemailpassword/api/implementation.js @@ -5,7 +5,7 @@ const implementation_2 = require("../../thirdparty/api/implementation"); const emailPasswordAPIImplementation_1 = require("./emailPasswordAPIImplementation"); const thirdPartyAPIImplementation_1 = require("./thirdPartyAPIImplementation"); function getAPIImplementation() { - var _a, _b, _c, _d, _e, _f, _g, _h; + var _a, _b, _c, _d, _e, _f, _g, _h, _j; let emailPasswordImplementation = implementation_1.default(); let thirdPartyImplementation = implementation_2.default(); return { @@ -29,18 +29,22 @@ function getAPIImplementation() { (_e = emailPasswordImplementation.generatePasswordResetTokenPOST) === null || _e === void 0 ? void 0 : _e.bind(emailPasswordAPIImplementation_1.default(this)), - passwordResetPOST: - (_f = emailPasswordImplementation.passwordResetPOST) === null || _f === void 0 + linkEmailPasswordAccountToExistingAccountPOST: + (_f = emailPasswordImplementation.linkAccountToExistingAccountPOST) === null || _f === void 0 ? void 0 : _f.bind(emailPasswordAPIImplementation_1.default(this)), + passwordResetPOST: + (_g = emailPasswordImplementation.passwordResetPOST) === null || _g === void 0 + ? void 0 + : _g.bind(emailPasswordAPIImplementation_1.default(this)), thirdPartySignInUpPOST: - (_g = thirdPartyImplementation.signInUpPOST) === null || _g === void 0 + (_h = thirdPartyImplementation.signInUpPOST) === null || _h === void 0 ? void 0 - : _g.bind(thirdPartyAPIImplementation_1.default(this)), + : _h.bind(thirdPartyAPIImplementation_1.default(this)), appleRedirectHandlerPOST: - (_h = thirdPartyImplementation.appleRedirectHandlerPOST) === null || _h === void 0 + (_j = thirdPartyImplementation.appleRedirectHandlerPOST) === null || _j === void 0 ? void 0 - : _h.bind(thirdPartyAPIImplementation_1.default(this)), + : _j.bind(thirdPartyAPIImplementation_1.default(this)), }; } exports.default = getAPIImplementation; diff --git a/lib/build/recipe/thirdpartyemailpassword/index.d.ts b/lib/build/recipe/thirdpartyemailpassword/index.d.ts index f3fe4e1e1..020257c41 100644 --- a/lib/build/recipe/thirdpartyemailpassword/index.d.ts +++ b/lib/build/recipe/thirdpartyemailpassword/index.d.ts @@ -52,6 +52,7 @@ export default class Wrapper { static getUsersByEmail(email: string, userContext?: any): Promise; static createResetPasswordToken( userId: string, + email: string, userContext?: any ): Promise< | { @@ -69,7 +70,8 @@ export default class Wrapper { ): Promise< | { status: "OK"; - userId?: string | undefined; + email: string; + userId: string; } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; diff --git a/lib/build/recipe/thirdpartyemailpassword/index.js b/lib/build/recipe/thirdpartyemailpassword/index.js index cba17f54e..90b6c5a35 100644 --- a/lib/build/recipe/thirdpartyemailpassword/index.js +++ b/lib/build/recipe/thirdpartyemailpassword/index.js @@ -84,10 +84,12 @@ class Wrapper { static getUsersByEmail(email, userContext = {}) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.getUsersByEmail({ email, userContext }); } - static createResetPasswordToken(userId, userContext = {}) { - return recipe_1.default - .getInstanceOrThrowError() - .recipeInterfaceImpl.createResetPasswordToken({ userId, userContext }); + static createResetPasswordToken(userId, email, userContext = {}) { + return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.createResetPasswordToken({ + userId, + email, + userContext, + }); } static resetPasswordUsingToken(token, newPassword, userContext = {}) { return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.resetPasswordUsingToken({ diff --git a/lib/build/recipe/thirdpartyemailpassword/types.d.ts b/lib/build/recipe/thirdpartyemailpassword/types.d.ts index 470716ecd..c2e3a3c57 100644 --- a/lib/build/recipe/thirdpartyemailpassword/types.d.ts +++ b/lib/build/recipe/thirdpartyemailpassword/types.d.ts @@ -118,6 +118,7 @@ export declare type RecipeInterface = { >; createResetPasswordToken(input: { userId: string; + email: string; userContext: any; }): Promise< | { @@ -135,11 +136,8 @@ export declare type RecipeInterface = { }): Promise< | { status: "OK"; - /** - * The id of the user whose password was reset. - * Defined for Core versions 3.9 or later - */ - userId?: string; + email: string; + userId: string; } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; @@ -196,6 +194,10 @@ export declare type APIInterface = { | { status: "OK"; } + | { + status: "PASSWORD_RESET_NOT_ALLOWED"; + reason: string; + } | GeneralErrorResponse >); passwordResetPOST: @@ -211,7 +213,8 @@ export declare type APIInterface = { }) => Promise< | { status: "OK"; - userId?: string; + email: string; + userId: string; } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; @@ -241,6 +244,45 @@ export declare type APIInterface = { status: "NO_EMAIL_GIVEN_BY_PROVIDER"; } >); + linkEmailPasswordAccountToExistingAccountPOST: + | undefined + | ((input: { + formFields: { + id: string; + value: string; + }[]; + session: SessionContainerInterface; + options: EmailPasswordAPIOptions; + userContext: any; + }) => Promise< + | { + status: "OK"; + user: User; + createdNewRecipeUser: boolean; + session: SessionContainerInterface; + wereAccountsAlreadyLinked: boolean; + } + | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + | { + status: "ACCOUNT_INFO_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + | { + status: "ACCOUNT_LINKING_NOT_ALLOWED_ERROR"; + description: string; + } + | { + status: "ACCOUNT_NOT_VERIFIED_ERROR"; + isNotVerifiedAccountFromInputSession: boolean; + description: string; + } + | GeneralErrorResponse + >); emailPasswordSignInPOST: | undefined | ((input: { @@ -274,11 +316,16 @@ export declare type APIInterface = { | { status: "OK"; user: User; + createdNewUser: boolean; session: SessionContainerInterface; } | { status: "EMAIL_ALREADY_EXISTS_ERROR"; } + | { + status: "SIGNUP_NOT_ALLOWED"; + reason: string; + } | GeneralErrorResponse >); appleRedirectHandlerPOST: diff --git a/lib/ts/recipe/accountlinking/types.ts b/lib/ts/recipe/accountlinking/types.ts index 80b34d1bd..0a6fe6885 100644 --- a/lib/ts/recipe/accountlinking/types.ts +++ b/lib/ts/recipe/accountlinking/types.ts @@ -81,9 +81,10 @@ export type RecipeInterface = { } | { status: - | "PRIMARY_USER_ALREADY_EXISTS_FOR_RECIPE_USER_ID_ERROR" - | "PRIMARY_USER_ALREADY_EXISTS_FOR_ACCOUNT_INFO_ERROR"; + | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" + | "ACCOUNT_INFO_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; primaryUserId: string; + description: string; } >; createPrimaryUser: (input: { @@ -96,9 +97,10 @@ export type RecipeInterface = { } | { status: - | "PRIMARY_USER_ALREADY_EXISTS_FOR_RECIPE_USER_ID_ERROR" - | "PRIMARY_USER_ALREADY_EXISTS_FOR_ACCOUNT_INFO_ERROR"; + | "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR" + | "ACCOUNT_INFO_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; primaryUserId: string; + description: string; } >; canLinkAccounts: (input: { @@ -111,14 +113,17 @@ export type RecipeInterface = { } | { status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + description: string; primaryUserId: string; } | { status: "ACCOUNTS_ALREADY_LINKED_ERROR"; + description: string; } | { status: "ACCOUNT_INFO_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; primaryUserId: string; + description: string; } >; linkAccounts: (input: { @@ -132,13 +137,16 @@ export type RecipeInterface = { | { status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; primaryUserId: string; + description: string; } | { status: "ACCOUNTS_ALREADY_LINKED_ERROR"; + description: string; } | { status: "ACCOUNT_INFO_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; primaryUserId: string; + description: string; } >; unlinkAccounts: (input: { diff --git a/lib/ts/recipe/dashboard/api/userdetails/userPasswordPut.ts b/lib/ts/recipe/dashboard/api/userdetails/userPasswordPut.ts index 0a58a4306..6c263a26e 100644 --- a/lib/ts/recipe/dashboard/api/userdetails/userPasswordPut.ts +++ b/lib/ts/recipe/dashboard/api/userdetails/userPasswordPut.ts @@ -10,6 +10,9 @@ type Response = | { status: "OK"; } + | { + status: "PROVIDE_RECIPE_USER_ID_AS_USER_ID_ERROR"; + } | { status: "INVALID_PASSWORD_ERROR"; error: string; @@ -18,6 +21,7 @@ type Response = export const userPasswordPut = async (_: APIInterface, options: APIOptions): Promise => { const requestBody = await options.req.getJSONBody(); const userId = requestBody.userId; + const email = requestBody.email; const newPassword = requestBody.newPassword; if (userId === undefined || typeof userId !== "string") { @@ -67,7 +71,7 @@ export const userPasswordPut = async (_: APIInterface, options: APIOptions): Pro }; } - const passwordResetToken = await EmailPassword.createResetPasswordToken(userId); + const passwordResetToken = await EmailPassword.createResetPasswordToken(userId, email); if (passwordResetToken.status === "UNKNOWN_USER_ID_ERROR") { // Techincally it can but its an edge case so we assume that it wont @@ -101,7 +105,7 @@ export const userPasswordPut = async (_: APIInterface, options: APIOptions): Pro }; } - const passwordResetToken = await ThirdPartyEmailPassword.createResetPasswordToken(userId); + const passwordResetToken = await ThirdPartyEmailPassword.createResetPasswordToken(userId, email); if (passwordResetToken.status === "UNKNOWN_USER_ID_ERROR") { // Techincally it can but its an edge case so we assume that it wont diff --git a/lib/ts/recipe/emailpassword/api/implementation.ts b/lib/ts/recipe/emailpassword/api/implementation.ts index dee3acdb8..4d5f36020 100644 --- a/lib/ts/recipe/emailpassword/api/implementation.ts +++ b/lib/ts/recipe/emailpassword/api/implementation.ts @@ -6,6 +6,49 @@ import { GeneralErrorResponse } from "../../../types"; export default function getAPIImplementation(): APIInterface { return { + linkAccountToExistingAccountPOST: async function (_input: { + formFields: { + id: string; + value: string; + }[]; + session: SessionContainerInterface; + options: APIOptions; + userContext: any; + }): Promise< + | { + status: "OK"; + user: User; + createdNewRecipeUser: boolean; + session: SessionContainerInterface; + wereAccountsAlreadyLinked: boolean; + } + | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + | { + status: "ACCOUNT_INFO_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + | { + status: "ACCOUNT_LINKING_NOT_ALLOWED_ERROR"; + description: string; + } + | { + status: "ACCOUNT_NOT_VERIFIED_ERROR"; + isNotVerifiedAccountFromInputSession: boolean; + description: string; + } + | GeneralErrorResponse + > { + return { + status: "ACCOUNT_NOT_VERIFIED_ERROR", + isNotVerifiedAccountFromInputSession: false, + description: "", + }; + }, emailExistsGET: async function ({ email, options, @@ -43,6 +86,7 @@ export default function getAPIImplementation(): APIInterface { | { status: "OK"; } + | { status: "PASSWORD_RESET_NOT_ALLOWED"; reason: string } | GeneralErrorResponse > { let email = formFields.filter((f) => f.id === "email")[0].value; @@ -55,7 +99,8 @@ export default function getAPIImplementation(): APIInterface { } let response = await options.recipeImplementation.createResetPasswordToken({ - userId: user.id, + userId: user.recipeUserId, + email: user.email, userContext, }); if (response.status === "UNKNOWN_USER_ID_ERROR") { @@ -101,11 +146,8 @@ export default function getAPIImplementation(): APIInterface { }): Promise< | { status: "OK"; - /** - * The id of the user whose password was reset. - * Defined for Core versions 3.9 or later - */ - userId?: string; + userId: string; + email: string; } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } | GeneralErrorResponse @@ -174,10 +216,16 @@ export default function getAPIImplementation(): APIInterface { status: "OK"; session: SessionContainerInterface; user: User; + createdNewUser: boolean; + createdNewRecipeUser: boolean; } | { status: "EMAIL_ALREADY_EXISTS_ERROR"; } + | { + status: "SIGNUP_NOT_ALLOWED"; + reason: string; + } | GeneralErrorResponse > { let email = formFields.filter((f) => f.id === "email")[0].value; @@ -194,6 +242,8 @@ export default function getAPIImplementation(): APIInterface { status: "OK", session, user, + createdNewUser: true, // TODO + createdNewRecipeUser: true, // TODO }; }, }; diff --git a/lib/ts/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.ts b/lib/ts/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.ts index 9bfd297ae..7928535eb 100644 --- a/lib/ts/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.ts +++ b/lib/ts/recipe/emailpassword/emaildelivery/services/backwardCompatibility/index.ts @@ -56,7 +56,7 @@ export default class BackwardCompatibilityService sendEmail = async (input: TypeEmailPasswordEmailDeliveryInput & { userContext: any }) => { let user = await this.recipeInterfaceImpl.getUserById({ - userId: input.user.id, + userId: input.user.recipeUserId, userContext: input.userContext, }); if (user === undefined) { diff --git a/lib/ts/recipe/emailpassword/index.ts b/lib/ts/recipe/emailpassword/index.ts index bd631ee66..42276add4 100644 --- a/lib/ts/recipe/emailpassword/index.ts +++ b/lib/ts/recipe/emailpassword/index.ts @@ -52,9 +52,21 @@ export default class Wrapper { }); } - static createResetPasswordToken(userId: string, userContext?: any) { + /** + * We do not make email optional here cause we want to + * allow passing in primaryUserId. If we make email optional, + * and if the user provides a primaryUserId, then it may result in two problems: + * - there is no recipeUserId = input primaryUserId, in this case, + * this function will throw an error + * - There is a recipe userId = input primaryUserId, but that recipe has no email, + * or has wrong email compared to what the user wanted to generate a reset token for. + * + * And we want to allow primaryUserId being passed in. + */ + static createResetPasswordToken(userId: string, email: string, userContext?: any) { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.createResetPasswordToken({ userId, + email, userContext: userContext === undefined ? {} : userContext, }); } diff --git a/lib/ts/recipe/emailpassword/recipeImplementation.ts b/lib/ts/recipe/emailpassword/recipeImplementation.ts index 1f74089b7..7ee518b0e 100644 --- a/lib/ts/recipe/emailpassword/recipeImplementation.ts +++ b/lib/ts/recipe/emailpassword/recipeImplementation.ts @@ -99,11 +99,8 @@ export default function getRecipeInterface(querier: Querier): RecipeInterface { }): Promise< | { status: "OK"; - /** - * The id of the user whose password was reset. - * Defined for Core versions 3.9 or later - */ - userId?: string; + userId: string; + email: string; } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } > { diff --git a/lib/ts/recipe/emailpassword/types.ts b/lib/ts/recipe/emailpassword/types.ts index 372c013e2..3a77e8507 100644 --- a/lib/ts/recipe/emailpassword/types.ts +++ b/lib/ts/recipe/emailpassword/types.ts @@ -115,8 +115,20 @@ export type RecipeInterface = { getUserByEmail(input: { email: string; userContext: any }): Promise; + /** + * We do not make email optional here cause we want to + * allow passing in primaryUserId. If we make email optional, + * and if the user provides a primaryUserId, then it may result in two problems: + * - there is no recipeUserId = input primaryUserId, in this case, + * this function will throw an error + * - There is a recipe userId = input primaryUserId, but that recipe has no email, + * or has wrong email compared to what the user wanted to generate a reset token for. + * + * And we want to allow primaryUserId being passed in. + */ createResetPasswordToken(input: { - userId: string; + userId: string; // the id can be either recipeUserId or primaryUserId + email: string; userContext: any; }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>; @@ -127,21 +139,20 @@ export type RecipeInterface = { }): Promise< | { status: "OK"; - /** - * The id of the user whose password was reset. - * Defined for Core versions 3.9 or later - */ - userId?: string; + email: string; + userId: string; } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } >; updateEmailOrPassword(input: { - userId: string; + userId: string; // the id can be either recipeUserId or primaryUserId email?: string; password?: string; userContext: any; - }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" }>; + }): Promise<{ + status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR"; + }>; }; export type APIOptions = { @@ -183,6 +194,10 @@ export type APIInterface = { | { status: "OK"; } + | { + status: "PASSWORD_RESET_NOT_ALLOWED"; + reason: string; + } | GeneralErrorResponse >); @@ -199,7 +214,8 @@ export type APIInterface = { }) => Promise< | { status: "OK"; - userId?: string; + email: string; + userId: string; } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; @@ -241,11 +257,56 @@ export type APIInterface = { | { status: "OK"; user: User; + createdNewUser: boolean; session: SessionContainerInterface; } | { status: "EMAIL_ALREADY_EXISTS_ERROR"; } + | { + status: "SIGNUP_NOT_ALLOWED"; + reason: string; + } + | GeneralErrorResponse + >); + + linkAccountToExistingAccountPOST: + | undefined + | ((input: { + formFields: { + id: string; + value: string; + }[]; + session: SessionContainerInterface; + options: APIOptions; + userContext: any; + }) => Promise< + | { + status: "OK"; + user: User; + createdNewRecipeUser: boolean; + session: SessionContainerInterface; + wereAccountsAlreadyLinked: boolean; + } + | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + | { + status: "ACCOUNT_INFO_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + | { + status: "ACCOUNT_LINKING_NOT_ALLOWED_ERROR"; + description: string; + } + | { + status: "ACCOUNT_NOT_VERIFIED_ERROR"; + isNotVerifiedAccountFromInputSession: boolean; + description: string; + } | GeneralErrorResponse >); }; @@ -254,6 +315,7 @@ export type TypeEmailPasswordPasswordResetEmailDeliveryInput = { type: "PASSWORD_RESET"; user: { id: string; + recipeUserId: string; email: string; }; passwordResetLink: string; diff --git a/lib/ts/recipe/thirdpartyemailpassword/api/emailPasswordAPIImplementation.ts b/lib/ts/recipe/thirdpartyemailpassword/api/emailPasswordAPIImplementation.ts index f6fcc2f5d..cf3100f6c 100644 --- a/lib/ts/recipe/thirdpartyemailpassword/api/emailPasswordAPIImplementation.ts +++ b/lib/ts/recipe/thirdpartyemailpassword/api/emailPasswordAPIImplementation.ts @@ -8,5 +8,8 @@ export default function getIterfaceImpl(apiImplmentation: ThirdPartyEmailPasswor passwordResetPOST: apiImplmentation.passwordResetPOST?.bind(apiImplmentation), signInPOST: apiImplmentation.emailPasswordSignInPOST?.bind(apiImplmentation), signUpPOST: apiImplmentation.emailPasswordSignUpPOST?.bind(apiImplmentation), + linkAccountToExistingAccountPOST: apiImplmentation.linkEmailPasswordAccountToExistingAccountPOST?.bind( + apiImplmentation + ), }; } diff --git a/lib/ts/recipe/thirdpartyemailpassword/api/implementation.ts b/lib/ts/recipe/thirdpartyemailpassword/api/implementation.ts index 534db5c56..76468ef3c 100644 --- a/lib/ts/recipe/thirdpartyemailpassword/api/implementation.ts +++ b/lib/ts/recipe/thirdpartyemailpassword/api/implementation.ts @@ -15,6 +15,9 @@ export default function getAPIImplementation(): APIInterface { generatePasswordResetTokenPOST: emailPasswordImplementation.generatePasswordResetTokenPOST?.bind( DerivedEP(this) ), + linkEmailPasswordAccountToExistingAccountPOST: emailPasswordImplementation.linkAccountToExistingAccountPOST?.bind( + DerivedEP(this) + ), passwordResetPOST: emailPasswordImplementation.passwordResetPOST?.bind(DerivedEP(this)), thirdPartySignInUpPOST: thirdPartyImplementation.signInUpPOST?.bind(DerivedTP(this)), appleRedirectHandlerPOST: thirdPartyImplementation.appleRedirectHandlerPOST?.bind(DerivedTP(this)), diff --git a/lib/ts/recipe/thirdpartyemailpassword/index.ts b/lib/ts/recipe/thirdpartyemailpassword/index.ts index 97d5f4da0..17cc46039 100644 --- a/lib/ts/recipe/thirdpartyemailpassword/index.ts +++ b/lib/ts/recipe/thirdpartyemailpassword/index.ts @@ -66,8 +66,12 @@ export default class Wrapper { return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.getUsersByEmail({ email, userContext }); } - static createResetPasswordToken(userId: string, userContext: any = {}) { - return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.createResetPasswordToken({ userId, userContext }); + static createResetPasswordToken(userId: string, email: string, userContext: any = {}) { + return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.createResetPasswordToken({ + userId, + email, + userContext, + }); } static resetPasswordUsingToken(token: string, newPassword: string, userContext: any = {}) { diff --git a/lib/ts/recipe/thirdpartyemailpassword/recipeImplementation/emailPasswordRecipeImplementation.ts b/lib/ts/recipe/thirdpartyemailpassword/recipeImplementation/emailPasswordRecipeImplementation.ts index c57671338..fa68edf9b 100644 --- a/lib/ts/recipe/thirdpartyemailpassword/recipeImplementation/emailPasswordRecipeImplementation.ts +++ b/lib/ts/recipe/thirdpartyemailpassword/recipeImplementation/emailPasswordRecipeImplementation.ts @@ -40,6 +40,7 @@ export default function getRecipeInterface(recipeInterface: ThirdPartyEmailPassw createResetPasswordToken: async function (input: { userId: string; + email: string; userContext: any; }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { return recipeInterface.createResetPasswordToken(input); diff --git a/lib/ts/recipe/thirdpartyemailpassword/recipeImplementation/index.ts b/lib/ts/recipe/thirdpartyemailpassword/recipeImplementation/index.ts index 41b2ee9a7..d61f91a64 100644 --- a/lib/ts/recipe/thirdpartyemailpassword/recipeImplementation/index.ts +++ b/lib/ts/recipe/thirdpartyemailpassword/recipeImplementation/index.ts @@ -90,6 +90,7 @@ export default function getRecipeInterface( createResetPasswordToken: async function (input: { userId: string; + email: string; userContext: any; }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }> { return originalEmailPasswordImplementation.createResetPasswordToken.bind(DerivedEP(this))(input); @@ -107,7 +108,9 @@ export default function getRecipeInterface( password?: string; userContext: any; } - ): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" }> { + ): Promise<{ + status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR"; + }> { let user = await this.getUserById({ userId: input.userId, userContext: input.userContext }); if (user === undefined) { return { diff --git a/lib/ts/recipe/thirdpartyemailpassword/types.ts b/lib/ts/recipe/thirdpartyemailpassword/types.ts index 428a60d1a..2ab4f1e2c 100644 --- a/lib/ts/recipe/thirdpartyemailpassword/types.ts +++ b/lib/ts/recipe/thirdpartyemailpassword/types.ts @@ -126,6 +126,7 @@ export type RecipeInterface = { createResetPasswordToken(input: { userId: string; + email: string; userContext: any; }): Promise<{ status: "OK"; token: string } | { status: "UNKNOWN_USER_ID_ERROR" }>; @@ -136,11 +137,8 @@ export type RecipeInterface = { }): Promise< | { status: "OK"; - /** - * The id of the user whose password was reset. - * Defined for Core versions 3.9 or later - */ - userId?: string; + email: string; + userId: string; } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } >; @@ -150,7 +148,9 @@ export type RecipeInterface = { email?: string; password?: string; userContext: any; - }): Promise<{ status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR" }>; + }): Promise<{ + status: "OK" | "UNKNOWN_USER_ID_ERROR" | "EMAIL_ALREADY_EXISTS_ERROR"; + }>; }; export type EmailPasswordAPIOptions = EmailPasswordAPIOptionsOriginal; @@ -198,6 +198,10 @@ export type APIInterface = { | { status: "OK"; } + | { + status: "PASSWORD_RESET_NOT_ALLOWED"; + reason: string; + } | GeneralErrorResponse >); @@ -214,7 +218,8 @@ export type APIInterface = { }) => Promise< | { status: "OK"; - userId?: string; + email: string; + userId: string; } | { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; @@ -246,6 +251,45 @@ export type APIInterface = { } >); + linkEmailPasswordAccountToExistingAccountPOST: + | undefined + | ((input: { + formFields: { + id: string; + value: string; + }[]; + session: SessionContainerInterface; + options: EmailPasswordAPIOptions; + userContext: any; + }) => Promise< + | { + status: "OK"; + user: User; + createdNewRecipeUser: boolean; + session: SessionContainerInterface; + wereAccountsAlreadyLinked: boolean; + } + | { + status: "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + | { + status: "ACCOUNT_INFO_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; + primaryUserId: string; + description: string; + } + | { + status: "ACCOUNT_LINKING_NOT_ALLOWED_ERROR"; + description: string; + } + | { + status: "ACCOUNT_NOT_VERIFIED_ERROR"; + isNotVerifiedAccountFromInputSession: boolean; + description: string; + } + | GeneralErrorResponse + >); emailPasswordSignInPOST: | undefined | ((input: { @@ -280,11 +324,16 @@ export type APIInterface = { | { status: "OK"; user: User; + createdNewUser: boolean; session: SessionContainerInterface; } | { status: "EMAIL_ALREADY_EXISTS_ERROR"; } + | { + status: "SIGNUP_NOT_ALLOWED"; + reason: string; + } | GeneralErrorResponse >); diff --git a/test/with-typescript/index.ts b/test/with-typescript/index.ts index 9914e21dc..4e38e7baf 100644 --- a/test/with-typescript/index.ts +++ b/test/with-typescript/index.ts @@ -1281,6 +1281,7 @@ ThirdPartyEmailPassword.sendEmail({ user: { email: "", id: "", + recipeUserId: "", }, }); ThirdPartyEmailPassword.sendEmail({ @@ -1289,6 +1290,7 @@ ThirdPartyEmailPassword.sendEmail({ user: { email: "", id: "", + recipeUserId: "", }, userContext: {}, });