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