Skip to content

Commit

Permalink
feat: Emailpassword accountlinking (#501)
Browse files Browse the repository at this point in the history
* recipe interface changes for account linking

* account linking implementation

* user context update

* code udpate

* types update

* Update lib/ts/recipe/accountlinking/types.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/recipe/accountlinking/types.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/recipe/accountlinking/types.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* return type update

* index file updated

* merge with account-linking interface

* Update lib/ts/recipe/accountlinking/recipeImplementation.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* code review changes

* Update lib/ts/recipe/accountlinking/recipeImplementation.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/recipe/accountlinking/recipe.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/recipe/accountlinking/recipe.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* code review changes

* code review changes

* code review changes

* code review changes

* review changes

* sign-up post login updated

* sign-up post login updated

* sign-up API implementation update

* import update

* function import update

* changes in signup recipeimplementation

* recipeImplementation types update

* merge with 13.0

* merge with account-linking implementation

* recipe implementation update

* account linking claim

* changes to dashboard recipe types

* removes unnecessary functions exposed from account linking recipe

* adds a precautionary check

* small change

* Update lib/ts/types.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/index.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/supertokens.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/recipe/dashboard/api/usersGet.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/index.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/index.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/index.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/recipe/accountlinking/types.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/recipe/accountlinking/recipe.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/recipe/accountlinking/recipe.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/recipe/accountlinking/recipe.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/recipe/accountlinking/types.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/recipe/accountlinking/recipe.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* code review changes

* mark email as verify if it is already verified in either primary user or the current recipe user

* fixes and refactors

* account linking: fixes bugs and refactors (#498)

* fixes bugs and refactors

* small refactors and fixes

* small refactor

* small type changes

* review changes

* removes redundant check

* account linking: removes ANOTHER from canCreatePrimaryUserId status return type (#500)

* removes ANOTHER from canCreatePrimaryUserId status return type

* small change related to status change in primary user id creation

* change to canLinkAccounts

* Update lib/ts/recipe/accountlinking/recipeImplementation.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/recipe/accountlinking/types.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* Update lib/ts/recipe/accountlinking/recipeImplementation.ts

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>

* code review changes

* review changes

* createdNewUser changes

* adds comments

* fixes

* review changes (#511)

* more changes

* small changes

* small changes

* small changes

* Update lib/ts/recipe/emailpassword/api/implementation.ts

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* more fixes - ts still not compiling

* fixes all ts issues

---------

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>
  • Loading branch information
bhumilsarvaiya and rishabhpoddar authored May 7, 2023
1 parent 35c1e93 commit 8b493a7
Show file tree
Hide file tree
Showing 66 changed files with 2,221 additions and 1,438 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Type of `User` object returned by get users function
- Functions `deleteuser`, `getUsersNewestFirst` and `getUsersOldestFirst` are now based on account linking recipe
- Function `deleteuser` takes a new parameter `removeAllLinkedAccounts` which will be `true` by default
- Generate Password Reset Token API logic updated

### Removed:

- For EmailPassword recipe input, resetPasswordUsingTokenFeature user input removed

## [13.0.2] - 2023-02-10

Expand Down
9 changes: 9 additions & 0 deletions lib/build/recipe/accountlinking/accountLinkingClaim.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// @ts-nocheck
import { PrimitiveClaim } from "../session/claims";
/**
* We include "Class" in the class name, because it makes it easier to import the right thing (the instance) instead of this.
* */
export declare class AccountLinkingClaimClass extends PrimitiveClaim<string> {
constructor();
}
export declare const AccountLinkingClaim: AccountLinkingClaimClass;
19 changes: 19 additions & 0 deletions lib/build/recipe/accountlinking/accountLinkingClaim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AccountLinkingClaim = exports.AccountLinkingClaimClass = void 0;
const claims_1 = require("../session/claims");
/**
* We include "Class" in the class name, because it makes it easier to import the right thing (the instance) instead of this.
* */
class AccountLinkingClaimClass extends claims_1.PrimitiveClaim {
constructor() {
super({
key: "st-linking",
fetchValue(_, __, ___) {
return undefined;
},
});
}
}
exports.AccountLinkingClaimClass = AccountLinkingClaimClass;
exports.AccountLinkingClaim = new AccountLinkingClaimClass();
4 changes: 2 additions & 2 deletions lib/build/recipe/accountlinking/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default class Wrapper {
): Promise<
| {
status: "OK";
user: import("../../types").User;
user: import("../emailpassword").User;
wasAlreadyAPrimaryUser: boolean;
}
| {
Expand Down Expand Up @@ -104,7 +104,7 @@ export default class Wrapper {
static fetchFromAccountToLinkTable(
recipeUserId: string,
userContext?: any
): Promise<import("../../types").User | undefined>;
): Promise<import("../emailpassword").User | undefined>;
static storeIntoAccountToLinkTable(
recipeUserId: string,
primaryUserId: string,
Expand Down
26 changes: 23 additions & 3 deletions lib/build/recipe/accountlinking/recipe.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,43 @@ export default class Recipe extends RecipeModule {
newUser: AccountInfoWithRecipeId;
userContext: any;
}) => Promise<boolean>;
linkAccountsWithUserFromSession: ({
linkAccountsWithUserFromSession: <T>({
session,
newUser,
createRecipeUserFunc,
verifyCredentialsFunc,
userContext,
}: {
session: SessionContainer;
newUser: AccountInfoWithRecipeId;
createRecipeUserFunc: (newUser: AccountInfoWithRecipeId) => Promise<void>;
createRecipeUserFunc: () => Promise<void>;
verifyCredentialsFunc: () => Promise<
| {
status: "OK";
}
| {
status: "CUSTOM_RESPONSE";
resp: T;
}
>;
userContext: any;
}) => Promise<
| {
status: "OK" | "NEW_ACCOUNT_NEEDS_TO_BE_VERIFIED_ERROR";
status: "OK";
wereAccountsAlreadyLinked: boolean;
}
| {
status: "ACCOUNT_LINKING_NOT_ALLOWED_ERROR";
description: string;
}
| {
status: "NEW_ACCOUNT_NEEDS_TO_BE_VERIFIED_ERROR";
primaryUserId: string;
recipeUserId: string;
}
| {
status: "CUSTOM_RESPONSE";
resp: T;
}
>;
}
63 changes: 49 additions & 14 deletions lib/build/recipe/accountlinking/recipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,13 @@ class Recipe extends recipeModule_1.default {
}
return false;
});
this.linkAccountsWithUserFromSession = ({ session, newUser, createRecipeUserFunc, userContext }) =>
this.linkAccountsWithUserFromSession = ({
session,
newUser,
createRecipeUserFunc,
verifyCredentialsFunc,
userContext,
}) =>
__awaiter(this, void 0, void 0, function* () {
// In order to link the newUser to the session user,
// we need to first make sure that the session user
Expand Down Expand Up @@ -384,6 +390,7 @@ class Recipe extends recipeModule_1.default {
session,
newUser,
createRecipeUserFunc,
verifyCredentialsFunc,
userContext,
});
} else if (
Expand Down Expand Up @@ -426,30 +433,24 @@ class Recipe extends recipeModule_1.default {
accountInfo: newUser,
userContext,
});
let newUserIsVerified = false;
const userObjThatHasSameAccountInfoAndRecipeIdAsNewUser = usersArrayThatHaveSameAccountInfoAsNewUser.find(
(u) =>
u.loginMethods.find((lU) => {
let found = false;
if (lU.recipeId !== newUser.recipeId) {
return false;
}
if (newUser.recipeId === "thirdparty") {
if (lU.thirdParty === undefined) {
return false;
}
found =
return (
lU.thirdParty.id === newUser.thirdParty.id &&
lU.thirdParty.userId === newUser.thirdParty.userId;
lU.thirdParty.userId === newUser.thirdParty.userId
);
} else {
found = lU.email === newUser.email || newUser.phoneNumber === newUser.phoneNumber;
}
if (!found) {
return false;
return lU.email === newUser.email || newUser.phoneNumber === newUser.phoneNumber;
}
newUserIsVerified = lU.verified;
return true;
})
}) !== undefined
);
if (userObjThatHasSameAccountInfoAndRecipeIdAsNewUser === undefined) {
/*
Expand All @@ -468,25 +469,58 @@ class Recipe extends recipeModule_1.default {
};
}
// we create the new recipe user
yield createRecipeUserFunc(newUser);
yield createRecipeUserFunc();
// now when we recurse, the new recipe user will be found and we can try linking again.
return yield this.linkAccountsWithUserFromSession({
session,
newUser,
createRecipeUserFunc,
verifyCredentialsFunc,
userContext,
});
} else {
// since the user already exists, we should first verify the credentials
// before continuing to link the accounts.
let verifyResult = yield verifyCredentialsFunc();
if (verifyResult.status === "CUSTOM_RESPONSE") {
return verifyResult;
}
// this means that the verification was fine and we can continue..
}
// we check if the userObjThatHasSameAccountInfoAndRecipeIdAsNewUser is
// a primary user or not, and if it is, then it means that our newUser
// is already linked so we can return early.
if (userObjThatHasSameAccountInfoAndRecipeIdAsNewUser.isPrimaryUser) {
if (userObjThatHasSameAccountInfoAndRecipeIdAsNewUser.id === existingUser.id) {
// this means that the accounts we want to link are already linked.
return {
status: "OK",
wereAccountsAlreadyLinked: true,
};
} else {
return {
status: "ACCOUNT_LINKING_NOT_ALLOWED_ERROR",
description: "New user is already linked to another account",
};
}
}
// now we check about the email verification of the new user. If it's verified, we proceed
// to try and link the accounts, and if not, we send email verification error ONLY if the email
// or phone number of the new account is different compared to the existing account.
if (usersArrayThatHaveSameAccountInfoAsNewUser.find((u) => u.id === existingUser.id) === undefined) {
// this means that the existing user does not share anything in common with the new user
// in terms of account info. So we check for email verification status..
if (!newUserIsVerified && shouldDoAccountLinking.shouldRequireVerification) {
if (
!userObjThatHasSameAccountInfoAndRecipeIdAsNewUser.loginMethods[0].verified &&
shouldDoAccountLinking.shouldRequireVerification
) {
// we stop the flow and ask the user to verify this email first.
// the recipe ID is the userObjThatHasSameAccountInfoAndRecipeIdAsNewUser.id
// cause above we checked that userObjThatHasSameAccountInfoAndRecipeIdAsNewUser.isPrimaryUser is false.
return {
status: "NEW_ACCOUNT_NEEDS_TO_BE_VERIFIED_ERROR",
primaryUserId: existingUser.id,
recipeUserId: userObjThatHasSameAccountInfoAndRecipeIdAsNewUser.id,
};
}
}
Expand All @@ -498,6 +532,7 @@ class Recipe extends recipeModule_1.default {
if (linkAccountResponse.status === "OK") {
return {
status: "OK",
wereAccountsAlreadyLinked: linkAccountResponse.accountsAlreadyLinked,
};
} else if (
linkAccountResponse.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"
Expand Down
64 changes: 11 additions & 53 deletions lib/build/recipe/accountlinking/recipeImplementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,24 +96,22 @@ function getRecipeImplementation(querier, config) {
},
canCreatePrimaryUserId: function ({ recipeUserId }) {
return __awaiter(this, void 0, void 0, function* () {
let result = yield querier.sendGetRequest(
return yield querier.sendGetRequest(
new normalisedURLPath_1.default("/recipe/accountlinking/user/primary/check"),
{
recipeUserId,
}
);
return result;
});
},
createPrimaryUser: function ({ recipeUserId }) {
return __awaiter(this, void 0, void 0, function* () {
let result = yield querier.sendPostRequest(
return yield querier.sendPostRequest(
new normalisedURLPath_1.default("/recipe/accountlinking/user/primary"),
{
recipeUserId,
}
);
return result;
});
},
canLinkAccounts: function ({ recipeUserId, primaryUserId }) {
Expand Down Expand Up @@ -157,59 +155,20 @@ function getRecipeImplementation(querier, config) {
},
unlinkAccounts: function ({ recipeUserId, userContext }) {
return __awaiter(this, void 0, void 0, function* () {
let recipeUserIdToPrimaryUserIdMapping = yield this.getPrimaryUserIdsForRecipeUserIds({
recipeUserIds: [recipeUserId],
userContext,
});
let primaryUserId = recipeUserIdToPrimaryUserIdMapping[recipeUserId];
if (primaryUserId === undefined) {
return {
status: "RECIPE_USER_NOT_FOUND_ERROR",
description: "No user exists with the provided recipeUserId",
};
}
if (primaryUserId === null) {
return {
status: "PRIMARY_USER_NOT_FOUND_ERROR",
description:
"The input recipeUserId is not linked to any primary user, or is not a primary user itself",
};
}
if (primaryUserId === recipeUserId) {
let user = yield this.getUser({
userId: primaryUserId,
userContext,
});
if (user === undefined) {
// this can happen cause of some race condition..
return this.unlinkAccounts({
recipeUserId,
userContext,
});
}
if (user.loginMethods.length > 1) {
// we delete the user here cause if we didn't
// do that, then it would result in the primary user ID having the same
// user ID as the recipe user ID, but they are not linked. So this is not allowed.
yield this.deleteUser({
userId: recipeUserId,
removeAllLinkedAccounts: false,
userContext,
});
return {
status: "OK",
wasRecipeUserDeleted: true,
};
}
}
let accountsUnlinkingResult = yield querier.sendPostRequest(
new normalisedURLPath_1.default("/recipe/accountlinking/user/unlink"),
{
recipeUserId,
primaryUserId,
}
);
if (accountsUnlinkingResult.status === "OK") {
if (accountsUnlinkingResult.status === "OK" && !accountsUnlinkingResult.wasRecipeUserDeleted) {
// we have the !accountsUnlinkingResult.wasRecipeUserDeleted check
// cause if the user was deleted, it means that it's user ID was the
// same as the primary user ID, AND that the primary user ID has more
// than one login method - so if we revoke the session in this case,
// it will revoke the session for all login methods as well (since recipeUserId == primaryUserID).
// The reason we don't do this in the core is that if the user has overriden
// session recipe, it goes through their logic.
yield session_1.default.revokeAllSessionsForUser(recipeUserId, userContext);
}
return accountsUnlinkingResult;
Expand Down Expand Up @@ -240,11 +199,10 @@ function getRecipeImplementation(querier, config) {
},
deleteUser: function ({ userId, removeAllLinkedAccounts }) {
return __awaiter(this, void 0, void 0, function* () {
let result = yield querier.sendPostRequest(new normalisedURLPath_1.default("/user/remove"), {
return yield querier.sendPostRequest(new normalisedURLPath_1.default("/user/remove"), {
userId,
removeAllLinkedAccounts,
});
return result;
});
},
fetchFromAccountToLinkTable: function ({ recipeUserId }) {
Expand Down
Loading

0 comments on commit 8b493a7

Please sign in to comment.