X-Git-Url: https://git.squeep.com/?a=blobdiff_plain;f=lib%2Fsession-manager.js;fp=lib%2Fsession-manager.js;h=7b33d8600b986cde34eab5dcf87a7a37b9aae150;hb=9c8e775e5ab96a1788f535760bfa72205c430d15;hp=2d3d96cd10706e07512ee3d908a6d849adb829b5;hpb=786f4aa122c3c3a1c1c8224abacd12d0ca079cd0;p=squeep-authentication-module diff --git a/lib/session-manager.js b/lib/session-manager.js index 2d3d96c..7b33d86 100644 --- a/lib/session-manager.js +++ b/lib/session-manager.js @@ -15,20 +15,31 @@ const Template = require('./template'); const _fileScope = common.fileScope(__filename); +/** + * @typedef {import('node:http')} http + * @typedef {import('./authenticator')} Authenticator + */ +/** + * @typedef {object} ConsoleLike + * @property {Function} debug log debug + * @property {Function} error log error + * @property {Function} info log info + */ + class SessionManager { /** - * @param {Console} logger - * @param {Authenticator} authenticator - * @param {Object} options - * @param {Object} options.authenticator - * @param {String[]} options.authenticator.authnEnabled - * @param {Number=} options.authenticator.inactiveSessionLifespanSeconds - * @param {Boolean} options.authenticator.secureAuthOnly - * @param {Object=} options.dingus - * @param {String=} options.dingus.proxyPrefix - * @param {String} options.dingus.selfBaseUrl - * @param {Object} options.manager - * @param {String} options.manager.pageTitle + * @param {ConsoleLike} logger logger + * @param {Authenticator} authenticator authenticator instance + * @param {object} options options + * @param {object} options.authenticator authenticator instance options + * @param {string[]} options.authenticator.authnEnabled authentication methods enabled + * @param {number=} options.authenticator.inactiveSessionLifespanSeconds session timeout + * @param {boolean} options.authenticator.secureAuthOnly allow only https + * @param {object=} options.dingus dingus options + * @param {string=} options.dingus.proxyPrefix prefix on route paths + * @param {string} options.dingus.selfBaseUrl base url + * @param {object} options.manager manager options + * @param {string} options.manager.pageTitle page title */ constructor(logger, authenticator, options) { this.logger = logger; @@ -46,10 +57,10 @@ class SessionManager { /** * Set or update our session cookie. - * @param {http.ServerResponse} res - * @param {Object=} session - * @param {Number=} maxAge - * @param {String=} path + * @param {http.ServerResponse} res respoonse + * @param {object=} session session object + * @param {number=} maxAge session validity in seconds + * @param {string=} path session cookie path */ async _sessionCookieSet(res, session, maxAge = this.cookieLifespan, path = '/') { const cookieName = Enum.SessionCookie; @@ -66,22 +77,22 @@ class SessionManager { /** * Remove any current session cookie. - * @param {http.ServerResponse} res - * @param {String} path + * @param {http.ServerResponse} res response + * @param {string} path session cookie path */ async _sessionCookieClear(res, path = '/') { await this._sessionCookieSet(res, undefined, 0, path); } /** - * @typedef {(pagePathLevel: Number, ctx: Object, htmlOptions: Object) => void} AppTemplateCallback + * @typedef {(pagePathLevel: number, ctx: object, htmlOptions: object) => void} AppTemplateCallback */ /** * GET request for establishing admin session. - * @param {http.ServerResponse} res - * @param {Object} ctx - * @param {AppTemplateCallback} appCb + * @param {http.ServerResponse} res response + * @param {object} ctx context + * @param {AppTemplateCallback} appCb function to mogrify template htmlOptions */ async getAdminLogin(res, ctx, appCb) { const _scope = _fileScope('getAdminLogin'); @@ -108,9 +119,9 @@ class SessionManager { /** * POST request for taking form data to establish admin session. - * @param {http.ServerResponse} res - * @param {Object} ctx - * @param {AppTemplateCallback} appCb + * @param {http.ServerResponse} res response + * @param {object} ctx context + * @param {AppTemplateCallback} appCb function to mogrify template htmlOptions */ async postAdminLogin(res, ctx, appCb) { const _scope = _fileScope('postAdminLogin'); @@ -136,7 +147,7 @@ class SessionManager { try { me = new URL(ctx.parsedBody['me']); meAutoScheme = !!ctx.parsedBody['me_auto_scheme']; - } catch (e) { + } catch (e) { // eslint-disable-line no-unused-vars this.logger.debug(_scope, 'failed to parse supplied profile url', { ctx }); ctx.errors.push(`Unable to understand '${ctx.parsedBody['me']}' as a profile URL.`); } @@ -161,7 +172,7 @@ class SessionManager { // fetch and parse me for 'authorization_endpoint' relation links try { authorizationEndpoint = new URL(profile.metadata.authorizationEndpoint); - } catch (e) { + } catch (e) { // eslint-disable-line no-unused-vars ctx.errors.push(`Unable to understand the authorization endpoint ('${profile.metadata.authorizationEndpoint}') indicated by that profile ('${me}') as a URL.`); } @@ -175,7 +186,7 @@ class SessionManager { this.logger.debug(_scope, 'supplied issuer url invalid', { ctx }); ctx.errors.push('Authorization server provided an invalid issuer field.'); } - } catch (e) { + } catch (e) { // eslint-disable-line no-unused-vars this.logger.debug(_scope, 'failed to parse supplied issuer url', { ctx }); ctx.errors.push('Authorization server provided an unparsable issuer field.'); } @@ -227,15 +238,15 @@ class SessionManager { /** - * @typedef {Object} OTPState - * @property {String} authenticatedIdentifier - * @property {Buffer|String} key - * @property {Number} attempt - * @property {Number} epochMs - * @property {String} redirect + * @typedef {object} OTPState + * @property {string} authenticatedIdentifier identifier of logging-in user + * @property {Buffer | string} key otp key + * @property {number} attempt counter + * @property {number} epochMs started + * @property {string} redirect where to go after successful otp entry */ /** - * @param {OTPState} otpState + * @param {OTPState} otpState otp state */ static _validateOTPState(otpState) { if (!otpState.authenticatedIdentifier) { @@ -260,12 +271,13 @@ class SessionManager { * Check if processing an OTP entry attempt. If not, resume login flow. * If so, validate otp and establish session, else reprompt for OTP, or * return to login entry after too many failures. - * @param {http.ServerResponse} res - * @param {Object} ctx - * @param {Object} ctx.parsedBody - * @param {String} ctx.parsedBody.state - * @param {String} ctx.parsedBody.otp - * @returns {Promise} true if otp was handled, otherwise false indicates further login processing needed + * @param {http.ServerResponse} res response + * @param {object} ctx context + * @param {object} ctx.parsedBody submitted data + * @param {string} ctx.parsedBody.state packed state + * @param {string} ctx.parsedBody.otp entered code + * @param {AppTemplateCallback} appCb function to mogrify template htmlOptions + * @returns {Promise} true if otp was handled, otherwise false indicates further login processing needed */ async _otpSubmission(res, ctx, appCb) { const _scope = _fileScope('_otpSubmission'); @@ -279,7 +291,7 @@ class SessionManager { // Ignore and continue back to main login. return false; } - /** @type OTPState */ + /** @type {OTPState} */ let state; try { state = await this.mysteryBox.unpack(stateBox); @@ -337,9 +349,10 @@ class SessionManager { /** * - * @param {http.ServerResponse} res - * @param {Object} ctx - * @returns {Promise} true if handled, false if flow should continue + * @param {http.ServerResponse} res response + * @param {object} ctx context + * @param {AppTemplateCallback} appCb function to mogrify template htmlOptions + * @returns {Promise} true if handled, false if flow should continue */ async _localUserAuth(res, ctx, appCb) { const _scope = _fileScope('_localUserAuth'); @@ -394,8 +407,8 @@ class SessionManager { /** * GET request to remove current credentials. - * @param {http.ServerResponse} res - * @param {Object} ctx + * @param {http.ServerResponse} res response + * @param {object} ctx context */ async getAdminLogout(res, ctx) { const _scope = _fileScope('getAdminLogout'); @@ -416,9 +429,9 @@ class SessionManager { /** * GET request for returning IndieAuth redirect. * This currently only redeems a scope-less profile. - * @param {http.ServerResponse} res - * @param {Object} ctx - * @param {AppTemplateCallback} appCb + * @param {http.ServerResponse} res response + * @param {object} ctx context + * @param {AppTemplateCallback} appCb function to mogrify template htmlOptions */ async getAdminIA(res, ctx, appCb) { const _scope = _fileScope('getAdminIA'); @@ -438,7 +451,7 @@ class SessionManager { ctx.session = await this.mysteryBox.unpack(cookieValue); this.logger.debug(_scope, 'restored session from cookie', { ctx }); } catch (e) { - this.logger.debug(_scope, 'could not unpack cookie'); + this.logger.debug(_scope, 'could not unpack cookie', { error: e }); ctx.errors.push('invalid cookie'); } } @@ -481,7 +494,7 @@ class SessionManager { let redeemProfileUrl; try { redeemProfileUrl = new URL(ctx.session.authorizationEndpoint); - } catch (e) { + } catch (e) { // eslint-disable-line no-unused-vars this.logger.debug(_scope, 'failed to parse restored session authorization endpoint as url', { ctx }); ctx.errors.push('invalid cookie'); } @@ -531,11 +544,17 @@ class SessionManager { } + /** + * @typedef {object} AuthInfo + * @property {string} identifier identifier + * @property {string} credential hashed credential + * @property {string=} otpKey otp key + */ /** * Page for modifying credentials and OTP. - * @param {http.ServerResponse} res - * @param {Object} ctx - * @param {AppTemplateCallback} appCb + * @param {http.ServerResponse} res response + * @param {object} ctx context + * @param {AppTemplateCallback} appCb function to mogrify template htmlOptions */ async getAdminSettings(res, ctx, appCb) { const _scope = _fileScope('getAdminSettings'); @@ -562,10 +581,9 @@ class SessionManager { /** * Page for modifying credentials and OTP. - * @param {http.ServerResponse} res - * @param {Object} ctx - * @param {Object[]=} appNavLinks - * @param {AppTemplateCallback} appCb + * @param {http.ServerResponse} res response + * @param {object} ctx context + * @param {AppTemplateCallback} appCb function to mogrify template htmlOptions */ async postAdminSettings(res, ctx, appCb) { const _scope = _fileScope('postAdminSettings'); @@ -614,9 +632,9 @@ class SessionManager { /** * Submission to disable OTP. - * @param {*} dbCtx - * @param {*} ctx - * @param {AuthInfo} authData + * @param {*} dbCtx db context + * @param {*} ctx context + * @param {AuthInfo} authData auth info */ async _otpDisable(dbCtx, ctx, authData) { const _scope = _fileScope('_otpDisable'); @@ -635,7 +653,7 @@ class SessionManager { /** * Submission to enable OTP. - * @param {Object} ctx + * @param {object} ctx context */ async _otpEnable(ctx) { const _scope = _fileScope('_otpEnable'); @@ -657,8 +675,8 @@ class SessionManager { /** * Submission to confirm enabling OTP. - * @param {*} dbCtx - * @param {Object} ctx + * @param {*} dbCtx db context + * @param {object} ctx context */ async _otpConfirm(dbCtx, ctx) { const _scope = _fileScope('_otpConfirm'); @@ -715,9 +733,9 @@ class SessionManager { /** * Submission to set new credential. - * @param {*} dbCtx - * @param {Object} ctx - * @param {AuthInfo} authData + * @param {*} dbCtx db context + * @param {object} ctx context + * @param {AuthInfo} authData auth info */ async _credentialUpdate(dbCtx, ctx, authData) { const _scope = _fileScope('_credentialUpdate');