Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/requester.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Copyright © 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*/
'use strict';
var createAuthInitializer = require('www-authenticate');
var createAuthInitializer = require('./www-authenticate/www-authenticate');
var Kerberos = require('./optional.js')
.libraryProperty('kerberos', 'Kerberos');
var Multipart = require('multipart-stream');
Expand Down
7 changes: 7 additions & 0 deletions lib/www-authenticate/md5.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
var crypto= require('crypto');

function md5(s) {
return crypto.createHash('md5').update(s).digest('hex');
}

module.exports= md5;
109 changes: 109 additions & 0 deletions lib/www-authenticate/parsers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
var ParseAuth= /(\w+)\s+(.*)/ // -> scheme, params
, Separators= /([",=])/
;

function parse_params(header) {
// This parser will definitely fail if there is more than one challenge
var tok, last_tok, _i, _len, key, value;
var state= 0; //0: token,
var m= header.split(Separators)
for (_i = 0, _len = m.length; _i < _len; _i++) {
last_tok= tok;
tok = m[_i];
if (!tok.length) continue;
switch (state) {
case 0: // token
key= tok.trim();
state= 1; // expect equals
continue;
case 1: // expect equals
if ('=' != tok) return 'Equal sign was expected after '+key;
state= 2;
continue;
case 2: // expect value
if ('"' == tok) {
value= '';
state= 3; // expect quoted
continue;
}
else {
this.parms[key]= value= tok.trim();
state= 9; // expect comma or end
continue;
}
case 3: // handling quoted string
if ('"' == tok) {
state= 8; // end quoted
continue;
}
else {
value+= tok;
state= 3; // continue accumulating quoted string
continue;
}
case 8: // end quote encountered
if ('"' == tok) {
// double quoted
value+= '"';
state= 3; // back to quoted string
continue;
}
if (',' == tok) {
this.parms[key]= value;
state= 0;
continue;
}
else {
return 'Unexpected token ('+tok+') after '+value+'"';
}
continue;
case 9: // expect commma
if (',' != tok) return 'Comma expected after '+value;
state= 0;
continue;
}
}
switch (state) { // terminal state
case 0: // Empty or ignoring terminal comma
case 9: // Expecting comma or end of header
return;
case 8: // Last token was end quote
this.parms[key]= value;
return;
default:
return 'Unexpected end of www-authenticate value.';
}
}

function Parse_WWW_Authenticate(to_parse)
{
var m= to_parse.match(ParseAuth);
this.scheme= m[1];
this.parms= {};
var err= this.parse_params(m[2]);
if (err) {
this.scheme= '';
this.parms= {};
this.err= err;
}
}

function Parse_Authentication_Info(to_parse)
{
this.scheme= 'Digest';
this.parms= {};
var err= this.parse_params(to_parse);
if (err) {
this.scheme= '';
this.parms= {};
this.err= err;
}
}

Parse_Authentication_Info.prototype.parse_params= parse_params;
Parse_WWW_Authenticate.prototype.parse_params= parse_params;

module.exports = {
WWW_Authenticate: Parse_WWW_Authenticate,
Authentication_Info: Parse_Authentication_Info
};
46 changes: 46 additions & 0 deletions lib/www-authenticate/user-credentials.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
var md5= require('./md5');

/*
* Hide the password. Uses the password to form authorization strings,
* but provides no interface for exporting it.
*/
function user_credentials(username,password,options) {
if (username.is_user_credentials &&
typeof username.basic === 'function' &&
typeof username.digest === 'function'
) {
return username;
}

var basic_string= options && options.hide_basic ?
''
:
(!password && password !== '' ?
Buffer.from(username, "ascii").toString("base64")
:
Buffer.from(username+':'+password, "ascii").toString("base64")
)
function Credentials()
{
this.username= username;
}
Credentials.prototype.basic= function()
{
return basic_string;
}
Credentials.prototype.digest= function(realm)
{
return !password && password !== '' ?
md5(username+':'+realm)
:
md5(username+':'+realm+':'+password)
}
Credentials.prototype.is_user_credentials= function()
{
return true;
}

return new Credentials;
}

module.exports= user_credentials;
218 changes: 218 additions & 0 deletions lib/www-authenticate/www-authenticate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/*
* www-authenticate
* https://github.com/randymized/www-authenticate
*
* Copyright (c) 2013 Randy McLaughlin
* Licensed under the MIT license.
*/

/*
* Copyright © 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*/

'use strict';

var crypto= require('crypto')
, 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');

/**
* @typedef {Object} Authenticator
* @property {any} [err]
* @property {function(string=, string=): string} [authorize]
* @property {any} [parms]
* @property {string} [cnonce]
*/

/**
* Parses the WWW-Authenticate header.
* @param {string} www_authenticate
* @returns {Authenticator}
*/
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);
}
Loading