connect-mongo
Version:
MongoDB session store for Express and Connect
1 lines • 34.2 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","names":["defaultCryptoOptions: ConcretCryptoOptions","merged: ConcretCryptoOptions","util","webcrypto","debug","unit: <T>(a: T) => T","result: session.SessionData","session","options: ConcretConnectMongoOptions<T>","_clientP: Promise<MongoClient>","MongoClient","parsed: unknown","result: T | undefined","s: InternalSessionType<T>","update: Record<string, unknown>","updateFields: {\n lastModified?: Date\n expires?: Date\n session?: T\n }","updateQuery: Record<string, unknown>","results: T[]"],"sources":["../src/lib/cryptoAdapters.ts","../src/lib/MongoStore.ts","../src/index.ts"],"sourcesContent":["import util from 'node:util'\nimport { webcrypto } from 'node:crypto'\nimport kruptein from 'kruptein'\n\nexport interface CryptoAdapter {\n encrypt: (unencryptedPayload: string) => Promise<string>\n decrypt: (encryptedPayload: string) => Promise<string>\n}\n\nexport type CryptoOptions = {\n secret: false | string\n algorithm?: string\n hashing?: string\n encodeas?: string\n key_size?: number\n iv_size?: number\n at_size?: number\n}\n\nexport type ConcretCryptoOptions = Required<CryptoOptions>\n\n/* eslint-disable camelcase */\nexport const defaultCryptoOptions: ConcretCryptoOptions = {\n secret: false,\n algorithm: 'aes-256-gcm',\n hashing: 'sha512',\n encodeas: 'base64',\n key_size: 32,\n iv_size: 16,\n at_size: 16,\n}\n/* eslint-enable camelcase */\n\nexport const createKrupteinAdapter = (\n options: CryptoOptions\n): CryptoAdapter => {\n const merged: ConcretCryptoOptions = {\n ...defaultCryptoOptions,\n ...options,\n }\n if (!merged.secret) {\n throw new Error('createKrupteinAdapter requires a non-empty secret')\n }\n const instance = kruptein(merged)\n const encrypt = util.promisify(instance.set).bind(instance)\n const decrypt = util.promisify(instance.get).bind(instance)\n\n return {\n async encrypt(plaintext: string): Promise<string> {\n const ciphertext = await encrypt(merged.secret as string, plaintext)\n return String(ciphertext)\n },\n async decrypt(ciphertext: string): Promise<string> {\n const plaintext = await decrypt(merged.secret as string, ciphertext)\n if (typeof plaintext === 'string') {\n return plaintext\n }\n return JSON.stringify(plaintext)\n },\n }\n}\n\nexport type WebCryptoEncoding = 'base64' | 'base64url' | 'hex'\n\nexport type WebCryptoAdapterOptions = {\n secret: string | ArrayBuffer | ArrayBufferView\n ivLength?: number\n encoding?: WebCryptoEncoding\n algorithm?: 'AES-GCM' | 'AES-CBC'\n salt?: string | ArrayBuffer | ArrayBufferView\n iterations?: number\n}\n\nconst encoder = new TextEncoder()\nconst decoder = new TextDecoder()\n\nconst toUint8Array = (\n input: string | ArrayBuffer | ArrayBufferView\n): Uint8Array => {\n if (typeof input === 'string') {\n return encoder.encode(input)\n }\n if (ArrayBuffer.isView(input)) {\n return new Uint8Array(input.buffer, input.byteOffset, input.byteLength)\n }\n if (input instanceof ArrayBuffer) {\n return new Uint8Array(input)\n }\n throw new TypeError('Unsupported secret type for Web Crypto adapter')\n}\n\nconst encodeBytes = (\n bytes: Uint8Array,\n encoding: WebCryptoEncoding\n): string => {\n switch (encoding) {\n case 'hex':\n return Buffer.from(bytes).toString('hex')\n case 'base64url':\n return Buffer.from(bytes).toString('base64url')\n case 'base64':\n default:\n return Buffer.from(bytes).toString('base64')\n }\n}\n\nconst decodeBytes = (\n payload: string,\n encoding: WebCryptoEncoding\n): Uint8Array => {\n switch (encoding) {\n case 'hex':\n return new Uint8Array(Buffer.from(payload, 'hex'))\n case 'base64url':\n return new Uint8Array(Buffer.from(payload, 'base64url'))\n case 'base64':\n default:\n return new Uint8Array(Buffer.from(payload, 'base64'))\n }\n}\n\nexport const createWebCryptoAdapter = ({\n secret,\n ivLength,\n encoding = 'base64',\n algorithm = 'AES-GCM',\n salt,\n iterations = 310000,\n}: WebCryptoAdapterOptions): CryptoAdapter => {\n if (!secret) {\n throw new Error('createWebCryptoAdapter requires a secret')\n }\n const { subtle } = webcrypto\n\n if (!subtle?.decrypt || !webcrypto?.getRandomValues) {\n throw new Error('Web Crypto API is not available in this runtime')\n }\n\n const resolvedIvLength = ivLength ?? (algorithm === 'AES-GCM' ? 12 : 16)\n const resolvedSalt =\n salt ?? encoder.encode('connect-mongo:webcrypto-default-salt')\n\n const deriveKey = async () => {\n const secretBytes = toUint8Array(secret)\n const baseKey = await subtle.importKey(\n 'raw',\n secretBytes,\n 'PBKDF2',\n false,\n ['deriveKey']\n )\n const saltBytes =\n typeof resolvedSalt === 'string'\n ? encoder.encode(resolvedSalt)\n : toUint8Array(resolvedSalt)\n return subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: saltBytes,\n iterations,\n hash: 'SHA-256',\n },\n baseKey,\n { name: algorithm, length: 256 },\n false,\n ['encrypt', 'decrypt']\n )\n }\n\n const keyPromise = deriveKey()\n\n return {\n async encrypt(plaintext: string): Promise<string> {\n const key = await keyPromise\n const iv = webcrypto.getRandomValues(new Uint8Array(resolvedIvLength))\n const data = encoder.encode(plaintext)\n const encrypted = await subtle.encrypt({ name: algorithm, iv }, key, data)\n const cipherBytes = new Uint8Array(encrypted)\n const combined = new Uint8Array(resolvedIvLength + cipherBytes.byteLength)\n combined.set(iv, 0)\n combined.set(cipherBytes, resolvedIvLength)\n return encodeBytes(combined, encoding)\n },\n async decrypt(ciphertext: string): Promise<string> {\n const key = await keyPromise\n const combined = decodeBytes(ciphertext, encoding)\n const iv = combined.slice(0, resolvedIvLength)\n const data = combined.slice(resolvedIvLength)\n const decrypted = await subtle.decrypt({ name: algorithm, iv }, key, data)\n return decoder.decode(decrypted)\n },\n }\n}\n","import assert from 'node:assert/strict'\nimport * as session from 'express-session'\nimport {\n Collection,\n MongoClient,\n MongoClientOptions,\n WriteConcernSettings,\n} from 'mongodb'\nimport Debug from 'debug'\nimport { createKrupteinAdapter } from './cryptoAdapters.js'\nimport type { CryptoAdapter, CryptoOptions } from './cryptoAdapters.js'\nexport type {\n CryptoAdapter,\n CryptoOptions,\n WebCryptoAdapterOptions,\n} from './cryptoAdapters.js'\nexport {\n createKrupteinAdapter,\n createWebCryptoAdapter,\n} from './cryptoAdapters.js'\n\nconst debug = Debug('connect-mongo')\n\ntype StoredSessionValue = session.SessionData | string\ntype Serialize<T extends session.SessionData> = (\n session: T\n) => StoredSessionValue\ntype Unserialize<T extends session.SessionData> = (\n payload: StoredSessionValue\n) => T\ntype TransformFunctions<T extends session.SessionData> = {\n serialize: Serialize<T>\n unserialize: Unserialize<T>\n}\n\nexport type ConnectMongoOptions<\n T extends session.SessionData = session.SessionData,\n> = {\n mongoUrl?: string\n clientPromise?: Promise<MongoClient>\n client?: MongoClient\n collectionName?: string\n mongoOptions?: MongoClientOptions\n dbName?: string\n ttl?: number\n touchAfter?: number\n stringify?: boolean\n createAutoRemoveIdx?: boolean\n autoRemove?: 'native' | 'interval' | 'disabled'\n autoRemoveInterval?: number\n serialize?: Serialize<T>\n unserialize?: Unserialize<T>\n writeOperationOptions?: WriteConcernSettings\n transformId?: (sid: string) => string\n crypto?: CryptoOptions\n cryptoAdapter?: CryptoAdapter\n timestamps?: boolean\n}\n\ntype ConcretConnectMongoOptions<\n T extends session.SessionData = session.SessionData,\n> = {\n mongoUrl?: string\n clientPromise?: Promise<MongoClient>\n client?: MongoClient\n collectionName: string\n mongoOptions: MongoClientOptions\n dbName?: string\n ttl: number\n createAutoRemoveIdx?: boolean\n autoRemove: 'native' | 'interval' | 'disabled'\n autoRemoveInterval: number\n touchAfter: number\n stringify: boolean\n timestamps: boolean\n serialize?: Serialize<T>\n unserialize?: Unserialize<T>\n writeOperationOptions?: WriteConcernSettings\n transformId?: (sid: string) => string\n crypto?: CryptoOptions\n cryptoAdapter?: CryptoAdapter | null\n}\n\ntype InternalSessionType<T extends session.SessionData> = {\n _id: string\n session: StoredSessionValue | T\n expires?: Date\n lastModified?: Date\n createdAt?: Date\n updatedAt?: Date\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-function\nconst noop = () => {}\nconst unit: <T>(a: T) => T = (a) => a\n\nfunction defaultSerializeFunction<T extends session.SessionData>(\n currentSession: T\n): T {\n const result: session.SessionData = {\n cookie: currentSession.cookie,\n } as session.SessionData\n Object.entries(currentSession).forEach(([key, value]) => {\n if (\n key === 'cookie' &&\n value &&\n typeof (value as { toJSON?: () => unknown }).toJSON === 'function'\n ) {\n result.cookie = (\n value as { toJSON: () => unknown }\n ).toJSON() as session.Cookie\n } else {\n ;(result as Record<string, unknown>)[key] = value as unknown\n }\n })\n\n return result as T\n}\n\nfunction computeTransformFunctions<T extends session.SessionData>(\n options: ConcretConnectMongoOptions<T>\n): TransformFunctions<T> {\n if (options.serialize || options.unserialize) {\n return {\n serialize: options.serialize || defaultSerializeFunction,\n unserialize: (options.unserialize || unit) as Unserialize<T>,\n }\n }\n\n if (options.stringify === false) {\n return {\n serialize: defaultSerializeFunction,\n unserialize: unit as Unserialize<T>,\n }\n }\n // Default case\n return {\n serialize: (value) => JSON.stringify(value),\n unserialize: (payload) => JSON.parse(payload as string) as T,\n }\n}\n\nfunction computeExpires(\n session: session.SessionData | undefined,\n fallbackTtlSeconds: number\n): Date {\n const cookie = session?.cookie as session.Cookie | undefined\n if (cookie?.expires) {\n return new Date(cookie.expires)\n }\n const now = Date.now()\n return new Date(now + fallbackTtlSeconds * 1000)\n}\n\nexport default class MongoStore<\n T extends session.SessionData = session.SessionData,\n>\n extends session.Store\n{\n private clientP: Promise<MongoClient>\n private readonly cryptoAdapter: CryptoAdapter | null = null\n private timer?: NodeJS.Timeout\n collectionP: Promise<Collection<InternalSessionType<T>>>\n private options: ConcretConnectMongoOptions<T>\n private transformFunctions: TransformFunctions<T>\n\n constructor({\n collectionName = 'sessions',\n ttl = 1209600,\n mongoOptions = {},\n autoRemove = 'native',\n autoRemoveInterval = 10,\n touchAfter = 0,\n stringify = true,\n timestamps = false,\n crypto,\n cryptoAdapter,\n ...required\n }: ConnectMongoOptions<T>) {\n super()\n debug('create MongoStore instance')\n const options: ConcretConnectMongoOptions<T> = {\n collectionName,\n ttl,\n mongoOptions,\n autoRemove,\n autoRemoveInterval,\n touchAfter,\n stringify,\n timestamps,\n crypto,\n cryptoAdapter: cryptoAdapter ?? null,\n ...required,\n }\n // Check params\n assert(\n options.mongoUrl || options.clientPromise || options.client,\n 'You must provide either mongoUrl|clientPromise|client in options'\n )\n assert(\n options.createAutoRemoveIdx === null ||\n options.createAutoRemoveIdx === undefined,\n 'options.createAutoRemoveIdx has been reverted to autoRemove and autoRemoveInterval'\n )\n assert(\n !options.autoRemoveInterval || options.autoRemoveInterval <= 71582,\n /* (Math.pow(2, 32) - 1) / (1000 * 60) */ 'autoRemoveInterval is too large. options.autoRemoveInterval is in minutes but not seconds nor mills'\n )\n if (crypto !== undefined && cryptoAdapter !== undefined) {\n throw new Error(\n 'Provide either the legacy crypto option or cryptoAdapter, not both'\n )\n }\n\n const legacyCryptoRequested =\n crypto !== undefined && crypto.secret !== false\n if (options.cryptoAdapter) {\n this.cryptoAdapter = options.cryptoAdapter\n } else if (legacyCryptoRequested) {\n this.cryptoAdapter = createKrupteinAdapter(options.crypto!)\n }\n options.cryptoAdapter = this.cryptoAdapter\n\n this.transformFunctions = computeTransformFunctions(options)\n let _clientP: Promise<MongoClient>\n if (options.mongoUrl) {\n _clientP = MongoClient.connect(options.mongoUrl, options.mongoOptions)\n } else if (options.clientPromise) {\n _clientP = options.clientPromise\n } else if (options.client) {\n _clientP = Promise.resolve(options.client)\n } else {\n throw new Error('Cannot init client. Please provide correct options')\n }\n assert(!!_clientP, 'Client is null|undefined')\n this.clientP = _clientP\n this.options = options\n this.collectionP = _clientP.then(async (con) => {\n const collection = con\n .db(options.dbName)\n .collection<InternalSessionType<T>>(options.collectionName)\n await this.setAutoRemove(collection)\n return collection\n })\n }\n\n static create<U extends session.SessionData = session.SessionData>(\n options: ConnectMongoOptions<U>\n ): MongoStore<U> {\n return new MongoStore<U>(options)\n }\n\n private setAutoRemove(\n collection: Collection<InternalSessionType<T>>\n ): Promise<unknown> {\n const removeQuery = () => ({\n expires: {\n $lt: new Date(),\n },\n })\n switch (this.options.autoRemove) {\n case 'native':\n debug('Creating MongoDB TTL index')\n return collection.createIndex(\n { expires: 1 },\n {\n background: true,\n expireAfterSeconds: 0,\n }\n )\n case 'interval': {\n debug('create Timer to remove expired sessions')\n const runIntervalRemove = () =>\n collection\n .deleteMany(removeQuery(), {\n writeConcern: {\n w: 0,\n },\n })\n .catch((err) => {\n debug(\n 'autoRemove interval cleanup failed: %s',\n (err as Error)?.message ?? err\n )\n })\n this.timer = setInterval(\n () => {\n void runIntervalRemove()\n },\n this.options.autoRemoveInterval * 1000 * 60\n )\n this.timer.unref()\n return Promise.resolve()\n }\n case 'disabled':\n default:\n return Promise.resolve()\n }\n }\n\n private computeStorageId(sessionId: string) {\n if (\n this.options.transformId &&\n typeof this.options.transformId === 'function'\n ) {\n return this.options.transformId(sessionId)\n }\n return sessionId\n }\n\n /**\n * Normalize payload before encryption so decrypt can restore the original\n * serialized session value.\n */\n private serializeForCrypto(payload: StoredSessionValue): string {\n if (typeof payload === 'string') {\n return payload\n }\n try {\n return JSON.stringify(payload)\n } catch (error) {\n debug(\n 'Falling back to string serialization for crypto payload: %O',\n error\n )\n return String(payload)\n }\n }\n\n private parseDecryptedPayload(plaintext: string): StoredSessionValue {\n let parsed: unknown\n try {\n parsed = JSON.parse(plaintext)\n } catch {\n parsed = plaintext\n }\n\n if (this.options.stringify === false) {\n if (typeof parsed === 'string') {\n try {\n return JSON.parse(parsed) as StoredSessionValue\n } catch {\n return parsed as StoredSessionValue\n }\n }\n return parsed as StoredSessionValue\n }\n\n // Default stringify path expects a string for unserialize(JSON.parse)\n if (!this.options.serialize && !this.options.unserialize) {\n return typeof parsed === 'string'\n ? (parsed as StoredSessionValue)\n : (JSON.stringify(parsed) as StoredSessionValue)\n }\n\n return parsed as StoredSessionValue\n }\n\n /**\n * Decrypt given session data\n * @param session session data to be decrypt. Mutate the input session.\n */\n private async decryptSession(\n sessionDoc: InternalSessionType<T> | undefined | null\n ) {\n if (\n this.cryptoAdapter &&\n sessionDoc &&\n typeof sessionDoc.session === 'string'\n ) {\n const plaintext = await this.cryptoAdapter.decrypt(sessionDoc.session)\n sessionDoc.session = this.parseDecryptedPayload(plaintext)\n }\n }\n\n /**\n * Get a session from the store given a session ID (sid)\n * @param sid session ID\n */\n get(sid: string, callback: (err: any, session?: T | null) => void): void {\n ;(async () => {\n try {\n debug(`MongoStore#get=${sid}`)\n const collection = await this.collectionP\n const sessionDoc = await collection.findOne({\n _id: this.computeStorageId(sid),\n $or: [\n { expires: { $exists: false } },\n { expires: { $gt: new Date() } },\n ],\n })\n if (this.cryptoAdapter && sessionDoc) {\n try {\n await this.decryptSession(sessionDoc)\n } catch (error) {\n callback(error)\n return\n }\n }\n let result: T | undefined\n if (sessionDoc) {\n result = this.transformFunctions.unserialize(sessionDoc.session)\n if (this.options.touchAfter > 0 && sessionDoc.lastModified) {\n ;(result as T & { lastModified?: Date }).lastModified =\n sessionDoc.lastModified\n }\n }\n this.emit('get', sid)\n callback(null, result ?? null)\n } catch (error) {\n callback(error)\n }\n })()\n }\n\n /**\n * Upsert a session into the store given a session ID (sid) and session (session) object.\n * @param sid session ID\n * @param session session object\n */\n set(sid: string, session: T, callback: (err: any) => void = noop): void {\n ;(async () => {\n try {\n debug(`MongoStore#set=${sid}`)\n // Removing the lastModified prop from the session object before update\n if (this.options.touchAfter > 0 && session?.lastModified) {\n delete (session as T & { lastModified?: Date }).lastModified\n }\n const s: InternalSessionType<T> = {\n _id: this.computeStorageId(sid),\n session: this.transformFunctions.serialize(session),\n }\n // Expire handling\n s.expires = computeExpires(session, this.options.ttl)\n // Last modify handling\n if (this.options.touchAfter > 0) {\n s.lastModified = new Date()\n }\n if (this.cryptoAdapter) {\n const plaintext = this.serializeForCrypto(s.session)\n const encrypted = await this.cryptoAdapter.encrypt(plaintext)\n s.session = encrypted as StoredSessionValue\n }\n const collection = await this.collectionP\n const update: Record<string, unknown> = { $set: s }\n if (this.options.timestamps) {\n update.$setOnInsert = { createdAt: new Date() }\n update.$currentDate = { updatedAt: true }\n }\n const rawResp = await collection.updateOne({ _id: s._id }, update, {\n upsert: true,\n writeConcern: this.options.writeOperationOptions,\n })\n if (rawResp.upsertedCount > 0) {\n this.emit('create', sid)\n } else {\n this.emit('update', sid)\n }\n this.emit('set', sid)\n } catch (error) {\n return callback(error)\n }\n return callback(null)\n })()\n }\n\n touch(\n sid: string,\n session: T & { lastModified?: Date },\n callback: (err: any) => void = noop\n ): void {\n ;(async () => {\n try {\n debug(`MongoStore#touch=${sid}`)\n const updateFields: {\n lastModified?: Date\n expires?: Date\n session?: T\n } = {}\n const touchAfter = this.options.touchAfter * 1000\n const lastModified = session.lastModified\n ? session.lastModified.getTime()\n : 0\n const currentDate = new Date()\n\n // If the given options has a touchAfter property, check if the\n // current timestamp - lastModified timestamp is bigger than\n // the specified, if it's not, don't touch the session\n if (touchAfter > 0 && lastModified > 0) {\n const timeElapsed = currentDate.getTime() - lastModified\n if (timeElapsed < touchAfter) {\n debug(`Skip touching session=${sid}`)\n return callback(null)\n }\n updateFields.lastModified = currentDate\n }\n\n updateFields.expires = computeExpires(session, this.options.ttl)\n const collection = await this.collectionP\n const updateQuery: Record<string, unknown> = { $set: updateFields }\n if (this.options.timestamps) {\n updateQuery.$currentDate = { updatedAt: true }\n }\n const rawResp = await collection.updateOne(\n { _id: this.computeStorageId(sid) },\n updateQuery,\n { writeConcern: this.options.writeOperationOptions }\n )\n if (rawResp.matchedCount === 0) {\n return callback(new Error('Unable to find the session to touch'))\n } else {\n this.emit('touch', sid, session)\n return callback(null)\n }\n } catch (error) {\n return callback(error)\n }\n })()\n }\n\n /**\n * Get all sessions in the store as an array\n */\n all(\n callback: (err: any, obj?: T[] | { [sid: string]: T } | null) => void\n ): void {\n ;(async () => {\n try {\n debug('MongoStore#all()')\n const collection = await this.collectionP\n const sessions = collection.find({\n $or: [\n { expires: { $exists: false } },\n { expires: { $gt: new Date() } },\n ],\n })\n const results: T[] = []\n for await (const sessionDoc of sessions) {\n if (this.cryptoAdapter && sessionDoc) {\n await this.decryptSession(sessionDoc)\n }\n results.push(this.transformFunctions.unserialize(sessionDoc.session))\n }\n this.emit('all', results)\n callback(null, results)\n } catch (error) {\n callback(error)\n }\n })()\n }\n\n /**\n * Destroy/delete a session from the store given a session ID (sid)\n * @param sid session ID\n */\n destroy(sid: string, callback: (err: any) => void = noop): void {\n debug(`MongoStore#destroy=${sid}`)\n this.collectionP\n .then((colleciton) =>\n colleciton.deleteOne(\n { _id: this.computeStorageId(sid) },\n { writeConcern: this.options.writeOperationOptions }\n )\n )\n .then(() => {\n this.emit('destroy', sid)\n callback(null)\n })\n .catch((err) => callback(err))\n }\n\n /**\n * Get the count of all sessions in the store\n */\n length(callback: (err: any, length: number) => void): void {\n debug('MongoStore#length()')\n this.collectionP\n .then((collection) => collection.countDocuments())\n .then((c) => callback(null, c))\n .catch((err: unknown) => callback(err, 0))\n }\n\n /**\n * Delete all sessions from the store.\n */\n clear(callback: (err: any) => void = noop): void {\n debug('MongoStore#clear()')\n this.collectionP\n .then((collection) =>\n collection.deleteMany(\n {},\n { writeConcern: this.options.writeOperationOptions }\n )\n )\n .then(() => callback(null))\n .catch((err: unknown) => {\n const message = (err as Error | undefined)?.message ?? ''\n // NamespaceNotFound (code 26) occurs if the collection was dropped earlier; treat as success to keep clear() idempotent.\n if (\n (err as { code?: number })?.code === 26 ||\n /ns not found/i.test(message)\n ) {\n callback(null)\n return\n }\n callback(err)\n })\n }\n\n /**\n * Close database connection\n */\n close(): Promise<void> {\n debug('MongoStore#close()')\n if (this.timer) {\n clearInterval(this.timer)\n this.timer = undefined\n }\n return this.clientP.then((c) => c.close())\n }\n}\n","import MongoStore from './lib/MongoStore.js'\nexport {\n createKrupteinAdapter,\n createWebCryptoAdapter,\n type CryptoAdapter,\n type CryptoOptions,\n type WebCryptoAdapterOptions,\n} from './lib/cryptoAdapters.js'\n\nexport default MongoStore\nexport { MongoStore }\n"],"mappings":"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"}