X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fauthenticator.js;h=3df0b9db17ccdf763ce13b6340e912b0d6545266;hb=HEAD;hp=eab1a1a2183ca67b660b826aae5481e76ffd8c98;hpb=9c8e775e5ab96a1788f535760bfa72205c430d15;p=squeep-authentication-module diff --git a/lib/authenticator.js b/lib/authenticator.js index eab1a1a..2534cfa 100644 --- a/lib/authenticator.js +++ b/lib/authenticator.js @@ -53,15 +53,16 @@ class Authenticator { * @param {object} options options * @param {string | string[]} options.encryptionSecret encryption secret * @param {object} options.authenticator authenticator options - * @param {boolean} options.authenticator.secureAuthOnly disable auth over non-https - * @param {string[]} options.authenticator.forbiddenPAMIdentifiers reject these identifiers for PAM auth - * @param {string[]} options.authenticator.authnEnabled in order of preference for storing new credentials + * @param {boolean=} options.authenticator.secureAuthOnly disable auth over non-https + * @param {string=} options.authenticator.sessionCookieSameSite sameSite setting for session cookie, default Lax + * @param {string[]=} options.authenticator.forbiddenPAMIdentifiers reject these identifiers for PAM auth + * @param {string[]=} options.authenticator.authnEnabled in order of preference for storing new credentials * @param {number=} options.authenticator.inactiveSessionLifespanSeconds session timeout * @param {string[]=} options.authenticator.loginBlurb text for login page * @param {string[]=} options.authenticator.indieAuthBlurb text for indieauth login section * @param {string[]=} options.authenticator.userBlurb text for local user login section * @param {string[]=} options.authenticator.otpBlurb text for otp entry - * @param {string=} options.dingus dingus options + * @param {object=} options.dingus dingus options * @param {string=} options.dingus.proxyPrefix base url prefix */ constructor(logger, db, options) { @@ -70,8 +71,13 @@ class Authenticator { this.options = options; this.basicRealm = options.authenticator.basicRealm || packageName; this.secureAuthOnly = options.authenticator.secureAuthOnly ?? true; + this.sameSite = options.authenticator.sessionCookieSameSite || 'Lax'; this.proxyPrefix = options.dingus?.proxyPrefix ?? ''; + if (!['None', 'Lax', 'Strict'].includes(this.sameSite)) { + throw new RangeError(`invalid sameSite value "${this.sameSite}"`); + } + // First construct map of all available code-supported auth mechanisms. this.authn = { indieAuth: {}, @@ -351,14 +357,15 @@ class Authenticator { /** * Check for valid Basic auth, updates ctx with identifier if valid. - * @param {string} credentials basic auth field (decoded) + * @param {string} authValue basic auth value (base64) * @param {object} ctx context * @returns {Promise} is valid */ - async isValidBasic(credentials, ctx) { + async isValidBasic(authValue, ctx) { const _scope = _fileScope('isValidBasic'); this.logger.debug(_scope, 'called', { ctx }); + const credentials = Buffer.from(authValue, 'base64').toString('utf-8'); const [identifier, credential] = common.splitFirst(credentials, ':', ''); return this.isValidIdentifierCredential(identifier, credential, ctx); @@ -379,8 +386,7 @@ class Authenticator { // eslint-disable-next-line sonarjs/no-small-switch switch (authMethod.toLowerCase()) { case 'basic': { - const credentials = Buffer.from(authString, 'base64').toString('utf-8'); // FIXME: move into isValidBasic, why is it here? - return this.isValidBasic(credentials, ctx); + return this.isValidBasic(authString, ctx); } default: @@ -476,10 +482,11 @@ class Authenticator { common.addCookie(res, Enum.SessionCookie, ctx.cookie[Enum.SessionCookie], { httpOnly: true, maxAge: this.cookieLifespan, - sameSite: 'Lax', + sameSite: this.sameSite, path: `${this.proxyPrefix}/`, secure: this.secureAuthOnly, }); + res.setHeader(Enum.Header.SetLogin, Enum.LoginState.LoggedIn); return true; } @@ -488,10 +495,11 @@ class Authenticator { common.addCookie(res, Enum.SessionCookie, '""', { httpOnly: true, maxAge: 0, - sameSite: 'Lax', + sameSite: this.sameSite, path: `${this.proxyPrefix}/`, secure: this.secureAuthOnly, }); + res.setHeader(Enum.Header.SetLogin, Enum.LoginState.LoggedOut); res.statusCode = 302; res.setHeader(Enum.Header.Location, `${loginPath}?r=${encodeURIComponent(req.url)}`); @@ -567,17 +575,22 @@ class Authenticator { const _scope = _fileScope('apiRequiredLocal'); this.logger.debug(_scope, 'called', { ctx, sessionAlsoValid }); - // If a Authorization header was provided, never consider session as a fallback. - const authorizationHeader = req.getHeader(Enum.Header.Authorization); - if (authorizationHeader) { - if (await this.isValidAuthorization(authorizationHeader, ctx)) { - this.logger.debug(_scope, 'valid authorization', { ctx, sessionAlsoValid }); + try { + // If a Authorization header was provided, never consider session as a fallback. + const authorizationHeader = req.getHeader(Enum.Header.Authorization); + if (authorizationHeader) { + if (await this.isValidAuthorization(authorizationHeader, ctx)) { + this.logger.debug(_scope, 'valid authorization', { ctx, sessionAlsoValid }); + return true; + } + } else if (sessionAlsoValid + && await this.sessionCheck(req, res, ctx, undefined, false, false)) { + this.logger.debug(_scope, 'valid session', { ctx, sessionAlsoValid }); return true; } - } else if (sessionAlsoValid - && await this.sessionCheck(req, res, ctx, undefined, false, false)) { - this.logger.debug(_scope, 'valid session', { ctx, sessionAlsoValid }); - return true; + } catch (e) { + this.logger.error(_scope, 'failed', { error: e }); + throw e; } this.logger.debug(_scope, 'invalid authorization', { ctx, sessionAlsoValid });