UNPKG

7.17 kBJavaScriptView Raw
1"use strict";
2
3const audit = require('./helper/audit');
4const access = require('./helper/access');
5const bootstrap = require('./helper/bootstrap');
6const cookieParser = require('cookie-parser');
7const expressSession = require('express-session');
8const json = require('body-parser')
9 .json();
10const parseFilters = require('./filters/parse');
11const passport = require('passport');
12
13const CollectionSessionStore = require('./session/CollectionSessionStore');
14const MemorySessionStore = require('./session/MemorySessionStore');
15
16/**
17 * Library entry point
18 *
19 * @param {Express} app result of express()
20 * @param {Config} config configuration
21 */
22function setup(app, config)
23{
24 // PASSPORT BOILERPLATE
25 app.use(bootstrap(app, config));
26 app.use(cookieParser());
27 app.use(expressSession({
28 secret: Math.round(Date.now() / 1000 / 3600 / 24)
29 .toString(16),
30 resave: true,
31 saveUninitialized: false,
32 store: config.sessions ? new CollectionSessionStore(config.sessions, config) : new MemorySessionStore(config),
33 }));
34 app.use(passport.initialize());
35 app.use(passport.session());
36
37 config.users = config.users || config.collection;
38
39 // User serialisation
40 passport.serializeUser(function (user, done)
41 {
42 done(null, user.id || 'none');
43 });
44 passport.deserializeUser(function (user, done)
45 {
46 done(null, config.users.lookup[user] || {});
47 });
48
49 // API endpoint
50 config.prefix = config.prefix || '/api/accounts';
51
52 // setup APIs
53 setupAuthAPI(app, config);
54 setupAccountsAPI(app, config);
55}
56
57function setupAuthAPI(app, config)
58{
59 config.custom = config.custom || {};
60
61 const prefix = config.prefix;
62
63 // login/registration
64 const methods = [];
65
66 for (let auth of config.auth || [])
67 {
68 methods.push(auth.description);
69 auth.install(app, `${prefix}/${auth.method}`, passport);
70 }
71 app.get(`${prefix}/methods.json`, (req, res) =>
72 {
73 res.json(methods);
74 });
75
76 // self read
77 app.get(`${prefix}/current.json`, (req, res) =>
78 {
79 if (req.user && req.user.id)
80 {
81 let output = Object.assign({}, req.user);
82
83 if (output.password)
84 {
85 output.password = true;
86 }
87 res.json(output);
88 }
89 else
90 {
91 res.json(false);
92 }
93 });
94
95 // self update
96 app.put(`${prefix}/current.json`, access.LOGGEDIN, json, (req, res) =>
97 {
98 let user = req.user;
99
100 if (req.body && req.body.password)
101 {
102 if (!config.crypt)
103 {
104 return res.error('A crypt is not configured', audit.INVALID_REQUEST);
105 }
106 if (config.auth.filter((a) => a.description.usesPassword)
107 .length === 0)
108 {
109 return res.error('Not using any modules that use password', audit.INVALID_REQUEST);
110 }
111 config.crypt.hash(req.body.password)
112 .then((hash) =>
113 {
114 user.password = hash;
115 res.resolve(config.users.updateRecord(user), 'Password set', audit.ACCOUNT_CHANGE_PASSWORD);
116 }, res.reject(audit.ACCOUNT_CHANGE_PASSWORD_FAILURE));
117 }
118 else
119 {
120 try
121 {
122 updateUserRecord(user, req.body || {}, false, config.custom);
123 res.resolve(config.users.updateRecord(user), 'Done', audit.ACCOUNT_CHANGE);
124 }
125 catch (e)
126 {
127 res.error(e.message, `${audit.ACCOUNT_CHANGE}_FAILURE`)
128 }
129 }
130 });
131
132 // logout
133 app.all(`${prefix}/logout.json`, (req, res) =>
134 {
135 res.success('Logged out', audit.LOGOUT);
136 req.logout();
137 req.session.destroy();
138 });
139}
140
141function setupAccountsAPI(app, config)
142{
143 // ACCOUNT MANAGEMENT API
144 const prefix = config.prefix;
145 const collection = config.users;
146
147 // discover
148 const USER_DATA_ACCESS = access.ROLE_ONE_OF(config.administratorRoles || {});
149
150 app.all(`${prefix}/search.json`, USER_DATA_ACCESS, (req, res) =>
151 {
152 let query = parseFilters(collection.searchMeta, req.query);
153
154 res.resolve(collection.searchRecords(query), false, audit.ACCOUNT_SEARCH, JSON.stringify(query));
155 });
156
157 // create - not needed in this model. TODO: might be worth implementing later
158 /*
159 app.post(`${prefix}/:user.json`, USER_DATA_ACCESS, json, (req, res) =>
160 {
161 res.json('TODO');
162 });
163 */
164
165 // read
166 app.get(`${prefix}/:user.json`, USER_DATA_ACCESS, (req, res) =>
167 {
168 let query = {};
169
170 query[collection.primaryKey] = req.params.user;
171 collection.readRecord(query)
172 .then((user) =>
173 {
174 let output = Object.assign({}, user);
175
176 if (output.password)
177 {
178 output.password = true;
179 }
180 res.json(output);
181 res.audit(audit.ACCOUNT_READ + audit.SUCCESS, JSON.stringify(query));
182 }, res.reject(audit.ACCOUNT_READ + audit.FAILURE), JSON.stringify(query));
183 });
184
185 // update
186 app.put(`${prefix}/:user.json`, USER_DATA_ACCESS, json, (req, res) =>
187 {
188 let query = {};
189
190 query[collection.primaryKey] = req.params.user;
191 collection.readRecord(query)
192 .then((record) =>
193 {
194 let params = Object.keys(req.body);
195
196 params = JSON.stringify(Object.assign({
197 params
198 }, query));
199 try
200 {
201 updateUserRecord(record, req.body, req.user, config.custom);
202 res.resolve(collection.updateRecord(record), 'Done', audit.ACCOUNT_UPDATE, params);
203 }
204 catch (e)
205 {
206 res.error(e.message, audit.ACCOUNT_UPDATE + audit.FAILURE, params);
207 }
208 }, res.reject(audit.ACCOUNT_UPDATE + audit.FAILURE, JSON.stringify(query)));
209 });
210
211 // delete
212 app.delete(`${prefix}/:user.json`, USER_DATA_ACCESS, (req, res) =>
213 {
214 let query = {};
215
216 query[collection.primaryKey] = req.params.user;
217 if (req.params.user !== req.user.id)
218 {
219 res.resolve(collection.deleteRecord(query), 'User deleted', audit.ACCOUNT_DELETE, JSON.stringify(req.params));
220 }
221 else
222 {
223 res.error("Can't delete yourself", audit.ACCOUNT_DELETE + audit.FAILURE);
224 }
225 });
226
227}
228
229function updateUserRecord(user, update, loginUser = false, custom)
230{
231 for (let field in update)
232 {
233 let value = update[field];
234
235 if (custom[field])
236 {
237 if (custom[field].validate)
238 {
239 custom[field].validate(value);
240 }
241 user[field] = value;
242 continue;
243 }
244
245 switch (field)
246 {
247 case 'displayName':
248 //~ case 'notificationInterval':
249 if (typeof value !== 'string')
250 {
251 throw new Error(`${field} value is not string`);
252 }
253 user[field] = value;
254 break;
255 case 'password':
256 delete user[field];
257 break;
258 case 'roles':
259 if (!loginUser || loginUser.id === user.id)
260 {
261 throw new Error(`Can't update your own ${field}`);
262 }
263 user[field] = value;
264 break;
265 //~ case 'notificationSubscriptions':
266 //~ if (typeof value !== 'object')
267 //~ {
268 //~ throw new Error(`${field} value is not object`);
269 //~ }
270 //~ for (let valueField in value)
271 //~ {
272 //~ if (typeof value[valueField] !== 'boolean')
273 //~ {
274 //~ throw new Error(`${valueField} value is not boolean`);
275 //~ }
276 //~ }
277 //~ user[field] = value;
278 //~ break;
279 default:
280 throw new Error(`${field} not editable`);
281 }
282 }
283}
284
285module.exports = setup;