UNPKG

payload-authjs

Version:
629 lines (628 loc) 25.1 kB
import crypto from "crypto"; import { getPayload } from "payload"; import { createUserAndBypassPasswordCheck } from "./utils/createUserAndBypassPasswordCheck"; import { transform } from "./utils/transform"; /** * Auth.js Database Adapter for Payload CMS * * @see https://authjs.dev/guides/creating-a-database-adapter * @see https://authjs.dev/reference/core/adapters#adapter */ export function PayloadAdapter({ payload, payloadConfig, userCollectionSlug = "users" }) { // Get the Payload instance if (!payload && payloadConfig) { payload = getPayload({ config: payloadConfig }); } if (!payload) { throw new Error("PayloadAdapter requires either a `payload` instance or a `payloadConfig` to be provided"); } // Create a logger const logger = (async ()=>(await payload).logger.child({ name: "payload-authjs (PayloadAdapter)" }))(); return { // #region User & Account management async createUser (adapterUser) { (await logger).debug({ userId: adapterUser.id, user: adapterUser }, `Creating user '${adapterUser.id ?? adapterUser.email}'`); let payloadUser; if (!(await payload).collections[userCollectionSlug]?.config.auth.disableLocalStrategy && !adapterUser.password) { // If the local strategy is enabled and the user does not have a password, bypass the password check payloadUser = await createUserAndBypassPasswordCheck(await payload, { collection: userCollectionSlug, data: transform.user.fromAdapter(adapterUser) }); } else { payloadUser = await (await payload).create({ collection: userCollectionSlug, data: transform.user.fromAdapter(adapterUser) }); } return transform.user.toAdapter(payloadUser); }, async getUser (userId) { (await logger).debug({ userId }, `Getting user by id '${userId}'`); const payloadUser = await (await payload).findByID({ collection: userCollectionSlug, id: userId, select: { accounts: false, sessions: false, verificationTokens: false }, disableErrors: true }); if (!payloadUser) { return null; // Return null if user is not found } return transform.user.toAdapter(payloadUser); }, async getUserByEmail (email) { (await logger).debug({ email }, `Getting user by email '${email}'`); const payloadUser = (await (await payload).find({ collection: userCollectionSlug, where: { email: { equals: email } }, select: { accounts: false, sessions: false, verificationTokens: false }, limit: 1 })).docs.at(0); if (!payloadUser) { return null; // Return null if user is not found } return transform.user.toAdapter(payloadUser); }, async getUserByAccount ({ provider, providerAccountId }) { (await logger).debug({ provider, providerAccountId }, `Getting user by account '${providerAccountId}' of provider '${provider}'`); const payloadUser = (await (await payload).find({ collection: userCollectionSlug, where: { "accounts.provider": { equals: provider }, "accounts.providerAccountId": { equals: providerAccountId } }, select: { accounts: false, sessions: false, verificationTokens: false }, limit: 1 })).docs.at(0); if (!payloadUser) { return null; // Return null if user is not found } return transform.user.toAdapter(payloadUser); }, async updateUser (partialUpdateUser) { (await logger).debug({ userId: partialUpdateUser.id, user: partialUpdateUser }, `Updating user '${partialUpdateUser.id}'`); const payloadUser = await (await payload).update({ collection: userCollectionSlug, id: partialUpdateUser.id, data: transform.user.fromPartialAdapter(partialUpdateUser), select: { accounts: false, sessions: false, verificationTokens: false } }); if (!payloadUser) { throw new Error(`Failed to update user: User '${partialUpdateUser.id}' not found`); } return transform.user.toAdapter(payloadUser); }, async deleteUser (userId) { (await logger).debug({ userId }, `Deleting user '${userId}'`); await (await payload).delete({ collection: userCollectionSlug, id: userId }); }, async linkAccount (adapterAccount) { const userId = adapterAccount.userId; (await logger).debug({ userId, account: adapterAccount }, `Linking account for user '${userId}'`); let payloadUser = await (await payload).findByID({ collection: userCollectionSlug, id: userId, select: { id: true, accounts: true }, disableErrors: true }); if (!payloadUser) { throw new Error(`Failed to link account: User '${userId}' not found`); } payloadUser = await (await payload).update({ collection: userCollectionSlug, id: payloadUser.id, data: { accounts: [ ...payloadUser.accounts || [], transform.account.fromAdapter(adapterAccount) ] }, select: { id: true, accounts: true } }); const createdAccount = payloadUser.accounts?.find((a)=>a.provider === adapterAccount.provider && a.providerAccountId === adapterAccount.providerAccountId); if (!createdAccount) { throw new Error(`Failed to link account: Account '${adapterAccount.providerAccountId}' of provider '${adapterAccount.provider}' not found`); } return transform.account.toAdapter(payloadUser, createdAccount); }, async unlinkAccount ({ provider, providerAccountId }) { (await logger).debug({ provider, providerAccountId }, `Unlinking account '${providerAccountId}' of provider '${provider}'`); let payloadUser = (await (await payload).find({ collection: userCollectionSlug, where: { "accounts.provider": { equals: provider }, "accounts.providerAccountId": { equals: providerAccountId } }, select: { id: true, accounts: true }, limit: 1 })).docs.at(0); if (!payloadUser) { throw new Error(`Failed to unlink account: Account '${providerAccountId}' of provider '${provider}' not found`); } payloadUser = await (await payload).update({ collection: userCollectionSlug, id: payloadUser.id, data: { accounts: payloadUser.accounts?.filter((account)=>!(account.provider === provider && account.providerAccountId === providerAccountId)) }, select: { id: true } }); }, async getAccount (providerAccountId, provider) { (await logger).debug({ provider, providerAccountId }, `Getting account '${providerAccountId}' of provider '${provider}'`); const payloadUser = (await (await payload).find({ collection: userCollectionSlug, where: { "accounts.provider": { equals: provider }, "accounts.providerAccountId": { equals: providerAccountId } }, select: { id: true, accounts: true }, limit: 1 })).docs.at(0); if (!payloadUser) { return null; } const account = payloadUser.accounts?.find((a)=>a.providerAccountId === providerAccountId && a.provider === provider); if (!account) { return null; } return transform.account.toAdapter(payloadUser, account); }, // #endregion // #region Database session management async createSession (adapterSession) { const userId = adapterSession.userId; (await logger).debug({ userId, session: adapterSession }, `Creating session for user '${userId}'`); let payloadUser = await (await payload).findByID({ collection: userCollectionSlug, id: userId, select: { id: true, sessions: true }, disableErrors: true }); if (!payloadUser) { throw new Error(`Failed to create session: User '${userId}' not found`); } payloadUser = await (await payload).update({ collection: userCollectionSlug, id: payloadUser.id, data: { sessions: [ ...payloadUser.sessions || [], transform.session.fromAdapter(adapterSession) ] }, select: { id: true, sessions: true } }); const createdSession = payloadUser.sessions?.find((s)=>s.sessionToken === adapterSession.sessionToken); if (!createdSession) { throw new Error(`Failed to create session: Session '${adapterSession.sessionToken}' not found`); } return transform.session.toAdapter(payloadUser, createdSession); }, async getSessionAndUser (sessionToken) { (await logger).debug({ sessionToken }, `Getting session and user by session token '${sessionToken}'`); const payloadUser = (await (await payload).find({ collection: userCollectionSlug, where: { "sessions.sessionToken": { equals: sessionToken } }, select: { accounts: false, verificationTokens: false }, limit: 1 })).docs.at(0); if (!payloadUser) { return null; } const session = payloadUser.sessions?.find((s)=>s.sessionToken === sessionToken); if (!session) { return null; } return { user: transform.user.toAdapter(payloadUser), session: transform.session.toAdapter(payloadUser, session) }; }, async updateSession (partialAdapterSession) { const userId = partialAdapterSession.userId; (await logger).debug({ userId, session: partialAdapterSession }, `Updating session '${partialAdapterSession.sessionToken}'`); let payloadUser = (await (await payload).find({ collection: userCollectionSlug, where: { "sessions.sessionToken": { equals: partialAdapterSession.sessionToken } }, select: { id: true, sessions: true }, limit: 1 })).docs.at(0); if (!payloadUser) { throw new Error(`Failed to update session: Session '${partialAdapterSession.sessionToken}' not found`); } payloadUser = await (await payload).update({ collection: userCollectionSlug, id: payloadUser.id, data: { sessions: payloadUser.sessions?.map((s)=>s.sessionToken === partialAdapterSession.sessionToken ? { ...s, ...transform.session.fromPartialAdapter(partialAdapterSession) } : s) }, select: { id: true, sessions: true } }); const updatedSession = payloadUser.sessions?.find((s)=>s.sessionToken === partialAdapterSession.sessionToken); if (!updatedSession) { throw new Error(`Failed to update session: Session '${partialAdapterSession.sessionToken}' not found`); } return transform.session.toAdapter(payloadUser, updatedSession); }, async deleteSession (sessionToken) { (await logger).debug({ sessionToken }, `Deleting session with token '${sessionToken}'`); const payloadUser = (await (await payload).find({ collection: userCollectionSlug, where: { "sessions.sessionToken": { equals: sessionToken } }, select: { id: true, sessions: true }, limit: 1 })).docs.at(0); if (!payloadUser) { throw new Error(`Failed to delete session: Session '${sessionToken}' not found`); } await (await payload).update({ collection: userCollectionSlug, id: payloadUser.id, data: { sessions: payloadUser.sessions?.filter((session)=>session.sessionToken !== sessionToken) }, select: { id: true } }); }, // #endregion // #region Verification tokens async createVerificationToken (adapterVerificationToken) { const email = adapterVerificationToken.identifier; (await logger).debug({ email, token: adapterVerificationToken }, `Creating verification token for email '${email}'`); let payloadUser = (await (await payload).find({ collection: userCollectionSlug, where: { email: { equals: email } }, select: { id: true, email: true, verificationTokens: true }, limit: 1 })).docs.at(0); if (!payloadUser) { const newUser = { id: crypto.randomUUID(), email, verificationTokens: [ transform.verificationToken.fromAdapter(adapterVerificationToken) ] }; if (!(await payload).collections[userCollectionSlug]?.config.auth.disableLocalStrategy) { // If the local strategy is enabled, bypass the password check payloadUser = await createUserAndBypassPasswordCheck(await payload, { collection: userCollectionSlug, data: newUser }); } else { payloadUser = await (await payload).create({ collection: userCollectionSlug, data: newUser, select: { id: true, email: true, verificationTokens: true } }); } } else { payloadUser = await (await payload).update({ collection: userCollectionSlug, id: payloadUser.id, data: { verificationTokens: [ ...payloadUser.verificationTokens || [], transform.verificationToken.fromAdapter(adapterVerificationToken) ] }, select: { id: true, email: true, verificationTokens: true } }); } const createdToken = payloadUser.verificationTokens?.find((t)=>t.token === adapterVerificationToken.token); if (!createdToken) { throw new Error(`Failed to create verification token for email '${email}'`); } return transform.verificationToken.toAdapter(payloadUser, createdToken); }, async useVerificationToken ({ identifier: email, token }) { (await logger).debug({ email, token }, `Using verification token for email '${email}'`); let payloadUser = (await (await payload).find({ collection: userCollectionSlug, where: { email: { equals: email }, "verificationTokens.token": { equals: token } }, select: { id: true, email: true, verificationTokens: true }, limit: 1 })).docs.at(0); if (!payloadUser) { return null; } const verificationToken = payloadUser.verificationTokens?.find((t)=>t.token === token); if (!verificationToken) { return null; } payloadUser = await (await payload).update({ collection: userCollectionSlug, id: payloadUser.id, data: { verificationTokens: payloadUser.verificationTokens?.filter((t)=>t.token !== token) }, select: { id: true, email: true } }); return transform.verificationToken.toAdapter(payloadUser, verificationToken); }, // #endregion // #region Authenticators async createAuthenticator (adapterAuthenticator) { const userId = adapterAuthenticator.userId; (await logger).debug({ userId, authenticator: adapterAuthenticator }, `Creating authenticator for user '${userId}'`); let payloadUser = await (await payload).findByID({ collection: userCollectionSlug, id: userId, select: { id: true, authenticators: true }, disableErrors: true }); if (!payloadUser) { throw new Error(`Failed to create authenticator: User '${userId}' not found`); } payloadUser = await (await payload).update({ collection: userCollectionSlug, id: payloadUser.id, data: { authenticators: [ ...payloadUser.authenticators || [], transform.authenticator.fromAdapter(adapterAuthenticator) ] }, select: { id: true, authenticators: true } }); const createdAuthenticator = payloadUser.authenticators?.find((a)=>a.credentialID === adapterAuthenticator.credentialID); if (!createdAuthenticator) { throw new Error(`Failed to create authenticator for user '${userId}'`); } return transform.authenticator.toAdapter(payloadUser, createdAuthenticator); }, async getAuthenticator (credentialID) { (await logger).debug({ credentialID }, `Getting authenticator '${credentialID}'`); const payloadUser = (await (await payload).find({ collection: userCollectionSlug, where: { "authenticators.credentialID": { equals: credentialID } }, select: { id: true, authenticators: true }, limit: 1 })).docs.at(0); if (!payloadUser) { return null; } const authenticator = payloadUser.authenticators?.find((a)=>a.credentialID === credentialID); if (!authenticator) { return null; } return transform.authenticator.toAdapter(payloadUser, authenticator); }, async listAuthenticatorsByUserId (userId) { (await logger).debug({ userId }, `Listing authenticators for user '${userId}'`); const payloadUser = await (await payload).findByID({ collection: userCollectionSlug, id: userId, select: { id: true, authenticators: true }, disableErrors: true }); if (!payloadUser) { return []; } return (payloadUser.authenticators || []).map((authenticator)=>transform.authenticator.toAdapter(payloadUser, authenticator)); }, async updateAuthenticatorCounter (credentialID, counter) { (await logger).debug({ credentialID, counter }, `Updating authenticator '${credentialID}' counter`); let payloadUser = (await (await payload).find({ collection: userCollectionSlug, where: { "authenticators.credentialID": { equals: credentialID } }, select: { id: true, authenticators: true }, limit: 1 })).docs.at(0); if (!payloadUser) { throw new Error(`Failed to update authenticator: Authenticator '${credentialID}' not found`); } payloadUser = await (await payload).update({ collection: userCollectionSlug, id: payloadUser.id, data: { authenticators: payloadUser.authenticators?.map((a)=>a.credentialID === credentialID ? { ...a, counter } : a) }, select: { id: true, authenticators: true } }); const authenticator = payloadUser.authenticators?.find((a)=>a.credentialID === credentialID); if (!authenticator) { throw new Error(`Failed to update authenticator: Authenticator '${credentialID}' not found`); } return transform.authenticator.toAdapter(payloadUser, authenticator); } }; } //# sourceMappingURL=PayloadAdapter.js.map