kaven-utils
Version:
Utils for Node.js.
114 lines (113 loc) • 4.76 kB
JavaScript
/********************************************************************
* @author: Kaven
* @email: kaven@wuwenkai.com
* @website: http://blog.kaven.xyz
* @file: [Kaven-Utils] /src/net/authentication/KavenDigestAuthentication.ts
* @create: 2019-03-26 14:22:50.325
* @modify: 2025-10-14 22:58:04.857
* @version: 6.1.0
* @times: 50
* @lines: 137
* @copyright: Copyright © 2019-2025 Kaven. All Rights Reserved.
* @description: [description]
* @license: [license]
********************************************************************/
import { GenerateGuid, GetMD5Async, KavenMD5, TrimStart } from "kaven-basic";
import { KavenAuthentication } from "./KavenAuthentication.js";
export class KavenDigestAuthentication extends KavenAuthentication {
/**
* A server-specified string which should be uniquely generated each
* time a 401 response is made. It is advised that this string be
* Base64 or hexadecimal data. Specifically, since the string is
* passed in the header field lines as a quoted string, the double-
* quote character is not allowed, unless suitably escaped.
*/
get Nonce() {
return GenerateGuid();
}
/**
* A string of data, specified by the server, that SHOULD be returned
* by the client unchanged in the Authorization header field of
* subsequent requests with URIs in the same protection space. It is
* RECOMMENDED that this string be Base64 or hexadecimal data.
*/
get Opaque() {
return GenerateGuid();
}
Name = "Digest";
/**
* Indicates the "quality of protection" options applied to the
* response by the server.
*
* The value "auth" indicates authentication;
*
* the value "auth-int" indicates authentication with integrity protection.
*
* The server SHOULD use the same value for the qop parameter in the response as was sent by the client in the
* corresponding request.
*/
QOP = "auth, auth-int";
/**
* Only support MD5 for now.
*/
Algorithm = "MD5"; // "SHA-256"; // SHA-256 seems like not supported by browsers
md5;
async Authenticate(req) {
try {
const authStr = req.authorization;
if (!authStr || !authStr.startsWith(this.Name)) {
return false;
}
const str = TrimStart(authStr, this.Name, 1).trimStart();
const json = {};
// Match key=value where value may be "quoted string" or token
const regex = /(\w+)=("([^"]*)"|[^,]+)/g;
let match;
while ((match = regex.exec(str)) !== null) {
const key = match[1];
const value = match[3] !== undefined ? match[3] : match[2]; // strip quotes if present
json[key] = value.trim();
}
const key = `${json.username}${req.ip ?? ""}`;
if (!this.CanAuthenticate(key)) {
return false;
}
if (json.username !== this.UserName) {
this.AddRecord(key, false);
return false;
}
if (this.md5 === undefined) {
this.md5 = new KavenMD5();
}
// the notation unq(X) means the value of the quoted-string X without the surrounding quotes and with quoting slashes removed.
// A1 = unq(username) ":" unq(realm) ":" password
const ha1 = await GetMD5Async(json.username + ":" + json.realm + ":" + this.Password);
// If the qop parameter's value is "auth" or is unspecified:
// A2 = Method ":" request-uri
const ha2 = await GetMD5Async(req.method + ":" + json.uri);
// If the qop value is "auth" or "auth-int":
// response = <"> < KD ( H(A1), unq(nonce)
// ":" nc
// ":" unq(cnonce)
// ":" unq(qop)
// ":" H(A2)
// ) <">
// ha1:nonce:nc:cnonce:qop:ha2
const response = await GetMD5Async([ha1, json.nonce, json.nc, json.cnonce, json.qop, ha2].join(":"));
if (json.response !== response) {
this.AddRecord(key, false);
return false;
}
this.AddRecord(key, true);
return true;
}
catch (ex) {
this.Logger?.Warn(ex);
return false;
}
}
Update(response) {
response.AddHeader(this.ResponseHeaderName, `${this.Name} realm="${this.Realm}", qop="${this.QOP}", algorithm=${this.Algorithm}, nonce="${this.Nonce}", opaque="${this.Opaque}"`);
return response;
}
}