UNPKG

koa-session

Version:

Koa cookie session middleware with external store support

319 lines 22.5 kB
import { debuglog } from 'node:util'; import { Session } from './session.js'; import util from './util.js'; const debug = debuglog('koa-session:context'); const COOKIE_EXP_DATE = new Date(util.CookieDateEpoch); const ONE_DAY = 24 * 60 * 60 * 1000; export class ContextSession { ctx; app; opts; store; session; externalKey; prevHash; /** * context session constructor */ constructor(ctx, opts) { this.ctx = ctx; this.app = ctx.app; this.opts = { ...opts }; this.store = this.opts.ContextStore ? new this.opts.ContextStore(ctx) : this.opts.store; } /** * internal logic of `ctx.session` * @return {Session} session object */ get() { // already retrieved if (this.session) return this.session; // unset if (this.session === false) return null; // create an empty session or init from cookie this.store ? this.create() : this.initFromCookie(); return this.session; } /** * internal logic of `ctx.session=` * @param {Object} val session object */ set(val) { if (val === null) { this.session = false; return; } if (typeof val === 'object') { // use the original `externalKey` if exists to avoid waste storage this.create(val, this.externalKey); return; } throw new Error('this.session can only be set as null or an object.'); } /** * init session from external store * will be called in the front of session middleware * * @public */ async initFromExternal() { debug('init from external'); const ctx = this.ctx; const opts = this.opts; let externalKey; if (opts.externalKey) { externalKey = opts.externalKey.get(ctx); debug('get external key from custom %s', externalKey); } else { externalKey = ctx.cookies.get(opts.key, opts); debug('get external key from cookie %s', externalKey); } if (!externalKey) { // create a new `externalKey` this.create(); return; } const sessionData = await this.store.get(externalKey, opts.maxAge, { ctx, rolling: opts.rolling }); if (!this.valid(sessionData, externalKey)) { debug('invalid session data, create a new session'); // create a new `externalKey` this.create(); return; } // create with original `externalKey` this.create(sessionData, externalKey); this.prevHash = util.hash(this.session.toJSON()); } /** * init session from cookie * @private */ initFromCookie() { debug('init from cookie'); const ctx = this.ctx; const opts = this.opts; const cookie = ctx.cookies.get(opts.key, opts); if (!cookie) { this.create(); return; } let sessionData; debug('parse cookie: %j', cookie); try { sessionData = opts.decode(cookie); } catch (err) { // backwards compatibility: // create a new session if parsing fails. // `Buffer.from(string, 'base64')` does not seem to crash // when `string` is not base64-encoded. // but `JSON.parse(string)` will crash. debug('decode %j error: %s', cookie, err); if (err instanceof Error && !(err instanceof SyntaxError)) { // clean this cookie to ensure next request won't throw again ctx.cookies.set(opts.key, '', opts); // `ctx.onerror` will unset all headers, and set those specified in err Reflect.set(err, 'headers', { 'set-cookie': ctx.response.get('set-cookie'), }); throw err; } this.create(); return; } debug('parsed session data: %j', sessionData); if (!this.valid(sessionData)) { // create a new session if the session data is invalid this.create(); debug('invalid session data, create a new session'); return; } // support access `ctx.session` before session middleware this.create(sessionData); this.prevHash = util.hash(this.session.toJSON()); } /** * verify session(expired or custom verification) * @param {Object} sessionData session data * @param {Object} [key] session externalKey(optional) * @private */ valid(sessionData, key) { const ctx = this.ctx; if (!sessionData) { this.emit('missed', { key, value: sessionData, ctx }); return false; } if (typeof sessionData._expire === 'number' && sessionData._expire < Date.now()) { debug('expired session'); this.emit('expired', { key, value: sessionData, ctx }); return false; } const valid = this.opts.valid; if (typeof valid === 'function' && !valid(ctx, sessionData)) { // valid session value fail, ignore this session debug('invalid session'); this.emit('invalid', { key, value: sessionData, ctx }); return false; } return true; } /** * @param {String} event event name * @param {Object} data event data * @private */ emit(event, data) { setImmediate(() => { this.app.emit(`session:${event}`, data); }); } /** * create a new session and attach to ctx.sess * * @param {Object} [sessionData] session data * @param {String} [externalKey] session external key */ create(sessionData, externalKey) { debug('create session with data: %j, externalKey: %s', sessionData, externalKey); if (this.store) { this.externalKey = externalKey ?? this.opts.genid?.(this.ctx); } this.session = new Session(this, sessionData, this.externalKey); } /** * Commit the session changes or removal. */ async commit({ save = false, regenerate = false } = {}) { const session = this.session; const opts = this.opts; const ctx = this.ctx; // not accessed if (session === undefined) { return; } // removed if (session === false) { await this.remove(); return; } if (regenerate) { await this.remove(); if (this.store) { this.externalKey = opts.genid?.(ctx); } } // force save session when `session._requireSave` set const reason = save || regenerate || session._requireSave ? 'force' : this._shouldSaveSession(); debug('should save session: %j', reason); if (!reason) { return; } if (typeof opts.beforeSave === 'function') { debug('before save'); opts.beforeSave(ctx, session); } const changed = reason === 'changed'; await this.save(changed); } _shouldSaveSession() { const prevHash = this.prevHash; const session = this.session; // do nothing if new and not populated const sessionData = session.toJSON(); if (!prevHash && !Object.keys(sessionData).length) { return ''; } // save if session changed const changed = prevHash !== util.hash(sessionData); if (changed) { return 'changed'; } // save if opts.rolling set if (this.opts.rolling) { return 'rolling'; } // save if opts.renew and session will expired if (this.opts.renew) { const expire = session._expire; const maxAge = session.maxAge; // renew when session will expired in maxAge / 2 if (expire && maxAge && expire - Date.now() < maxAge / 2) { return 'renew'; } } // don't save return ''; } /** * remove session * @private */ async remove() { // Override the default options so that we can properly expire the session cookies const opts = { ...this.opts, expires: COOKIE_EXP_DATE, maxAge: false, }; const ctx = this.ctx; const key = opts.key; const externalKey = this.externalKey; if (externalKey) { await this.store.destroy(externalKey, { ctx }); } ctx.cookies.set(key, '', opts); } /** * save session * @private */ async save(changed) { const opts = this.opts; const key = opts.key; const externalKey = this.externalKey; const sessionData = this.session.toJSON(); // set expire for check let maxAge = opts.maxAge ? opts.maxAge : ONE_DAY; if (maxAge === 'session') { // do not set _expire in json if maxAge is set to 'session' // also delete maxAge from options opts.maxAge = undefined; sessionData._session = true; } else { // set expire for check sessionData._expire = maxAge + Date.now(); sessionData._maxAge = maxAge; } // save to external store if (externalKey) { debug('save %j to external key %s', sessionData, externalKey); if (typeof maxAge === 'number') { // ensure store expired after cookie maxAge += 10000; } await this.store.set(externalKey, sessionData, maxAge, { changed, ctx: this.ctx, rolling: opts.rolling, }); if (opts.externalKey) { opts.externalKey.set(this.ctx, externalKey); } else { this.ctx.cookies.set(key, externalKey, opts); } return; } // save to cookie with base64 encode string debug('save session data %j to cookie', sessionData); const base64String = opts.encode(sessionData); debug('save session data json base64 format: %s to cookie key: %s with options: %j', base64String, key, opts); this.ctx.cookies.set(key, base64String, opts); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGV4dC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb250ZXh0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFDckMsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUN2QyxPQUFPLElBQUksTUFBTSxXQUFXLENBQUM7QUFHN0IsTUFBTSxLQUFLLEdBQUcsUUFBUSxDQUFDLHFCQUFxQixDQUFDLENBQUM7QUFFOUMsTUFBTSxlQUFlLEdBQUcsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxDQUFDO0FBQ3ZELE1BQU0sT0FBTyxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztBQUVwQyxNQUFNLE9BQU8sY0FBYztJQUN6QixHQUFHLENBQU07SUFDVCxHQUFHLENBQU07SUFDVCxJQUFJLENBQWlCO0lBQ3JCLEtBQUssQ0FBMEI7SUFDL0IsT0FBTyxDQUFrQjtJQUN6QixXQUFXLENBQVU7SUFDckIsUUFBUSxDQUFVO0lBRWxCOztPQUVHO0lBQ0gsWUFBWSxHQUFRLEVBQUUsSUFBb0I7UUFDeEMsSUFBSSxDQUFDLEdBQUcsR0FBRyxHQUFHLENBQUM7UUFDZixJQUFJLENBQUMsR0FBRyxHQUFHLEdBQUcsQ0FBQyxHQUFHLENBQUM7UUFDbkIsSUFBSSxDQUFDLElBQUksR0FBRyxFQUFFLEdBQUcsSUFBSSxFQUFFLENBQUM7UUFDeEIsSUFBSSxDQUFDLEtBQUssR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUM7SUFDMUYsQ0FBQztJQUVEOzs7T0FHRztJQUNILEdBQUc7UUFDRCxvQkFBb0I7UUFDcEIs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