UNPKG

http-auth-client

Version:

Client side HTTP Authorization header handling

147 lines (121 loc) 4.47 kB
const Auth = require('./auth') const DigestAlgorithms = [ "sha-256", "md5" ] var g_crypto function getcrypto() { if(!g_crypto) { const crypto = require('crypto') g_crypto = Object.freeze({ MD5(data) { return crypto.createHash('MD5').update(data).digest('hex') }, SHA256(data) { return crypto.createHash('sha256').update(data).digest('hex') }, SHA512(data) { return crypto.createHash('sha512').update(data).digest('hex') }, SHA512_256(data) { return crypto.createHash('sha512').update(data).digest().slice(0, 32).toString('hex') }, Cnonce() { return crypto.randomBytes(16).toString('hex') }, }) } return g_crypto } class Digest { constructor(params) { params = params ? params.params || params : {} this.realm = params.realm || "" this.algorithm = params.algorithm || "MD5" this.crypto = Digest.Crypto() switch(this.algorithm.toLowerCase()) { case "sha-256": case "sha-256-sess": this.hash = this.crypto.SHA256; break case "sha-512": case "sha-512-sess": this.hash = this.crypto.SHA512; break case "sha-512-256": case "sha-512-256-sess": this.hash = this.crypto.SHA512_256; break default: this.hash = this.crypto.MD5 } this.session = this.algorithm.endsWith("-sess") this.qop = params.qop == "auth-int" ? "auth-int" : "auth" this.userhash = (params.userhash || false).toString() == 'true' this.opaque = params.opaque this.nonce = params.nonce this.nc = 0 } credentials(username, password) { this.username = this.userhash ? this.hash(username).toString('hex') : username this._a1 = this.hash([username, this.realm, password].join(':')) return this } authorization(method, uri, requestbody) { var cnonce = this.crypto.Cnonce() var a1 = this.session ? [this._a1, this.nonce, cnonce].join(':') : this._a1 var a2 = this.qop == "auth-int" ? this.hash([method, uri, this.hash(requestbody || "")].join(':')) : this.hash(method + ":" + uri) var ncbuf = Buffer.allocUnsafe(4) ncbuf.writeUInt32BE(++this.nc) var nc = ncbuf.toString('hex') var response = this.hash([a1, this.nonce, nc, cnonce, this.qop, a2].join(':')) return `${this.type} username="${this.username}", realm="${this.realm}", uri="${uri}", algorithm=${this.algorithm}, nonce="${this.nonce}", qop=${this.qop}, cnonce="${cnonce}", nc=${nc}, userhash="${this.userhash}", response="${response}"${this.opaque?`, opaque="${this.opaque}"`:''}` } update(params) { var needs_credentials = false if(typeof params == 'string') { // params is a www-authenticate header var challenges = Auth.parseHeaders(params) // find ours challenges = Auth.pick(challenges, [Digest], [this.algorithm].concat(Digest.Algorithms)) if(challenges.length == 0) throw new Error(`Challenge type ${this.type} not available`) if(challenges[0].params.algorithm != this.algorithm) { // Algorithm change! needs_credentials = true delete this._a1 } params = challenges[0].params } else if(params && params.params) { params = params.params } if(params) { if(params.realm && params.realm != this.realm) { this.realm = params.realm needs_credentials = true delete this._a1 } if(params.algorithm && params.algorithm != this.algorithm) { switch(params.algorithm.toLowerCase()) { case "sha-256": case "sha-256-sess": { this.algorithm = params.algorithm this.hash = SHA256 break } case "sha-512-256": case "sha-512-256-sess": { this.algorithm = params.algorithm this.hash = SHA512 break } case "md5": case "md5-sess": { this.algorithm = params.algorithm this.hash = MD5 } } this.session = this.algorithm.endsWith("-sess") } if(params.qop) this.qop = params.qop == "auth-int" ? "auth-int" : "auth" this.userhash = (params.userhash || false).toString() == 'true' if(params.opaque) this.opaque = params.opaque if(params.nonce && params.nonce != this.nonce) { this.nonce = params.nonce this.nc = 0 } if(!(params.stale && params.stale != "false" && params.stale != "0")) { needs_credentials = true delete this._a1 } } return needs_credentials } } Digest.prototype.type = "Digest" Digest.Algorithms = DigestAlgorithms Digest.Crypto = getcrypto module.exports = Digest