@mgcrea/fastify-session
Version:
Session plugin for fastify written in TypeScript supporting both stateless and stateful sessions
1 lines • 29.7 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/crypto/Hmac.ts","../src/utils/buffer.ts","../src/utils/crypto.ts","../src/utils/error.ts","../src/crypto/SessionCrypto.ts","../src/session/Session.ts","../src/store/MemoryStore.ts","../src/store/SessionStore.ts","../src/plugin.ts"],"sourcesContent":["import fastifyPlugin from \"fastify-plugin\";\nimport { plugin } from \"./plugin\";\n\nexport { HMAC, Hmac, SessionCrypto, type SecretKey } from \"./crypto\";\nexport type { FastifySessionOptions } from \"./plugin\";\nexport { Session, type SessionData } from \"./session\";\nexport { MemoryStore, SessionStore } from \"./store\";\nexport { createError, CRYPTO_SPLIT_CHAR, ErrorWithCode } from \"./utils\";\n\nexport default fastifyPlugin(plugin, {\n fastify: \">=4.x\",\n name: \"fastify-session\",\n});\n","import crypto, { type BinaryToTextEncoding } from \"crypto\";\nimport { asBuffer, createError, CRYPTO_SPLIT_CHAR } from \"../utils\";\nimport type { SecretKey, SessionCrypto } from \"./SessionCrypto\";\n\nexport class Hmac implements SessionCrypto {\n public readonly protocol = \"/hmac\";\n public readonly stateless = false;\n private readonly algorithm: string;\n private readonly encoding: BinaryToTextEncoding;\n constructor(encoding: BinaryToTextEncoding = \"base64\", algorithm = \"sha256\") {\n this.encoding = encoding;\n this.algorithm = algorithm;\n }\n public deriveSecretKeys(key?: SecretKey, secret?: Buffer | string): Buffer[] {\n if (key) {\n return sanitizeSecretKeys(key, this.encoding);\n } else if (secret) {\n return [asBuffer(secret)];\n }\n throw createError(\"SecretKeyDerivation\", \"Failed to derive keys from options\");\n }\n public sealMessage(message: Buffer, secretKey: Buffer): string {\n return (\n message.toString(this.encoding) +\n CRYPTO_SPLIT_CHAR +\n crypto.createHmac(this.algorithm, secretKey).update(message).digest(this.encoding).replace(/=+$/, \"\")\n );\n }\n public unsealMessage(message: string, secretKeys: Buffer[]): { buffer: Buffer; rotated: boolean } {\n const splitCharIndex = message.lastIndexOf(CRYPTO_SPLIT_CHAR);\n if (splitCharIndex === -1) {\n throw createError(\"MalformedMessageError\", \"The message is malformed\");\n }\n const cleartext = Buffer.from(message.slice(0, splitCharIndex), this.encoding);\n // const signature = Buffer.from(message.slice(splitCharIndex + 1), this.encoding);\n // if (signature.length !== 64) {\n // throw createError('SignatureLengthError', 'The signature does not have the required length');\n // }\n\n let rotated = false;\n // const messageBuffer = Buffer.from(message);\n const success = secretKeys.some((secretKey, index) => {\n const signedBuffer = Buffer.from(this.sealMessage(cleartext, secretKey));\n const messageBuffer = Buffer.alloc(signedBuffer.length);\n messageBuffer.write(message);\n const verified = crypto.timingSafeEqual(signedBuffer, messageBuffer);\n rotated = verified && index > 0;\n return verified;\n });\n\n if (!success) {\n throw createError(\"VerifyError\", \"Unable to verify\");\n }\n\n return { buffer: cleartext, rotated };\n }\n}\n\nexport const HMAC = new Hmac();\n\nconst sanitizeSecretKeys = (key: SecretKey, encoding: BufferEncoding = \"base64\"): Buffer[] => {\n const secretKeys: Buffer[] = Array.isArray(key)\n ? key.map((v) => asBuffer(v, encoding))\n : [asBuffer(key, encoding)];\n return secretKeys;\n};\n","export const asBuffer = (maybeBuffer: Buffer | string, encoding: BufferEncoding = \"ascii\"): Buffer =>\n Buffer.isBuffer(maybeBuffer) ? maybeBuffer : Buffer.from(maybeBuffer, encoding);\n","import { randomBytes } from \"crypto\";\n\nexport const CRYPTO_SPLIT_CHAR = \".\";\n\nexport const generateRandomKey = (): Buffer => {\n return randomBytes(32);\n};\n\nexport const generateSalt = (): Buffer => {\n return randomBytes(32);\n};\n","export class ErrorWithCode extends Error {\n code: string;\n constructor(code: string, message?: string) {\n super(`${code}: ${message}`); // 'Error' breaks prototype chain here\n Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain\n this.code = code;\n }\n}\n\nexport const createError = (code: string, message?: string): ErrorWithCode =>\n new ErrorWithCode(code, message);\n","export type SecretKey = Buffer | string | (Buffer | string)[];\n\nexport abstract class SessionCrypto {\n abstract readonly protocol: string;\n abstract readonly stateless: boolean;\n abstract deriveSecretKeys(key?: SecretKey, secret?: Buffer | string, salt?: Buffer | string): Buffer[];\n // Sign or encrypt a message (eg. stringified to cookie)\n abstract sealMessage(message: Buffer, secretKey: Buffer): string;\n // Verify or decrypt a message (eg. stringified from cookie)\n abstract unsealMessage(message: string, secretKeys: Buffer[]): { buffer: Buffer; rotated: boolean };\n}\n","import type { CookieSerializeOptions } from \"@fastify/cookie\";\nimport { nanoid } from \"nanoid\";\nimport assert from \"node:assert\";\nimport { HMAC } from \"../crypto/Hmac\";\nimport type { SessionCrypto } from \"../crypto/SessionCrypto\";\nimport { MEMORY_STORE, SessionStore } from \"../store\";\nimport { createError } from \"../utils\";\nimport type { SessionData } from \"./SessionData\";\n\nexport const kSessionData = Symbol(\"kSessionData\");\nexport const kCookieOptions = Symbol(\"kCookieOptions\");\n\nexport type SessionConfiguration = {\n cookieOptions?: CookieSerializeOptions;\n crypto?: SessionCrypto;\n store?: SessionStore | undefined;\n secretKeys: Buffer[];\n};\n\nexport type SessionOptions = CookieSerializeOptions & {\n id?: string;\n};\n\n/**\n * The Session class is responsible for managing user session data.\n * It can operate in a stateful or stateless mode depending on the configuration.\n */\nexport class Session<T extends SessionData = SessionData> {\n // Session metadata\n public readonly id: string;\n public created = false;\n public rotated = false;\n public changed = false;\n public deleted = false;\n public saved = false;\n public skipped = false;\n\n // Private instance fields\n #sessionData: Partial<T>;\n #cookieOptions: CookieSerializeOptions;\n #expiry: number | null = null; // expiration timestamp in ms\n\n // Private static fields\n static #secretKeys: Buffer[];\n static #sessionCrypto: SessionCrypto;\n static #sessionStore?: SessionStore;\n static #globalCookieOptions: CookieSerializeOptions;\n static #configured = false;\n\n /**\n * This method is used to setup the Session class with global options.\n */\n static configure({\n secretKeys,\n crypto = HMAC,\n store = MEMORY_STORE,\n cookieOptions = {},\n }: SessionConfiguration): void {\n Session.#secretKeys = secretKeys;\n Session.#sessionCrypto = crypto;\n Session.#sessionStore = store;\n Session.#globalCookieOptions = cookieOptions;\n Session.#configured = true;\n }\n\n /**\n * Private constructor - instances should be created via the static `create` method\n */\n private constructor(data?: Partial<T>, options: SessionOptions = {}) {\n const { id = nanoid(), ...cookieOptions } = options;\n this.#sessionData = data ?? {};\n this.#cookieOptions = { ...Session.#globalCookieOptions, ...cookieOptions };\n this.id = id;\n this.created = !data;\n }\n\n /**\n * Create a new session instance.\n * Checks if the class is configured before creating a session.\n */\n static async create<T extends SessionData = SessionData>(\n data?: Partial<T>,\n options: SessionOptions = {},\n ): Promise<Session> {\n if (!Session.#configured) {\n throw createError(\n \"MissingConfiguration\",\n \"Session is not configured. Please call Session.configure before creating a Session instance.\",\n );\n }\n const session = new Session(data, options);\n await session.touch();\n return session;\n }\n\n /**\n * Decoding\n */\n\n /**\n * Create a Session from a serialized cookie.\n * Determines the type of the session (stateful/stateless) and delegates to the appropriate method.\n */\n static async fromCookie(cookie: string): Promise<Session> {\n const { buffer: cleartext, rotated } = Session.#sessionCrypto.unsealMessage(cookie, Session.#secretKeys);\n\n if (Session.#sessionCrypto.stateless) {\n return await Session.fromStatelessCookie(cleartext.toString(), rotated);\n }\n return await Session.fromStatefulCookie(cleartext.toString(), rotated);\n }\n\n /**\n * Create a Session from a stateless cookie. The session data is all stored in the cookie.\n */\n private static async fromStatelessCookie(payload: string, rotated: boolean): Promise<Session> {\n let data;\n try {\n data = JSON.parse(payload) as SessionData;\n } catch (error) {\n throw createError(\n \"InvalidData\",\n `Failed to parse session data from cookie. Original error: ${String(error)}`,\n );\n }\n const session = await Session.create(data, { id: data.id });\n session.rotated = rotated;\n return session;\n }\n\n /**\n * Create a Session from a stateful cookie. The cookie only contains the session id.\n * The rest of the session data is retrieved from the session store.\n */\n private static async fromStatefulCookie(sessionId: string, rotated: boolean): Promise<Session> {\n assert(Session.#sessionStore);\n const result = await Session.#sessionStore.get(sessionId);\n if (!result) {\n throw createError(\"SessionNotFound\", \"did not found a matching session in the store\");\n }\n const [data, expiry] = result;\n if (expiry && expiry <= Date.now()) {\n throw createError(\"ExpiredSession\", \"the store returned an expired session\");\n }\n const session = await Session.create(data, {\n id: sessionId,\n expires: expiry ? new Date(expiry) : undefined,\n });\n session.rotated = rotated;\n return session;\n }\n\n /**\n * Encoding\n */\n\n /**\n * Serialize the Session into a cookie.\n * The format of the cookie depends on whether the session is stateful or stateless.\n */\n // eslint-disable-next-line @typescript-eslint/require-await\n async toCookie(): Promise<string> {\n const buffer = Buffer.from(\n Session.#sessionCrypto.stateless ? JSON.stringify({ ...this.#sessionData, id: this.id }) : this.id,\n );\n if (!Session.#secretKeys[0]) {\n throw createError(\"MissingSecretKey\", \"Missing secret key for session encryption\");\n }\n return Session.#sessionCrypto.sealMessage(buffer, Session.#secretKeys[0]);\n }\n\n /**\n * Refresh the expiration time of the session. Only applicable for stateful sessions.\n */\n async touch(): Promise<void> {\n if (Session.#sessionCrypto.stateless) {\n return;\n }\n const { maxAge = Session.#globalCookieOptions.maxAge, expires = Session.#globalCookieOptions.expires } =\n this.#cookieOptions;\n if (maxAge) {\n const expiry = Date.now() + maxAge * 1000;\n // Get the longest lifespan between \"expires\" and \"maxAge\"\n this.#expiry = expires ? Math.max(expires.getTime(), expiry) : expiry;\n } else if (expires) {\n this.#expiry = expires.getTime();\n }\n assert(Session.#sessionStore);\n // Only relay touch to the store for existing sessions to propagate expiry changes\n if (!this.created && Session.#sessionStore.touch) {\n await Session.#sessionStore.touch(this.id, this.#expiry);\n }\n }\n\n /**\n * Delete the session.\n */\n async destroy(): Promise<void> {\n this.deleted = true;\n if (Session.#sessionCrypto.stateless) {\n return;\n }\n // Only relay destroy to the store for existing sessions that have not been saved\n if (this.created && !this.saved) {\n return;\n }\n assert(Session.#sessionStore);\n await Session.#sessionStore.destroy(this.id);\n }\n\n /**\n * Save the session data to the session store. Only applicable for stateful sessions.\n */\n async save(): Promise<void> {\n if (Session.#sessionCrypto.stateless) {\n return;\n }\n this.saved = true;\n // Save session to store with store-handled expiry\n assert(Session.#sessionStore);\n await Session.#sessionStore.set(this.id, this.#sessionData, this.#expiry);\n }\n\n get data(): SessionData {\n return this.#sessionData;\n }\n\n get expiry(): number | null {\n return this.#expiry;\n }\n\n /**\n * Get a value from the session data.\n */\n get<K extends keyof T = keyof T>(key: K): T[K] | undefined {\n return this.#sessionData[key];\n }\n\n set<K extends keyof T = keyof T>(key: K, value: T[K]): void {\n this.#sessionData[key] = value;\n this.changed = true;\n }\n\n get options(): CookieSerializeOptions {\n return this.#cookieOptions;\n }\n\n /**\n * Update the session cookie options.\n */\n setOptions(options: CookieSerializeOptions): void {\n Object.assign(this.#cookieOptions, options);\n }\n\n /**\n * Check if the session data is empty.\n */\n isEmpty(): boolean {\n return Object.keys(this.#sessionData).length === 0;\n }\n}\n","/* eslint-disable @typescript-eslint/require-await */\n\nimport { EventEmitter } from \"events\";\nimport type { SessionData, SessionStore } from \"./SessionStore\";\n\ntype StoredData<T> = [T, number | null]; // [session data, expiry time in ms]\nexport type MemoryStoreOptions<T> = { store?: Map<string, StoredData<T>>; prefix?: string };\n\nexport class MemoryStore<T extends SessionData = SessionData> extends EventEmitter implements SessionStore {\n private store: Map<string, StoredData<T>>;\n private readonly prefix: string;\n\n constructor({ store = new Map(), prefix = \"sess:\" }: MemoryStoreOptions<T> = {}) {\n super();\n this.store = store;\n this.prefix = prefix;\n }\n\n private getKey(sessionId: string) {\n return `${this.prefix}${sessionId}`;\n }\n\n async set(sessionId: string, session: T, expiry: number | null = null): Promise<void> {\n this.store.set(this.getKey(sessionId), [session, expiry]);\n }\n\n async get(sessionId: string): Promise<[T, number | null] | null> {\n const result = this.store.get(this.getKey(sessionId)) ?? null;\n if (!result) {\n return null;\n }\n const [session, expiry] = result;\n if (expiry && expiry <= Date.now()) {\n return null;\n }\n return [session, expiry];\n }\n\n async destroy(sessionId: string): Promise<void> {\n this.store.delete(this.getKey(sessionId));\n }\n\n async touch(sessionId: string, expiry: number | null = null): Promise<void> {\n const sessionData = await this.get(sessionId);\n if (!sessionData) {\n return;\n }\n const [session] = sessionData;\n await this.set(sessionId, session, expiry);\n }\n\n async all(): Promise<Record<string, SessionData>> {\n return [...this.store.entries()].reduce<Record<string, SessionData>>((soFar, [k, v]) => {\n soFar[k] = v[0];\n return soFar;\n }, {});\n }\n}\n\nexport const MEMORY_STORE = new MemoryStore();\n","import type { SessionData } from \"../session/SessionData\";\nexport type { SessionData };\n\nexport abstract class SessionStore {\n // Gets the session from the store given a session ID.\n abstract get(sid: string): Promise<[SessionData, number | null] | null>;\n\n // Upsert a session in the store given a session ID and `SessionData`.\n abstract set(sid: string, session: SessionData, expiry?: number | null): Promise<void>;\n\n // Destroys the session with the given session ID.\n abstract destroy(sid: string): Promise<void>;\n\n // Returns all sessions in the store\n async all?(): Promise<SessionData[] | Record<string, SessionData> | null>;\n\n // Returns the amount of sessions in the store.\n async length?(): Promise<number>;\n\n // Delete all sessions from the store.\n async clear?(): Promise<void>;\n\n // \"Touches\" a given session, resetting the idle timer.\n async touch?(sid: string, expiry?: number | null): Promise<void>;\n}\n","/* eslint-disable @typescript-eslint/require-await */\nimport type { CookieSerializeOptions } from \"@fastify/cookie\";\nimport type { FastifyPluginAsync, FastifyRequest } from \"fastify\";\nimport { HMAC, SessionCrypto, type SecretKey } from \"./crypto\";\nimport { Session } from \"./session\";\nimport type { SessionStore } from \"./store\";\n\nexport const DEFAULT_COOKIE_NAME = \"Session\";\nexport const DEFAULT_COOKIE_PATH = \"/\";\n\nexport type FastifySessionOptions = {\n salt?: Buffer | string;\n secret?: Buffer | string;\n key?: SecretKey;\n cookieName?: string;\n cookie?: CookieSerializeOptions;\n store?: SessionStore;\n crypto?: SessionCrypto;\n saveUninitialized?: boolean;\n logBindings?: Record<string, unknown>;\n};\n\nexport const plugin: FastifyPluginAsync<FastifySessionOptions> = async (\n fastify,\n options = {},\n): Promise<void> => {\n const {\n key,\n secret,\n salt,\n cookieName = DEFAULT_COOKIE_NAME,\n cookie: cookieOptions = {},\n store,\n crypto = HMAC as SessionCrypto,\n saveUninitialized = true,\n logBindings = { plugin: \"fastify-session\" },\n } = options;\n\n if (!key && !secret) {\n throw new Error(\"key or secret must specified\");\n }\n if (!crypto) {\n throw new Error(\"invalid crypto specified\");\n }\n\n if (!cookieOptions.path) {\n cookieOptions.path = DEFAULT_COOKIE_PATH;\n }\n const secretKeys: Buffer[] = crypto.deriveSecretKeys(key, secret, salt);\n Session.configure({ cookieOptions, secretKeys, store, crypto });\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n fastify.decorateRequest(\"session\", null!);\n async function destroySession(this: FastifyRequest) {\n if (!this.session) {\n return;\n }\n await this.session.destroy();\n }\n fastify.decorateRequest(\"destroySession\", destroySession);\n\n // decode/create a session for every request\n fastify.addHook(\"onRequest\", async (request) => {\n const { cookies, log } = request;\n const bindings = { ...logBindings, hook: \"onRequest\" };\n\n const cookie = cookies[cookieName];\n if (!cookie) {\n request.session = await Session.create();\n log.debug(\n { ...bindings, sessionId: request.session.id },\n \"There was no cookie, created an empty session\",\n );\n return;\n }\n\n try {\n log.debug(bindings, \"Found an existing cookie, attempting to decode session ...\");\n request.session = await Session.fromCookie(cookie);\n log.debug({ ...bindings, sessionId: request.session.id }, \"Session successfully decoded\");\n return;\n } catch (err) {\n request.session = await Session.create();\n log.warn(\n { ...bindings, err, sessionId: request.session.id },\n `Failed to decode existing cookie, created an empty session`,\n );\n return;\n }\n });\n\n // encode a cookie\n fastify.addHook(\"onSend\", async (request, reply) => {\n const { session, log } = request;\n const bindings = { ...logBindings, hook: \"onSend\" };\n\n if (!session) {\n log.debug(bindings, \"There was no session, leaving it as is\");\n return;\n } else if (session.deleted) {\n reply.setCookie(cookieName, \"\", {\n ...session.options,\n expires: new Date(0),\n maxAge: 0,\n });\n log.debug(\n { ...bindings, sessionId: session.id },\n `Deleted ${session.created ? \"newly created\" : \"existing\"} session`,\n );\n return;\n } else if (!saveUninitialized && session.isEmpty()) {\n log.debug(\n { ...bindings, sessionId: session.id },\n \"Created session is empty and won't be saved, leaving it as is\",\n );\n return;\n } else if (!session.changed && !session.created && !session.rotated) {\n log.debug(\n { ...bindings, sessionId: session.id },\n \"The existing session was not changed, leaving it as is\",\n );\n return;\n } else if (session.skipped) {\n log.debug({ ...bindings, sessionId: session.id }, \"Skipped session\");\n return;\n }\n\n if (session.created || session.changed) {\n log.debug(\n { ...bindings, sessionId: session.id },\n `About to save a ${session.created ? \"created\" : \"changed\"} session, saving ...`,\n );\n await session.save();\n log.debug(\n { ...bindings, sessionId: session.id },\n `${session.created ? \"Created\" : \"Changed\"} session successfully saved`,\n );\n }\n reply.setCookie(cookieName, await session.toCookie(), session.options);\n });\n};\n"],"mappings":"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","names":["crypto","crypto","assert","crypto","fastifyPlugin"]}