3 const argon2
= require('argon2');
4 const common
= require('./common');
5 const Enum
= require('./enum');
6 const Errors
= require('./errors');
8 const _fileScope
= common
.fileScope(__filename
);
11 constructor(logger
, db
, options
) {
14 this.basicRealm
= options
.authenticator
.basicRealm
;
15 this.secureAuthOnly
= options
.authenticator
.secureAuthOnly
;
20 * Check for valid Basic auth, updates ctx with identifier if valid.
21 * @param {String} credentials
25 async
isValidBasic(credentials
, ctx
) {
26 const _scope
= _fileScope('isValidBasic');
27 this.logger
.debug(_scope
, 'called', { ctx
});
29 const [identifier
, credential
] = common
.splitFirst(credentials
, ':', '');
32 await
this.db
.context(async (dbCtx
) => {
33 const authData
= await
this.db
.authenticationGet(dbCtx
, identifier
);
35 this.logger
.debug(_scope
, 'failed, invalid authentication id', { ctx
});
39 if (authData
.credential
.startsWith('$argon2')) {
40 valid
= await argon2
.verify(authData
.credential
, credential
);
42 this.logger
.error(_scope
, 'failed, unknown type of stored password hash', { ctx
});
45 ctx
.authenticationId
= identifier
;
46 await
this.db
.authenticationSuccess(dbCtx
, identifier
);
55 * Determine which Authorization header is available, and if it is valid.
56 * @param {String} authorizationHeader
59 async
isValidAuthorization(authorizationHeader
, ctx
) {
60 const _scope
= _fileScope('isValidAuthorization');
61 this.logger
.debug(_scope
, 'called', { authorizationHeader
, ctx
});
63 const [authMethod
, authString
] = common
.splitFirst(authorizationHeader
, ' ', '').map((x
) => x
.trim());
64 // eslint-disable-next-line sonarjs/no-small-switch
65 switch (authMethod
.toLowerCase()) {
67 const credentials
= Buffer
.from(authString
, 'base64').toString('utf-8');
68 return await
this.isValidBasic(credentials
, ctx
);
72 this.logger
.debug(_scope
, 'unknown authorization scheme', { ctx
});
79 * Send a response requesting basic auth.
80 * @param {http.ServerResponse} res
83 res
.setHeader(Enum
.Header
.WWWAuthenticate
, `Basic realm="${this.basicRealm}", charset="UTF-8"`);
84 throw new Errors
.ResponseError(Enum
.ErrorResponse
.Unauthorized
);
89 * Require that a request has valid auth over secure channel, requests if missing.
90 * @param {http.ClientRequest} req
91 * @param {http.ServerResponse} res
94 async
required(req
, res
, ctx
) {
95 const _scope
= _fileScope('required');
96 this.logger
.debug(_scope
, 'called', { ctx
});
98 if (this.secureAuthOnly
&& ctx
.clientProtocol
.toLowerCase() !== 'https') {
99 this.logger
.debug(_scope
, 'rejecting insecure auth', ctx
);
100 throw new Errors
.ResponseError(Enum
.ErrorResponse
.Forbidden
, 'authentication required, but connection is insecure; cannot continue');
103 const authData
= req
.getHeader(Enum
.Header
.Authorization
);
105 && await
this.isValidAuthorization(authData
, ctx
)) {
108 return this.requestBasic(res
);
113 module
.exports
= Authenticator
;