1+ /*
2+ * www-authenticate
3+ * https://github.com/randymized/www-authenticate
4+ *
5+ * Copyright (c) 2013 Randy McLaughlin
6+ * Licensed under the MIT license.
7+ */
8+
9+ /*
10+ * Copyright © 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
11+ */
12+
13+ 'use strict' ;
14+
15+ var crypto = require ( 'crypto' )
16+ , parsers = require ( './parsers' )
17+ , md5 = require ( './md5' )
18+ , user_credentials = require ( './user-credentials' )
19+ , basic_challenge = {
20+ statusCode : 401 ,
21+ headers : {
22+ 'www-authenticate' : 'Basic realm="sample"'
23+ }
24+ }
25+ ;
26+
27+ function hex8 ( num )
28+ {
29+ return ( "00000000" + num . toString ( 16 ) ) . slice ( - 8 ) ;
30+ }
31+
32+ var www_authenticator = function ( username , password , options )
33+ {
34+ if ( 2 == arguments . length && toString . call ( password ) != '[object String]' ) {
35+ options = password ;
36+ password = null ;
37+ }
38+ var credentials = user_credentials ( username , password )
39+ var cnonce ;
40+ if ( options ) {
41+ if ( toString . call ( options . cnonce ) == '[object String]' )
42+ cnonce = options . cnonce ;
43+ }
44+ if ( cnonce === void 0 ) cnonce = crypto . pseudoRandomBytes ( 8 ) . toString ( 'hex' ) ;
45+
46+ /**
47+ * @typedef {Object } Authenticator
48+ * @property {any } [err]
49+ * @property {function(string=, string=): string } [authorize]
50+ * @property {any } [parms]
51+ * @property {string } [cnonce]
52+ */
53+
54+ /**
55+ * Parses the WWW-Authenticate header.
56+ * @param {string } www_authenticate
57+ * @returns {Authenticator }
58+ */
59+ var parse_header = function ( www_authenticate )
60+ {
61+ function Authenticator ( )
62+ {
63+ function note_error ( err )
64+ {
65+ this . err = err
66+ }
67+ var nc = 0 ;
68+
69+ var parsed = new parsers . WWW_Authenticate ( www_authenticate ) ;
70+ if ( parsed . err ) return note_error ( parsed . err ) ;
71+ var auth_parms = this . parms = parsed . parms ;
72+ this . cnonce = cnonce ;
73+
74+ switch ( parsed . scheme ) {
75+ case 'Basic' :
76+ var auth_string = 'Basic ' + credentials . basic ( ) ;
77+ this . authorize = function ( ) {
78+ return auth_string ;
79+ } ;
80+ return ;
81+ case 'Digest' :
82+ var realm = auth_parms . realm ;
83+ if ( ! realm ) {
84+ return note_error ( "Realm not found in www-authenticate header." ) ;
85+ }
86+
87+ var ha1 =
88+ credentials . digest ( realm ) ;
89+ var nonce = auth_parms . nonce ;
90+ if ( ! nonce ) {
91+ return note_error ( "Nonce not found in www-authenticate header." ) ;
92+ }
93+
94+ var fixed = 'Digest username="' + credentials . username + '",' +
95+ ' realm="' + realm + '",' +
96+ ' nonce="' + nonce + '",' ;
97+ var qop = auth_parms . qop ;
98+ if ( ! qop ) {
99+ this . authorize = function ( method , digestURI ) {
100+ var ha2 = md5 ( method + ':' + digestURI ) ;
101+ return fixed +
102+ ' uri="' + digestURI + '",' +
103+ ' response="' + md5 ( ha1 + ':' + nonce + ':' + ha2 ) + '",' ;
104+ } ;
105+ return ;
106+ }
107+ else {
108+ var qopa = qop . split ( ',' ) ;
109+ var q , x , _i , _len ;
110+ for ( _i = 0 , _len = qopa . length ; _i < _len ; _i ++ ) {
111+ if ( 'auth' === qopa [ _i ] ) {
112+ var opaque = auth_parms . opaque ;
113+ var algorithm = auth_parms . algorithm ;
114+ if ( algorithm ) {
115+ fixed += ' algorithm="' + algorithm + '",' ;
116+ }
117+ else {
118+ algorithm = 'MD5' ;
119+ }
120+ var a1 = 'MD5-sess' == algorithm ?
121+ md5 ( ha1 + ':' + nonce + ':' + cnonce )
122+ :
123+ ha1 ;
124+ this . authorize = function ( method , digestURI ) {
125+ var ha2 = md5 ( method + ':' + digestURI ) ;
126+ nc = nc + 1 ;
127+ var hexed_nc = hex8 ( nc ) ;
128+ var s = fixed +
129+ ' uri="' + digestURI + '",' +
130+ ' qop=auth,' +
131+ ' nc=' + hexed_nc + ',' +
132+ ' cnonce="' + cnonce + '",' +
133+ ' response="' + md5 ( a1 + ':' + nonce + ':' + hexed_nc + ':' + cnonce + ':auth:' + ha2 ) + '"' ;
134+ if ( opaque ) {
135+ s += ', opaque="' + opaque + '"' ;
136+ }
137+ return s ;
138+ } ;
139+ return ;
140+ }
141+ return note_error ( 'Server does not accept any supported quality of protection techniques.' ) ;
142+ }
143+ }
144+ break ;
145+ default :
146+ return note_error ( "Unknown scheme" ) ;
147+ }
148+ }
149+
150+ return new Authenticator ( ) ;
151+ } ;
152+
153+ parse_header . authenticator = new HigherLevel ( credentials , options ) ; // deprecated
154+ return parse_header ;
155+ } ;
156+
157+
158+ function HigherLevel ( credentials , options )
159+ {
160+ this . credentials = credentials
161+ this . options = options
162+ if ( options && options . sendImmediately ) {
163+ this . sendImmediately = true ;
164+ }
165+ }
166+ HigherLevel . prototype . get_challenge = function ( request ) {
167+ if ( 401 == request . statusCode && 'www-authenticate' in request . headers ) {
168+ if ( ! this . parse_header ) {
169+ this . parse_header = www_authenticator ( this . credentials , this . options )
170+ }
171+ this . challenge = this . parse_header ( request . headers [ 'www-authenticate' ] )
172+ return this . challenge . err ;
173+ }
174+ }
175+ HigherLevel . prototype . _challenge = function ( ) {
176+ if ( ! this . challenge ) {
177+ if ( this . sendImmediately ) {
178+ // simulate receipt of a basic challenge
179+ this . get_challenge ( basic_challenge )
180+ return this . challenge
181+ }
182+ else return ; // simply won't produce an 'Authorization' header
183+ }
184+ return this . challenge ;
185+ }
186+ HigherLevel . prototype . authentication_string = function ( method , digestURI ) {
187+ var challenge = this . _challenge ( ) ;
188+ if ( ! challenge ) return ; // simply won't produce an 'Authorization' header
189+ if ( challenge . err ) return challenge . err ;
190+ return challenge . authorize ( method , digestURI ) ;
191+ }
192+ HigherLevel . prototype . authenticate_headers = function ( headers , method , digestURI ) {
193+ var challenge = this . _challenge ( ) ;
194+ if ( ! challenge ) return ; // simply won't produce an 'Authorization' header
195+ if ( challenge . err ) return challenge . err ;
196+ headers . authorization = challenge . authorize ( method , digestURI ) ;
197+ }
198+ HigherLevel . prototype . authenticate_request_options = function ( request_options ) {
199+ var challenge = this . _challenge ( ) ;
200+ if ( ! challenge ) return ; // simply won't produce an 'Authorization' header
201+ if ( challenge . err ) return challenge . err ;
202+ if ( ! request_options . headers ) request_options . headers = { } ;
203+ request_options . headers . authorization = challenge . authorize ( request_options . method , request_options . path ) ;
204+ }
205+
206+ module . exports = www_authenticator ;
207+ module . exports . parsers = parsers ;
208+ module . exports . user_credentials = user_credentials ;
209+ module . exports . basic_challenge = basic_challenge ;
210+ module . exports . authenticator = function ( username , password , options )
211+ {
212+ if ( 2 == arguments . length && toString . call ( password ) != '[object String]' ) {
213+ options = password ;
214+ password = null ;
215+ }
216+ var credentials = user_credentials ( username , password )
217+ return new HigherLevel ( credentials , options ) ;
218+ }
0 commit comments