From 3b9dba8641efd64a84f089d8ee798c2a74aa1173 Mon Sep 17 00:00:00 2001 From: Jarrod Flesch <30633324+JarrodMFlesch@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:53:36 -0400 Subject: [PATCH] chore: adds session helper exports (#13367) Creates/exports `addSessionToUser` helper and also exports the `removeExpiredSessions` helper. --- packages/payload/src/auth/operations/login.ts | 39 +++-------- .../payload/src/auth/operations/refresh.ts | 2 +- .../payload/src/auth/removeExpiredSessions.ts | 10 --- packages/payload/src/auth/sessions.ts | 67 +++++++++++++++++++ packages/payload/src/exports/shared.ts | 1 + 5 files changed, 78 insertions(+), 41 deletions(-) delete mode 100644 packages/payload/src/auth/removeExpiredSessions.ts create mode 100644 packages/payload/src/auth/sessions.ts diff --git a/packages/payload/src/auth/operations/login.ts b/packages/payload/src/auth/operations/login.ts index 48db4eecf..190f09755 100644 --- a/packages/payload/src/auth/operations/login.ts +++ b/packages/payload/src/auth/operations/login.ts @@ -1,5 +1,3 @@ -import { v4 as uuid } from 'uuid' - import type { AuthOperationsFromCollectionSlug, Collection, @@ -24,7 +22,7 @@ import { getFieldsToSign } from '../getFieldsToSign.js' import { getLoginOptions } from '../getLoginOptions.js' import { isUserLocked } from '../isUserLocked.js' import { jwtSign } from '../jwt.js' -import { removeExpiredSessions } from '../removeExpiredSessions.js' +import { addSessionToUser } from '../sessions.js' import { authenticateLocalStrategy } from '../strategies/local/authenticate.js' import { incrementLoginAttempts } from '../strategies/local/incrementLoginAttempts.js' import { resetLoginAttempts } from '../strategies/local/resetLoginAttempts.js' @@ -285,34 +283,15 @@ export const loginOperation = async ( user, } - if (collectionConfig.auth.useSessions) { - // Add session to user - const newSessionID = uuid() - const now = new Date() - const tokenExpInMs = collectionConfig.auth.tokenExpiration * 1000 - const expiresAt = new Date(now.getTime() + tokenExpInMs) + const { sid } = await addSessionToUser({ + collectionConfig, + payload, + req, + user, + }) - const session = { id: newSessionID, createdAt: now, expiresAt } - - if (!user.sessions?.length) { - user.sessions = [session] - } else { - user.sessions = removeExpiredSessions(user.sessions) - user.sessions.push(session) - } - - await payload.db.updateOne({ - id: user.id, - collection: collectionConfig.slug, - data: user, - req, - returning: false, - }) - - user.collection = collectionConfig.slug - user._strategy = 'local-jwt' - - fieldsToSignArgs.sid = newSessionID + if (sid) { + fieldsToSignArgs.sid = sid } const fieldsToSign = getFieldsToSign(fieldsToSignArgs) diff --git a/packages/payload/src/auth/operations/refresh.ts b/packages/payload/src/auth/operations/refresh.ts index 7889ebc1b..037bcc4da 100644 --- a/packages/payload/src/auth/operations/refresh.ts +++ b/packages/payload/src/auth/operations/refresh.ts @@ -10,7 +10,7 @@ import { initTransaction } from '../../utilities/initTransaction.js' import { killTransaction } from '../../utilities/killTransaction.js' import { getFieldsToSign } from '../getFieldsToSign.js' import { jwtSign } from '../jwt.js' -import { removeExpiredSessions } from '../removeExpiredSessions.js' +import { removeExpiredSessions } from '../sessions.js' export type Result = { exp: number diff --git a/packages/payload/src/auth/removeExpiredSessions.ts b/packages/payload/src/auth/removeExpiredSessions.ts deleted file mode 100644 index c0dd7476d..000000000 --- a/packages/payload/src/auth/removeExpiredSessions.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { UserSession } from './types.js' - -export const removeExpiredSessions = (sessions: UserSession[]) => { - const now = new Date() - - return sessions.filter(({ expiresAt }) => { - const expiry = expiresAt instanceof Date ? expiresAt : new Date(expiresAt) - return expiry > now - }) -} diff --git a/packages/payload/src/auth/sessions.ts b/packages/payload/src/auth/sessions.ts new file mode 100644 index 000000000..1073599e2 --- /dev/null +++ b/packages/payload/src/auth/sessions.ts @@ -0,0 +1,67 @@ +import { v4 as uuid } from 'uuid' + +import type { SanitizedCollectionConfig } from '../collections/config/types.js' +import type { TypedUser } from '../index.js' +import type { Payload, PayloadRequest } from '../types/index.js' +import type { UserSession } from './types.js' + +/** + * Removes expired sessions from an array of sessions + */ +export const removeExpiredSessions = (sessions: UserSession[]) => { + const now = new Date() + + return sessions.filter(({ expiresAt }) => { + const expiry = expiresAt instanceof Date ? expiresAt : new Date(expiresAt) + return expiry > now + }) +} + +/** + * Adds a session to the user and removes expired sessions + * @returns The session ID (sid) if sessions are used + */ +export const addSessionToUser = async ({ + collectionConfig, + payload, + req, + user, +}: { + collectionConfig: SanitizedCollectionConfig + payload: Payload + req: PayloadRequest + user: TypedUser +}): Promise<{ sid?: string }> => { + let sid: string | undefined + if (collectionConfig.auth.useSessions) { + // Add session to user + sid = uuid() + const now = new Date() + const tokenExpInMs = collectionConfig.auth.tokenExpiration * 1000 + const expiresAt = new Date(now.getTime() + tokenExpInMs) + + const session = { id: sid, createdAt: now, expiresAt } + + if (!user.sessions?.length) { + user.sessions = [session] + } else { + user.sessions = removeExpiredSessions(user.sessions) + user.sessions.push(session) + } + + await payload.db.updateOne({ + id: user.id, + collection: collectionConfig.slug, + data: user, + req, + returning: false, + }) + + user.collection = collectionConfig.slug + user._strategy = 'local-jwt' + } + + return { + sid, + } +} diff --git a/packages/payload/src/exports/shared.ts b/packages/payload/src/exports/shared.ts index 92c50e991..8bdc3ead9 100644 --- a/packages/payload/src/exports/shared.ts +++ b/packages/payload/src/exports/shared.ts @@ -6,6 +6,7 @@ export { parseCookies, } from '../auth/cookies.js' export { getLoginOptions } from '../auth/getLoginOptions.js' +export { addSessionToUser, removeExpiredSessions } from '../auth/sessions.js' export { getFromImportMap } from '../bin/generateImportMap/utilities/getFromImportMap.js' export { parsePayloadComponent } from '../bin/generateImportMap/utilities/parsePayloadComponent.js' export { defaults as collectionDefaults } from '../collections/config/defaults.js'