http-auth
Version:
Node.js package for HTTP basic and digest access authentication.
212 lines (173 loc) • 4.96 kB
JavaScript
// Base class.
const Base = require("./base");
// Utility module.
const utils = require("./utils");
// Unique id.
const { v4: uuid } = require("uuid");
// Reuse.
const digestSchemeRegExp = /^digest\s(.*)/i;
// Define digest auth.
class Digest extends Base {
// Constructor.
constructor(options, checker) {
super(options, checker);
// Array of random strings sent to clients.
this.nonces = [];
// Algorithm of encryption, could be MD5 or MD5-sess, default is MD5.
if ("MD5-sess" !== options.algorithm) {
this.options.algorithm = "MD5";
}
// Quality of protection is by default auth.
if (options.qop === "none") {
this.options.qop = "";
} else {
this.options.qop = "auth";
}
}
// Process user line.
processLine(line) {
let tokens = line.split(":");
// We need only users for given realm.
if (this.options.realm === tokens[1]) {
this.options.users.push({ username: tokens[0], hash: tokens[2] });
}
}
// Parse authorization heder.
parseAuthorization(header) {
let match = header.match(digestSchemeRegExp);
if (match) {
let opts = {};
let params = match[1];
// Split the parameters by comma.
let tokens = params.split(/,(?=(?:[^"]|"[^"]*")*$)/);
// Parse parameters.
let i = 0;
let len = tokens.length;
while (i < len) {
// Strip quotes and whitespace.
let param = /(\w+)=["]?([^"]*)["]?$/.exec(tokens[i]);
if (param) {
opts[param[1]] = param[2];
}
++i;
}
// Return options.
return opts;
}
}
// Validating hash.
validate(ha2, co, hash) {
let ha1 = hash;
// Algorithm.
if (co.algorithm === "MD5-sess") {
ha1 = utils.md5(`${ha1}:${co.nonce}:${co.cnonce}`);
}
let response = undefined;
// Quality of protection.
if (co.qop) {
response = utils.md5(
`${ha1}:${co.nonce}:${co.nc}:${co.cnonce}:${co.qop}:${ha2}`
);
} else {
response = utils.md5(`${ha1}:${co.nonce}:${ha2}`);
}
// If calculated response is equal to client's response.
return response === co.response;
}
// Searching for user.
findUser(req, co, callback) {
if (this.validateNonce(co.nonce, co.qop, co.nc)) {
let ha2 = utils.md5(`${req.method}:${co.uri}`);
if (this.checker) {
// Custom authentication.
this.checker.apply(this, [
co.username,
hash => {
let params = undefined;
if (hash instanceof Error) {
params = [hash];
} else {
params = [
{ user: co.username, pass: !!this.validate(ha2, co, hash) }
];
}
// Call callback.
callback.apply(this, params);
},
req
]);
} else {
let pass = false;
// File based, loop users to find the matching one.
this.options.users.forEach(user => {
if (
user.username === co.username &&
this.validate(ha2, co, user.hash)
) {
pass = true;
}
});
callback.apply(this, [{ user: co.username, pass: pass }]);
}
} else {
callback.apply(this, [{ stale: true }]);
}
}
// Remove nonces.
removeNonces(noncesToRemove) {
noncesToRemove.forEach(nonce => {
let index = this.nonces.indexOf(nonce);
if (index != -1) {
this.nonces.splice(index, 1);
}
});
}
// Validate nonce.
validateNonce(nonce, qop, nc) {
let found = false;
// Current time.
let now = Date.now();
// Nonces for removal.
let noncesToRemove = [];
// Request counter is hexadecimal.
let ncNum = Number.parseInt(nc, 16);
// Searching for not expired ones.
this.nonces.forEach(serverNonce => {
if (serverNonce[1] + 3600000 > now) {
if (serverNonce[0] === nonce) {
if (qop) {
if (ncNum > serverNonce[2]) {
found = true;
serverNonce[2] = ncNum;
}
} else {
found = true;
}
}
} else {
noncesToRemove.push(serverNonce);
}
});
// Remove expired nonces.
this.removeNonces(noncesToRemove);
return found;
}
// Generates and returns new random nonce.
askNonce() {
let nonce = utils.md5(uuid());
this.nonces.push([nonce, Date.now(), 0]);
return nonce;
}
// Generates request header.
generateHeader(result) {
let nonce = this.askNonce();
let stale = result.stale ? true : false;
// Returning it.
return `Digest realm="${this.options.realm}", qop="${this.options.qop}", nonce="${nonce}", algorithm="${this.options.algorithm}", stale="${stale}"`;
}
}
// Export digest auth.
module.exports = (options, checker) => {
return new Digest(options, checker);
};
;