]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.maps.server/node/node-v4.8.0-win-x64/node_modules/npm/node_modules/request/node_modules/hawk/lib/server.js
Adding integrated tile server
[simantics/district.git] / org.simantics.maps.server / node / node-v4.8.0-win-x64 / node_modules / npm / node_modules / request / node_modules / hawk / lib / server.js
1 // Load modules
2
3 var Boom = require('boom');
4 var Hoek = require('hoek');
5 var Cryptiles = require('cryptiles');
6 var Crypto = require('./crypto');
7 var Utils = require('./utils');
8
9
10 // Declare internals
11
12 var internals = {};
13
14
15 // Hawk authentication
16
17 /*
18    req:                 node's HTTP request object or an object as follows:
19
20                         var request = {
21                             method: 'GET',
22                             url: '/resource/4?a=1&b=2',
23                             host: 'example.com',
24                             port: 8080,
25                             authorization: 'Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", ext="some-app-ext-data", mac="6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE="'
26                         };
27
28    credentialsFunc:     required function to lookup the set of Hawk credentials based on the provided credentials id.
29                         The credentials include the MAC key, MAC algorithm, and other attributes (such as username)
30                         needed by the application. This function is the equivalent of verifying the username and
31                         password in Basic authentication.
32
33                         var credentialsFunc = function (id, callback) {
34
35                             // Lookup credentials in database
36                             db.lookup(id, function (err, item) {
37
38                                 if (err || !item) {
39                                     return callback(err);
40                                 }
41
42                                 var credentials = {
43                                     // Required
44                                     key: item.key,
45                                     algorithm: item.algorithm,
46                                     // Application specific
47                                     user: item.user
48                                 };
49
50                                 return callback(null, credentials);
51                             });
52                         };
53
54    options: {
55
56         hostHeaderName:        optional header field name, used to override the default 'Host' header when used
57                                behind a cache of a proxy. Apache2 changes the value of the 'Host' header while preserving
58                                the original (which is what the module must verify) in the 'x-forwarded-host' header field.
59                                Only used when passed a node Http.ServerRequest object.
60
61         nonceFunc:             optional nonce validation function. The function signature is function(key, nonce, ts, callback)
62                                where 'callback' must be called using the signature function(err).
63
64         timestampSkewSec:      optional number of seconds of permitted clock skew for incoming timestamps. Defaults to 60 seconds.
65                                Provides a +/- skew which means actual allowed window is double the number of seconds.
66
67         localtimeOffsetMsec:   optional local clock time offset express in a number of milliseconds (positive or negative).
68                                Defaults to 0.
69
70         payload:               optional payload for validation. The client calculates the hash value and includes it via the 'hash'
71                                header attribute. The server always ensures the value provided has been included in the request
72                                MAC. When this option is provided, it validates the hash value itself. Validation is done by calculating
73                                a hash value over the entire payload (assuming it has already be normalized to the same format and
74                                encoding used by the client to calculate the hash on request). If the payload is not available at the time
75                                of authentication, the authenticatePayload() method can be used by passing it the credentials and
76                                attributes.hash returned in the authenticate callback.
77
78         host:                  optional host name override. Only used when passed a node request object.
79         port:                  optional port override. Only used when passed a node request object.
80     }
81
82     callback: function (err, credentials, artifacts) { }
83  */
84
85 exports.authenticate = function (req, credentialsFunc, options, callback) {
86
87     callback = Hoek.nextTick(callback);
88
89     // Default options
90
91     options.nonceFunc = options.nonceFunc || internals.nonceFunc;
92     options.timestampSkewSec = options.timestampSkewSec || 60;                                                  // 60 seconds
93
94     // Application time
95
96     var now = Utils.now(options.localtimeOffsetMsec);                           // Measure now before any other processing
97
98     // Convert node Http request object to a request configuration object
99
100     var request = Utils.parseRequest(req, options);
101     if (request instanceof Error) {
102         return callback(Boom.badRequest(request.message));
103     }
104
105     // Parse HTTP Authorization header
106
107     var attributes = Utils.parseAuthorizationHeader(request.authorization);
108     if (attributes instanceof Error) {
109         return callback(attributes);
110     }
111
112     // Construct artifacts container
113
114     var artifacts = {
115         method: request.method,
116         host: request.host,
117         port: request.port,
118         resource: request.url,
119         ts: attributes.ts,
120         nonce: attributes.nonce,
121         hash: attributes.hash,
122         ext: attributes.ext,
123         app: attributes.app,
124         dlg: attributes.dlg,
125         mac: attributes.mac,
126         id: attributes.id
127     };
128
129     // Verify required header attributes
130
131     if (!attributes.id ||
132         !attributes.ts ||
133         !attributes.nonce ||
134         !attributes.mac) {
135
136         return callback(Boom.badRequest('Missing attributes'), null, artifacts);
137     }
138
139     // Fetch Hawk credentials
140
141     credentialsFunc(attributes.id, function (err, credentials) {
142
143         if (err) {
144             return callback(err, credentials || null, artifacts);
145         }
146
147         if (!credentials) {
148             return callback(Boom.unauthorized('Unknown credentials', 'Hawk'), null, artifacts);
149         }
150
151         if (!credentials.key ||
152             !credentials.algorithm) {
153
154             return callback(Boom.internal('Invalid credentials'), credentials, artifacts);
155         }
156
157         if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
158             return callback(Boom.internal('Unknown algorithm'), credentials, artifacts);
159         }
160
161         // Calculate MAC
162
163         var mac = Crypto.calculateMac('header', credentials, artifacts);
164         if (!Cryptiles.fixedTimeComparison(mac, attributes.mac)) {
165             return callback(Boom.unauthorized('Bad mac', 'Hawk'), credentials, artifacts);
166         }
167
168         // Check payload hash
169
170         if (options.payload ||
171             options.payload === '') {
172
173             if (!attributes.hash) {
174                 return callback(Boom.unauthorized('Missing required payload hash', 'Hawk'), credentials, artifacts);
175             }
176
177             var hash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, request.contentType);
178             if (!Cryptiles.fixedTimeComparison(hash, attributes.hash)) {
179                 return callback(Boom.unauthorized('Bad payload hash', 'Hawk'), credentials, artifacts);
180             }
181         }
182
183         // Check nonce
184
185         options.nonceFunc(credentials.key, attributes.nonce, attributes.ts, function (err) {
186
187             if (err) {
188                 return callback(Boom.unauthorized('Invalid nonce', 'Hawk'), credentials, artifacts);
189             }
190
191             // Check timestamp staleness
192
193             if (Math.abs((attributes.ts * 1000) - now) > (options.timestampSkewSec * 1000)) {
194                 var tsm = Crypto.timestampMessage(credentials, options.localtimeOffsetMsec);
195                 return callback(Boom.unauthorized('Stale timestamp', 'Hawk', tsm), credentials, artifacts);
196             }
197
198             // Successful authentication
199
200             return callback(null, credentials, artifacts);
201         });
202     });
203 };
204
205
206 // Authenticate payload hash - used when payload cannot be provided during authenticate()
207
208 /*
209     payload:        raw request payload
210     credentials:    from authenticate callback
211     artifacts:      from authenticate callback
212     contentType:    req.headers['content-type']
213 */
214
215 exports.authenticatePayload = function (payload, credentials, artifacts, contentType) {
216
217     var calculatedHash = Crypto.calculatePayloadHash(payload, credentials.algorithm, contentType);
218     return Cryptiles.fixedTimeComparison(calculatedHash, artifacts.hash);
219 };
220
221
222 // Authenticate payload hash - used when payload cannot be provided during authenticate()
223
224 /*
225     calculatedHash: the payload hash calculated using Crypto.calculatePayloadHash()
226     artifacts:      from authenticate callback
227 */
228
229 exports.authenticatePayloadHash = function (calculatedHash, artifacts) {
230
231     return Cryptiles.fixedTimeComparison(calculatedHash, artifacts.hash);
232 };
233
234
235 // Generate a Server-Authorization header for a given response
236
237 /*
238     credentials: {},                                        // Object received from authenticate()
239     artifacts: {}                                           // Object received from authenticate(); 'mac', 'hash', and 'ext' - ignored
240     options: {
241         ext: 'application-specific',                        // Application specific data sent via the ext attribute
242         payload: '{"some":"payload"}',                      // UTF-8 encoded string for body hash generation (ignored if hash provided)
243         contentType: 'application/json',                    // Payload content-type (ignored if hash provided)
244         hash: 'U4MKKSmiVxk37JCCrAVIjV='                     // Pre-calculated payload hash
245     }
246 */
247
248 exports.header = function (credentials, artifacts, options) {
249
250     // Prepare inputs
251
252     options = options || {};
253
254     if (!artifacts ||
255         typeof artifacts !== 'object' ||
256         typeof options !== 'object') {
257
258         return '';
259     }
260
261     artifacts = Hoek.clone(artifacts);
262     delete artifacts.mac;
263     artifacts.hash = options.hash;
264     artifacts.ext = options.ext;
265
266     // Validate credentials
267
268     if (!credentials ||
269         !credentials.key ||
270         !credentials.algorithm) {
271
272         // Invalid credential object
273         return '';
274     }
275
276     if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
277         return '';
278     }
279
280     // Calculate payload hash
281
282     if (!artifacts.hash &&
283         (options.payload || options.payload === '')) {
284
285         artifacts.hash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, options.contentType);
286     }
287
288     var mac = Crypto.calculateMac('response', credentials, artifacts);
289
290     // Construct header
291
292     var header = 'Hawk mac="' + mac + '"' +
293                  (artifacts.hash ? ', hash="' + artifacts.hash + '"' : '');
294
295     if (artifacts.ext !== null &&
296         artifacts.ext !== undefined &&
297         artifacts.ext !== '') {                       // Other falsey values allowed
298
299         header += ', ext="' + Hoek.escapeHeaderAttribute(artifacts.ext) + '"';
300     }
301
302     return header;
303 };
304
305
306 /*
307  * Arguments and options are the same as authenticate() with the exception that the only supported options are:
308  * 'hostHeaderName', 'localtimeOffsetMsec', 'host', 'port'
309  */
310
311
312 //                       1     2             3           4
313 internals.bewitRegex = /^(\/.*)([\?&])bewit\=([^&$]*)(?:&(.+))?$/;
314
315
316 exports.authenticateBewit = function (req, credentialsFunc, options, callback) {
317
318     callback = Hoek.nextTick(callback);
319
320     // Application time
321
322     var now = Utils.now(options.localtimeOffsetMsec);
323
324     // Convert node Http request object to a request configuration object
325
326     var request = Utils.parseRequest(req, options);
327     if (request instanceof Error) {
328         return callback(Boom.badRequest(request.message));
329     }
330
331     // Extract bewit
332
333     if (request.url.length > Utils.limits.maxMatchLength) {
334         return callback(Boom.badRequest('Resource path exceeds max length'));
335     }
336
337     var resource = request.url.match(internals.bewitRegex);
338     if (!resource) {
339         return callback(Boom.unauthorized(null, 'Hawk'));
340     }
341
342     // Bewit not empty
343
344     if (!resource[3]) {
345         return callback(Boom.unauthorized('Empty bewit', 'Hawk'));
346     }
347
348     // Verify method is GET
349
350     if (request.method !== 'GET' &&
351         request.method !== 'HEAD') {
352
353         return callback(Boom.unauthorized('Invalid method', 'Hawk'));
354     }
355
356     // No other authentication
357
358     if (request.authorization) {
359         return callback(Boom.badRequest('Multiple authentications'));
360     }
361
362     // Parse bewit
363
364     var bewitString = Hoek.base64urlDecode(resource[3]);
365     if (bewitString instanceof Error) {
366         return callback(Boom.badRequest('Invalid bewit encoding'));
367     }
368
369     // Bewit format: id\exp\mac\ext ('\' is used because it is a reserved header attribute character)
370
371     var bewitParts = bewitString.split('\\');
372     if (bewitParts.length !== 4) {
373         return callback(Boom.badRequest('Invalid bewit structure'));
374     }
375
376     var bewit = {
377         id: bewitParts[0],
378         exp: parseInt(bewitParts[1], 10),
379         mac: bewitParts[2],
380         ext: bewitParts[3] || ''
381     };
382
383     if (!bewit.id ||
384         !bewit.exp ||
385         !bewit.mac) {
386
387         return callback(Boom.badRequest('Missing bewit attributes'));
388     }
389
390     // Construct URL without bewit
391
392     var url = resource[1];
393     if (resource[4]) {
394         url += resource[2] + resource[4];
395     }
396
397     // Check expiration
398
399     if (bewit.exp * 1000 <= now) {
400         return callback(Boom.unauthorized('Access expired', 'Hawk'), null, bewit);
401     }
402
403     // Fetch Hawk credentials
404
405     credentialsFunc(bewit.id, function (err, credentials) {
406
407         if (err) {
408             return callback(err, credentials || null, bewit.ext);
409         }
410
411         if (!credentials) {
412             return callback(Boom.unauthorized('Unknown credentials', 'Hawk'), null, bewit);
413         }
414
415         if (!credentials.key ||
416             !credentials.algorithm) {
417
418             return callback(Boom.internal('Invalid credentials'), credentials, bewit);
419         }
420
421         if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
422             return callback(Boom.internal('Unknown algorithm'), credentials, bewit);
423         }
424
425         // Calculate MAC
426
427         var mac = Crypto.calculateMac('bewit', credentials, {
428             ts: bewit.exp,
429             nonce: '',
430             method: 'GET',
431             resource: url,
432             host: request.host,
433             port: request.port,
434             ext: bewit.ext
435         });
436
437         if (!Cryptiles.fixedTimeComparison(mac, bewit.mac)) {
438             return callback(Boom.unauthorized('Bad mac', 'Hawk'), credentials, bewit);
439         }
440
441         // Successful authentication
442
443         return callback(null, credentials, bewit);
444     });
445 };
446
447
448 /*
449  *  options are the same as authenticate() with the exception that the only supported options are:
450  * 'nonceFunc', 'timestampSkewSec', 'localtimeOffsetMsec'
451  */
452
453 exports.authenticateMessage = function (host, port, message, authorization, credentialsFunc, options, callback) {
454
455     callback = Hoek.nextTick(callback);
456
457     // Default options
458
459     options.nonceFunc = options.nonceFunc || internals.nonceFunc;
460     options.timestampSkewSec = options.timestampSkewSec || 60;                                                  // 60 seconds
461
462     // Application time
463
464     var now = Utils.now(options.localtimeOffsetMsec);                       // Measure now before any other processing
465
466     // Validate authorization
467
468     if (!authorization.id ||
469         !authorization.ts ||
470         !authorization.nonce ||
471         !authorization.hash ||
472         !authorization.mac) {
473
474         return callback(Boom.badRequest('Invalid authorization'));
475     }
476
477     // Fetch Hawk credentials
478
479     credentialsFunc(authorization.id, function (err, credentials) {
480
481         if (err) {
482             return callback(err, credentials || null);
483         }
484
485         if (!credentials) {
486             return callback(Boom.unauthorized('Unknown credentials', 'Hawk'));
487         }
488
489         if (!credentials.key ||
490             !credentials.algorithm) {
491
492             return callback(Boom.internal('Invalid credentials'), credentials);
493         }
494
495         if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
496             return callback(Boom.internal('Unknown algorithm'), credentials);
497         }
498
499         // Construct artifacts container
500
501         var artifacts = {
502             ts: authorization.ts,
503             nonce: authorization.nonce,
504             host: host,
505             port: port,
506             hash: authorization.hash
507         };
508
509         // Calculate MAC
510
511         var mac = Crypto.calculateMac('message', credentials, artifacts);
512         if (!Cryptiles.fixedTimeComparison(mac, authorization.mac)) {
513             return callback(Boom.unauthorized('Bad mac', 'Hawk'), credentials);
514         }
515
516         // Check payload hash
517
518         var hash = Crypto.calculatePayloadHash(message, credentials.algorithm);
519         if (!Cryptiles.fixedTimeComparison(hash, authorization.hash)) {
520             return callback(Boom.unauthorized('Bad message hash', 'Hawk'), credentials);
521         }
522
523         // Check nonce
524
525         options.nonceFunc(credentials.key, authorization.nonce, authorization.ts, function (err) {
526
527             if (err) {
528                 return callback(Boom.unauthorized('Invalid nonce', 'Hawk'), credentials);
529             }
530
531             // Check timestamp staleness
532
533             if (Math.abs((authorization.ts * 1000) - now) > (options.timestampSkewSec * 1000)) {
534                 return callback(Boom.unauthorized('Stale timestamp'), credentials);
535             }
536
537             // Successful authentication
538
539             return callback(null, credentials);
540         });
541     });
542 };
543
544
545 internals.nonceFunc = function (key, nonce, ts, nonceCallback) {
546
547     return nonceCallback();         // No validation
548 };