UNPKG

koa-session

Version:

Koa cookie session middleware with external store support

284 lines 19.2 kB
import assert from 'node:assert'; import { debuglog } from 'node:util'; import { randomUUID } from 'node:crypto'; import { isClass } from 'is-type-of'; import z from 'zod'; import { ContextSession } from './context.js'; import util from './util.js'; const debug = debuglog('koa-session'); const GET_CONTEXT_SESSION = Symbol('get contextSession'); const CONTEXT_SESSION_INSTANCE = Symbol('contextSession instance'); export const SessionOptions = z.object({ /** * cookie key * Default is `koa.sess` */ key: z.string().default('koa.sess'), /** * maxAge in ms * Default is `86400000`, one day * If set to 'session' will result in a cookie that expires when session/browser is closed * * Warning: If a session cookie is stolen, this cookie will never expire */ maxAge: z.union([z.number(), z.literal('session')]).optional(), /** * automatically commit headers * Default is `true` */ autoCommit: z.boolean().default(true), /** * cookie value can overwrite or not * Default is `true` */ overwrite: z.boolean().default(true), /** * httpOnly or not * Default is `true` */ httpOnly: z.boolean().default(true), /** * signed or not * Default is `true` */ signed: z.boolean().default(true), /** * Force a session identifier cookie to be set on every response. * The expiration is reset to the original `maxAge`, resetting the expiration countdown. * Default is `false` */ rolling: z.boolean().default(false), /** * renew session when session is nearly expired, so we can always keep user logged in. * Default is `false` */ renew: z.boolean().default(false), /** * secure cookie * Default is `undefined`, will be set to `true` if the connection is over HTTPS, otherwise `false`. */ secure: z.boolean().optional(), /** * session cookie sameSite options * Default is `undefined`, meaning don't set it */ sameSite: z.string().optional(), /** * External key is used the cookie by default, * but you can use `options.externalKey` to customize your own external key methods. */ externalKey: z.object({ /** * get the external key * `(ctx) => string` */ get: z.function() .args(z.any()) .returns(z.string()), /** * set the external key * `(ctx, key) => void` */ set: z.function() .args(z.any(), z.string()) .returns(z.void()), }).optional(), /** * session storage is dependent on your external store */ store: z.object({ /** * get session data by key * `(key, maxAge, { rolling, ctx }) => sessionData | Promise<sessionData>` */ get: z.function() .args(z.string(), z.number(), z.object({ rolling: z.boolean(), ctx: z.any() })) .returns(z.promise(z.any())), /** * set session data for key, with a `maxAge` (in ms) * `(key, sess, maxAge, { rolling, changed, ctx }) => void | Promise<void>` */ set: z.function() .args(z.string(), z.any(), z.number(), z.object({ rolling: z.boolean(), changed: z.boolean(), ctx: z.any() })) .returns(z.promise(z.void())), /** * destroy session data for key * `(key, { ctx })=> void | Promise<void>` */ destroy: z.function() .args(z.string(), z.object({ ctx: z.any() })) .returns(z.promise(z.void())), }).optional(), /** * If your session store requires data or utilities from context, `opts.ContextStore` is also supported. * `ContextStore` must be a class which claims three instance methods demonstrated above. * `new ContextStore(ctx)` will be executed on every request. */ ContextStore: z.any().optional(), encode: z.function() .args(z.any()) .returns(z.string()) .optional() .default(() => util.encode), decode: z.function() .args(z.string()) .returns(z.any()) .default(() => util.decode), /** * If you want to generate a new session id, you can use `genid` option to customize it. * Default is a function that uses `randomUUID()`. * `(ctx) => string` */ genid: z.function() .args(z.any()) .returns(z.string()) .optional(), /** * If you want to prefix the session id, you can use `prefix` option to customize it. * It will not work if `options.genid(ctx)` present. */ prefix: z.string().optional(), /** * valid session value before use it * `(ctx, sessionData) => boolean` */ valid: z.function() .args(z.any(), z.any()) .returns(z.any()) .optional(), /** * hook before save session * `(ctx, sessionModel) => void` */ beforeSave: z.function() .args(z.any(), z.any()) .returns(z.void()) .optional(), }); const DEFAULT_SESSION_OPTIONS = SessionOptions.parse({}); export function createSession(opts, app) { // session(app[, opts]) if (opts && 'use' in opts && typeof opts.use === 'function') { [app, opts] = [opts, app]; } // app required if (typeof app?.use !== 'function') { throw new TypeError('app instance required: `session(opts, app)`'); } const options = opts ?? {}; // back-compat maxage if (!('maxAge' in options) && 'maxage' in options) { Reflect.set(options, 'maxAge', Reflect.get(options, 'maxage')); if (process.env.NODE_ENV !== 'production') { console.warn('[koa-session] DeprecationWarning: `maxage` option has been renamed to `maxAge`'); } } // keep backwards compatibility: make sure options instance is not mutated Object.assign(options, { ...DEFAULT_SESSION_OPTIONS, ...options, }); SessionOptions.parse(options); formatOptions(options); extendContext(app.context, options); return async function session(ctx, next) { const sess = ctx[GET_CONTEXT_SESSION]; if (sess.store) { await sess.initFromExternal(); } try { await next(); } catch (err) { throw err; } finally { if (options.autoCommit) { await sess.commit(); } } }; } // Usage: `import session from 'koa-session'` export default createSession; /** * format and check session options */ function formatOptions(opts) { // defaults if (opts.overwrite == null) opts.overwrite = true; if (opts.httpOnly == null) opts.httpOnly = true; // delete null sameSite config if (opts.sameSite == null) delete opts.sameSite; if (opts.signed == null) opts.signed = true; if (opts.autoCommit == null) opts.autoCommit = true; debug('session options %j', opts); const store = opts.store; if (store) { assert(typeof store.get === 'function', 'store.get must be function'); assert(typeof store.set === 'function', 'store.set must be function'); assert(typeof store.destroy === 'function', 'store.destroy must be function'); } const externalKey = opts.externalKey; if (externalKey) { assert(typeof externalKey.get === 'function', 'externalKey.get must be function'); assert(typeof externalKey.set === 'function', 'externalKey.set must be function'); } const ContextStore = opts.ContextStore; if (ContextStore) { assert(isClass(ContextStore), 'ContextStore must be a class'); assert(typeof ContextStore.prototype.get === 'function', 'ContextStore.prototype.get must be function'); assert(typeof ContextStore.prototype.set === 'function', 'ContextStore.prototype.set must be function'); assert(typeof ContextStore.prototype.destroy === 'function', 'ContextStore.prototype.destroy must be function'); } if (!opts.genid) { if (opts.prefix) { opts.genid = () => `${opts.prefix}${randomUUID()}`; } else { opts.genid = () => randomUUID(); } } } /** * extend context prototype, add session properties * * @param {Object} context koa's context prototype * @param {Object} opts session options */ function extendContext(context, opts) { if (context.hasOwnProperty(GET_CONTEXT_SESSION)) { return; } Object.defineProperties(context, { [GET_CONTEXT_SESSION]: { get() { if (this[CONTEXT_SESSION_INSTANCE]) { return this[CONTEXT_SESSION_INSTANCE]; } this[CONTEXT_SESSION_INSTANCE] = new ContextSession(this, opts); return this[CONTEXT_SESSION_INSTANCE]; }, }, session: { get() { return this[GET_CONTEXT_SESSION].get(); }, set(val) { this[GET_CONTEXT_SESSION].set(val); }, configurable: true, }, sessionOptions: { get() { return this[GET_CONTEXT_SESSION].opts; }, }, }); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxNQUFNLE1BQU0sYUFBYSxDQUFDO0FBQ2pDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFDckMsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUN6QyxPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQ3JDLE9BQU8sQ0FBQyxNQUFNLEtBQUssQ0FBQztBQUNwQixPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQzlDLE9BQU8sSUFBSSxNQUFNLFdBQVcsQ0FBQztBQUU3QixNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsYUFBYSxDQUFDLENBQUM7QUFFdEMsTUFBTSxtQkFBbUIsR0FBRyxNQUFNLENBQUMsb0JBQW9CLENBQUMsQ0FBQztBQUN6RCxNQUFNLHdCQUF3QixHQUFHLE1BQU0sQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDO0FBRW5FLE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ3JDOzs7T0FHRztJQUNILEdBQUcsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQztJQUNuQzs7Ozs7O09BTUc7SUFDSCxNQUFNLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFFLENBQUMsQ0FBQyxRQUFRLEVBQUU7SUFDaEU7OztPQUdHO0lBQ0gsVUFBVSxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDO0lBQ3JDOzs7T0FHRztJQUNILFNBQVMsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUMsT0FBTyxDQUFD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