mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
3.5 KiB
133 lines
3.5 KiB
'use strict'
|
|
|
|
var caseless = require('caseless')
|
|
, uuid = require('node-uuid')
|
|
, helpers = require('./helpers')
|
|
|
|
var md5 = helpers.md5
|
|
, toBase64 = helpers.toBase64
|
|
|
|
|
|
function Auth () {
|
|
// define all public properties here
|
|
this.hasAuth = false
|
|
this.sentAuth = false
|
|
this.bearerToken = null
|
|
this.user = null
|
|
this.pass = null
|
|
}
|
|
|
|
Auth.prototype.basic = function (user, pass, sendImmediately) {
|
|
var self = this
|
|
if (typeof user !== 'string' || (pass !== undefined && typeof pass !== 'string')) {
|
|
throw new Error('auth() received invalid user or password')
|
|
}
|
|
self.user = user
|
|
self.pass = pass
|
|
self.hasAuth = true
|
|
var header = typeof pass !== 'undefined' ? user + ':' + pass : user
|
|
if (sendImmediately || typeof sendImmediately === 'undefined') {
|
|
var authHeader = 'Basic ' + toBase64(header)
|
|
self.sentAuth = true
|
|
return authHeader
|
|
}
|
|
}
|
|
|
|
Auth.prototype.bearer = function (bearer, sendImmediately) {
|
|
var self = this
|
|
self.bearerToken = bearer
|
|
self.hasAuth = true
|
|
if (sendImmediately || typeof sendImmediately === 'undefined') {
|
|
if (typeof bearer === 'function') {
|
|
bearer = bearer()
|
|
}
|
|
var authHeader = 'Bearer ' + bearer
|
|
self.sentAuth = true
|
|
return authHeader
|
|
}
|
|
}
|
|
|
|
Auth.prototype.digest = function (method, path, authHeader) {
|
|
// TODO: More complete implementation of RFC 2617.
|
|
// - check challenge.algorithm
|
|
// - support algorithm="MD5-sess"
|
|
// - handle challenge.domain
|
|
// - support qop="auth-int" only
|
|
// - handle Authentication-Info (not necessarily?)
|
|
// - check challenge.stale (not necessarily?)
|
|
// - increase nc (not necessarily?)
|
|
// For reference:
|
|
// http://tools.ietf.org/html/rfc2617#section-3
|
|
// https://github.com/bagder/curl/blob/master/lib/http_digest.c
|
|
|
|
var self = this
|
|
|
|
var challenge = {}
|
|
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
|
|
for (;;) {
|
|
var match = re.exec(authHeader)
|
|
if (!match) {
|
|
break
|
|
}
|
|
challenge[match[1]] = match[2] || match[3]
|
|
}
|
|
|
|
var ha1 = md5(self.user + ':' + challenge.realm + ':' + self.pass)
|
|
var ha2 = md5(method + ':' + path)
|
|
var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth'
|
|
var nc = qop && '00000001'
|
|
var cnonce = qop && uuid().replace(/-/g, '')
|
|
var digestResponse = qop
|
|
? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
|
|
: md5(ha1 + ':' + challenge.nonce + ':' + ha2)
|
|
var authValues = {
|
|
username: self.user,
|
|
realm: challenge.realm,
|
|
nonce: challenge.nonce,
|
|
uri: path,
|
|
qop: qop,
|
|
response: digestResponse,
|
|
nc: nc,
|
|
cnonce: cnonce,
|
|
algorithm: challenge.algorithm,
|
|
opaque: challenge.opaque
|
|
}
|
|
|
|
authHeader = []
|
|
for (var k in authValues) {
|
|
if (authValues[k]) {
|
|
if (k === 'qop' || k === 'nc' || k === 'algorithm') {
|
|
authHeader.push(k + '=' + authValues[k])
|
|
} else {
|
|
authHeader.push(k + '="' + authValues[k] + '"')
|
|
}
|
|
}
|
|
}
|
|
authHeader = 'Digest ' + authHeader.join(', ')
|
|
self.sentAuth = true
|
|
return authHeader
|
|
}
|
|
|
|
Auth.prototype.response = function (method, path, headers) {
|
|
var self = this
|
|
if (!self.hasAuth || self.sentAuth) { return null }
|
|
|
|
var c = caseless(headers)
|
|
|
|
var authHeader = c.get('www-authenticate')
|
|
var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase()
|
|
// debug('reauth', authVerb)
|
|
|
|
switch (authVerb) {
|
|
case 'basic':
|
|
return self.basic(self.user, self.pass, true)
|
|
|
|
case 'bearer':
|
|
return self.bearer(self.bearerToken, true)
|
|
|
|
case 'digest':
|
|
return self.digest(method, path, authHeader)
|
|
}
|
|
}
|
|
|
|
exports.Auth = Auth
|
|
|