* @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) {
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: {},
/**
* 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<boolean>} 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);
// 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:
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;
}
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)}`);
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 });