UNPKG

6.69 kBJavaScriptView Raw
1/**
2 * @copyright Copyright (c) 2019 Maxim Khorin <maksimovichu@gmail.com>
3 */
4'use strict';
5
6const Base = require('../base/Component');
7
8module.exports = class Auth extends Base {
9
10 static getConstants () {
11 return {
12 EVENT_BEFORE_LOGIN: 'beforeLogin',
13 EVENT_AFTER_LOGIN: 'afterLogin',
14 EVENT_BEFORE_LOGOUT: 'beforeLogout',
15 EVENT_AFTER_LOGOUT: 'afterLogout'
16 };
17 }
18
19 constructor (config) {
20 super({
21 depends: ['cookie', 'session'],
22 autoRenewCookie: true,
23 enableAutoLogin: false,
24 enableSession: true,
25 identityCookie: {httpOnly: true},
26 loginUrl: '',
27 defaultAssignments: ['user'],
28 guestAssignments: ['guest'],
29 timeout: null, // in seconds (depends on last user activity)
30 absoluteTimeout: null, // in seconds (depends on last login)
31 rbac: 'rbac',
32 timeoutParam: '__expire',
33 absoluteTimeoutParam: '__absoluteExpire',
34 identityCookieParam: '__identity',
35 idParam: '__id',
36 returnUrlParam: '__returnUrl',
37 csrf: true,
38 csrfParam: '__csrf',
39 csrfLength: 16,
40 Identity: null, // user model
41 WebUser: require('./WebUser'),
42 ...config
43 });
44 }
45
46 init () {
47 this.rbac = this.module.get(this.rbac);
48 this.module.addHandler('use', this.createUser.bind(this));
49 }
50
51 createUser (req, res, next) {
52 res.locals.user = new this.WebUser({
53 auth: this,
54 module: res.locals.module,
55 req, res
56 });
57 this.resolveUser(res.locals.user).then(next, next);
58 }
59
60 resolveUser (user) {
61 return user.ensureIdentity();
62 }
63
64 // EVENTS
65
66 beforeLogin (data) {
67 // call await super.beforeLogin(data) if override it
68 return this.trigger(this.EVENT_BEFORE_LOGIN, new Event(data));
69 }
70
71 afterLogin (data) {
72 // call await super.afterLogin(data) if override it
73 return this.trigger(this.EVENT_AFTER_LOGIN, new Event(data));
74 }
75
76 beforeLogout (data) {
77 // call await super.beforeLogout(data) if override it
78 return this.trigger(this.EVENT_BEFORE_LOGOUT, new Event(data));
79 }
80
81 afterLogout (data) {
82 // call await super.afterLogout(data) if override it
83 return this.trigger(this.EVENT_AFTER_LOGOUT, new Event(data));
84 }
85
86 // LOGIN
87
88 async login (user, data) {
89 await this.beforeLogin({user, ...data});
90 await user.switchIdentity(data.identity, data.duration);
91 await this.afterLogin({user, ...data});
92 }
93
94 async logout (user) {
95 await this.beforeLogout({user});
96 await user.switchIdentity(null);
97 await this.afterLogout({user});
98 }
99
100 async loginByCookie (user) {
101 const data = user.getCookie(this.identityCookieParam);
102 if (!this.validateCookieData(data)) {
103 return false;
104 }
105 const identity = await user.findIdentity(data.id).one();
106 if (identity && identity.checkAuthKey(data.key)) {
107 return this.login(user, {
108 identity,
109 duration: this.autoRenewCookie ? data.duration : 0,
110 cookieBased: true
111 });
112 }
113 }
114
115 validateCookieData (data) {
116 return data
117 && typeof data.id === 'string'
118 && typeof data.key === 'string'
119 && typeof data.duration === 'number'
120 }
121
122 // RENEW
123
124 async renew (user) {
125 const id = user.getSession(this.idParam);
126 const identity = id ? await user.findIdentity(id).one() : null;
127 user.identity = identity;
128 if (identity && this.renewTimeout(user) === false) {
129 await this.logout(user);
130 }
131 if (this.enableAutoLogin) {
132 if (user.isGuest()) {
133 await this.loginByCookie(user);
134 }
135 if (this.autoRenewCookie) {
136 this.renewCookie(user);
137 }
138 }
139 }
140
141 async renewTimeout (user) {
142 if (this.timeout !== null || this.absoluteTimeout !== null) {
143 const now = Math.floor(Date.now() / 1000);
144 const expire = this.timeout !== null
145 ? user.getSession(this.timeoutParam)
146 : null;
147 const absoluteExpire = this.absoluteTimeout !== null
148 ? user.getSession(this.absoluteTimeoutParam)
149 : null;
150 if (expire !== null && expire < now || absoluteExpire !== null && absoluteExpire < now) {
151 return false;
152 }
153 if (this.timeout !== null) {
154 user.setSession(this.timeoutParam, now + this.timeout);
155 }
156 }
157 }
158
159 renewCookie (user) {
160 const value = user.getCookie(this.identityCookieParam);
161 if (value && typeof value.duration === 'number') {
162 this.identityCookie.maxAge = value.duration * 1000;
163 user.setCookie(this.identityCookieParam, value, this.identityCookie);
164 }
165 }
166
167 renewIdentity (user, duration) {
168 if (user.identity) {
169 this.renewIdentitySession(user);
170 if (duration > 0 && this.enableAutoLogin) {
171 this.renewIdentityCookie(user, duration);
172 }
173 } else if (this.enableAutoLogin) {
174 user.clearCookie(this.identityCookieParam, this.identityCookie);
175 }
176 }
177
178 renewIdentitySession (user) {
179 const now = Math.floor(Date.now() / 1000);
180 user.setSession(this.idParam, user.getId());
181 if (this.timeout !== null) {
182 user.setSession(this.timeoutParam, now + this.timeout);
183 }
184 if (this.absoluteTimeout !== null) {
185 user.setSession(this.absoluteTimeoutParam, now + this.absoluteTimeout);
186 }
187 if (this.csrf) {
188 user.setSession(this.csrfParam, this.getCsrfToken());
189 }
190 }
191
192 getCsrfToken () {
193 return SecurityHelper.getRandomString(this.csrfLength);
194 }
195
196 renewIdentityCookie (user, duration) {
197 this.identityCookie.maxAge = duration * 1000;
198 user.setCookie(this.identityCookieParam, {
199 id: user.getId(),
200 key: user.getAuthKey(),
201 duration
202 }, this.identityCookie);
203 }
204};
205
206const Event = require('../base/Event');
207const SecurityHelper = require('../helper/SecurityHelper');
\No newline at end of file