UNPKG

@foal/core

Version:

Full-featured Node.js framework, with no complexity

218 lines (217 loc) 7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Session = void 0; // FoalTS const common_1 = require("../../common"); const core_1 = require("../../core"); const constants_1 = require("./constants"); /** * Representation of a server/database/file session. * * @export * @class Session */ class Session { store; state; oldFlash; oldId = ''; status; /** * Retuns the user ID. If the session is anonymous, the value is null. * * @readonly * @type {(string|number|null)} * @memberof Session */ get userId() { return this.state.userId; } /** * Returns true if the session has expired due to inactivity * or because it reached the maximum lifetime allowed. * * @readonly * @type {boolean} * @memberof Session */ get isExpired() { const { absoluteTimeout, inactivityTimeout } = this.getTimeouts(); const now = this.getTime(); if (now - this.state.updatedAt >= inactivityTimeout) { return true; } if (now - this.state.createdAt >= absoluteTimeout) { return true; } return false; } /** * Returns the session expiration time in seconds. * * @readonly * @type {number} * @memberof Session */ get expirationTime() { const { absoluteTimeout, inactivityTimeout } = this.getTimeouts(); return Math.min(this.state.updatedAt + inactivityTimeout, this.state.createdAt + absoluteTimeout); } constructor(store, state, options) { this.store = store; this.state = state; this.status = options.exists ? 'exists' : 'new'; this.oldFlash = state.flash; state.flash = {}; } /** * Sets or replaces the userId associated with the session. * * @param {({ id: number|string })} user - The user containing the ID. * @memberof Session */ setUser(user) { // tslint:disable-next-line const id = user.id ?? user._id; if (typeof id === 'object') { this.state.userId = id.toString(); return; } this.state.userId = id; } /** * Adds or replaces an element in the session. This operation is not saved * in the saved unless you call the "commit" function. * * @param {string} key - The property key. * @param {*} value - The property value. * @param {{ flash?: boolean }} [options={}] If flash is true, the key/value * will be erased at the end of the next request. * @memberof Session */ set(key, value, options = {}) { if (options.flash) { this.state.flash[key] = value; } else { this.state.content[key] = value; } } get(key, defaultValue) { if (this.oldFlash.hasOwnProperty(key)) { return this.oldFlash[key]; } if (this.state.content.hasOwnProperty(key)) { return this.state.content[key]; } return defaultValue; } /** * Gets the session ID. * * @returns {string} - The session ID. * @memberof Session */ getToken() { return this.state.id; } /** * Regenerates the session with a new ID. It is recommended * to regenerate the session ID after any privilege level change * within the associated user session. * * Common scenario: an anonymous user is authenticated. * * @returns {Promise<void>} * @memberof Session */ async regenerateID() { this.oldId = this.state.id; this.state.id = await (0, common_1.generateToken)(); this.status = 'regenerated'; } /** * Destroys the session. * * @returns {Promise<void>} * @memberof Session */ async destroy() { await this.store.destroy(this.state.id); this.status = 'destroyed'; } /** * Returns true if the method `destroy` has previously been called. * * @readonly * @type {boolean} * @memberof Session */ get isDestroyed() { return this.status === 'destroyed'; } /** * Saves or updates the session and extends its lifetime. * * If the session has already been destroyed, an error is thrown. * * This function calls periodically the store method "cleanUpExpiredSessions". * * @returns {Promise<void>} * @memberof Session */ async commit() { const { absoluteTimeout, inactivityTimeout } = this.getTimeouts(); if (this.shouldCleanUpExpiredSessions()) { await this.store.cleanUpExpiredSessions(inactivityTimeout, absoluteTimeout); } this.state.updatedAt = this.getTime(); switch (this.status) { case 'regenerated': await this.store.destroy(this.oldId); await this.store.save(this.state, inactivityTimeout); this.status = 'exists'; break; case 'new': await this.store.save(this.state, inactivityTimeout); this.status = 'exists'; break; case 'exists': await this.store.update(this.state, inactivityTimeout); break; case 'destroyed': throw new Error('Impossible to commit the session. Session already destroyed.'); default: break; } } /** * Returns the current time in seconds. * * @private * @returns {number} The current time. * @memberof Session */ getTime() { return Math.trunc(Date.now() / 1000); } shouldCleanUpExpiredSessions() { const periodicity = core_1.Config.get('settings.session.garbageCollector.periodicity', 'number', constants_1.SESSION_DEFAULT_GARBAGE_COLLECTOR_PERIODICITY); return Math.trunc(Math.random() * periodicity) === 0; } getTimeouts() { const inactivityTimeout = core_1.Config.get('settings.session.expirationTimeouts.inactivity', 'number', constants_1.SESSION_DEFAULT_INACTIVITY_TIMEOUT); if (inactivityTimeout < 0) { throw new Error('[CONFIG] The value of settings.session.expirationTimeouts.inactivity must be a positive number.'); } const absoluteTimeout = core_1.Config.get('settings.session.expirationTimeouts.absolute', 'number', constants_1.SESSION_DEFAULT_ABSOLUTE_TIMEOUT); if (absoluteTimeout < 0) { throw new Error('[CONFIG] The value of settings.session.expirationTimeouts.absolute must be a positive number.'); } if (absoluteTimeout < inactivityTimeout) { throw new Error('[CONFIG] The value of settings.session.expirationTimeouts.absolute must be greater than *.inactivity.'); } return { absoluteTimeout, inactivityTimeout }; } } exports.Session = Session;