@lucidcms/core
Version:
The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.
1 lines • 16.7 kB
Source Map (JSON)
{"version":3,"file":"process-provider-auth.mjs","names":["constants","formatter"],"sources":["../../../../../src/services/auth/providers/helpers/process-provider-auth.ts"],"sourcesContent":["import constants from \"../../../../constants/constants.js\";\nimport formatter from \"../../../../libs/formatters/index.js\";\nimport { copy } from \"../../../../libs/i18n/index.js\";\nimport {\n\tUserAuthProvidersRepository,\n\tUsersRepository,\n\tUserTokensRepository,\n} from \"../../../../libs/repositories/index.js\";\nimport type { AuthStateActionType } from \"../../../../types.js\";\nimport { getBaseUrl } from \"../../../../utils/helpers/index.js\";\nimport urlAddPath from \"../../../../utils/helpers/url-add-path.js\";\nimport type { ServiceFn } from \"../../../../utils/services/types.js\";\n\n/**\n * A shared service for the oidc and saml callbacks to process the provider auth regarding\n * how it links to the users account.\n */\nconst processProviderAuth: ServiceFn<\n\t[\n\t\t{\n\t\t\tproviderKey: string;\n\t\t\tproviderUserId: string;\n\t\t\tfirstName?: string;\n\t\t\tlastName?: string;\n\t\t\tinvitationTokenId?: number;\n\t\t\tredirectPath?: string;\n\t\t\tactionType: AuthStateActionType;\n\t\t\tauthenticatedUserId?: number;\n\t\t},\n\t],\n\t{\n\t\tuserId: number;\n\t\tredirectUrl: string;\n\t\tgrantAuthentication: boolean;\n\t}\n> = async (context, data) => {\n\tconst UserAuthProviders = new UserAuthProvidersRepository(\n\t\tcontext.db.client,\n\t\tcontext.config.db,\n\t);\n\tconst UserTokens = new UserTokensRepository(\n\t\tcontext.db.client,\n\t\tcontext.config.db,\n\t);\n\tconst Users = new UsersRepository(context.db.client, context.config.db);\n\n\tconst redirectUrl = urlAddPath(\n\t\tgetBaseUrl(context),\n\t\tdata.redirectPath ?? constants.authState.defaultRedirectPath,\n\t);\n\n\t// -----------------------------------------------------\n\t//* invitation flow\n\tif (\n\t\tdata.actionType === constants.authState.actionTypes.invitation &&\n\t\tdata.invitationTokenId\n\t) {\n\t\tconst [invitationTokenRes, userAuthProviderRes] = await Promise.all([\n\t\t\tUserTokens.selectUserInvitation({\n\t\t\t\tid: data.invitationTokenId,\n\t\t\t\tvalidation: {\n\t\t\t\t\tenabled: true,\n\t\t\t\t\tdefaultError: {\n\t\t\t\t\t\tstatus: 404,\n\t\t\t\t\t\tmessage: copy(\n\t\t\t\t\t\t\t\"server:core.auth.invitations.token.not.found.message\",\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tUserAuthProviders.selectSingle({\n\t\t\t\tselect: [\"user_id\"],\n\t\t\t\twhere: [\n\t\t\t\t\t{\n\t\t\t\t\t\tkey: \"provider_key\",\n\t\t\t\t\t\toperator: \"=\",\n\t\t\t\t\t\tvalue: data.providerKey,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tkey: \"provider_user_id\",\n\t\t\t\t\t\toperator: \"=\",\n\t\t\t\t\t\tvalue: data.providerUserId,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}),\n\t\t]);\n\t\tif (invitationTokenRes.error) return invitationTokenRes;\n\t\tif (userAuthProviderRes.error) return userAuthProviderRes;\n\n\t\t//* check if the user is soft deleted\n\t\tif (formatter.formatBoolean(invitationTokenRes.data.user_is_deleted)) {\n\t\t\treturn {\n\t\t\t\terror: {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tname: copy(\"server:core.auth.providers.user.is.deleted.name\"),\n\t\t\t\t\tmessage: copy(\"server:core.auth.providers.user.is.deleted.message\"),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t//* check if the user is locked\n\t\tif (formatter.formatBoolean(invitationTokenRes.data.user_is_locked)) {\n\t\t\treturn {\n\t\t\t\terror: {\n\t\t\t\t\tstatus: 401,\n\t\t\t\t\tname: copy(\"server:core.auth.providers.user.is.locked.name\"),\n\t\t\t\t\tmessage: copy(\"server:core.auth.providers.user.is.locked.message\"),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t//* check if the user has accepted an invitation already\n\t\tif (\n\t\t\tformatter.formatBoolean(invitationTokenRes.data.user_invitation_accepted)\n\t\t) {\n\t\t\treturn {\n\t\t\t\terror: {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tname: copy(\n\t\t\t\t\t\t\"server:core.auth.providers.user.invitation.accepted.message\",\n\t\t\t\t\t),\n\t\t\t\t\tmessage: copy(\n\t\t\t\t\t\t\"server:core.auth.providers.user.invitation.accepted.message\",\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\t//* we've found a user auth provider entry for the users provider userid,\n\t\t//* but the entry doesnt match the users id that theyre accepting the invitation on behalf of\n\t\tif (\n\t\t\tuserAuthProviderRes.data &&\n\t\t\tinvitationTokenRes.data.user_id !== userAuthProviderRes.data.user_id\n\t\t) {\n\t\t\treturn {\n\t\t\t\terror: {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tname: copy(\"server:core.auth.providers.user.id.mismatch.name\"),\n\t\t\t\t\tmessage: copy(\"server:core.auth.providers.user.id.mismatch.message\"),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t//* consume the invitation token first to enforce single-use\n\t\tconst now = new Date().toISOString();\n\t\tconst consumeTokenRes = await UserTokens.updateSingle({\n\t\t\tdata: {\n\t\t\t\tconsumed_at: now,\n\t\t\t\trevoked_at: now,\n\t\t\t\trevoke_reason: constants.userTokenRevokeReasons.invitationAccepted,\n\t\t\t\texpiry_date: now,\n\t\t\t},\n\t\t\twhere: [\n\t\t\t\t{\n\t\t\t\t\tkey: \"id\",\n\t\t\t\t\toperator: \"=\",\n\t\t\t\t\tvalue: data.invitationTokenId,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkey: \"token_type\",\n\t\t\t\t\toperator: \"=\",\n\t\t\t\t\tvalue: constants.userTokens.invitation,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkey: \"expiry_date\",\n\t\t\t\t\toperator: \">\",\n\t\t\t\t\tvalue: now,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkey: \"revoked_at\",\n\t\t\t\t\toperator: \"is\",\n\t\t\t\t\tvalue: null,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkey: \"consumed_at\",\n\t\t\t\t\toperator: \"is\",\n\t\t\t\t\tvalue: null,\n\t\t\t\t},\n\t\t\t],\n\t\t\treturning: [\"id\"],\n\t\t});\n\t\tif (consumeTokenRes.error) return consumeTokenRes;\n\t\tif (!consumeTokenRes.data) {\n\t\t\treturn {\n\t\t\t\terror: {\n\t\t\t\t\tstatus: 404,\n\t\t\t\t\tmessage: copy(\"server:core.auth.invitations.token.not.found.message\"),\n\t\t\t\t},\n\t\t\t\tdata: undefined,\n\t\t\t};\n\t\t}\n\n\t\t//* update the user, create a user auth provider entry\n\t\tconst [linkRes, updateUserRes] = await Promise.all([\n\t\t\tuserAuthProviderRes.data\n\t\t\t\t? undefined\n\t\t\t\t: UserAuthProviders.createSingle({\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\tuser_id: invitationTokenRes.data.user_id,\n\t\t\t\t\t\t\tprovider_key: data.providerKey,\n\t\t\t\t\t\t\tprovider_user_id: data.providerUserId,\n\t\t\t\t\t\t\tlinked_at: new Date().toISOString(),\n\t\t\t\t\t\t\tupdated_at: new Date().toISOString(),\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\tUsers.updateSingle({\n\t\t\t\twhere: [\n\t\t\t\t\t{ key: \"id\", operator: \"=\", value: invitationTokenRes.data.user_id },\n\t\t\t\t],\n\t\t\t\tdata: {\n\t\t\t\t\tfirst_name: invitationTokenRes.data.user_first_name\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: data.firstName,\n\t\t\t\t\tlast_name: invitationTokenRes.data.user_last_name\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: data.lastName,\n\t\t\t\t\tinvitation_accepted: true,\n\t\t\t\t\tupdated_at: new Date().toISOString(),\n\t\t\t\t},\n\t\t\t}),\n\t\t]);\n\t\tif (linkRes?.error) return linkRes;\n\t\tif (updateUserRes.error) return updateUserRes;\n\n\t\treturn {\n\t\t\terror: undefined,\n\t\t\tdata: {\n\t\t\t\tuserId: invitationTokenRes.data.user_id,\n\t\t\t\tredirectUrl: redirectUrl,\n\t\t\t\tgrantAuthentication: true,\n\t\t\t},\n\t\t};\n\t}\n\n\t// ----------------------------------------------------\n\t// authLink flow\n\tif (data.actionType === constants.authState.actionTypes.authLink) {\n\t\t//* the user is trying to link a provider, but the autenticatedUserId is not provided\n\t\tif (!data.authenticatedUserId) {\n\t\t\treturn {\n\t\t\t\terror: {\n\t\t\t\t\tstatus: 401,\n\t\t\t\t\tname: copy(\n\t\t\t\t\t\t\"server:core.auth.providers.link.user.not.authenticated.message\",\n\t\t\t\t\t),\n\t\t\t\t\tmessage: copy(\n\t\t\t\t\t\t\"server:core.auth.providers.link.user.not.authenticated.message\",\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\tdata: undefined,\n\t\t\t};\n\t\t}\n\n\t\tconst userAuthProviderRes = await UserAuthProviders.selectUserAuthProvider({\n\t\t\tproviderKey: data.providerKey,\n\t\t\tproviderUserId: data.providerUserId,\n\t\t});\n\t\tif (userAuthProviderRes.error) return userAuthProviderRes;\n\n\t\t//* check if the target user is deleted\n\t\tif (\n\t\t\tuserAuthProviderRes.data &&\n\t\t\tformatter.formatBoolean(userAuthProviderRes.data.user_is_deleted)\n\t\t) {\n\t\t\treturn {\n\t\t\t\terror: {\n\t\t\t\t\tstatus: 404,\n\t\t\t\t\tname: copy(\"server:core.auth.providers.user.is.deleted.name\"),\n\t\t\t\t\tmessage: copy(\"server:core.auth.providers.user.is.deleted.message\"),\n\t\t\t\t},\n\t\t\t\tdata: undefined,\n\t\t\t};\n\t\t}\n\n\t\t//* check if the target user is locked\n\t\tif (\n\t\t\tuserAuthProviderRes.data &&\n\t\t\tformatter.formatBoolean(userAuthProviderRes.data.user_is_locked)\n\t\t) {\n\t\t\treturn {\n\t\t\t\terror: {\n\t\t\t\t\tstatus: 401,\n\t\t\t\t\tname: copy(\"server:core.auth.providers.user.is.locked.name\"),\n\t\t\t\t\tmessage: copy(\"server:core.auth.providers.user.is.locked.message\"),\n\t\t\t\t},\n\t\t\t\tdata: undefined,\n\t\t\t};\n\t\t}\n\n\t\t//* check if the user auth provider is exists, but linked to another user\n\t\tif (\n\t\t\tuserAuthProviderRes.data &&\n\t\t\tuserAuthProviderRes.data.user_id !== data.authenticatedUserId\n\t\t) {\n\t\t\treturn {\n\t\t\t\terror: {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tname: copy(\"server:core.auth.providers.user.id.mismatch.name\"),\n\t\t\t\t\tmessage: copy(\"server:core.auth.providers.user.id.mismatch.message\"),\n\t\t\t\t},\n\t\t\t\tdata: undefined,\n\t\t\t};\n\t\t}\n\n\t\t//* check if the authenticated user is trying to link to a provider they already have\n\t\tif (\n\t\t\tuserAuthProviderRes.data &&\n\t\t\tuserAuthProviderRes.data.user_id === data.authenticatedUserId\n\t\t) {\n\t\t\treturn {\n\t\t\t\terror: {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tname: copy(\"server:core.auth.providers.already.linked.name\"),\n\t\t\t\t\tmessage: copy(\"server:core.auth.providers.already.linked.message\"),\n\t\t\t\t},\n\t\t\t\tdata: undefined,\n\t\t\t};\n\t\t}\n\n\t\t//* create the auth provider link\n\t\tconst [linkRes, updateUserRes] = await Promise.all([\n\t\t\tUserAuthProviders.createSingle({\n\t\t\t\tdata: {\n\t\t\t\t\tuser_id: data.authenticatedUserId,\n\t\t\t\t\tprovider_key: data.providerKey,\n\t\t\t\t\tprovider_user_id: data.providerUserId,\n\t\t\t\t\tlinked_at: new Date().toISOString(),\n\t\t\t\t\tupdated_at: new Date().toISOString(),\n\t\t\t\t},\n\t\t\t}),\n\t\t\tUsers.updateSingle({\n\t\t\t\twhere: [{ key: \"id\", operator: \"=\", value: data.authenticatedUserId }],\n\t\t\t\tdata: {\n\t\t\t\t\tupdated_at: new Date().toISOString(),\n\t\t\t\t},\n\t\t\t}),\n\t\t]);\n\t\tif (linkRes.error) return linkRes;\n\t\tif (updateUserRes.error) return updateUserRes;\n\n\t\treturn {\n\t\t\terror: undefined,\n\t\t\tdata: {\n\t\t\t\tuserId: data.authenticatedUserId,\n\t\t\t\tredirectUrl: redirectUrl,\n\t\t\t\t// they're already authenticated, so dont bother refreshing their access/refresh tokens\n\t\t\t\tgrantAuthentication: false,\n\t\t\t},\n\t\t};\n\t}\n\n\t// -----------------------------------------------------\n\t// login flow\n\tconst userAuthProviderRes = await UserAuthProviders.selectUserAuthProvider({\n\t\tproviderKey: data.providerKey,\n\t\tproviderUserId: data.providerUserId,\n\t\tvalidation: {\n\t\t\tenabled: true,\n\t\t\tdefaultError: {\n\t\t\t\tstatus: 404,\n\t\t\t\tname: copy(\"server:core.auth.providers.user.not.found.name\"),\n\t\t\t\tmessage: copy(\"server:core.auth.providers.user.not.found.message\"),\n\t\t\t},\n\t\t},\n\t});\n\tif (userAuthProviderRes.error) return userAuthProviderRes;\n\n\tif (formatter.formatBoolean(userAuthProviderRes.data.user_is_deleted)) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\tstatus: 404,\n\t\t\t\tname: copy(\"server:core.auth.providers.user.is.deleted.name\"),\n\t\t\t\tmessage: copy(\"server:core.auth.providers.user.is.deleted.message\"),\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tif (formatter.formatBoolean(userAuthProviderRes.data.user_is_locked)) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\tstatus: 401,\n\t\t\t\tname: copy(\"server:core.auth.providers.user.is.locked.name\"),\n\t\t\t\tmessage: copy(\"server:core.auth.providers.user.is.locked.message\"),\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\treturn {\n\t\terror: undefined,\n\t\tdata: {\n\t\t\tuserId: userAuthProviderRes.data.user_id,\n\t\t\tredirectUrl: redirectUrl,\n\t\t\tgrantAuthentication: true,\n\t\t},\n\t};\n};\n\nexport default processProviderAuth;\n"],"mappings":"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"}