type SessionRecord = { sid: string; uid: string | null; claims: object; issuedAt: number; /** * Absolute TTL — after this the session is dead. */ expiresAt: number; /** * Refreshed on `touch`; drives the idle-TTL check. */ lastSeenAt: number; /** * Last fresh-auth timestamp (sudo mode). */ freshAt?: number | undefined; deviceLabel?: string | undefined; ip?: string | undefined; ua?: string | undefined; /** * Admin user ID if this is an impersonation. */ impersonatedBy?: string | undefined; isAnonymous: boolean; /** * `true` once `revoke` has been called. */ revoked: boolean; revokedReason?: string | undefined; }; type SessionStore = { get: (sid: string) => Promise; /** * Insert-or-replace. Also updates the reverse `user → set(sid)` index. */ put: (record: SessionRecord) => Promise; /** * Merge-patch a session. Returns the updated record, or `null` if the * sid isn't present. */ update: (sid: string, patch: Partial) => Promise; /** * Mark a single session as revoked. Returns `true` if it was already * in the store (whether previously revoked or not), `false` if not * present. */ revoke: (sid: string, reason?: string) => Promise; /** * Revoke every session belonging to `uid`. Returns the count revoked. */ revokeAllForUser: (uid: string, reason?: string) => Promise; /** * Revoke every session for `uid` other than `keepSid`. Returns the * count revoked. */ revokeAllExcept: (uid: string, keepSid: string, reason?: string) => Promise; /** * Return every non-revoked, non-expired session for `uid`, newest first. */ listByUser: (uid: string) => Promise; /** * Number of non-revoked, non-expired sessions for `uid`. Cheaper than * materialising the array when you only need the count. */ countActive: (uid: string) => Promise; /** * Optional cleanup — memory store starts a sweep timer that * `_stop()` cancels. Called from tests; production doesn't need it. */ _stop?: (() => void) | undefined; }; type CookieOptions = { domain?: string | undefined; path?: string | undefined; secure?: boolean | undefined; httpOnly?: boolean | undefined; sameSite?: "lax" | "strict" | "none" | undefined; /** * Seconds. */ maxAge?: number | undefined; expires?: Date | undefined; }; /** * @typedef {import('./token.js').SessionTokenPayload} SessionTokenPayload * @typedef {import('./stores/memory.js').SessionStore} SessionStore * @typedef {import('./stores/memory.js').SessionRecord} SessionRecord * @typedef {import('./cookie.js').CookieOptions} CookieOptions */ /** * @typedef {object} SessionEvents * @property {(session: Session) => void | Promise} [onIssue] * @property {(session: Session) => void | Promise} [onVerify] * @property {(oldId: string, session: Session) => void | Promise} [onRotate] * @property {(sessionId: string, reason?: string) => void | Promise} [onRevoke] * @property {(reason: string, req: any) => void | Promise} [onDeny] * @property {(payload: { userId: string | null, sessionId: string, reason: string, previous: object, current: object }) => void | Promise} [onSuspicious] */ /** * @typedef {object} SessionManagerConfig * @property {string | Buffer | Uint8Array | Array} secret * @property {string | number} ttl * @property {string | number} idleTtl * @property {CookieOptions & { name?: string }} [cookie] * @property {SessionStore} [store] * @property {boolean} [anonymous=false] * @property {string | number} [touchEvery] How often `verify` persists a * rolling `lastSeenAt` update to the * store. Defaults to 60s (or half the * idleTtl, whichever is smaller). * Larger values cut store write * traffic; the idle-timeout check * only ever errs by at most this * much. Must be shorter than idleTtl. * @property {number} [concurrentLimit] * @property {ReadonlyArray<'ip' | 'ua'>} [bindTo] Fingerprint binding. * @property {'strict' | 'soft'} [bindStrictness='strict'] Behaviour on fingerprint mismatch. * `strict` (default) — hard revoke, * verify returns null. * `soft` — fire `onSuspicious` with * `reason: 'fingerprint-mismatch'` but * let the request through. Useful for * mobile users who move between wifi * and 5G. * @property {boolean} [impersonation=false] Enable impersonate() API. * @property {string | number} [impersonationTtl='30m'] Absolute lifetime of an * impersonation session. Defaults to * 30 minutes — impersonation is a * high-risk mode; the tight window * matches AWS / GCP admin console * patterns and cuts audit surface. * @property {boolean} [deviceLabels=false] Auto-generate device labels from UA. * @property {SessionEvents} [events] Audit trail callbacks. * @property {boolean | { onDetected?: SessionEvents['onSuspicious'] }} [suspiciousActivity] * IP-change detection. */ /** * @typedef {object} IssueOptions * @property {string | null} [userId=null] * @property {object} [claims={}] * @property {string} [deviceLabel] * @property {boolean} [rememberMe=false] * @property {number} [now] * @property {any} [req] When present, used to sample fingerprint (ip/ua). */ /** * @typedef {object} Session * @property {string} id * @property {string | null} userId * @property {object} claims * @property {number} issuedAt * @property {number} expiresAt * @property {number} lastSeenAt * @property {number} [freshAt] * @property {string} [deviceLabel] * @property {string} [ip] * @property {string} [ua] * @property {string} [impersonatedBy] * @property {string} [impersonationReason] * @property {boolean} isAnonymous */ /** * @typedef {object} IssueResult * @property {string} token * @property {string} cookie * @property {Session} session */ /** * Create a session manager — the package's main entrypoint. Issues * sealed cookie/bearer tokens backed by a server-side store, and * exposes the verify / rotate / revoke lifecycle around them. * * The returned object's type is inferred — consumers get the full * method surface in the generated `.d.ts` without a hand-maintained * typedef drifting out of sync. * * @param {SessionManagerConfig} config */ declare function createSessionManager(config: SessionManagerConfig): { issue: (options?: IssueOptions$1) => Promise; verify: (req: any, options?: { now?: number; }) => Promise; touch: (sessionId: string, options?: { now?: number; }) => Promise; rotate: (req: any, options?: {}) => Promise<{ token: string; cookie: string; session: Session$1; previousId: any; }>; markFresh: (req: any, options?: { now?: number; }) => Promise<{ freshAt: number; }>; requireFreshAuth: (req: any, options?: { maxAgeSeconds: number; now?: number; }) => Promise; impersonate: (adminReq: any, targetUserId: string, options?: { ttl?: string | number; claims?: object; reason?: string; now?: number; }) => Promise; revoke: (req: any, options?: { reason?: string; now?: number; }) => Promise<{ cookie: string; revoked: boolean; }>; revokeById: (sessionId: string, options?: { reason?: string; }) => Promise; revokeAllForUser: (userId: string, options?: { reason?: string; }) => Promise; revokeAllExceptCurrent: (req: any, options?: { reason?: string; now?: number; }) => Promise; listActive: (userId: string) => Promise; upgrade: (req: any, userId: string, options?: { mergeClaims?: object; now?: number; }) => Promise; readonly cookieName: string; readonly store: SessionStore; extractToken: (req: any) => string | undefined; }; type SessionEvents = { onIssue?: ((session: Session$1) => void | Promise) | undefined; onVerify?: ((session: Session$1) => void | Promise) | undefined; onRotate?: ((oldId: string, session: Session$1) => void | Promise) | undefined; onRevoke?: ((sessionId: string, reason?: string) => void | Promise) | undefined; onDeny?: ((reason: string, req: any) => void | Promise) | undefined; onSuspicious?: ((payload: { userId: string | null; sessionId: string; reason: string; previous: object; current: object; }) => void | Promise) | undefined; }; type SessionManagerConfig = { secret: string | Buffer | Uint8Array | Array; ttl: string | number; idleTtl: string | number; cookie?: (CookieOptions & { name?: string; }) | undefined; store?: SessionStore | undefined; anonymous?: boolean | undefined; /** * How often `verify` persists a * rolling `lastSeenAt` update to the * store. Defaults to 60s (or half the * idleTtl, whichever is smaller). * Larger values cut store write * traffic; the idle-timeout check * only ever errs by at most this * much. Must be shorter than idleTtl. */ touchEvery?: string | number | undefined; concurrentLimit?: number | undefined; /** * Fingerprint binding. */ bindTo?: readonly ("ip" | "ua")[] | undefined; /** * Behaviour on fingerprint mismatch. * `strict` (default) — hard revoke, * verify returns null. * `soft` — fire `onSuspicious` with * `reason: 'fingerprint-mismatch'` but * let the request through. Useful for * mobile users who move between wifi * and 5G. */ bindStrictness?: "strict" | "soft" | undefined; /** * Enable impersonate() API. */ impersonation?: boolean | undefined; /** * Absolute lifetime of an * impersonation session. Defaults to * 30 minutes — impersonation is a * high-risk mode; the tight window * matches AWS / GCP admin console * patterns and cuts audit surface. */ impersonationTtl?: string | number | undefined; /** * Auto-generate device labels from UA. */ deviceLabels?: boolean | undefined; /** * Audit trail callbacks. */ events?: SessionEvents | undefined; /** * IP-change detection. */ suspiciousActivity?: boolean | { onDetected?: SessionEvents["onSuspicious"]; } | undefined; }; type IssueOptions$1 = { userId?: string | null | undefined; claims?: object | undefined; deviceLabel?: string | undefined; rememberMe?: boolean | undefined; now?: number | undefined; /** * When present, used to sample fingerprint (ip/ua). */ req?: any; }; type Session$1 = { id: string; userId: string | null; claims: object; issuedAt: number; expiresAt: number; lastSeenAt: number; freshAt?: number | undefined; deviceLabel?: string | undefined; ip?: string | undefined; ua?: string | undefined; impersonatedBy?: string | undefined; impersonationReason?: string | undefined; isAnonymous: boolean; }; type IssueResult$1 = { token: string; cookie: string; session: Session$1; }; /** * Fastify plugin factory. Registers preHandler + onSend hooks so that: * * 1. `request.session` is populated on every request (or `null` if * unauthenticated). * 2. `reply.setSessionCookie(token)` and `reply.clearSessionCookie()` * convenience methods are added to the reply, wiring the * `Set-Cookie` header automatically. * 3. `request.sessions` exposes the manager for handlers that need * the full API (rotate, requireFreshAuth, impersonate, …). * * @param {import('../manager.js').SessionManagerConfig | ReturnType} configOrManager */ declare function sessionPlugin(configOrManager: SessionManagerConfig | ReturnType): { manager: SessionManagerConfig | { issue: (options?: IssueOptions) => Promise; verify: (req: any, options?: { now?: number; }) => Promise; touch: (sessionId: string, options?: { now?: number; }) => Promise; rotate: (req: any, options?: {}) => Promise<{ token: string; cookie: string; session: Session$1; previousId: any; }>; markFresh: (req: any, options?: { now?: number; }) => Promise<{ freshAt: number; }>; requireFreshAuth: (req: any, options?: { maxAgeSeconds: number; now?: number; }) => Promise; impersonate: (adminReq: any, targetUserId: string, options?: { ttl?: string | number; claims?: object; reason?: string; now?: number; }) => Promise; revoke: (req: any, options?: { reason?: string; now?: number; }) => Promise<{ cookie: string; revoked: boolean; }>; revokeById: (sessionId: string, options?: { reason?: string; }) => Promise; revokeAllForUser: (userId: string, options?: { reason?: string; }) => Promise; revokeAllExceptCurrent: (req: any, options?: { reason?: string; now?: number; }) => Promise; listActive: (userId: string) => Promise; upgrade: (req: any, userId: string, options?: { mergeClaims?: object; now?: number; }) => Promise; readonly cookieName: string; readonly store: SessionStore; extractToken: (req: any) => string | undefined; }; plugin: Function; }; export { sessionPlugin as default, sessionPlugin };