From 1aa66060f3e9c5294dbdbc9400fc4eb7ea9250a6 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Fri, 12 Apr 2024 18:06:34 +0545 Subject: [PATCH 1/3] feat(user): add custom session token validator for role --- packages/user/src/supertokens/UserRoleClaim.ts | 10 ++++++++++ packages/user/src/supertokens/helpers.ts | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 packages/user/src/supertokens/UserRoleClaim.ts diff --git a/packages/user/src/supertokens/UserRoleClaim.ts b/packages/user/src/supertokens/UserRoleClaim.ts new file mode 100644 index 000000000..ee074cc88 --- /dev/null +++ b/packages/user/src/supertokens/UserRoleClaim.ts @@ -0,0 +1,10 @@ +import { PrimitiveArrayClaim } from "supertokens-web-js/recipe/session"; + +const UserRoleClaim = new PrimitiveArrayClaim({ + id: "role", + // eslint-disable-next-line @typescript-eslint/no-empty-function + refresh: async () => {}, + defaultMaxAgeInSeconds: Number.MAX_SAFE_INTEGER, +}); + +export default UserRoleClaim; diff --git a/packages/user/src/supertokens/helpers.ts b/packages/user/src/supertokens/helpers.ts index e3aa9e1ff..853393ef5 100644 --- a/packages/user/src/supertokens/helpers.ts +++ b/packages/user/src/supertokens/helpers.ts @@ -1,8 +1,8 @@ import EmailVerification from "supertokens-web-js/recipe/emailverification"; import Session from "supertokens-web-js/recipe/session"; -import { UserRoleClaim } from "supertokens-web-js/recipe/userroles"; import logout from "./logout"; +import UserRoleClaim from "./UserRoleClaim"; import { removeUserData } from "../helpers"; /** @@ -59,7 +59,7 @@ async function verifySessionRoles(claims: string[]): Promise { return true; } else { // all user roles claim check failed - await removeUserData(); + removeUserData(); await logout(); } } From 239325b7601f9ef093808a3507eba29be6c453af Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Tue, 16 Apr 2024 16:58:15 +0545 Subject: [PATCH 2/3] refactor(user): change role type --- .../user/src/components/UsersTable/UsersTable.tsx | 11 ++++++----- .../__test__/DropdownUserMenu.snapshot.test.tsx | 10 +++++++++- .../src/components/__test__/DropdownUserMenu.test.tsx | 10 +++++++++- packages/user/src/context/UserProvider.tsx | 4 ++-- packages/user/src/supertokens/helpers.ts | 6 ++++-- packages/user/src/types/index.ts | 2 ++ packages/user/src/types/types.ts | 9 ++++++++- 7 files changed, 40 insertions(+), 12 deletions(-) diff --git a/packages/user/src/components/UsersTable/UsersTable.tsx b/packages/user/src/components/UsersTable/UsersTable.tsx index f05c4e14a..e932680cc 100644 --- a/packages/user/src/components/UsersTable/UsersTable.tsx +++ b/packages/user/src/components/UsersTable/UsersTable.tsx @@ -18,6 +18,7 @@ import type { InvitationRoleOption, InvitationExpiryDateField, UserType, + Role, } from "@/types"; type VisibleColumn = @@ -112,16 +113,16 @@ export const UsersTable = ({ id: "roles", header: t("table.defaultColumns.roles"), cell: ({ getValue, row: { original } }) => { - const roles = (original as unknown as { roles: string[] })?.roles; + const { roles } = original; if (Array.isArray(roles)) { return ( <> - {roles?.map((role: string, index: number) => ( + {roles?.map((role: Role) => ( ))} diff --git a/packages/user/src/components/__test__/DropdownUserMenu.snapshot.test.tsx b/packages/user/src/components/__test__/DropdownUserMenu.snapshot.test.tsx index 2cde967ef..3f5d884a5 100644 --- a/packages/user/src/components/__test__/DropdownUserMenu.snapshot.test.tsx +++ b/packages/user/src/components/__test__/DropdownUserMenu.snapshot.test.tsx @@ -19,6 +19,14 @@ function toJson(component: ReactTestRenderer) { return result as ReactTestRendererJSON; } +const roles = [ + { + id: 2, + role: "USER", + default: true, + }, +]; + test("Component matches snapshot", () => { const values = { setUser: vi.fn(), @@ -31,7 +39,7 @@ test("Component matches snapshot", () => { signedUpAt: 0, surname: "name", givenName: "test", - roles: ["USER"], + roles, }, loading: false, }; diff --git a/packages/user/src/components/__test__/DropdownUserMenu.test.tsx b/packages/user/src/components/__test__/DropdownUserMenu.test.tsx index 31e1576c5..9241b8852 100644 --- a/packages/user/src/components/__test__/DropdownUserMenu.test.tsx +++ b/packages/user/src/components/__test__/DropdownUserMenu.test.tsx @@ -8,6 +8,14 @@ import { userContext } from "../../context/UserProvider"; import DropdownUserMenu from "../DropdownUserMenu"; const setup = () => { + const roles = [ + { + id: 2, + role: "USER", + default: true, + }, + ]; + const values = { setUser: vi.fn(), user: { @@ -19,7 +27,7 @@ const setup = () => { signedUpAt: 0, surname: null, givenName: null, - roles: ["USER"], + roles, }, loading: false, }; diff --git a/packages/user/src/context/UserProvider.tsx b/packages/user/src/context/UserProvider.tsx index a8b8b492e..b0d1ef3ea 100644 --- a/packages/user/src/context/UserProvider.tsx +++ b/packages/user/src/context/UserProvider.tsx @@ -7,7 +7,7 @@ import { isUserVerified, verifySessionRoles, } from "../supertokens/helpers"; -import { UserContextType, UserType } from "../types"; +import { Role, UserContextType, UserType } from "../types"; interface Properties { children: React.ReactNode; @@ -49,7 +49,7 @@ const UserProvider = ({ children }: Properties) => { const userData = { ...user, - roles: roles, + roles, }; if (appConfig.user.features?.signUp?.emailVerification) { diff --git a/packages/user/src/supertokens/helpers.ts b/packages/user/src/supertokens/helpers.ts index 853393ef5..fc707cac6 100644 --- a/packages/user/src/supertokens/helpers.ts +++ b/packages/user/src/supertokens/helpers.ts @@ -4,15 +4,17 @@ import Session from "supertokens-web-js/recipe/session"; import logout from "./logout"; import UserRoleClaim from "./UserRoleClaim"; import { removeUserData } from "../helpers"; +// eslint-disable-next-line import/no-unresolved +import { Role } from "../types"; /** * Get User roles */ -async function getUserRoles(): Promise { +async function getUserRoles(): Promise { if (await Session.doesSessionExist()) { const roles = await Session.getClaimValue({ claim: UserRoleClaim }); - return roles ? roles : []; + return (roles ? roles.map((role) => ({ role })) : []) as Role[]; } return []; diff --git a/packages/user/src/types/index.ts b/packages/user/src/types/index.ts index f1f4f20e2..b8d8c7227 100644 --- a/packages/user/src/types/index.ts +++ b/packages/user/src/types/index.ts @@ -16,6 +16,7 @@ import { ErrorResponse, ExtendedUser, LoginCredentials, + Role, SignInUpPromise, UpdateProfileInputType, UserContextType, @@ -39,6 +40,7 @@ export type { LoginCredentials, ResendInvitationResponse, RevokeInvitationResponse, + Role, SignInUpPromise, UpdateProfileInputType, UserContextType, diff --git a/packages/user/src/types/types.ts b/packages/user/src/types/types.ts index c308fdd14..fe79122d4 100644 --- a/packages/user/src/types/types.ts +++ b/packages/user/src/types/types.ts @@ -1,12 +1,19 @@ import { EmailPasswordUserType } from "supertokens-web-js/recipe/thirdpartyemailpassword"; +export interface Role { + id: number; + role: string; + default: boolean; + permissions?: string; +} + export interface UserType extends EmailPasswordUserType { disabled?: boolean; givenName: string | null; isEmailVerified?: boolean; lastLoginAt: number; middleNames: string | null; - roles: string[]; + roles: Role[]; signedUpAt: number; surname: string | null; } From 6fd520db663b0fc76c39bfef1448cc1f1da0206e Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Mon, 22 Apr 2024 12:26:02 +0545 Subject: [PATCH 3/3] feat(user): add custom permission claim --- packages/user/src/index.ts | 4 ++++ packages/user/src/supertokens/UserPermissionClaim.ts | 10 ++++++++++ 2 files changed, 14 insertions(+) create mode 100644 packages/user/src/supertokens/UserPermissionClaim.ts diff --git a/packages/user/src/index.ts b/packages/user/src/index.ts index bf7b83b7c..1b466d200 100644 --- a/packages/user/src/index.ts +++ b/packages/user/src/index.ts @@ -32,6 +32,8 @@ import login from "./supertokens/login"; import logout from "./supertokens/logout"; import resetPassword from "./supertokens/reset-password"; import signup from "./supertokens/signup"; +import UserPermissionClaim from "./supertokens/UserPermissionClaim"; +import UserRoleClaim from "./supertokens/UserRoleClaim"; import verifyEmail from "./supertokens/verify-email"; import { AcceptInvitation } from "./views/AcceptInvitation"; import { ChangePassword } from "./views/ChangePassword"; @@ -107,6 +109,8 @@ export { resetPassword, setUserData, signup, + UserPermissionClaim, + UserRoleClaim, superTokens, useEmailVerification, useFirstUserSignup, diff --git a/packages/user/src/supertokens/UserPermissionClaim.ts b/packages/user/src/supertokens/UserPermissionClaim.ts new file mode 100644 index 000000000..7471324f0 --- /dev/null +++ b/packages/user/src/supertokens/UserPermissionClaim.ts @@ -0,0 +1,10 @@ +import { PrimitiveArrayClaim } from "supertokens-web-js/recipe/session"; + +const UserPermissionClaim = new PrimitiveArrayClaim({ + id: "permission", + // eslint-disable-next-line @typescript-eslint/no-empty-function + refresh: async () => {}, + defaultMaxAgeInSeconds: Number.MAX_SAFE_INTEGER, +}); + +export default UserPermissionClaim;