koa-session
Version:
Koa cookie session middleware with external store support
326 lines • 22.9 kB
JavaScript
"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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