This repository has been archived by the owner on Apr 16, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 52
/
Copy pathrequest.js
executable file
·250 lines (170 loc) · 6.6 KB
/
request.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// Load modules
var Url = require('url');
var Boom = require('boom');
var Crypto = require('./crypto');
var Ticket = require('./ticket');
var Settings = require('./settings');
var Utils = require('./utils');
// Declare internals
var internals = {};
// MAC request
/*
var request = {
method: 'GET',
resource: '/path?query',
host: 'example.com',
port: 80
};
var attributes = {
ts: 1348170630020 // Date.now()
ext: 'app data' // Server-specific extension data (string)
};
*/
exports.mac = function (request, ticket, attributes) {
var normalized = ticket.id + '\n' +
ticket.app + '\n' +
(attributes.dlg || '') + '\n' +
attributes.ts + '\n' +
request.method.toUpperCase() + '\n' +
request.resource + '\n' +
request.host.toLowerCase() + '\n' +
request.port + '\n' +
(attributes.ext || '');
var mac = Crypto.hmacKey(ticket.key, ticket.algorithm, normalized);
return mac;
};
exports.validateMac = function (request, ticket, attributes) {
var mac = exports.mac(request, ticket, attributes);
return mac === attributes.mac;
};
/*
var options = {
hostHeader: X-Forwarded-Host, // Alternative host header modified by proxies
isHttps: false // Used to determine default port if not present in Host header, defaults to false
};
*/
exports.parse = function (req, options) {
// Obtain host and port information
var hostHeader = (options.hostHeader ? req.headers[options.hostHeader.toLowerCase()] : req.headers.host);
if (!hostHeader) {
return Boom.badRequest('Missing Host header field');
}
var hostHeaderRegex = /^(?:(?:\r\n)?[\t ])*([^:]+)(?::(\d+))*(?:(?:\r\n)?[\t ])*$/; // Does not support IPv6
var hostParts = hostHeaderRegex.exec(hostHeader);
if (!hostParts ||
hostParts.length <= 2 ||
!hostParts[1]) {
return Boom.badRequest('Invalid Host header field');
}
var host = hostParts[1];
var port = hostParts[2] || (options.isHttps ? 443 : 80);
// Parse URI
var uri = Url.parse(req.url);
var resource = uri.pathname + (uri.search || '');
// Parse HTTP Authorization header
if (!req.headers.authorization) {
return Boom.unauthorized(null, 'Oz');
}
var attributes = exports.parseAuthHeader(req.headers.authorization);
// Verify MAC authentication scheme
if (attributes instanceof Error) {
return attributes;
}
// Verify required header attributes
if (!attributes.id ||
!attributes.app ||
!attributes.ts ||
!attributes.mac) {
return Boom.badRequest('Missing authentication attributes in Authorization header field');
}
// Assemble components
var request = {
method: req.method.toLowerCase(),
resource: resource,
host: host,
port: port,
auth: attributes
};
return request;
};
// Extract attributes from OZ header (strict)
exports.parseAuthHeader = function (header) {
// Authorization: Oz id="asdlaskjdlaksjdlaksjd", app="123423234", ts="1348191870082", ext="", mac=""
var headerRegex = /^[Oo][Zz]\s+(.*)$/;
var headerParts = headerRegex.exec(header);
if (!headerParts ||
headerParts.length !== 2 ||
!headerParts[1]) {
// Invalid header format
return (Boom.internal('Wrong authentication scheme'));
}
var attributes = {};
var attributesRegex = /(id|app|ts|ext|mac|dlg)="((?:[^"\\]|\\\\|\\\")*)"\s*(?:,\s*|$)/g;
var verify = headerParts[1].replace(attributesRegex, function ($0, $1, $2) {
if (attributes[$1] === undefined) {
attributes[$1] = $2.replace(/\\\"/g, '"').replace(/\\\\/g, '\\');
return '';
}
});
if (verify) { // verify will be empty on full match
// Did not match all parts
return (Boom.badRequest('Authorization header field includes unknown attributes: ' + verify));
}
return attributes;
};
// Validate an incoming request
// options: see exports.parse()
exports.authenticate = function (req, encryptionPassword, options, callback) {
if (!encryptionPassword) {
return callback(Boom.internal('Invalid encryption password'));
}
var now = Date.now();
// Parse request
var request = exports.parse(req, options);
if (request instanceof Error) {
return callback(request);
}
// Parse ticket id
Ticket.parse(request.auth.id, encryptionPassword, function (err, ticket) {
if (err) {
return callback(err);
}
// Check expiration
if (ticket.exp <= now) {
return callback(Boom.unauthorized('Expired ticket', 'Oz'));
}
// Check application
if ((request.auth.dlg || ticket.delegatedBy) &&
ticket.delegatedBy !== request.auth.dlg) {
return callback(Boom.unauthorized('Mismatching delegated application id', 'Oz'));
}
if (ticket.app !== request.auth.app) {
return callback(Boom.unauthorized('Mismatching application id', 'Oz'));
}
// Validate MAC
if (!exports.validateMac(request, ticket, request.auth)) {
return callback(Boom.unauthorized('Invalid request MAC', 'Oz'));
}
// Check timestamp
if (Math.abs(now - ticket.offset - request.auth.ts) >= Settings.ticket.timestampWindow) {
return callback(Boom.unauthorized('Request includes stale timestamp', 'Oz'));
}
// Return result
return callback(null, ticket, request.auth.ext);
});
};
// Generate an Authorization header
exports.formatHeader = function (attributes, id, app, mac) {
// Construct header
return 'Oz id="' + id + '", app="' + Utils.escapeHeaderAttribute(app) + '", ts="' + attributes.ts + (attributes.ext ? '", ext="' + Utils.escapeHeaderAttribute(attributes.ext) : '') + (attributes.dlg ? '", dlg="' + Utils.escapeHeaderAttribute(attributes.dlg) : '') + '", mac="' + mac + '"';
};
// Calculate mac and generate header
exports.generateHeader = function (request, ticket, attributes) {
attributes = attributes || { ts: Date.now() };
if (ticket.delegatedBy && !attributes.dlg) {
attributes.dlg = ticket.delegatedBy;
}
var mac = exports.mac(request, ticket, attributes);
var header = exports.formatHeader(attributes, ticket.id, ticket.app, mac);
return header;
};