From 3be9447f0653e4877f3c796e9ee58bbec68cfaa2 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Wed, 10 Apr 2024 16:43:53 +0545 Subject: [PATCH 01/15] feat(user): export RoleService from user package --- packages/user/src/index.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/user/src/index.ts b/packages/user/src/index.ts index 1f033e191..52d1c270a 100644 --- a/packages/user/src/index.ts +++ b/packages/user/src/index.ts @@ -102,7 +102,8 @@ export { default as getInvitationService } from "./lib/getInvitationService"; export { default as invitationRoutes } from "./model/invitations/controller"; export { default as permissionResolver } from "./model/permissions/resolver"; export { default as permissionRoutes } from "./model/permissions/controller"; -export { default as RoleService } from "./model/supertokens-roles/service"; +export { default as RoleService } from "./model/roles/service"; +export { default as SupertokensRoleService } from "./model/supertokens-roles/service"; export { default as supertokensRoleResolver } from "./model/supertokens-roles/resolver"; export { default as supertokensRoleRoutes } from "./model/supertokens-roles/controller"; export { default as roleRoutes } from "./model/roles/controller"; @@ -129,9 +130,12 @@ export type { ThirdPartyEmailPasswordRecipe } from "./supertokens/types/thirdPar export type { AuthUser, ChangePasswordInput, + Role, + RoleCreateInput, + RoleUpdateInput, + User, UserCreateInput, UserUpdateInput, - User, } from "./types"; export type { Invitation, From 98c16fe9e9531d172701ae1c6cb980f85fd493a3 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Wed, 10 Apr 2024 17:05:11 +0545 Subject: [PATCH 02/15] feat: use new role service by createTenantOwnerRole --- .../src/lib/createTenantOwnerRole.ts | 31 +++++++++++++++++-- packages/multi-tenant/src/plugin.ts | 4 +-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/packages/multi-tenant/src/lib/createTenantOwnerRole.ts b/packages/multi-tenant/src/lib/createTenantOwnerRole.ts index 4a3a27fea..fe457beec 100644 --- a/packages/multi-tenant/src/lib/createTenantOwnerRole.ts +++ b/packages/multi-tenant/src/lib/createTenantOwnerRole.ts @@ -1,9 +1,34 @@ -import UserRoles from "supertokens-node/recipe/userroles"; +import { RoleService } from "@dzangolab/fastify-user"; import { ROLE_TENANT_OWNER } from "../constants"; -const createTenantOwnerRole = async () => { - await UserRoles.createNewRoleOrAddPermissions(ROLE_TENANT_OWNER, []); +import type { ApiConfig } from "@dzangolab/fastify-config"; +import type { Database } from "@dzangolab/fastify-slonik"; +import type { + Role, + RoleCreateInput, + RoleUpdateInput, +} from "@dzangolab/fastify-user"; + +const createTenantOwnerRole = async (config: ApiConfig, slonik: Database) => { + const service = new RoleService( + config, + slonik + ); + + const roles = await service.list(undefined, undefined, { + key: "role", + operator: "eq", + value: ROLE_TENANT_OWNER, + }); + + if (!roles.filteredCount) { + await service.create({ + role: ROLE_TENANT_OWNER, + permissions: [], + default: false, + }); + } }; export default createTenantOwnerRole; diff --git a/packages/multi-tenant/src/plugin.ts b/packages/multi-tenant/src/plugin.ts index 2678c5b16..bde369a5b 100644 --- a/packages/multi-tenant/src/plugin.ts +++ b/packages/multi-tenant/src/plugin.ts @@ -19,7 +19,7 @@ const plugin = async ( // Register domain discovery plugin await fastify.register(tenantDiscoveryPlugin); - const { config } = fastify; + const { config, slonik } = fastify; const supertokensConfig = { recipes }; @@ -27,7 +27,7 @@ const plugin = async ( config.user.supertokens = merge(supertokensConfig, config.user.supertokens); fastify.addHook("onReady", async () => { - await createTenantOwnerRole(); + await createTenantOwnerRole(config, slonik); }); done(); From 6576b2eca712440c96051aaa9d32f2d91d6b6fc0 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Wed, 10 Apr 2024 17:30:50 +0545 Subject: [PATCH 03/15] feat(user): update roles type in user context --- packages/multi-tenant/src/lib/updateContext.ts | 3 ++- packages/user/src/index.ts | 9 +++++++-- packages/user/src/userContext.ts | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/multi-tenant/src/lib/updateContext.ts b/packages/multi-tenant/src/lib/updateContext.ts index c150c66fc..88709b5bc 100644 --- a/packages/multi-tenant/src/lib/updateContext.ts +++ b/packages/multi-tenant/src/lib/updateContext.ts @@ -4,6 +4,7 @@ import UserRoles from "supertokens-node/recipe/userroles"; import getUserService from "../lib/getUserService"; +import type { User } from "@dzangolab/fastify-user"; import type { FastifyRequest, FastifyReply } from "fastify"; import type { MercuriusContext } from "mercurius"; @@ -39,7 +40,7 @@ const updateContext = async ( throw new Error("Unable to find user"); } - const { roles } = await UserRoles.getRolesForUser(userId); + const { roles } = (await service.findById(userId)) as User; context.user = user; context.roles = roles; diff --git a/packages/user/src/index.ts b/packages/user/src/index.ts index 52d1c270a..74402d624 100644 --- a/packages/user/src/index.ts +++ b/packages/user/src/index.ts @@ -7,7 +7,12 @@ import userHandlers from "./model/users/handlers"; import UserService from "./model/users/service"; import type { SupertokensConfig } from "./supertokens"; -import type { IsEmailOptions, StrongPasswordOptions, User } from "./types"; +import type { + IsEmailOptions, + Role, + StrongPasswordOptions, + User, +} from "./types"; import type { Invitation } from "./types/invitation"; import type { FastifyRequest } from "fastify"; @@ -19,7 +24,7 @@ declare module "fastify" { declare module "mercurius" { interface MercuriusContext { - roles: string[] | undefined; + roles: Omit[] | undefined; user: User | undefined; } } diff --git a/packages/user/src/userContext.ts b/packages/user/src/userContext.ts index 5b976ef91..0194189bb 100644 --- a/packages/user/src/userContext.ts +++ b/packages/user/src/userContext.ts @@ -64,7 +64,7 @@ const userContext = async ( throw new Error("Unable to find user"); } - const { roles } = await UserRoles.getRolesForUser(userId); + const { roles } = (await service.findById(userId)) as User; context.user = user; context.roles = roles; From 55ae00ed4b4fe9f7f66de4bfbaf8d197c23e5fd4 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Wed, 10 Apr 2024 18:23:24 +0545 Subject: [PATCH 04/15] chore: remove supertokens UserRoles from context and tenant resolver --- .../multi-tenant/src/lib/updateContext.ts | 1 - .../src/model/tenants/resolver.ts | 33 +++++++++++++++---- packages/user/src/userContext.ts | 1 - 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/packages/multi-tenant/src/lib/updateContext.ts b/packages/multi-tenant/src/lib/updateContext.ts index 88709b5bc..a7a80a709 100644 --- a/packages/multi-tenant/src/lib/updateContext.ts +++ b/packages/multi-tenant/src/lib/updateContext.ts @@ -1,6 +1,5 @@ import { wrapResponse } from "supertokens-node/framework/fastify"; import Session from "supertokens-node/recipe/session"; -import UserRoles from "supertokens-node/recipe/userroles"; import getUserService from "../lib/getUserService"; diff --git a/packages/multi-tenant/src/model/tenants/resolver.ts b/packages/multi-tenant/src/model/tenants/resolver.ts index d4cf7430b..9468cb76a 100644 --- a/packages/multi-tenant/src/model/tenants/resolver.ts +++ b/packages/multi-tenant/src/model/tenants/resolver.ts @@ -1,9 +1,10 @@ +import { User } from "@dzangolab/fastify-user"; import mercurius from "mercurius"; -import UserRoles from "supertokens-node/recipe/userroles"; import Service from "./service"; import { ROLE_TENANT_OWNER } from "../../constants"; import getMultiTenantConfig from "../../lib/getMultiTenantConfig"; +import getUserService from "../../lib/getUserService"; import type { TenantCreateInput } from "./../../types"; import type { FilterInput, SortInput } from "@dzangolab/fastify-slonik"; @@ -99,11 +100,17 @@ const Query = { context.dbSchema ); - const { roles } = await UserRoles.getRolesForUser(userId); + const userService = getUserService( + context.config, + context.database, + context.tenant + ); + + const { roles } = (await userService.findById(userId)) as User; // [DU 2024-JAN-15] TODO: address the scenario in which a user possesses // both roles: ADMIN and TENANT_OWNER - if (roles.includes(ROLE_TENANT_OWNER)) { + if (roles.some(({ role }) => role === ROLE_TENANT_OWNER)) { service.ownerId = userId; } @@ -138,11 +145,17 @@ const Query = { context.dbSchema ); - const { roles } = await UserRoles.getRolesForUser(userId); + const userService = getUserService( + context.config, + context.database, + context.tenant + ); + + const { roles } = (await userService.findById(userId)) as User; // [DU 2024-JAN-15] TODO: address the scenario in which a user possesses // both roles: ADMIN and TENANT_OWNER - if (roles.includes(ROLE_TENANT_OWNER)) { + if (roles.some(({ role }) => role === ROLE_TENANT_OWNER)) { service.ownerId = userId; } @@ -182,11 +195,17 @@ const Query = { context.dbSchema ); - const { roles } = await UserRoles.getRolesForUser(userId); + const userService = getUserService( + context.config, + context.database, + context.tenant + ); + + const { roles } = (await userService.findById(userId)) as User; // [DU 2024-JAN-15] TODO: address the scenario in which a user possesses // both roles: ADMIN and TENANT_OWNER - if (roles.includes(ROLE_TENANT_OWNER)) { + if (roles.some(({ role }) => role === ROLE_TENANT_OWNER)) { service.ownerId = userId; } diff --git a/packages/user/src/userContext.ts b/packages/user/src/userContext.ts index 0194189bb..945e520d9 100644 --- a/packages/user/src/userContext.ts +++ b/packages/user/src/userContext.ts @@ -2,7 +2,6 @@ import mercurius from "mercurius"; import { wrapResponse } from "supertokens-node/framework/fastify"; import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification"; import Session from "supertokens-node/recipe/session"; -import UserRoles from "supertokens-node/recipe/userroles"; import getUserService from "./lib/getUserService"; From 944f86b06b276204e3e6b45908969da51c76b7d8 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Thu, 11 Apr 2024 12:43:58 +0545 Subject: [PATCH 05/15] refactor: replace supertokens role --- .../multi-tenant/src/lib/updateContext.ts | 6 +-- .../emailPasswordSignUp.ts | 31 ++++++++------- .../thirdPartySignInUp.ts | 35 +++++++---------- .../thirdPartySignInUpPost.ts | 20 +++++++--- packages/user/src/index.ts | 1 + packages/user/src/lib/getRolesByNames.ts | 33 ++++++++++++++++ packages/user/src/lib/hasUserPermission.ts | 31 ++++++++------- .../user/src/middlewares/hasPermission.ts | 3 +- .../src/model/users/handlers/adminSignUp.ts | 1 + .../emailPasswordSignUp.ts | 23 +++++------ .../thirdPartySignInUp.ts | 38 +++++++------------ .../thirdPartySignInUpPost.ts | 19 +++++++--- .../src/supertokens/utils/areRolesExist.ts | 35 +++++++++++++++-- .../src/supertokens/utils/isRoleExists.ts | 33 ++++++++++++++-- packages/user/src/userContext.ts | 4 +- 15 files changed, 194 insertions(+), 119 deletions(-) create mode 100644 packages/user/src/lib/getRolesByNames.ts diff --git a/packages/multi-tenant/src/lib/updateContext.ts b/packages/multi-tenant/src/lib/updateContext.ts index a7a80a709..d1fc96655 100644 --- a/packages/multi-tenant/src/lib/updateContext.ts +++ b/packages/multi-tenant/src/lib/updateContext.ts @@ -26,7 +26,7 @@ const updateContext = async ( const service = getUserService(config, slonik, tenant); /* eslint-disable-next-line unicorn/no-null */ - let user; + let user: User | null = null; try { user = await service.findById(userId); @@ -39,10 +39,8 @@ const updateContext = async ( throw new Error("Unable to find user"); } - const { roles } = (await service.findById(userId)) as User; - context.user = user; - context.roles = roles; + context.roles = user.roles; } }; diff --git a/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts b/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts index 83e80de50..72ba529fa 100644 --- a/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts +++ b/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts @@ -1,7 +1,11 @@ -import { areRolesExist, sendEmail, verifyEmail } from "@dzangolab/fastify-user"; +import { + areRolesExist, + getRolesByNames, + sendEmail, + verifyEmail, +} from "@dzangolab/fastify-user"; import { deleteUser } from "supertokens-node"; import EmailVerification from "supertokens-node/recipe/emailverification"; -import UserRoles from "supertokens-node/recipe/userroles"; import getUserService from "../../../lib/getUserService"; import Email from "../../utils/email"; @@ -19,7 +23,7 @@ const emailPasswordSignUp = ( return async (input) => { const roles = (input.userContext.roles || []) as string[]; - if (!(await areRolesExist(roles))) { + if (!(await areRolesExist(roles, config, slonik))) { log.error(`At least one role from ${roles.join(", ")} does not exist.`); throw { @@ -48,8 +52,6 @@ const emailPasswordSignUp = ( input.userContext.tenant ); - // TODO: assign roles to user - let user: User | null | undefined; try { @@ -75,22 +77,19 @@ const emailPasswordSignUp = ( }; } + const rolesResponse = await getRolesByNames(roles, config, slonik); + + const rolesIds = rolesResponse.map(({ id }) => id); + + await userService.addRolesToUser(originalResponse.user.id, rolesIds); + + user = (await userService.findById(originalResponse.user.id)) as User; + originalResponse.user = { ...originalResponse.user, ...user, }; - for (const role of roles) { - const rolesResponse = await UserRoles.addRoleToUser( - originalResponse.user.id, - role - ); - - if (rolesResponse.status !== "OK") { - log.error(rolesResponse.status); - } - } - if (config.user.features?.signUp?.emailVerification) { try { if (input.userContext.autoVerifyEmail) { diff --git a/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/thirdPartySignInUp.ts b/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/thirdPartySignInUp.ts index c4a103062..54743d261 100644 --- a/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/thirdPartySignInUp.ts +++ b/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/thirdPartySignInUp.ts @@ -13,7 +13,7 @@ const thirdPartySignInUp = ( originalImplementation: RecipeInterface, fastify: FastifyInstance ): RecipeInterface["thirdPartySignInUp"] => { - const { config, log } = fastify; + const { config, log, slonik } = fastify; return async (input) => { const roles = (input.userContext.roles || []) as string[]; @@ -43,29 +43,20 @@ const thirdPartySignInUp = ( input ); - if (originalResponse.status === "OK" && originalResponse.createdNewUser) { - if (!(await areRolesExist(roles))) { - await deleteUser(originalResponse.user.id); + if ( + originalResponse.status === "OK" && + originalResponse.createdNewUser && + !(await areRolesExist(roles, config, slonik)) + ) { + await deleteUser(originalResponse.user.id); - log.error(`At least one role from ${roles.join(", ")} does not exist.`); + log.error(`At least one role from ${roles.join(", ")} does not exist.`); - throw { - name: "SIGN_UP_FAILED", - message: "Something went wrong", - statusCode: 500, - } as FastifyError; - } - - for (const role of roles) { - const rolesResponse = await UserRoles.addRoleToUser( - originalResponse.user.id, - role - ); - - if (rolesResponse.status !== "OK") { - log.error(rolesResponse.status); - } - } + throw { + name: "SIGN_UP_FAILED", + message: "Something went wrong", + statusCode: 500, + } as FastifyError; } return originalResponse; diff --git a/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/thirdPartySignInUpPost.ts b/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/thirdPartySignInUpPost.ts index 4d3759dc1..9f06a2637 100644 --- a/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/thirdPartySignInUpPost.ts +++ b/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/thirdPartySignInUpPost.ts @@ -1,5 +1,5 @@ import { formatDate } from "@dzangolab/fastify-slonik"; -import { ROLE_USER } from "@dzangolab/fastify-user"; +import { ROLE_USER, getRolesByNames } from "@dzangolab/fastify-user"; import { deleteUser } from "supertokens-node"; import { ROLE_TENANT_OWNER } from "../../../constants"; @@ -80,7 +80,18 @@ const thirdPartySignInUpPOST = ( throw new Error("User not found"); } - user.roles = input.userContext.roles; + const rolesResponse = await getRolesByNames( + input.userContext.roles, + config, + slonik + ); + + const rolesIds = rolesResponse.map(({ id }) => id); + + await userService.addRolesToUser(originalResponse.user.id, rolesIds); + + user = (await userService.findById(originalResponse.user.id)) as User; + /*eslint-disable-next-line @typescript-eslint/no-explicit-any */ } catch (error: any) { log.error("Error while creating user"); @@ -123,14 +134,11 @@ const thirdPartySignInUpPOST = ( }); } return { - status: "OK", - createdNewUser: originalResponse.createdNewUser, + ...originalResponse, user: { ...originalResponse.user, ...user, }, - session: originalResponse.session, - authCodeResponse: originalResponse.authCodeResponse, }; } diff --git a/packages/user/src/index.ts b/packages/user/src/index.ts index 74402d624..169b18fe4 100644 --- a/packages/user/src/index.ts +++ b/packages/user/src/index.ts @@ -126,6 +126,7 @@ export { default as validateEmail } from "./validator/email"; export { default as validatePassword } from "./validator/password"; export { default as hasUserPermission } from "./lib/hasUserPermission"; export { default as CustomApiError } from "./customApiError"; +export { default as getRolesByNames } from "./lib/getRolesByNames"; export * from "./constants"; diff --git a/packages/user/src/lib/getRolesByNames.ts b/packages/user/src/lib/getRolesByNames.ts new file mode 100644 index 000000000..8dcdb9a2a --- /dev/null +++ b/packages/user/src/lib/getRolesByNames.ts @@ -0,0 +1,33 @@ +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"; + +const getRolesByNames = async ( + roles: string[], + config: ApiConfig, + slonik: Database, + schema?: string +): Promise => { + const service = new RoleService( + config, + slonik, + schema + ); + + const filterInput: FilterInput = { + OR: roles.map((role) => ({ + key: "role", + operator: "eq", + value: role, + })), + }; + + // TODO: user all method instead of list + const { data } = await service.list(undefined, undefined, filterInput); + + return data; +}; + +export default getRolesByNames; diff --git a/packages/user/src/lib/hasUserPermission.ts b/packages/user/src/lib/hasUserPermission.ts index 032696e15..a0375a0d4 100644 --- a/packages/user/src/lib/hasUserPermission.ts +++ b/packages/user/src/lib/hasUserPermission.ts @@ -1,21 +1,18 @@ -import UserRoles from "supertokens-node/recipe/userroles"; - +import getRolesByNames from "./getRolesByNames"; +import getUserService from "./getUserService"; import { ROLE_SUPERADMIN } from "../constants"; +import type { Role, User } from "../types"; import type { FastifyInstance } from "fastify"; -const getPermissions = async (roles: string[]) => { - let permissions: string[] = []; - - for (const role of roles) { - const response = await UserRoles.getPermissionsForRole(role); +const getPermissions = async (fastify: FastifyInstance, roles: Role[]) => { + const rolesWithPermissions = (await getRolesByNames( + roles.map(({ role }) => role), + fastify.config, + fastify.slonik + )) as Required[]; - if (response.status === "OK") { - permissions = [...new Set([...permissions, ...response.permissions])]; - } - } - - return permissions; + return rolesWithPermissions.flatMap((role) => role.permissions); }; const hasUserPermission = async ( @@ -30,14 +27,16 @@ const hasUserPermission = async ( return true; } - const { roles } = await UserRoles.getRolesForUser(userId); + const userService = getUserService(fastify.config, fastify.slonik); + + const { roles } = (await userService.findById(userId)) as User; // Allow if user has super admin role - if (roles && roles.includes(ROLE_SUPERADMIN)) { + if (roles && roles.some(({ role }) => role === ROLE_SUPERADMIN)) { return true; } - const rolePermissions = await getPermissions(roles); + const rolePermissions = await getPermissions(fastify, roles); if (!rolePermissions || !rolePermissions.includes(permission)) { return false; diff --git a/packages/user/src/middlewares/hasPermission.ts b/packages/user/src/middlewares/hasPermission.ts index 3c89afdb1..223580df2 100644 --- a/packages/user/src/middlewares/hasPermission.ts +++ b/packages/user/src/middlewares/hasPermission.ts @@ -1,5 +1,4 @@ import { Error as STError } from "supertokens-node/recipe/session"; -import UserRoles from "supertokens-node/recipe/userroles"; import hasUserPermission from "../lib/hasUserPermission"; @@ -24,7 +23,7 @@ const hasPermission = message: "Not have enough permission", payload: [ { - id: UserRoles.PermissionClaim.key, + id: "st-prem", reason: { message: "Not have enough permission", expectedToInclude: permission, diff --git a/packages/user/src/model/users/handlers/adminSignUp.ts b/packages/user/src/model/users/handlers/adminSignUp.ts index 7fbe1f84a..7034eab7e 100644 --- a/packages/user/src/model/users/handlers/adminSignUp.ts +++ b/packages/user/src/model/users/handlers/adminSignUp.ts @@ -21,6 +21,7 @@ const adminSignUp = async (request: FastifyRequest, reply: FastifyReply) => { const { email, password } = body; // check if already admin user exists + // TODO: This is not currently support by our user service. const adminUsers = await UserRoles.getUsersThatHaveRole(ROLE_ADMIN); const superAdminUsers = await UserRoles.getUsersThatHaveRole( ROLE_SUPERADMIN diff --git a/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts b/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts index 35a2acd1a..893788e3a 100644 --- a/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts +++ b/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts @@ -1,7 +1,7 @@ import { deleteUser } from "supertokens-node"; import EmailVerification from "supertokens-node/recipe/emailverification"; -import UserRoles from "supertokens-node/recipe/userroles"; +import getRolesByNames from "../../../../lib/getRolesByNames"; import getUserService from "../../../../lib/getUserService"; import sendEmail from "../../../../lib/sendEmail"; import verifyEmail from "../../../../lib/verifyEmail"; @@ -20,7 +20,7 @@ const emailPasswordSignUp = ( return async (input) => { const roles = (input.userContext.roles || []) as string[]; - if (!(await areRolesExist(roles))) { + if (!(await areRolesExist(roles, config, slonik))) { log.error(`At least one role from ${roles.join(", ")} does not exist.`); throw { @@ -64,22 +64,19 @@ const emailPasswordSignUp = ( }; } + const rolesResponse = await getRolesByNames(roles, config, slonik); + + const rolesIds = rolesResponse.map(({ id }) => id); + + await userService.addRolesToUser(originalResponse.user.id, rolesIds); + + user = (await userService.findById(originalResponse.user.id)) as User; + originalResponse.user = { ...originalResponse.user, ...user, }; - for (const role of roles) { - const rolesResponse = await UserRoles.addRoleToUser( - originalResponse.user.id, - role - ); - - if (rolesResponse.status !== "OK") { - log.error(rolesResponse.status); - } - } - if (config.user.features?.signUp?.emailVerification) { try { if (input.userContext.autoVerifyEmail) { diff --git a/packages/user/src/supertokens/recipes/config/third-party-email-password/thirdPartySignInUp.ts b/packages/user/src/supertokens/recipes/config/third-party-email-password/thirdPartySignInUp.ts index 6b3463674..6e1442173 100644 --- a/packages/user/src/supertokens/recipes/config/third-party-email-password/thirdPartySignInUp.ts +++ b/packages/user/src/supertokens/recipes/config/third-party-email-password/thirdPartySignInUp.ts @@ -1,6 +1,5 @@ import { deleteUser } from "supertokens-node"; import { getUserByThirdPartyInfo } from "supertokens-node/recipe/thirdpartyemailpassword"; -import UserRoles from "supertokens-node/recipe/userroles"; import areRolesExist from "../../../utils/areRolesExist"; @@ -11,7 +10,7 @@ const thirdPartySignInUp = ( originalImplementation: RecipeInterface, fastify: FastifyInstance ): RecipeInterface["thirdPartySignInUp"] => { - const { config, log } = fastify; + const { config, log, slonik } = fastify; return async (input) => { const roles = (input.userContext.roles || []) as string[]; @@ -34,32 +33,23 @@ const thirdPartySignInUp = ( input ); - if (originalResponse.status === "OK" && originalResponse.createdNewUser) { - if (!(await areRolesExist(roles))) { - await deleteUser(originalResponse.user.id); + if ( + originalResponse.status === "OK" && + originalResponse.createdNewUser && + !(await areRolesExist(roles, config, slonik)) + ) { + await deleteUser(originalResponse.user.id); - log.error(`At least one role from ${roles.join(", ")} does not exist.`); + log.error(`At least one role from ${roles.join(", ")} does not exist.`); - throw { - name: "SIGN_UP_FAILED", - message: "Something went wrong", - statusCode: 500, - } as FastifyError; - } - - for (const role of roles) { - const rolesResponse = await UserRoles.addRoleToUser( - originalResponse.user.id, - role - ); - - if (rolesResponse.status !== "OK") { - log.error(rolesResponse.status); - } - } + throw { + name: "SIGN_UP_FAILED", + message: "Something went wrong", + statusCode: 500, + } as FastifyError; } - return originalResponse; + return { ...originalResponse, roles }; }; }; diff --git a/packages/user/src/supertokens/recipes/config/third-party-email-password/thirdPartySignInUpPost.ts b/packages/user/src/supertokens/recipes/config/third-party-email-password/thirdPartySignInUpPost.ts index c9b8995fa..618f971e5 100644 --- a/packages/user/src/supertokens/recipes/config/third-party-email-password/thirdPartySignInUpPost.ts +++ b/packages/user/src/supertokens/recipes/config/third-party-email-password/thirdPartySignInUpPost.ts @@ -2,6 +2,7 @@ import { formatDate } from "@dzangolab/fastify-slonik"; import { deleteUser } from "supertokens-node"; import { ROLE_USER } from "../../../../constants"; +import getRolesByNames from "../../../../lib/getRolesByNames"; import getUserService from "../../../../lib/getUserService"; import type { User } from "../../../../types"; @@ -40,7 +41,18 @@ const thirdPartySignInUpPOST = ( throw new Error("User not found"); } - user.roles = input.userContext.roles; + const rolesResponse = await getRolesByNames( + input.userContext.roles, + config, + slonik + ); + + const rolesIds = rolesResponse.map(({ id }) => id); + + await userService.addRolesToUser(originalResponse.user.id, rolesIds); + + user = (await userService.findById(originalResponse.user.id)) as User; + /*eslint-disable-next-line @typescript-eslint/no-explicit-any */ } catch (error: any) { log.error("Error while creating user"); @@ -84,14 +96,11 @@ const thirdPartySignInUpPOST = ( } return { - status: "OK", - createdNewUser: originalResponse.createdNewUser, + ...originalResponse, user: { ...originalResponse.user, ...user, }, - session: originalResponse.session, - authCodeResponse: originalResponse.authCodeResponse, }; } diff --git a/packages/user/src/supertokens/utils/areRolesExist.ts b/packages/user/src/supertokens/utils/areRolesExist.ts index b6cb6b042..4bdd7d0cd 100644 --- a/packages/user/src/supertokens/utils/areRolesExist.ts +++ b/packages/user/src/supertokens/utils/areRolesExist.ts @@ -1,9 +1,36 @@ -import UserRoles from "supertokens-node/recipe/userroles"; +import RoleService from "../../model/roles/service"; -const areRolesExist = async (roles: string[]): Promise => { - const { roles: allRoles } = await UserRoles.getAllRoles(); +import type { Role, RoleCreateInput, RoleUpdateInput } from "../../types"; +import type { ApiConfig } from "@dzangolab/fastify-config"; +import type { Database, FilterInput } from "@dzangolab/fastify-slonik"; - return roles.every((role) => allRoles.includes(role)); +const areRolesExist = async ( + roles: string[], + config: ApiConfig, + slonik: Database, + dbSchema?: string +): Promise => { + const service = new RoleService( + config, + slonik, + dbSchema + ); + + const filterInput: FilterInput = { + OR: roles.map((role) => ({ + key: "role", + operator: "eq", + value: role, + })), + }; + + const { filteredCount } = await service.list( + undefined, + undefined, + filterInput + ); + + return !!filteredCount; }; export default areRolesExist; diff --git a/packages/user/src/supertokens/utils/isRoleExists.ts b/packages/user/src/supertokens/utils/isRoleExists.ts index 9ac253176..8cec20e9e 100644 --- a/packages/user/src/supertokens/utils/isRoleExists.ts +++ b/packages/user/src/supertokens/utils/isRoleExists.ts @@ -1,9 +1,34 @@ -import UserRoles from "supertokens-node/recipe/userroles"; +import RoleService from "../../model/roles/service"; -const isRoleExists = async (role: string): Promise => { - const { roles } = await UserRoles.getAllRoles(); +import type { Role, RoleCreateInput, RoleUpdateInput } from "../../types"; +import type { ApiConfig } from "@dzangolab/fastify-config"; +import type { Database, FilterInput } from "@dzangolab/fastify-slonik"; - return roles.includes(role); +const isRoleExists = async ( + role: string, + config: ApiConfig, + slonik: Database, + dbSchema?: string +): Promise => { + const service = new RoleService( + config, + slonik, + dbSchema + ); + + const filterInput: FilterInput = { + key: "role", + operator: "eq", + value: role, + }; + + const { filteredCount } = await service.list( + undefined, + undefined, + filterInput + ); + + return !!filteredCount; }; export default isRoleExists; diff --git a/packages/user/src/userContext.ts b/packages/user/src/userContext.ts index 945e520d9..584419ef4 100644 --- a/packages/user/src/userContext.ts +++ b/packages/user/src/userContext.ts @@ -63,10 +63,8 @@ const userContext = async ( throw new Error("Unable to find user"); } - const { roles } = (await service.findById(userId)) as User; - context.user = user; - context.roles = roles; + context.roles = user.roles; } }; From a4229cdc3a4743890bf0a3b5d22040729260b272 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Thu, 11 Apr 2024 13:23:12 +0545 Subject: [PATCH 06/15] chore(user): fix add add to user sql --- packages/user/src/model/users/sqlFactory.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/user/src/model/users/sqlFactory.ts b/packages/user/src/model/users/sqlFactory.ts index 724e4b57f..b2641b47a 100644 --- a/packages/user/src/model/users/sqlFactory.ts +++ b/packages/user/src/model/users/sqlFactory.ts @@ -44,19 +44,6 @@ class UserSqlFactory< }), ["varchar", "int4"] )} ON CONFLICT DO NOTHING - return - ${this.getTableFragment()}.*, - COALESCE(user_role.role, '[]') AS roles - FROM ${this.getTableFragment()} - LEFT JOIN LATERAL ( - SELECT jsonb_agg(r ${createSortRoleFragment( - sql.identifier(["r", "id"]) - )}) AS role - FROM "public"."user_roles" as ur - JOIN roles r ON ur.role_id = r.id - WHERE ur.user_id = users.id - ) AS user_role ON TRUE - WHERE id = ${id}; `; }; From a9299c2d6bf71e41f38ae367d3e9e72eea7fb186 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Thu, 11 Apr 2024 14:29:30 +0545 Subject: [PATCH 07/15] chore: fix style --- packages/user/src/model/users/service.ts | 48 ++++++++++++------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/user/src/model/users/service.ts b/packages/user/src/model/users/service.ts index 3a17faf30..b2c6493c9 100644 --- a/packages/user/src/model/users/service.ts +++ b/packages/user/src/model/users/service.ts @@ -28,30 +28,6 @@ class UserService< return result; }; - get table() { - return this.config.user?.table?.name || TABLE_USERS; - } - - get factory() { - if (!this.table) { - throw new Error(`Service table is not defined`); - } - - if (!this._factory) { - this._factory = new UserSqlFactory< - User, - UserCreateInput, - UserUpdateInput - >(this); - } - - return this._factory as UserSqlFactory< - User, - UserCreateInput, - UserUpdateInput - >; - } - changePassword = async ( userId: string, oldPassword: string, @@ -114,6 +90,30 @@ class UserService< }; } }; + + get table() { + return this.config.user?.table?.name || TABLE_USERS; + } + + get factory() { + if (!this.table) { + throw new Error(`Service table is not defined`); + } + + if (!this._factory) { + this._factory = new UserSqlFactory< + User, + UserCreateInput, + UserUpdateInput + >(this); + } + + return this._factory as UserSqlFactory< + User, + UserCreateInput, + UserUpdateInput + >; + } } export default UserService; From 383bda931b405d4e4b91526f2ed2724c3b818fcb Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Mon, 15 Apr 2024 14:12:18 +0545 Subject: [PATCH 08/15] feat(user): update adminSignUp --- .../src/model/users/handlers/adminSignUp.ts | 39 ++++++++----------- .../model/users/handlers/canAdminSignUp.ts | 29 ++++---------- 2 files changed, 24 insertions(+), 44 deletions(-) diff --git a/packages/user/src/model/users/handlers/adminSignUp.ts b/packages/user/src/model/users/handlers/adminSignUp.ts index 7034eab7e..bf5c75614 100644 --- a/packages/user/src/model/users/handlers/adminSignUp.ts +++ b/packages/user/src/model/users/handlers/adminSignUp.ts @@ -1,10 +1,11 @@ import { createNewSession } from "supertokens-node/recipe/session"; import { emailPasswordSignUp } from "supertokens-node/recipe/thirdpartyemailpassword"; -import UserRoles from "supertokens-node/recipe/userroles"; import { ROLE_ADMIN, ROLE_SUPERADMIN } from "../../../constants"; +import getUserService from "../../../lib/getUserService"; import validateEmail from "../../../validator/email"; import validatePassword from "../../../validator/password"; +import RoleService from "../../roles/service"; import type { FastifyReply, FastifyRequest } from "fastify"; @@ -14,31 +15,17 @@ interface FieldInput { } const adminSignUp = async (request: FastifyRequest, reply: FastifyReply) => { - const { body, config, log } = request as FastifyRequest<{ + const { body, config, log, slonik } = request as FastifyRequest<{ Body: FieldInput; }>; try { const { email, password } = body; - // check if already admin user exists - // TODO: This is not currently support by our user service. - const adminUsers = await UserRoles.getUsersThatHaveRole(ROLE_ADMIN); - const superAdminUsers = await UserRoles.getUsersThatHaveRole( - ROLE_SUPERADMIN - ); - - if ( - adminUsers.status === "UNKNOWN_ROLE_ERROR" && - superAdminUsers.status === "UNKNOWN_ROLE_ERROR" - ) { - return reply.send({ - status: "ERROR", - message: adminUsers.status, - }); - } else if ( - (adminUsers.status === "OK" && adminUsers.users.length > 0) || - (superAdminUsers.status === "OK" && superAdminUsers.users.length > 0) - ) { + const userService = getUserService(config, slonik); + + const isAdminExists = await userService.isAdminExists(); + + if (isAdminExists) { return reply.send({ status: "ERROR", message: "First admin user already exists", @@ -65,12 +52,20 @@ const adminSignUp = async (request: FastifyRequest, reply: FastifyReply) => { }); } + const roleService = new RoleService(config, slonik); + + const isSuperadminRole = await roleService.list(undefined, undefined, { + key: "role", + operator: "eq", + value: ROLE_SUPERADMIN, + }); + // signup const signUpResponse = await emailPasswordSignUp(email, password, { autoVerifyEmail: true, roles: [ ROLE_ADMIN, - ...(superAdminUsers.status === "OK" ? [ROLE_SUPERADMIN] : []), + ...(isSuperadminRole.filteredCount ? [ROLE_SUPERADMIN] : []), ], _default: { request: { diff --git a/packages/user/src/model/users/handlers/canAdminSignUp.ts b/packages/user/src/model/users/handlers/canAdminSignUp.ts index c2683a743..aa1ccab0b 100644 --- a/packages/user/src/model/users/handlers/canAdminSignUp.ts +++ b/packages/user/src/model/users/handlers/canAdminSignUp.ts @@ -1,31 +1,16 @@ -import UserRoles from "supertokens-node/recipe/userroles"; - -import { ROLE_ADMIN, ROLE_SUPERADMIN } from "../../../constants"; +import getUserService from "../../../lib/getUserService"; import type { FastifyReply, FastifyRequest } from "fastify"; const canAdminSignUp = async (request: FastifyRequest, reply: FastifyReply) => { - const { log } = request; + const { config, log, slonik } = request; try { - // check if already admin user exists - const adminUsers = await UserRoles.getUsersThatHaveRole(ROLE_ADMIN); - const superAdminUsers = await UserRoles.getUsersThatHaveRole( - ROLE_SUPERADMIN - ); - - if ( - adminUsers.status === "UNKNOWN_ROLE_ERROR" && - superAdminUsers.status === "UNKNOWN_ROLE_ERROR" - ) { - return reply.send({ - status: "ERROR", - message: adminUsers.status, - }); - } else if ( - (adminUsers.status === "OK" && adminUsers.users.length > 0) || - (superAdminUsers.status === "OK" && superAdminUsers.users.length > 0) - ) { + const userService = getUserService(config, slonik); + + const isAdminExists = await userService.isAdminExists(); + + if (isAdminExists) { return reply.send({ signUp: false }); } From 28929d961f742fa1f295ad936f360300822d90ed Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Mon, 15 Apr 2024 15:37:02 +0545 Subject: [PATCH 09/15] chore(user): fix style --- packages/user/src/lib/getRolesByNames.ts | 16 ++++++---------- .../src/supertokens/utils/areRolesExist.ts | 18 +++++------------- .../user/src/supertokens/utils/isRoleExists.ts | 12 +++--------- 3 files changed, 14 insertions(+), 32 deletions(-) diff --git a/packages/user/src/lib/getRolesByNames.ts b/packages/user/src/lib/getRolesByNames.ts index 8dcdb9a2a..7e7545aac 100644 --- a/packages/user/src/lib/getRolesByNames.ts +++ b/packages/user/src/lib/getRolesByNames.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 getRolesByNames = async ( roles: string[], @@ -16,16 +16,12 @@ const getRolesByNames = async ( schema ); - const filterInput: FilterInput = { - OR: roles.map((role) => ({ - key: "role", - operator: "eq", - value: role, - })), - }; - // TODO: user all method instead of list - const { data } = await service.list(undefined, undefined, filterInput); + const { data } = await service.list(undefined, undefined, { + key: "role", + operator: "in", + value: roles.join(","), + }); return data; }; diff --git a/packages/user/src/supertokens/utils/areRolesExist.ts b/packages/user/src/supertokens/utils/areRolesExist.ts index 4bdd7d0cd..e626c515c 100644 --- a/packages/user/src/supertokens/utils/areRolesExist.ts +++ b/packages/user/src/supertokens/utils/areRolesExist.ts @@ -16,19 +16,11 @@ const areRolesExist = async ( dbSchema ); - const filterInput: FilterInput = { - OR: roles.map((role) => ({ - key: "role", - operator: "eq", - value: role, - })), - }; - - const { filteredCount } = await service.list( - undefined, - undefined, - filterInput - ); + const { filteredCount } = await service.list(undefined, undefined, { + key: "role", + operator: "in", + value: roles.join(","), + }); return !!filteredCount; }; diff --git a/packages/user/src/supertokens/utils/isRoleExists.ts b/packages/user/src/supertokens/utils/isRoleExists.ts index 8cec20e9e..9ed28ef4c 100644 --- a/packages/user/src/supertokens/utils/isRoleExists.ts +++ b/packages/user/src/supertokens/utils/isRoleExists.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 isRoleExists = async ( role: string, @@ -16,17 +16,11 @@ const isRoleExists = async ( dbSchema ); - const filterInput: FilterInput = { + const { filteredCount } = await service.list(undefined, undefined, { key: "role", operator: "eq", value: role, - }; - - const { filteredCount } = await service.list( - undefined, - undefined, - filterInput - ); + }); return !!filteredCount; }; From 6b03179a9097fa6264fe52efe040a2d2be443cdc Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Tue, 23 Apr 2024 10:36:27 +0545 Subject: [PATCH 10/15] chore(user): update hasPermissionPlugin --- packages/user/src/lib/hasUserPermission.ts | 5 +++-- packages/user/src/mercurius-auth/hasPermissionPlugin.ts | 3 ++- packages/user/src/middlewares/hasPermission.ts | 9 ++++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/user/src/lib/hasUserPermission.ts b/packages/user/src/lib/hasUserPermission.ts index a0375a0d4..2c4820fae 100644 --- a/packages/user/src/lib/hasUserPermission.ts +++ b/packages/user/src/lib/hasUserPermission.ts @@ -18,7 +18,8 @@ const getPermissions = async (fastify: FastifyInstance, roles: Role[]) => { const hasUserPermission = async ( fastify: FastifyInstance, userId: string, - permission: string + permission: string, + dbSchema?: string ): Promise => { const permissions = fastify.config.user.permissions; @@ -27,7 +28,7 @@ const hasUserPermission = async ( return true; } - const userService = getUserService(fastify.config, fastify.slonik); + const userService = getUserService(fastify.config, fastify.slonik, dbSchema); const { roles } = (await userService.findById(userId)) as User; diff --git a/packages/user/src/mercurius-auth/hasPermissionPlugin.ts b/packages/user/src/mercurius-auth/hasPermissionPlugin.ts index dfa10b07a..faf837f7c 100644 --- a/packages/user/src/mercurius-auth/hasPermissionPlugin.ts +++ b/packages/user/src/mercurius-auth/hasPermissionPlugin.ts @@ -21,7 +21,8 @@ const plugin = FastifyPlugin(async (fastify: FastifyInstance) => { const hasPermission = await hasUserPermission( context.app, context.user?.id, - permission + permission, + context.dbSchema ); if (!hasPermission) { diff --git a/packages/user/src/middlewares/hasPermission.ts b/packages/user/src/middlewares/hasPermission.ts index 223580df2..a2fb031df 100644 --- a/packages/user/src/middlewares/hasPermission.ts +++ b/packages/user/src/middlewares/hasPermission.ts @@ -16,7 +16,14 @@ const hasPermission = }); } - if (!(await hasUserPermission(request.server, userId, permission))) { + if ( + !(await hasUserPermission( + request.server, + userId, + permission, + request.dbSchema + )) + ) { // this error tells SuperTokens to return a 403 http response. throw new STError({ type: "INVALID_CLAIMS", From 239dc8a1f77ecd4108f29e2b34adc7e9aa5dfbb2 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Tue, 23 Apr 2024 12:34:48 +0545 Subject: [PATCH 11/15] chore(user): improve query for isRoleExists and areRolesexist --- packages/user/src/supertokens/utils/areRolesExist.ts | 4 ++-- packages/user/src/supertokens/utils/isRoleExists.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/user/src/supertokens/utils/areRolesExist.ts b/packages/user/src/supertokens/utils/areRolesExist.ts index e626c515c..901392bfa 100644 --- a/packages/user/src/supertokens/utils/areRolesExist.ts +++ b/packages/user/src/supertokens/utils/areRolesExist.ts @@ -16,13 +16,13 @@ const areRolesExist = async ( dbSchema ); - const { filteredCount } = await service.list(undefined, undefined, { + const count = await service.count({ key: "role", operator: "in", value: roles.join(","), }); - return !!filteredCount; + return !!count; }; export default areRolesExist; diff --git a/packages/user/src/supertokens/utils/isRoleExists.ts b/packages/user/src/supertokens/utils/isRoleExists.ts index 9ed28ef4c..9bb548c8d 100644 --- a/packages/user/src/supertokens/utils/isRoleExists.ts +++ b/packages/user/src/supertokens/utils/isRoleExists.ts @@ -16,13 +16,13 @@ const isRoleExists = async ( dbSchema ); - const { filteredCount } = await service.list(undefined, undefined, { + const count = await service.count({ key: "role", operator: "eq", value: role, }); - return !!filteredCount; + return !!count; }; export default isRoleExists; From 9b4321efa0260e8bc71b1078f7e266a32371862b Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Tue, 23 Apr 2024 14:15:18 +0545 Subject: [PATCH 12/15] chore(user): update user resolver --- .../src/model/users/handlers/adminSignUp.ts | 26 +++---- packages/user/src/model/users/resolver.ts | 72 +++++++------------ 2 files changed, 40 insertions(+), 58 deletions(-) diff --git a/packages/user/src/model/users/handlers/adminSignUp.ts b/packages/user/src/model/users/handlers/adminSignUp.ts index bf5c75614..6aeab06cb 100644 --- a/packages/user/src/model/users/handlers/adminSignUp.ts +++ b/packages/user/src/model/users/handlers/adminSignUp.ts @@ -21,17 +21,6 @@ const adminSignUp = async (request: FastifyRequest, reply: FastifyReply) => { try { const { email, password } = body; - const userService = getUserService(config, slonik); - - const isAdminExists = await userService.isAdminExists(); - - if (isAdminExists) { - return reply.send({ - status: "ERROR", - message: "First admin user already exists", - }); - } - // check if the email is valid const emailResult = validateEmail(email, config); @@ -52,9 +41,20 @@ const adminSignUp = async (request: FastifyRequest, reply: FastifyReply) => { }); } + const userService = getUserService(config, slonik); + + const isAdminExists = await userService.isAdminExists(); + + if (isAdminExists) { + return reply.send({ + status: "ERROR", + message: "First admin user already exists", + }); + } + const roleService = new RoleService(config, slonik); - const isSuperadminRole = await roleService.list(undefined, undefined, { + const superadminFilteredCount = await roleService.count({ key: "role", operator: "eq", value: ROLE_SUPERADMIN, @@ -65,7 +65,7 @@ const adminSignUp = async (request: FastifyRequest, reply: FastifyReply) => { autoVerifyEmail: true, roles: [ ROLE_ADMIN, - ...(isSuperadminRole.filteredCount ? [ROLE_SUPERADMIN] : []), + ...(superadminFilteredCount ? [ROLE_SUPERADMIN] : []), ], _default: { request: { diff --git a/packages/user/src/model/users/resolver.ts b/packages/user/src/model/users/resolver.ts index b6cb17041..8df2c7156 100644 --- a/packages/user/src/model/users/resolver.ts +++ b/packages/user/src/model/users/resolver.ts @@ -1,13 +1,13 @@ import mercurius from "mercurius"; import { createNewSession } from "supertokens-node/recipe/session"; import { emailPasswordSignUp } from "supertokens-node/recipe/thirdpartyemailpassword"; -import UserRoles from "supertokens-node/recipe/userroles"; import filterUserUpdateInput from "./filterUserUpdateInput"; import { ROLE_ADMIN, ROLE_SUPERADMIN } from "../../constants"; import getUserService from "../../lib/getUserService"; import validateEmail from "../../validator/email"; import validatePassword from "../../validator/password"; +import RoleService from "../roles/service"; import type { UserUpdateInput } from "./../../types"; import type { FilterInput, SortInput } from "@dzangolab/fastify-slonik"; @@ -24,37 +24,11 @@ const Mutation = { }, context: MercuriusContext ) => { - const { app, config, reply } = context; + const { app, config, database, reply } = context; try { const { email, password } = arguments_.data; - // check if already admin user exists - const adminUsers = await UserRoles.getUsersThatHaveRole(ROLE_ADMIN); - const superAdminUsers = await UserRoles.getUsersThatHaveRole( - ROLE_SUPERADMIN - ); - - let errorMessage: string | undefined; - - if ( - adminUsers.status === "UNKNOWN_ROLE_ERROR" && - superAdminUsers.status === "UNKNOWN_ROLE_ERROR" - ) { - errorMessage = adminUsers.status; - } else if ( - (adminUsers.status === "OK" && adminUsers.users.length > 0) || - (superAdminUsers.status === "OK" && superAdminUsers.users.length > 0) - ) { - errorMessage = "First admin user already exists"; - } - - if (errorMessage) { - const mercuriusError = new mercurius.ErrorWithProps(errorMessage); - - return mercuriusError; - } - // check if the email is valid const emailResult = validateEmail(email, config); @@ -77,12 +51,32 @@ const Mutation = { return mercuriusError; } + const userService = getUserService(config, database); + + const isAdminExists = await userService.isAdminExists(); + + if (isAdminExists) { + const mercuriusError = new mercurius.ErrorWithProps( + "First admin user already exists" + ); + + return mercuriusError; + } + + const roleService = new RoleService(config, database); + + const superadminFilteredCount = await roleService.count({ + key: "role", + operator: "eq", + value: ROLE_SUPERADMIN, + }); + // signup const signUpResponse = await emailPasswordSignUp(email, password, { autoVerifyEmail: true, roles: [ ROLE_ADMIN, - ...(superAdminUsers.status === "OK" ? [ROLE_SUPERADMIN] : []), + ...(superadminFilteredCount ? [ROLE_SUPERADMIN] : []), ], _default: { request: { @@ -255,26 +249,14 @@ const Query = { arguments_: { id: string }, context: MercuriusContext ) => { - const { app } = context; + const { app, database, config } = context; try { - // check if already admin user exists - const adminUsers = await UserRoles.getUsersThatHaveRole(ROLE_ADMIN); - const superAdminUsers = await UserRoles.getUsersThatHaveRole( - ROLE_SUPERADMIN - ); + const userService = getUserService(config, database); - if ( - adminUsers.status === "UNKNOWN_ROLE_ERROR" && - superAdminUsers.status === "UNKNOWN_ROLE_ERROR" - ) { - const mercuriusError = new mercurius.ErrorWithProps(adminUsers.status); + const isAdminExists = await userService.isAdminExists(); - return mercuriusError; - } else if ( - (adminUsers.status === "OK" && adminUsers.users.length > 0) || - (superAdminUsers.status === "OK" && superAdminUsers.users.length > 0) - ) { + if (isAdminExists) { return { signUp: false }; } From c504deaf0543de190eaa091564c6be3d2ff317f3 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Tue, 23 Apr 2024 15:49:36 +0545 Subject: [PATCH 13/15] chore(user): remove supertokens user roles recipe --- .../src/model/tenants/handlers/all.ts | 10 ++++++---- .../src/model/tenants/handlers/tenant.ts | 10 ++++++---- .../src/model/tenants/handlers/tenants.ts | 10 ++++++---- .../thirdPartySignInUp.ts | 1 - .../recipes/config/userRolesRecipeConfig.ts | 7 ------- packages/user/src/supertokens/recipes/index.ts | 2 -- .../supertokens/recipes/initUserRolesRecipe.ts | 18 ------------------ packages/user/src/supertokens/types/index.ts | 2 -- .../src/supertokens/utils/areRolesExist.ts | 2 +- 9 files changed, 19 insertions(+), 43 deletions(-) delete mode 100644 packages/user/src/supertokens/recipes/config/userRolesRecipeConfig.ts delete mode 100644 packages/user/src/supertokens/recipes/initUserRolesRecipe.ts diff --git a/packages/multi-tenant/src/model/tenants/handlers/all.ts b/packages/multi-tenant/src/model/tenants/handlers/all.ts index 0a6ded340..9df151a03 100644 --- a/packages/multi-tenant/src/model/tenants/handlers/all.ts +++ b/packages/multi-tenant/src/model/tenants/handlers/all.ts @@ -1,8 +1,8 @@ -import UserRoles from "supertokens-node/recipe/userroles"; - import { ROLE_TENANT_OWNER } from "../../../constants"; +import getUserService from "../../../lib/getUserService"; import Service from "../service"; +import type { User } from "@dzangolab/fastify-user"; import type { FastifyReply } from "fastify"; import type { SessionRequest } from "supertokens-node/framework/fastify"; @@ -24,11 +24,13 @@ const all = async (request: SessionRequest, reply: FastifyReply) => { const service = new Service(request.config, request.slonik, request.dbSchema); - const { roles } = await UserRoles.getRolesForUser(userId); + const userService = getUserService(request.config, request.slonik); + + const user = (await userService.findById(userId)) as User; // [DU 2024-JAN-15] TODO: address the scenario in which a user possesses // both roles: ADMIN and TENANT_OWNER - if (roles.includes(ROLE_TENANT_OWNER)) { + if (user.roles.some(({ role }) => role === ROLE_TENANT_OWNER)) { service.ownerId = userId; } diff --git a/packages/multi-tenant/src/model/tenants/handlers/tenant.ts b/packages/multi-tenant/src/model/tenants/handlers/tenant.ts index 59fadaa64..b00366856 100644 --- a/packages/multi-tenant/src/model/tenants/handlers/tenant.ts +++ b/packages/multi-tenant/src/model/tenants/handlers/tenant.ts @@ -1,8 +1,8 @@ -import UserRoles from "supertokens-node/recipe/userroles"; - import { ROLE_TENANT_OWNER } from "../../../constants"; +import getUserService from "../../../lib/getUserService"; import Service from "../service"; +import type { User } from "@dzangolab/fastify-user"; import type { FastifyReply } from "fastify"; import type { SessionRequest } from "supertokens-node/framework/fastify"; @@ -24,11 +24,13 @@ const tenant = async (request: SessionRequest, reply: FastifyReply) => { const service = new Service(request.config, request.slonik, request.dbSchema); - const { roles } = await UserRoles.getRolesForUser(userId); + const userService = getUserService(request.config, request.slonik); + + const user = (await userService.findById(userId)) as User; // [DU 2024-JAN-15] TODO: address the scenario in which a user possesses // both roles: ADMIN and TENANT_OWNER - if (roles.includes(ROLE_TENANT_OWNER)) { + if (user.roles.some(({ role }) => role === ROLE_TENANT_OWNER)) { service.ownerId = userId; } diff --git a/packages/multi-tenant/src/model/tenants/handlers/tenants.ts b/packages/multi-tenant/src/model/tenants/handlers/tenants.ts index 868a15738..de1cf1b7b 100644 --- a/packages/multi-tenant/src/model/tenants/handlers/tenants.ts +++ b/packages/multi-tenant/src/model/tenants/handlers/tenants.ts @@ -1,8 +1,8 @@ -import UserRoles from "supertokens-node/recipe/userroles"; - import { ROLE_TENANT_OWNER } from "../../../constants"; +import getUserService from "../../../lib/getUserService"; import Service from "../service"; +import type { User } from "@dzangolab/fastify-user"; import type { FastifyReply } from "fastify"; import type { SessionRequest } from "supertokens-node/framework/fastify"; @@ -24,11 +24,13 @@ const tenants = async (request: SessionRequest, reply: FastifyReply) => { const service = new Service(request.config, request.slonik, request.dbSchema); - const { roles } = await UserRoles.getRolesForUser(userId); + const userService = getUserService(request.config, request.slonik); + + const user = (await userService.findById(userId)) as User; // [DU 2024-JAN-15] TODO: address the scenario in which a user possesses // both roles: ADMIN and TENANT_OWNER - if (roles.includes(ROLE_TENANT_OWNER)) { + if (user.roles.some(({ role }) => role === ROLE_TENANT_OWNER)) { service.ownerId = userId; } diff --git a/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/thirdPartySignInUp.ts b/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/thirdPartySignInUp.ts index 54743d261..7a770c39a 100644 --- a/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/thirdPartySignInUp.ts +++ b/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/thirdPartySignInUp.ts @@ -1,7 +1,6 @@ import { areRolesExist } from "@dzangolab/fastify-user"; import { deleteUser } from "supertokens-node"; import { getUserByThirdPartyInfo } from "supertokens-node/recipe/thirdpartyemailpassword"; -import UserRoles from "supertokens-node/recipe/userroles"; import getMultiTenantConfig from "../../../lib/getMultiTenantConfig"; diff --git a/packages/user/src/supertokens/recipes/config/userRolesRecipeConfig.ts b/packages/user/src/supertokens/recipes/config/userRolesRecipeConfig.ts deleted file mode 100644 index 2dbf2df9e..000000000 --- a/packages/user/src/supertokens/recipes/config/userRolesRecipeConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { TypeInput as UserRolesRecipeConfig } from "supertokens-node/recipe/userroles/types"; - -const getUserRolesRecipeConfig = (): UserRolesRecipeConfig => { - return {}; -}; - -export default getUserRolesRecipeConfig; diff --git a/packages/user/src/supertokens/recipes/index.ts b/packages/user/src/supertokens/recipes/index.ts index 46d702268..28e1f054d 100644 --- a/packages/user/src/supertokens/recipes/index.ts +++ b/packages/user/src/supertokens/recipes/index.ts @@ -1,7 +1,6 @@ import initEmailVerificationRecipe from "./initEmailVerificationRecipe"; import initSessionRecipe from "./initSessionRecipe"; import initThirdPartyEmailPassword from "./initThirdPartyEmailPasswordRecipe"; -import initUserRolesRecipe from "./initUserRolesRecipe"; import type { FastifyInstance } from "fastify"; import type { RecipeListFunction } from "supertokens-node/types"; @@ -10,7 +9,6 @@ const getRecipeList = (fastify: FastifyInstance): RecipeListFunction[] => { const recipeList = [ initSessionRecipe(fastify), initThirdPartyEmailPassword(fastify), - initUserRolesRecipe(fastify), ]; if (fastify.config.user.features?.signUp?.emailVerification) { diff --git a/packages/user/src/supertokens/recipes/initUserRolesRecipe.ts b/packages/user/src/supertokens/recipes/initUserRolesRecipe.ts deleted file mode 100644 index b2b1f412c..000000000 --- a/packages/user/src/supertokens/recipes/initUserRolesRecipe.ts +++ /dev/null @@ -1,18 +0,0 @@ -import UserRoles from "supertokens-node/recipe/userroles"; - -import getUserRolesRecipeConfig from "./config/userRolesRecipeConfig"; - -import type { SupertokensRecipes } from "../types"; -import type { FastifyInstance } from "fastify"; - -const init = (fastify: FastifyInstance) => { - const recipes = fastify.config.user.supertokens.recipes as SupertokensRecipes; - - if (recipes && recipes.userRoles) { - return UserRoles.init(recipes.userRoles(fastify)); - } - - return UserRoles.init(getUserRolesRecipeConfig()); -}; - -export default init; diff --git a/packages/user/src/supertokens/types/index.ts b/packages/user/src/supertokens/types/index.ts index 4eabbb2b7..dc824fe75 100644 --- a/packages/user/src/supertokens/types/index.ts +++ b/packages/user/src/supertokens/types/index.ts @@ -8,7 +8,6 @@ import type { TypeInput as EmailVerificationRecipeConfig } from "supertokens-nod import type { TypeInput as SessionRecipeConfig } from "supertokens-node/recipe/session/types"; import type { TypeProvider } from "supertokens-node/recipe/thirdpartyemailpassword"; import type { TypeInput as ThirdPartyEmailPasswordRecipeConfig } from "supertokens-node/recipe/thirdpartyemailpassword/types"; -import type { TypeInput as UserRolesRecipeConfig } from "supertokens-node/recipe/userroles/types"; const { Apple, Facebook, Github, Google } = ThirdPartyEmailPassword; @@ -17,7 +16,6 @@ interface SupertokensRecipes { | EmailVerificationRecipe | ((fastify: FastifyInstance) => EmailVerificationRecipeConfig); session?: SessionRecipe | ((fastify: FastifyInstance) => SessionRecipeConfig); - userRoles?: (fastify: FastifyInstance) => UserRolesRecipeConfig; thirdPartyEmailPassword?: | ThirdPartyEmailPasswordRecipe | ((fastify: FastifyInstance) => ThirdPartyEmailPasswordRecipeConfig); diff --git a/packages/user/src/supertokens/utils/areRolesExist.ts b/packages/user/src/supertokens/utils/areRolesExist.ts index 901392bfa..71722215c 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[], From 7cd8a0e6865566f88c79296623ae4f5427e90f22 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Tue, 23 Apr 2024 15:57:23 +0545 Subject: [PATCH 14/15] chore(user): improve createTenantOwnerRole --- packages/multi-tenant/src/lib/createTenantOwnerRole.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/multi-tenant/src/lib/createTenantOwnerRole.ts b/packages/multi-tenant/src/lib/createTenantOwnerRole.ts index fe457beec..c0242593e 100644 --- a/packages/multi-tenant/src/lib/createTenantOwnerRole.ts +++ b/packages/multi-tenant/src/lib/createTenantOwnerRole.ts @@ -16,13 +16,13 @@ const createTenantOwnerRole = async (config: ApiConfig, slonik: Database) => { slonik ); - const roles = await service.list(undefined, undefined, { + const filteredCount = await service.count({ key: "role", operator: "eq", value: ROLE_TENANT_OWNER, }); - if (!roles.filteredCount) { + if (!filteredCount) { await service.create({ role: ROLE_TENANT_OWNER, permissions: [], From a201374d62ba6a91728c9197dda8008142e1a975 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Tue, 23 Apr 2024 17:12:54 +0545 Subject: [PATCH 15/15] chore(user): update getRolesbyName --- packages/user/src/lib/getRolesByNames.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/user/src/lib/getRolesByNames.ts b/packages/user/src/lib/getRolesByNames.ts index 7e7545aac..3562b088a 100644 --- a/packages/user/src/lib/getRolesByNames.ts +++ b/packages/user/src/lib/getRolesByNames.ts @@ -16,14 +16,17 @@ const getRolesByNames = async ( schema ); - // TODO: user all method instead of list - const { data } = await service.list(undefined, undefined, { - key: "role", - operator: "in", - value: roles.join(","), - }); + const filteredRoles = await service.all( + ["id", "role", "default", "permissions"], + undefined, + { + key: "role", + operator: "in", + value: roles.join(","), + } + ); - return data; + return filteredRoles as Role[]; }; export default getRolesByNames;