diff --git a/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignIn.ts b/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignIn.ts index 319807d2f..cbf3e11ce 100644 --- a/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignIn.ts +++ b/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignIn.ts @@ -1,10 +1,15 @@ import { formatDate } from "@dzangolab/fastify-slonik"; +import { ROLE_USER } from "@dzangolab/fastify-user"; +import UserRoles from "supertokens-node/recipe/userroles"; +import { ROLE_TENANT_OWNER } from "../../../constants"; import getUserService from "../../../lib/getUserService"; import Email from "../../utils/email"; +import isTenantOwnerEmail from "../../utils/isTenantOwnerEmail"; -import type { AuthUser } from "@dzangolab/fastify-user"; +import type { AuthUser, User, UserCreateInput } from "@dzangolab/fastify-user"; import type { FastifyInstance } from "fastify"; +import type { QueryResultRow } from "slonik"; import type { RecipeInterface } from "supertokens-node/recipe/thirdpartyemailpassword"; const emailPasswordSignIn = ( @@ -14,11 +19,21 @@ const emailPasswordSignIn = ( const { config, log, slonik } = fastify; return async (input) => { - input.email = Email.addTenantPrefix( - config, - input.email, - input.userContext.tenant - ); + if ( + input.userContext.tenant && + !(await isTenantOwnerEmail( + config, + slonik, + input.email, + input.userContext.tenant + )) + ) { + input.email = Email.addTenantPrefix( + config, + input.email, + input.userContext.tenant + ); + } const originalResponse = await originalImplementation.emailPasswordSignIn( input @@ -31,15 +46,43 @@ const emailPasswordSignIn = ( const userService = getUserService( config, slonik, - input.userContext.dbSchema + input.userContext.tenant ); - const user = await userService.findById(originalResponse.user.id); + let user = await userService.findById(originalResponse.user.id); if (!user) { - log.error(`User record not found for userId ${originalResponse.user.id}`); + const { roles } = await UserRoles.getRolesForUser( + originalResponse.user.id + ); + + if (input.userContext.tenant && roles.includes(ROLE_TENANT_OWNER)) { + // This is the first time the tenant owner is signing in to the tenant app. + if (input.userContext.tenant) { + await UserRoles.addRoleToUser(originalResponse.user.id, ROLE_USER); + } + + // Get user details from from default schema + const userDetails = (await getUserService(config, slonik).findById( + originalResponse.user.id + )) as (User & UserCreateInput) | null; + + if (!userDetails) { + throw new Error("Unable to find user"); + } + + delete userDetails.roles; + delete userDetails.lastLoginAt; + delete userDetails.signedUpAt; + + user = (await userService.create(userDetails)) as User & QueryResultRow; + } else { + log.error( + `User record not found for userId ${originalResponse.user.id}` + ); - return { status: "WRONG_CREDENTIALS_ERROR" }; + return { status: "WRONG_CREDENTIALS_ERROR" }; + } } user.lastLoginAt = Date.now(); 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..ca70dff35 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 @@ -4,6 +4,7 @@ import { getUserByThirdPartyInfo } from "supertokens-node/recipe/thirdpartyemail import UserRoles from "supertokens-node/recipe/userroles"; import getMultiTenantConfig from "../../../lib/getMultiTenantConfig"; +import isTenantOwnerEmail from "../../utils/isTenantOwnerEmail"; import type { Tenant } from "../../../types"; import type { FastifyInstance, FastifyError } from "fastify"; @@ -13,13 +14,21 @@ 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[]; const tenant: Tenant | undefined = input.userContext.tenant; - if (tenant) { + if ( + tenant && + !(await isTenantOwnerEmail( + config, + slonik, + input.email, + input.userContext.tenant + )) + ) { const tenantId = tenant[getMultiTenantConfig(config).table.columns.id]; input.thirdPartyUserId = tenantId + "_" + input.thirdPartyUserId; 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..3affa9cb3 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,14 +1,16 @@ import { formatDate } from "@dzangolab/fastify-slonik"; import { ROLE_USER } from "@dzangolab/fastify-user"; import { deleteUser } from "supertokens-node"; +import UserRoles from "supertokens-node/recipe/userroles"; import { ROLE_TENANT_OWNER } from "../../../constants"; import getHost from "../../../lib/getHost"; import getMultiTenantConfig from "../../../lib/getMultiTenantConfig"; import getUserService from "../../../lib/getUserService"; -import type { User } from "@dzangolab/fastify-user"; +import type { User, UserCreateInput } from "@dzangolab/fastify-user"; import type { FastifyInstance, FastifyRequest } from "fastify"; +import type { QueryResultRow } from "slonik"; import type { APIInterface } from "supertokens-node/recipe/thirdpartyemailpassword/types"; const thirdPartySignInUpPOST = ( @@ -98,14 +100,44 @@ const thirdPartySignInUpPOST = ( user = await userService.findById(originalResponse.user.id); if (!user) { - log.error( - `User record not found for userId ${originalResponse.user.id}` + const { roles } = await UserRoles.getRolesForUser( + originalResponse.user.id ); - return { - status: "GENERAL_ERROR", - message: "Something went wrong", - }; + if (input.userContext.tenant && roles.includes(ROLE_TENANT_OWNER)) { + // This is the first time the tenant owner is signing in to the tenant app. + if (input.userContext.tenant) { + await UserRoles.addRoleToUser( + originalResponse.user.id, + ROLE_USER + ); + } + + // Get user details from from default schema + const userDetails = (await getUserService(config, slonik).findById( + originalResponse.user.id + )) as (User & UserCreateInput) | null; + + if (!userDetails) { + throw new Error("Unable to find user"); + } + + delete userDetails.roles; + delete userDetails.lastLoginAt; + delete userDetails.signedUpAt; + + user = (await userService.create(userDetails)) as User & + QueryResultRow; + } else { + log.error( + `User record not found for userId ${originalResponse.user.id}` + ); + + return { + status: "GENERAL_ERROR", + message: "Something went wrong", + }; + } } user.lastLoginAt = Date.now(); diff --git a/packages/multi-tenant/src/supertokens/utils/isTenantOwnerEmail.ts b/packages/multi-tenant/src/supertokens/utils/isTenantOwnerEmail.ts new file mode 100644 index 000000000..b5b492330 --- /dev/null +++ b/packages/multi-tenant/src/supertokens/utils/isTenantOwnerEmail.ts @@ -0,0 +1,41 @@ +import { UserService } from "@dzangolab/fastify-user"; +import humps from "humps"; + +import getMultiTenantConfig from "../../lib/getMultiTenantConfig"; + +import type { Tenant } from "../../types/tenant"; +import type { ApiConfig } from "@dzangolab/fastify-config"; +import type { Database } from "@dzangolab/fastify-slonik"; +import type { + User, + UserCreateInput, + UserUpdateInput, +} from "@dzangolab/fastify-user"; +import type { QueryResultRow } from "slonik"; + +const isTenantOwnerEmail = async ( + config: ApiConfig, + database: Database, + email: string, + tenant: Tenant +) => { + const userService = new UserService< + User & QueryResultRow, + UserCreateInput, + UserUpdateInput + >(config, database); + + const multiTenantConfig = getMultiTenantConfig(config); + + const owner = await userService.findById( + tenant[humps.camelize(multiTenantConfig.table.columns.ownerId)] + ); + + if (owner) { + return email === owner.email; + } + + return false; +}; + +export default isTenantOwnerEmail;