UNPKG

koa-session

Version:

Koa cookie session middleware with external store support

326 lines 22.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ContextSession = void 0; const node_util_1 = require("node:util"); const session_js_1 = require("./session.js"); const util_js_1 = __importDefault(require("./util.js")); const debug = (0, node_util_1.debuglog)('koa-session:context'); const COOKIE_EXP_DATE = new Date(util_js_1.default.CookieDateEpoch); const ONE_DAY = 24 * 60 * 60 * 1000; 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_js_1.default.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_js_1.default.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_js_1.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_js_1.default.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); } } exports.ContextSession = ContextSession; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGV4dC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb250ZXh0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUFBLHlDQUFxQztBQUNyQyw2Q0FBdUM7QUFDdkMsd0RBQTZCO0FBRzdCLE1BQU0sS0FBSyxHQUFHLElBQUEsb0JBQVEsRUFBQyxxQkFBcUIsQ0FBQyxDQUFDO0FBRTlDLE1BQU0sZUFBZSxHQUFHLElBQUksSUFBSSxDQUFDLGlCQUFJLENBQUMsZUFBZSxDQUFDLENBQUM7QUFDdkQsTUFBTSxPQUFPLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDO0FBRXBDLE1BQWEsY0FBYztJQUN6QixHQUFHLENBQU07SUFDVCxHQUFHLENBQU07SUFDVCxJQUFJLENBQWlCO0lBQ3JCLEtBQUssQ0FBMEI7SUFDL0IsT0FBTyxDQUFrQjtJQUN6QixXQUFXLENBQVU7SUFDckIsUUFBUSxDQUFVO0lBRWxCOztPQUVHO0lBQ0gsWUFBWSxHQUFRLEVBQUUsSUFBb0I7UUFDeEMsSUFBSSxDQUFDLEdBQUcsR0FBRyxHQUFHLENBQUM7UUFDZixJQUFJLENBQUMsR0FBRyxHQUFHLEdBQUcsQ0FBQyxHQUFHLENBQUM7UUFDbkIsSUFBSSxDQUFDLElBQUksR0FBRyxFQUFFLEdBQUcsSUFBSSxFQUFFLENBQUM7UUFDeEIsSUFBSSxDQUFDLEtBQUssR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUM7SUFDMUYsQ0FBQztJQUVEOzs7T0FHRztJQUNILEdBQUc7UUFDRCxvQkFBb0I7UUFDcEIsSUFBSSxJQUFJLENBQUMsT0FBTztZQUFFLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQztRQUN0QyxRQUFRO1FBQ1Is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