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 69b47b48f..110faa100 100644 --- a/packages/user/src/index.ts +++ b/packages/user/src/index.ts @@ -125,6 +125,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"; diff --git a/packages/user/src/supertokens/recipes/config/session/createNewSession.ts b/packages/user/src/supertokens/recipes/config/session/createNewSession.ts index 1cb2cca68..228293e97 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,30 @@ const createNewSession = ( } as FastifyError; } + 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 + ); + + input.accessTokenPayload = { + ...input.accessTokenPayload, + ...userRoleBuild, + ...userPermissionBuild, + }; + + const originalResponse = await originalImplementation.createNewSession( + input + ); + return originalResponse; }; }; diff --git a/packages/user/src/supertokens/utils/userPermissionClaim.ts b/packages/user/src/supertokens/utils/userPermissionClaim.ts new file mode 100644 index 000000000..9960058f6 --- /dev/null +++ b/packages/user/src/supertokens/utils/userPermissionClaim.ts @@ -0,0 +1,36 @@ +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 roles = await roleService.list(undefined, undefined, { + key: "role", + operator: "in", + value: userContext.roles.join(","), + }); + + 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..b42c3b953 --- /dev/null +++ b/packages/user/src/supertokens/utils/userRoleClaim.ts @@ -0,0 +1,15 @@ +import { PrimitiveArrayClaim } from "supertokens-node/lib/build/recipe/session/claims"; + +class UserRoleClaim extends PrimitiveArrayClaim { + constructor() { + super({ + key: "role", + fetchValue: async (userId: string, userContext) => { + return userContext.roles; + }, + defaultMaxAgeInSeconds: 300, + }); + } +} + +export default UserRoleClaim; 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",