www-authenticate
Version:
Provides the functionality needed for a client to use HTTP Basic or Digest authentication. Also provides primitives for parsing WWW-Authenticate and Authentication_Info headers.
202 lines (188 loc) • 6.46 kB
JavaScript
/*
* www-authenticate
* https://github.com/randymized/www-authenticate
*
* Copyright (c) 2013 Randy McLaughlin
* Licensed under the MIT license.
*/
'use strict';
var crypto= require('crypto')
, md5sum = crypto.createHash('md5')
, parsers= require('./parsers')
, md5= require('./md5')
, user_credentials= require('./user-credentials')
, basic_challenge= {
statusCode: 401,
headers: {
'www-authenticate': 'Basic realm="sample"'
}
}
;
function hex8(num)
{
return ("00000000" + num.toString(16)).slice(-8);
}
var www_authenticator = function(username,password,options)
{
if (2 == arguments.length && toString.call(password) != '[object String]') {
options= password;
password= null;
}
var credentials= user_credentials(username,password)
var cnonce;
if (options) {
if (toString.call(options.cnonce) == '[object String]')
cnonce= options.cnonce;
}
if (cnonce === void 0) cnonce= crypto.pseudoRandomBytes(8).toString('hex');
var parse_header= function(www_authenticate)
{
function Authenticator()
{
function note_error(err)
{
this.err= err
}
var nc= 0;
var parsed= new parsers.WWW_Authenticate(www_authenticate);
if (parsed.err) return note_error(parsed.err);
var auth_parms= this.parms= parsed.parms;
this.cnonce= cnonce;
switch(parsed.scheme) {
case 'Basic':
var auth_string= 'Basic '+credentials.basic();
this.authorize= function() {
return auth_string;
};
return;
case 'Digest':
var realm= auth_parms.realm;
if (!realm) {
return note_error("Realm not found in www-authenticate header.");
}
var ha1=
credentials.digest(realm);
var nonce= auth_parms.nonce;
if (!nonce) {
return note_error("Nonce not found in www-authenticate header.");
}
var fixed= 'Digest username="'+credentials.username+'",'+
' realm="'+realm+'",'+
' nonce="'+nonce+'",';
var qop= auth_parms.qop;
if (!qop) {
this.authorize= function(method,digestURI) {
var ha2= md5(method+':'+digestURI);
return fixed+
' uri="'+digestURI+'",'+
' response="'+md5(ha1+':'+nonce+':'+ha2)+'",';
};
return;
}
else {
var qopa= qop.split(',');
var q, x, _i, _len;
for (_i = 0, _len = qopa.length; _i < _len; _i++) {
if ('auth' === qopa[_i]) {
var opaque= auth_parms.opaque;
var algorithm= auth_parms.algorithm;
if (algorithm) {
fixed+= ' algorithm="'+algorithm+'",';
}
else {
algorithm= 'MD5';
}
var a1= 'MD5-sess' == algorithm ?
md5(ha1+':'+nonce+':'+cnonce)
:
ha1;
this.authorize= function(method,digestURI) {
var ha2= md5(method+':'+digestURI);
nc= nc+1;
var hexed_nc= hex8(nc);
var s= fixed+
' uri="'+digestURI+'",'+
' qop=auth,'+
' nc='+hexed_nc+','+
' cnonce="'+cnonce+'",'+
' response="'+md5(a1+':'+nonce+':'+hexed_nc+':'+cnonce+':auth:'+ha2)+'"';
if (opaque) {
s+= ', opaque="'+opaque+'"';
}
return s;
};
return;
}
return note_error('Server does not accept any supported quality of protection techniques.');
}
}
break;
default:
return note_error("Unknown scheme");
}
}
return new Authenticator();
};
parse_header.authenticator= new HigherLevel(credentials,options); // deprecated
return parse_header;
};
function HigherLevel(credentials,options)
{
this.credentials= credentials
this.options= options
if (options && options.sendImmediately) {
this.sendImmediately= true;
}
}
HigherLevel.prototype.get_challenge= function(request) {
if (401 == request.statusCode && 'www-authenticate' in request.headers) {
if (!this.parse_header) {
this.parse_header= www_authenticator(this.credentials,this.options)
}
this.challenge= this.parse_header(request.headers['www-authenticate'])
return this.challenge.err;
}
}
HigherLevel.prototype._challenge= function() {
if (!this.challenge) {
if (this.sendImmediately) {
// simulate receipt of a basic challenge
this.get_challenge(basic_challenge)
return this.challenge
}
else return; // simply won't produce an 'Authorization' header
}
return this.challenge;
}
HigherLevel.prototype.authentication_string= function(method,digestURI) {
var challenge= this._challenge();
if (!challenge) return; // simply won't produce an 'Authorization' header
if (challenge.err) return challenge.err;
return challenge.authorize(method,digestURI);
}
HigherLevel.prototype.authenticate_headers= function(headers,method,digestURI) {
var challenge= this._challenge();
if (!challenge) return; // simply won't produce an 'Authorization' header
if (challenge.err) return challenge.err;
headers.authorization= challenge.authorize(method,digestURI);
}
HigherLevel.prototype.authenticate_request_options= function(request_options) {
var challenge= this._challenge();
if (!challenge) return; // simply won't produce an 'Authorization' header
if (challenge.err) return challenge.err;
if (!request_options.headers) request_options.headers= {};
request_options.headers.authorization= challenge.authorize(request_options.method,request_options.path);
}
module.exports = www_authenticator;
module.exports.parsers= parsers;
module.exports.user_credentials= user_credentials;
module.exports.basic_challenge= basic_challenge;
module.exports.authenticator= function(username,password,options)
{
if (2 == arguments.length && toString.call(password) != '[object String]') {
options= password;
password= null;
}
var credentials= user_credentials(username,password)
return new HigherLevel(credentials,options);
}