From 2c11dcde0644b0f036c15fee686d021cc12fcdc3 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Fri, 12 Apr 2024 17:08:44 +0545 Subject: [PATCH 1/6] feat(user): add role and permissions in session --- .../config/session/createNewSession.ts | 35 ++++++++++++---- .../user/src/supertokens/recipes/index.ts | 2 +- .../supertokens/utils/userPermissionClaim.ts | 41 +++++++++++++++++++ .../src/supertokens/utils/userRoleClaim.ts | 20 +++++++++ 4 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 packages/user/src/supertokens/utils/userPermissionClaim.ts create mode 100644 packages/user/src/supertokens/utils/userRoleClaim.ts diff --git a/packages/user/src/supertokens/recipes/config/session/createNewSession.ts b/packages/user/src/supertokens/recipes/config/session/createNewSession.ts index 1cb2cca68..f526dc1f3 100644 --- a/packages/user/src/supertokens/recipes/config/session/createNewSession.ts +++ b/packages/user/src/supertokens/recipes/config/session/createNewSession.ts @@ -1,4 +1,6 @@ import getUserService from "../../../../lib/getUserService"; +import UserPermissionClaim from "../../../utils/userPermissionClaim"; +import UserRoleClaim from "../../../utils/userRoleClaim"; import type { FastifyError, FastifyInstance } from "fastify"; import type { SessionRequest } from "supertokens-node/framework/fastify"; @@ -17,23 +19,15 @@ const createNewSession = ( const request = input.userContext._default.request .request as SessionRequest; - const originalResponse = await originalImplementation.createNewSession( - input - ); - - const userId = originalResponse.getUserId(); - const userService = getUserService( request.config, request.slonik, request.dbSchema ); - const user = await userService.findById(userId); + const user = await userService.findById(input.userId); if (user?.disabled) { - await originalResponse.revokeSession(); - throw { name: "SIGN_IN_FAILED", message: "user is disabled", @@ -41,6 +35,29 @@ const createNewSession = ( } as FastifyError; } + const userRoleBuild = await new UserRoleClaim().build(input.userId, { + ...input.userContext, + user, + }); + + const userPermissionBuild = await new UserPermissionClaim(fastify).build( + input.userId, + { + ...input.userContext, + user, + } + ); + + input.accessTokenPayload = { + ...input.accessTokenPayload, + ...userRoleBuild, + ...userPermissionBuild, + }; + + const originalResponse = await originalImplementation.createNewSession( + input + ); + return originalResponse; }; }; diff --git a/packages/user/src/supertokens/recipes/index.ts b/packages/user/src/supertokens/recipes/index.ts index 46d702268..c31a239d1 100644 --- a/packages/user/src/supertokens/recipes/index.ts +++ b/packages/user/src/supertokens/recipes/index.ts @@ -10,7 +10,7 @@ const getRecipeList = (fastify: FastifyInstance): RecipeListFunction[] => { const recipeList = [ initSessionRecipe(fastify), initThirdPartyEmailPassword(fastify), - initUserRolesRecipe(fastify), + // initUserRolesRecipe(fastify), ]; if (fastify.config.user.features?.signUp?.emailVerification) { diff --git a/packages/user/src/supertokens/utils/userPermissionClaim.ts b/packages/user/src/supertokens/utils/userPermissionClaim.ts new file mode 100644 index 000000000..0674bd038 --- /dev/null +++ b/packages/user/src/supertokens/utils/userPermissionClaim.ts @@ -0,0 +1,41 @@ +import { PrimitiveArrayClaim } from "supertokens-node/lib/build/recipe/session/claims"; + +import RoleService from "../../model/roles/service"; + +import type { Role, RoleCreateInput, RoleUpdateInput, User } from "../../types"; +import type { FastifyInstance } from "fastify"; + +class UserPermissionClaim extends PrimitiveArrayClaim { + constructor(fastify: FastifyInstance) { + super({ + key: "permission", + fetchValue: async (userId, userContext) => { + const roleService = new RoleService< + Role, + RoleCreateInput, + RoleUpdateInput + >(fastify.config, fastify.slonik); + + const user = userContext.user as User; + + const roles = await roleService.list(undefined, undefined, { + OR: user.roles.map(({ role }) => ({ + key: "role", + operator: "eq", + value: role, + })), + }); + + return [ + ...new Set( + roles.data.flatMap(({ permissions }) => permissions || []) + ), + ]; + }, + + defaultMaxAgeInSeconds: 300, + }); + } +} + +export default UserPermissionClaim; diff --git a/packages/user/src/supertokens/utils/userRoleClaim.ts b/packages/user/src/supertokens/utils/userRoleClaim.ts new file mode 100644 index 000000000..1306de624 --- /dev/null +++ b/packages/user/src/supertokens/utils/userRoleClaim.ts @@ -0,0 +1,20 @@ +import { PrimitiveArrayClaim } from "supertokens-node/lib/build/recipe/session/claims"; + +import type { User } from "../../types"; + +class UserRoleClaim extends PrimitiveArrayClaim { + constructor() { + super({ + key: "role", + fetchValue: async (userId: string, userContext) => { + const user = userContext.user as User; + + return user.roles.map(({ role }) => role); + }, + + defaultMaxAgeInSeconds: 300, + }); + } +} + +export default UserRoleClaim; From 95a1e2d9748090952f4762b9c46e8ed981ed4844 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Mon, 15 Apr 2024 15:44:46 +0545 Subject: [PATCH 2/6] chore(user): improve style --- .../user/src/supertokens/utils/userPermissionClaim.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/user/src/supertokens/utils/userPermissionClaim.ts b/packages/user/src/supertokens/utils/userPermissionClaim.ts index 0674bd038..62ddc6afa 100644 --- a/packages/user/src/supertokens/utils/userPermissionClaim.ts +++ b/packages/user/src/supertokens/utils/userPermissionClaim.ts @@ -19,11 +19,9 @@ class UserPermissionClaim extends PrimitiveArrayClaim { const user = userContext.user as User; const roles = await roleService.list(undefined, undefined, { - OR: user.roles.map(({ role }) => ({ - key: "role", - operator: "eq", - value: role, - })), + key: "role", + operator: "in", + value: user.roles.join(","), }); return [ From 782bdd546b8273fae9dc93efff40029a37462efc Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Mon, 15 Apr 2024 16:34:32 +0545 Subject: [PATCH 3/6] chore: fix style --- packages/user/src/supertokens/utils/userPermissionClaim.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/user/src/supertokens/utils/userPermissionClaim.ts b/packages/user/src/supertokens/utils/userPermissionClaim.ts index 62ddc6afa..22966bce8 100644 --- a/packages/user/src/supertokens/utils/userPermissionClaim.ts +++ b/packages/user/src/supertokens/utils/userPermissionClaim.ts @@ -19,9 +19,9 @@ class UserPermissionClaim extends PrimitiveArrayClaim { const user = userContext.user as User; const roles = await roleService.list(undefined, undefined, { - key: "role", + key: "id", operator: "in", - value: user.roles.join(","), + value: user.roles.map(({ id }) => id).join(","), }); return [ From 54b2a63a46722cc9ec69dea8efb9c7fa58fb2ad4 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Mon, 15 Apr 2024 17:49:53 +0545 Subject: [PATCH 4/6] chore(user): fix create New session --- .../recipes/config/session/createNewSession.ts | 17 +++++++++-------- .../user/src/supertokens/utils/areRolesExist.ts | 2 +- .../supertokens/utils/userPermissionClaim.ts | 7 ++----- .../user/src/supertokens/utils/userRoleClaim.ts | 7 +------ 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/packages/user/src/supertokens/recipes/config/session/createNewSession.ts b/packages/user/src/supertokens/recipes/config/session/createNewSession.ts index f526dc1f3..228293e97 100644 --- a/packages/user/src/supertokens/recipes/config/session/createNewSession.ts +++ b/packages/user/src/supertokens/recipes/config/session/createNewSession.ts @@ -35,17 +35,18 @@ const createNewSession = ( } as FastifyError; } - const userRoleBuild = await new UserRoleClaim().build(input.userId, { - ...input.userContext, - user, - }); + if (!input.userContext.roles) { + input.userContext.roles = user?.roles.map(({ role }) => role) || []; + } + + const userRoleBuild = await new UserRoleClaim().build( + input.userId, + input.userContext + ); const userPermissionBuild = await new UserPermissionClaim(fastify).build( input.userId, - { - ...input.userContext, - user, - } + input.userContext ); input.accessTokenPayload = { diff --git a/packages/user/src/supertokens/utils/areRolesExist.ts b/packages/user/src/supertokens/utils/areRolesExist.ts index e626c515c..2433759cf 100644 --- a/packages/user/src/supertokens/utils/areRolesExist.ts +++ b/packages/user/src/supertokens/utils/areRolesExist.ts @@ -2,7 +2,7 @@ import RoleService from "../../model/roles/service"; import type { Role, RoleCreateInput, RoleUpdateInput } from "../../types"; import type { ApiConfig } from "@dzangolab/fastify-config"; -import type { Database, FilterInput } from "@dzangolab/fastify-slonik"; +import type { Database } from "@dzangolab/fastify-slonik"; const areRolesExist = async ( roles: string[], diff --git a/packages/user/src/supertokens/utils/userPermissionClaim.ts b/packages/user/src/supertokens/utils/userPermissionClaim.ts index 22966bce8..9960058f6 100644 --- a/packages/user/src/supertokens/utils/userPermissionClaim.ts +++ b/packages/user/src/supertokens/utils/userPermissionClaim.ts @@ -16,12 +16,10 @@ class UserPermissionClaim extends PrimitiveArrayClaim { RoleUpdateInput >(fastify.config, fastify.slonik); - const user = userContext.user as User; - const roles = await roleService.list(undefined, undefined, { - key: "id", + key: "role", operator: "in", - value: user.roles.map(({ id }) => id).join(","), + value: userContext.roles.join(","), }); return [ @@ -30,7 +28,6 @@ class UserPermissionClaim extends PrimitiveArrayClaim { ), ]; }, - defaultMaxAgeInSeconds: 300, }); } diff --git a/packages/user/src/supertokens/utils/userRoleClaim.ts b/packages/user/src/supertokens/utils/userRoleClaim.ts index 1306de624..b42c3b953 100644 --- a/packages/user/src/supertokens/utils/userRoleClaim.ts +++ b/packages/user/src/supertokens/utils/userRoleClaim.ts @@ -1,17 +1,12 @@ import { PrimitiveArrayClaim } from "supertokens-node/lib/build/recipe/session/claims"; -import type { User } from "../../types"; - class UserRoleClaim extends PrimitiveArrayClaim { constructor() { super({ key: "role", fetchValue: async (userId: string, userContext) => { - const user = userContext.user as User; - - return user.roles.map(({ role }) => role); + return userContext.roles; }, - defaultMaxAgeInSeconds: 300, }); } From 66ad503aa10c7c79873a3a3eaa1d202973f1e888 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Wed, 17 Apr 2024 16:03:24 +0545 Subject: [PATCH 5/6] chore(user): update vite config --- packages/user/vite.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/user/vite.config.ts b/packages/user/vite.config.ts index 9922fdf15..8902d34bc 100644 --- a/packages/user/vite.config.ts +++ b/packages/user/vite.config.ts @@ -39,6 +39,7 @@ export default defineConfig(({ mode }) => { slonik: "Slonik", "supertokens-node": "SupertokensNode", "supertokens-node/framework/fastify": "SupertokensFastify", + "supertokens-node/lib/build/recipe/session/claims": "claims", "supertokens-node/recipe/emailverification": "EmailVerification", "supertokens-node/recipe/session/framework/fastify": "SupertokensSessionFastify", From cd6e0d7cc7523261dd46bf90b4ded9e63b6caa34 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Mon, 22 Apr 2024 13:01:31 +0545 Subject: [PATCH 6/6] chore(multi-tenant): update createNewSession --- .../recipes/session/createNewSession.ts | 48 +++++++++++++------ packages/user/src/index.ts | 2 + 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/packages/multi-tenant/src/supertokens/recipes/session/createNewSession.ts b/packages/multi-tenant/src/supertokens/recipes/session/createNewSession.ts index c7dda0b1e..80f0ab7a5 100644 --- a/packages/multi-tenant/src/supertokens/recipes/session/createNewSession.ts +++ b/packages/multi-tenant/src/supertokens/recipes/session/createNewSession.ts @@ -1,3 +1,5 @@ +import { UserPermissionClaim, UserRoleClaim } from "@dzangolab/fastify-user"; + import getMultiTenantConfig from "../../../lib/getMultiTenantConfig"; import getUserService from "../../../lib/getUserService"; @@ -17,6 +19,18 @@ const createNewSession = ( const tenant = input.userContext.tenant as Tenant; + const userService = getUserService(fastify.config, fastify.slonik, tenant); + + const user = await userService.findById(input.userId); + + if (user?.disabled) { + throw { + name: "SIGN_IN_FAILED", + message: "user is disabled", + statusCode: 401, + } as FastifyError; + } + if (tenant) { const request = input.userContext._default.request .request as FastifyRequest; @@ -29,25 +43,29 @@ const createNewSession = ( }; } - const originalResponse = await originalImplementation.createNewSession( - input - ); - - const userId = originalResponse.getUserId(); + if (!input.userContext.roles) { + input.userContext.roles = user?.roles.map(({ role }) => role) || []; + } - const userService = getUserService(fastify.config, fastify.slonik, tenant); + const userRoleBuild = await new UserRoleClaim().build( + input.userId, + input.userContext + ); - const user = await userService.findById(userId); + const userPermissionBuild = await new UserPermissionClaim(fastify).build( + input.userId, + input.userContext + ); - if (user?.disabled) { - await originalResponse.revokeSession(); + input.accessTokenPayload = { + ...input.accessTokenPayload, + ...userRoleBuild, + ...userPermissionBuild, + }; - throw { - name: "SIGN_IN_FAILED", - message: "user is disabled", - statusCode: 401, - } as FastifyError; - } + const originalResponse = await originalImplementation.createNewSession( + input + ); return originalResponse; }; diff --git a/packages/user/src/index.ts b/packages/user/src/index.ts index 30a43548a..bb36328d9 100644 --- a/packages/user/src/index.ts +++ b/packages/user/src/index.ts @@ -128,6 +128,8 @@ export { default as hasUserPermission } from "./lib/hasUserPermission"; export { default as CustomApiError } from "./customApiError"; export { default as getRolesByNames } from "./lib/getRolesByNames"; export { emailSchema, passwordSchema, roleSchema } from "./schemas"; +export { default as UserPermissionClaim } from "./supertokens/utils/userPermissionClaim"; +export { default as UserRoleClaim } from "./supertokens/utils/userRoleClaim"; export * from "./constants";