UNPKG

6.28 kBJavaScriptView Raw
1"use strict";
2
3const audit = require('../helper/audit');
4const generateId = require('../helper/generateId');
5
6/**
7 * Authenticator/passport strategy wrapper abstraction.
8 *
9 * I.e. it is the parent class of all auth classes.
10 */
11class Auth
12{
13
14 /**
15 * @param {string} method
16 * @param {object} options common options for all auth classes; see properties
17 */
18 constructor(method, options)
19 {
20
21 /**
22 * Authentication method name. E.g. 'email' for Email based authentication.
23 * This is used as an unique id for various things.
24 * @type string
25 */
26 this.method = method;
27
28 /**
29 * This is a standard descriptor of this authentication mechanism that is publicly shared.
30 * Clients should use this to figure out how to use a login auth from outside.
31 *
32 * Not directly configurable.
33 *
34 * @type object
35 */
36 this.description = {
37 method: this.method
38 };
39
40 /**
41 * Additional settings given to passport.authenticate.
42 *
43 * Not directly configurable.
44 *
45 * @type object
46 */
47 this.authenticateOptions = {
48 failureMessage: 'login failed',
49 badRequestMessage: 'XXX'
50 };
51
52 /**
53 * Users collection.
54 *
55 * Note: various things all assume that a CachedCollection is being used.
56 *
57 * @type {CachedCollection}
58 */
59 this.users = options.users || options.collection;
60
61 /**
62 * Default roles new registered users should assume.
63 *
64 * @type {object}
65 */
66 this.defaultRoles = options.defaultRoles || {};
67
68 /**
69 * Custom fields
70 *
71 * @type {object}
72 */
73 this.custom = options.custom || {};
74
75 //~ /**
76 //~ * Instance of email sender class for sending emails.
77 //~ *
78 //~ * @type {EmailSender}
79 //~ */
80 //~ this.emailSender = options.emailSender;
81
82
83 //~ this.defaultNotificationInterval = options.defaultNotificationInterval || -1;
84
85 //~ if (options.recaptcha)
86 //~ {
87 //~ this.description.recaptcha = true;
88 //~ }
89 }
90
91 /**
92 * Must be overridden to provide implementation of said authentication method.
93 * @param {ExpressApplication} app express application
94 * @param {string} prefix all route prefix
95 * @param {Passport} passport passport class
96 * @abstract
97 */
98 install(app, prefix, passport)
99 {
100 throw new Error('TODO: ABSTRACT');
101 }
102
103 /**
104 * Helper method that finds an user based on a credential.
105 *
106 * A credential is something like an email address or a facebook user id.
107 *
108 * This is something that uniquely identifies an account.
109 *
110 * @param {string} value
111 * @return {User|false}
112 */
113 findUser(value)
114 {
115 const users = this.users.lookup;
116
117 for (let userId in users)
118 {
119 let user = users[userId];
120 let credentials = (user.credentials || [])
121 .filter((credential) => credential.type === this.method && credential.value === value);
122
123 if (credentials.length > 0)
124 {
125 return user;
126 }
127 }
128
129 return false;
130 }
131
132 /**
133 * Helper method for SSO type logins.
134 *
135 * This method finds existing or creates new accounts basen on profile
136 * information returned from oauth partner.
137 *
138 * @param {string} username unique id
139 * @param {Profile} profile unique id
140 * @param {Function} done callback to call when our work is done
141 * @param {Request} [req] request object
142 */
143 handleUserLoginByProfile(username, profile, done, req = undefined)
144 {
145 username = username || profile.id;
146 // find an account
147 let user = this.findUser(username);
148
149 if (user) // if found, log in found account
150 {
151 done(null, user);
152 }
153 else // if not found, make a new user and log new user in
154 {
155 user = this.createUserFromProfile(profile);
156 this.users.createRecord(user)
157 .then((user) =>
158 {
159 req && req.audit(audit.ACCOUNT_CREATE, JSON.stringify({
160 user,
161 profile
162 }));
163 done(null, user);
164 }, done);
165 }
166 }
167
168 /**
169 * Helper method that creates an User object from a Profile
170 *
171 * @param {Profile} profile
172 * @return {User}
173 */
174 createUserFromProfile(profile)
175 {
176 let user = {
177 // unique id
178 // can't use id from profile as these might conflict across login providers
179 id: generateId(),
180 // login credentials
181 credentials: [{
182 type: this.method,
183 value: profile.id
184 }],
185 // new user roles
186 roles: this.defaultRoles,
187 // profile bs
188 displayName: profile.displayName,
189 //~ name: profile.name,
190 photos: profile.photos,
191 //~ // notification settings
192 //~ notifications: [],
193 //~ notificationInterval: -1,
194 //~ notificationLastSent: 0,
195 //~ notificationSubscriptions: {},
196 };
197
198 for (let field in this.custom)
199 {
200 if (this.custom[field].derive)
201 {
202 let value = this.custom[field].derive(profile);
203
204 if (value)
205 {
206 user[field] = value;
207 }
208 }
209 }
210
211 return user;
212 }
213
214 /**
215 * Helper method that produces a middleware to handle successful logged in
216 * case.
217 *
218 * @param {boolean} [redirect=false] redirect based login is used
219 * @return {ExpressMiddleware}
220 */
221 loggedIn(redirect = false)
222 {
223 if (redirect && typeof redirect !== 'string')
224 {
225 redirect = '/';
226 }
227
228 return function (req, res)
229 {
230 // if not req.user.id then it is not a real use
231 // i.e. we are forwarding error or custom payload
232 if (!req.user.id)
233 {
234 res.error(req.user.error, audit.LOGIN_FAILURE);
235 req.logout();
236 }
237 else
238 {
239 // otherwise, we make a fuss about logging in
240 res.audit(audit.LOGIN, `Logged in via ${this.method}`, JSON.stringify({
241 id: req.user.id,
242 displayName: req.user.displayName,
243 roles: req.user.roles
244 }));
245
246 // for redirect based login methods, we redirect back to some url
247 if (redirect)
248 {
249 res.redirect(redirect);
250 }
251 else
252 {
253 // otherwise we return a login success message
254 res.success(`Logged in via ${this.method}`, audit.LOGIN);
255 }
256 }
257 }.bind(this);
258 }
259
260}
261
262module.exports = Auth;