kaven-utils
Version:
Utils for Node.js.
117 lines (116 loc) • 4.81 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: 2023-12-07 10:59:05.566
* @version: 5.4.0
* @times: 44
* @lines: 143
* @copyright: Copyright © 2019-2023 Kaven. All Rights Reserved.
* @description: [description]
* @license: [license]
********************************************************************/
import { GenerateGuid, GetMD5, KavenMD5, TrimAll } from "kaven-basic";
import { KavenAuthentication } from "./KavenAuthentication.js";
import { InternalLogger } from "../../KavenUtility.Internal.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 = authStr.TrimStart(this.Name, 1).trimStart();
const json = {};
const parts = str.split(",");
for (const part of parts) {
const index = part.indexOf("=");
if (index === -1) {
return false;
}
const name = part.substring(0, index).trimStart();
const value = TrimAll(part.substring(index + 1), "\"", 1);
json[name] = value;
}
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 GetMD5(json.username + ":" + json.realm + ":" + this.Password);
// If the qop parameter's value is "auth" or is unspecified:
// A2 = Method ":" request-uri
const ha2 = await GetMD5(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 GetMD5([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) {
InternalLogger()?.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;
}
}