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