1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | var TraceUtils = require('@themost/common/utils').TraceUtils;
|
10 | var RandomUtils = require('@themost/common/utils').RandomUtils;
|
11 | var AbstractClassError = require('@themost/common/errors').AbstractClassError;
|
12 | var AbstractMethodError = require('@themost/common/errors').AbstractMethodError;
|
13 | var HttpUnauthorizedError = require('@themost/common/errors').HttpUnauthorizedError;
|
14 | var HttpForbiddenError = require('@themost/common/errors').HttpForbiddenError;
|
15 | var LangUtils = require('@themost/common/utils').LangUtils;
|
16 | var Args = require('@themost/common/utils').Args;
|
17 | var HttpApplicationService = require('./../types').HttpApplicationService;
|
18 | var Symbol = require('symbol');
|
19 | var _ = require('lodash');
|
20 | var moment = require('moment');
|
21 | var crypto = require('crypto');
|
22 | var Q = require('q');
|
23 |
|
24 | var optionsProperty = Symbol('options');
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 | function AuthHandler() {
|
33 |
|
34 | }
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | AuthHandler.parseCookies = function(request) {
|
40 | var list = {},
|
41 | rc = request.headers.cookie;
|
42 | rc && rc.split(';').forEach(function( cookie ) {
|
43 | var parts = cookie.split('=');
|
44 | list[parts.shift().trim()] = unescape(parts.join('='));
|
45 | });
|
46 | return list;
|
47 | };
|
48 |
|
49 | AuthHandler.ANONYMOUS_IDENTITY = { name: 'anonymous', authenticationType:'None' };
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | AuthHandler.prototype.authenticateRequest = function (context, callback) {
|
57 | try {
|
58 | callback = callback || function() {};
|
59 | var cookies = {}, model = context.model('User');
|
60 | var config = context.getApplication().getConfiguration();
|
61 | var settings = config.settings ? (config.settings.auth || { }) : { } ;
|
62 | settings.name = settings.name || '.MAUTH';
|
63 | if (context && context.request)
|
64 | cookies = AuthHandler.parseCookies(context.request);
|
65 | if (cookies[settings.name]) {
|
66 | var str = null;
|
67 | try {
|
68 | str =context.getApplication().getEncryptionStrategy().decrypt(cookies[settings.name]);
|
69 | }
|
70 | catch (err) {
|
71 | TraceUtils.log(err);
|
72 | }
|
73 |
|
74 | var userName = null;
|
75 | if (str) {
|
76 | var authCookie = JSON.parse(str);
|
77 |
|
78 | if (authCookie.user)
|
79 | userName = authCookie.user;
|
80 | }
|
81 | if (typeof model === 'undefined' || model === null) {
|
82 |
|
83 | context.user = { name: userName || 'anonymous', authenticationType:'Basic' };
|
84 | return callback();
|
85 | }
|
86 |
|
87 | if (userName) {
|
88 |
|
89 | context.user = model.convert({ name: userName, authenticationType:'Basic' });
|
90 | }
|
91 | else {
|
92 |
|
93 |
|
94 | context.user = model.convert(AuthHandler.ANONYMOUS_IDENTITY);
|
95 | }
|
96 | return callback();
|
97 | }
|
98 | else {
|
99 |
|
100 | if (model)
|
101 | context.user = model.convert(AuthHandler.ANONYMOUS_IDENTITY);
|
102 | else
|
103 | context.user = AuthHandler.ANONYMOUS_IDENTITY;
|
104 |
|
105 | return callback();
|
106 | }
|
107 | }
|
108 | catch (err) {
|
109 | return callback(err);
|
110 | }
|
111 | };
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 | AuthHandler.prototype.preExecuteResult = function (args, callback) {
|
118 | try {
|
119 | callback = callback || function() {};
|
120 | var context = args.context, model = context.model('User');
|
121 | if (typeof model === 'undefined' || model === null) {
|
122 | return callback();
|
123 | }
|
124 | var authenticationType = context.user.authenticationType;
|
125 | model.where('name').equal(context.user.name).expand('groups').silent().first(function(err, result) {
|
126 | if (err) { return callback(err); }
|
127 | if (result) {
|
128 |
|
129 | context.user = model.convert(result);
|
130 | context.user.authenticationType = authenticationType;
|
131 | return callback();
|
132 | }
|
133 | else if (context.user.name!=='anonymous') {
|
134 | model.where('name').equal('anonymous').expand('groups').silent().first(function(err, result) {
|
135 | if (err) {
|
136 | return callback(err);
|
137 | }
|
138 | if (result) {
|
139 | context.user = model.convert(result);
|
140 | context.user.authenticationType = authenticationType;
|
141 | }
|
142 | return callback();
|
143 | });
|
144 | }
|
145 | else {
|
146 |
|
147 | return callback();
|
148 | }
|
149 | });
|
150 | }
|
151 | catch (err) {
|
152 | callback(err);
|
153 | }
|
154 | };
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 | AuthHandler.createInstance = function() {
|
161 | return new AuthHandler();
|
162 | };
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 | function AuthStrategy(app) {
|
172 | AuthStrategy.super_.bind(this)(app);
|
173 | if (this.constructor === AuthStrategy.prototype.constructor) {
|
174 | throw new AbstractClassError();
|
175 | }
|
176 | }
|
177 | LangUtils.inherits(AuthStrategy, HttpApplicationService);
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 |
|
188 | AuthStrategy.prototype.setAuthCookie = function(thisContext, userName, options) {
|
189 | throw new AbstractMethodError();
|
190 | };
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 | AuthStrategy.prototype.getAuthCookie = function(thisContext) {
|
200 | throw new AbstractMethodError();
|
201 | };
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 | AuthStrategy.prototype.login = function(thisContext, userName, userPassword) {
|
214 | throw new AbstractMethodError();
|
215 | };
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 | AuthStrategy.prototype.logout = function(thisContext) {
|
227 | throw new AbstractMethodError();
|
228 | };
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 | AuthStrategy.prototype.getUnattendedExecutionAccount = function() {
|
236 | throw new AbstractMethodError();
|
237 | };
|
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 | AuthStrategy.prototype.getOptions = function() {
|
245 | throw new AbstractMethodError();
|
246 | };
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 | function DefaultAuthStrategy(app) {
|
255 | DefaultAuthStrategy.super_.bind(this)(app);
|
256 |
|
257 | this[optionsProperty] = {
|
258 | "name":".MAUTH",
|
259 | "slidingExpiration": false,
|
260 | "expirationTimeout":420,
|
261 | "unattendedExecutionAccount":RandomUtils.randomChars(16)
|
262 | };
|
263 |
|
264 | var keys = _.keys(this[optionsProperty]);
|
265 |
|
266 | var authSettings = _.pick(app.getConfiguration().settings.auth, keys);
|
267 |
|
268 | _.assign(this[optionsProperty], authSettings);
|
269 | }
|
270 | LangUtils.inherits(DefaultAuthStrategy, AuthStrategy);
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 | DefaultAuthStrategy.prototype.getOptions = function() {
|
278 | return this[optionsProperty];
|
279 | };
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 | DefaultAuthStrategy.prototype.setAuthCookie = function(thisContext, userName, options) {
|
287 | var defaultOptions = { user:userName, dateCreated:new Date()};
|
288 | var value;
|
289 | var expires;
|
290 | if (_.isObject(options)) {
|
291 | value = JSON.stringify(_.assign(options, defaultOptions));
|
292 | if (_.isDate(options['expires'])) {
|
293 | expires = options['expires'].toUTCString();
|
294 | }
|
295 | }
|
296 | else {
|
297 | value = JSON.stringify(defaultOptions);
|
298 | }
|
299 |
|
300 | if (_.isNil(expires) && _.isNumber(this.getOptions().expirationTimeout)) {
|
301 | var expirationTimeout = LangUtils.parseInt(this.getOptions().expirationTimeout);
|
302 | if (expirationTimeout>0) {
|
303 | expires = moment(new Date()).add(expirationTimeout,'minutes').toDate().toUTCString();
|
304 | }
|
305 | }
|
306 | var str = this[optionsProperty].name.concat('=', this.getApplication().getEncryptionStrategy().encrypt(value)) + ';path=/';
|
307 | if (typeof expires === 'string') {
|
308 | str +=';expires=' + expires;
|
309 | }
|
310 | thisContext.response.setHeader('Set-Cookie',str);
|
311 | };
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 | DefaultAuthStrategy.prototype.login = function(thisContext, userName, userPassword) {
|
322 | var self = this;
|
323 | return Q.nfbind(function(context, userName, password, callback) {
|
324 | try {
|
325 | context.model('user').where('name').equal(userName).select('id','enabled').silent().first(function(err, result) {
|
326 | if (err) {
|
327 | return callback(new Error('Login failed due to server error. Please try again or contact your system administrator.'));
|
328 | }
|
329 | if (_.isNil(result)) {
|
330 | return callback(new HttpUnauthorizedError('Unknown username. Please try again.'));
|
331 | }
|
332 | if (!result.enabled) {
|
333 | return callback(new HttpForbiddenError('The account is disabled. Please contact your system administrator.'));
|
334 | }
|
335 |
|
336 | var model = context.model('UserCredential');
|
337 | if (typeof model === 'undefined' || model === null) {
|
338 | TraceUtils.log('UserCredential model is missing.');
|
339 | return callback(new Error('Login failed due to server error.'));
|
340 | }
|
341 | model.where('id').equal(result.id).prepare()
|
342 | .and('userPassword').equal('{clear}'.concat(userPassword))
|
343 | .or('userPassword').equal('{md5}'.concat(crypto.createHash('md5').update(userPassword).digest('hex')))
|
344 | .or('userPassword').equal('{sha1}'.concat(crypto.createHash('sha1').update(userPassword).digest('hex')))
|
345 | .silent().count().then(function(count) {
|
346 | if (count===1) {
|
347 |
|
348 | self.setAuthCookie(context, userName);
|
349 | context.user = { name: userName, authenticationType:'Basic' };
|
350 | return callback(null, true);
|
351 | }
|
352 | return callback(new HttpUnauthorizedError('Unknown username or bad password.'));
|
353 | }).catch(function(err) {
|
354 | TraceUtils.log(err);
|
355 | return callback(new Error('Login failed due to server error. Please try again or contact your system administrator.'));
|
356 | });
|
357 | });
|
358 | }
|
359 | catch (err) {
|
360 | TraceUtils.log(err);
|
361 | return callback(new Error('Login failed due to internal server error.'));
|
362 | }
|
363 |
|
364 | })(thisContext, userName, userPassword);
|
365 |
|
366 | };
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 | DefaultAuthStrategy.prototype.logout = function(thisContext) {
|
375 | var self = this;
|
376 | return Q.nfbind(function(callback) {
|
377 |
|
378 | self.setAuthCookie(thisContext,'anonymous');
|
379 | return callback();
|
380 | })();
|
381 | };
|
382 |
|
383 |
|
384 |
|
385 |
|
386 |
|
387 |
|
388 |
|
389 | DefaultAuthStrategy.prototype.getAuthCookie = function(thisContext) {
|
390 | var name = this.getOptions().name;
|
391 | var cookie = thisContext.getCookie(name);
|
392 | if (cookie) {
|
393 | return this.getApplication().getEncryptionStrategy().decrypt(cookie);
|
394 | }
|
395 | };
|
396 |
|
397 |
|
398 |
|
399 |
|
400 |
|
401 |
|
402 | DefaultAuthStrategy.prototype.getUnattendedExecutionAccount = function() {
|
403 | return this[optionsProperty].unattendedExecutionAccount;
|
404 | };
|
405 |
|
406 |
|
407 |
|
408 |
|
409 |
|
410 |
|
411 |
|
412 |
|
413 | function EncryptionStrategy(app) {
|
414 | EncryptionStrategy.super_.bind(this)(app);
|
415 | if (this.constructor === EncryptionStrategy.prototype.constructor) {
|
416 | throw new AbstractClassError();
|
417 | }
|
418 | }
|
419 | LangUtils.inherits(EncryptionStrategy, HttpApplicationService);
|
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 |
|
426 |
|
427 |
|
428 |
|
429 | EncryptionStrategy.prototype.encrypt = function(data) {
|
430 | throw new AbstractMethodError();
|
431 | };
|
432 |
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 | EncryptionStrategy.prototype.decrypt = function(data) {
|
442 | throw new AbstractMethodError();
|
443 | };
|
444 |
|
445 | var cryptoProperty = Symbol('crypto');
|
446 |
|
447 |
|
448 |
|
449 |
|
450 |
|
451 |
|
452 |
|
453 | function DefaultEncryptionStrategy(app) {
|
454 | DefaultEncryptionStrategy.super_.bind(this)(app);
|
455 | this[cryptoProperty] = { };
|
456 | _.assign(this[cryptoProperty], app.getConfiguration().settings.crypto);
|
457 | }
|
458 | LangUtils.inherits(DefaultEncryptionStrategy, EncryptionStrategy);
|
459 |
|
460 |
|
461 |
|
462 | DefaultEncryptionStrategy.prototype.getOptions = function() {
|
463 | return this[cryptoProperty];
|
464 | };
|
465 |
|
466 |
|
467 |
|
468 |
|
469 |
|
470 |
|
471 | DefaultEncryptionStrategy.prototype.encrypt = function(data) {
|
472 | if (_.isNil(data)) {
|
473 | return;
|
474 | }
|
475 | Args.check(this.getApplication().hasService(EncryptionStrategy),'Encryption strategy is missing');
|
476 | var options = this.getOptions();
|
477 |
|
478 | Args.check(!_.isNil(options.algorithm), 'Data encryption algorithm is missing. The operation cannot be completed');
|
479 | Args.check(!_.isNil(options.key), 'Data encryption key is missing. The operation cannot be completed');
|
480 |
|
481 | var cipher = crypto.createCipher(options.algorithm, options.key);
|
482 | return cipher.update(data, 'utf8', 'hex') + cipher.final('hex');
|
483 | };
|
484 |
|
485 |
|
486 |
|
487 |
|
488 |
|
489 |
|
490 |
|
491 | DefaultEncryptionStrategy.prototype.decrypt = function(data) {
|
492 | if (_.isNil(data))
|
493 | return;
|
494 | Args.check(this.getApplication().hasService(EncryptionStrategy),'Encryption strategy is missing');
|
495 |
|
496 | var options = this.getOptions();
|
497 |
|
498 | Args.check(!_.isNil(options.algorithm), 'Data encryption algorithm is missing. The operation cannot be completed');
|
499 | Args.check(!_.isNil(options.key), 'Data encryption key is missing. The operation cannot be completed');
|
500 |
|
501 | var decipher = crypto.createDecipher(options.algorithm, options.key);
|
502 | return decipher.update(data, 'hex', 'utf8') + decipher.final('utf8');
|
503 | };
|
504 |
|
505 |
|
506 | if (typeof exports !== 'undefined') {
|
507 | module.exports.AuthHandler = AuthHandler;
|
508 | |
509 |
|
510 |
|
511 | module.exports.createInstance = function () {
|
512 | return AuthHandler.createInstance()
|
513 | };
|
514 | module.exports.AuthStrategy = AuthStrategy;
|
515 | module.exports.DefaultAuthStrategy = DefaultAuthStrategy;
|
516 | module.exports.EncryptionStrategy = EncryptionStrategy;
|
517 | module.exports.DefaultEncryptionStrategy = DefaultEncryptionStrategy;
|
518 | }
|