http-auth-client
Version:
Client side HTTP Authorization header handling
147 lines (121 loc) • 4.47 kB
JavaScript
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