+++ /dev/null
-'use strict';
-
-const argon2 = require('argon2');
-const common = require('./common');
-const Enum = require('./enum');
-const Errors = require('./errors');
-
-const _fileScope = common.fileScope(__filename);
-
-class Authenticator {
- constructor(logger, db, options) {
- this.logger = logger;
- this.db = db;
- this.basicRealm = options.authenticator.basicRealm;
- this.secureAuthOnly = options.authenticator.secureAuthOnly;
- }
-
-
- /**
- * Check for valid Basic auth, updates ctx with identifier if valid.
- * @param {String} credentials
- * @param {Object} ctx
- * @returns {Boolean}
- */
- async isValidBasic(credentials, ctx) {
- const _scope = _fileScope('isValidBasic');
- this.logger.debug(_scope, 'called', { ctx });
-
- const [identifier, credential] = common.splitFirst(credentials, ':', '');
-
- let valid = false;
- await this.db.context(async (dbCtx) => {
- const authData = await this.db.authenticationGet(dbCtx, identifier);
- if (!authData) {
- this.logger.debug(_scope, 'failed, invalid authentication id', { ctx });
- return false;
- }
-
- if (authData.credential.startsWith('$argon2')) {
- valid = await argon2.verify(authData.credential, credential);
- } else {
- this.logger.error(_scope, 'failed, unknown type of stored password hash', { ctx });
- }
- if (valid) {
- ctx.authenticationId = identifier;
- await this.db.authenticationSuccess(dbCtx, identifier);
- }
- });
-
- return valid;
- }
-
-
- /**
- * Determine which Authorization header is available, and if it is valid.
- * @param {String} authorizationHeader
- * @param {Object} ctx
- */
- async isValidAuthorization(authorizationHeader, ctx) {
- const _scope = _fileScope('isValidAuthorization');
- this.logger.debug(_scope, 'called', { authorizationHeader, ctx });
-
- const [authMethod, authString] = common.splitFirst(authorizationHeader, ' ', '').map((x) => x.trim());
- // eslint-disable-next-line sonarjs/no-small-switch
- switch (authMethod.toLowerCase()) {
- case 'basic': {
- const credentials = Buffer.from(authString, 'base64').toString('utf-8');
- return await this.isValidBasic(credentials, ctx);
- }
-
- default:
- this.logger.debug(_scope, 'unknown authorization scheme', { ctx });
- return false;
- }
- }
-
-
- /**
- * Send a response requesting basic auth.
- * @param {http.ServerResponse} res
- */
- requestBasic(res) {
- res.setHeader(Enum.Header.WWWAuthenticate, `Basic realm="${this.basicRealm}", charset="UTF-8"`);
- throw new Errors.ResponseError(Enum.ErrorResponse.Unauthorized);
- }
-
-
- /**
- * Require that a request has valid auth over secure channel, requests if missing.
- * @param {http.ClientRequest} req
- * @param {http.ServerResponse} res
- * @param {Object} ctx
- */
- async required(req, res, ctx) {
- const _scope = _fileScope('required');
- this.logger.debug(_scope, 'called', { ctx });
-
- if (this.secureAuthOnly && ctx.clientProtocol.toLowerCase() !== 'https') {
- this.logger.debug(_scope, 'rejecting insecure auth', ctx);
- throw new Errors.ResponseError(Enum.ErrorResponse.Forbidden, 'authentication required, but connection is insecure; cannot continue');
- }
-
- const authData = req.getHeader(Enum.Header.Authorization);
- if (authData
- && await this.isValidAuthorization(authData, ctx)) {
- return true;
- }
- return this.requestBasic(res);
- }
-
-}
-
-module.exports = Authenticator;
\ No newline at end of file