UNPKG

7.86 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
16const passwordField = require('./fields/password');
17const defaultFields = require('./fields/defaultFields');
18
19/**
20 * Library entry point
21 *
22 * @param {Express} app result of express()
23 * @param {Config} config configuration
24 */
25function setup(app, config)
26{
27 // PASSPORT BOILERPLATE
28 app.use(bootstrap(app, config));
29 app.use(cookieParser());
30 app.use(expressSession({
31 secret: Math.round(Date.now() / 1000 / 3600 / 24)
32 .toString(16),
33 resave: true,
34 saveUninitialized: false,
35 store: config.sessions ? new CollectionSessionStore(config.sessions, config) : new MemorySessionStore(config),
36 }));
37 app.use(passport.initialize());
38 app.use(passport.session());
39
40 config.users = config.users || config.collection;
41
42 // User serialisation
43 passport.serializeUser(function (user, done)
44 {
45 done(null, user.id || 'none');
46 });
47 passport.deserializeUser(function (user, done)
48 {
49 done(null, config.users.lookup[user] || {});
50 });
51
52 // API endpoint
53 config.prefix = config.prefix || '/api/accounts';
54 config.fields = config.fields || {};
55 for (let field in defaultFields)
56 {
57 if (!config.fields[field])
58 {
59 config.fields[field] = defaultFields[field];
60 }
61 }
62
63 // setup APIs
64 setupAuthAPI(app, config);
65 setupAccountsAPI(app, config);
66}
67
68function setupAuthAPI(app, config)
69{
70 const prefix = config.prefix;
71
72 // login/registration
73 const methods = [];
74
75 for (let auth of config.auth || [])
76 {
77 methods.push(auth.description);
78 auth.install(app, `${prefix}/${auth.method}`, passport);
79 if (auth.description.usesPassword && !config.fields.password)
80 {
81 config.fields.password = passwordField;
82 }
83 }
84 app.get(`${prefix}/methods.json`, (req, res) =>
85 {
86 res.json(methods);
87 });
88
89 const fields = summariseFields(config.fields);
90 app.get(`${prefix}/fields.json`, (req, res) =>
91 {
92 res.json(fields);
93 });
94
95 // self read
96 app.get(`${prefix}/current.json`, (req, res) =>
97 {
98 if (req.user && req.user.id)
99 {
100 res.json(summariseUserRecord(req.user, config.fields));
101 }
102 else
103 {
104 res.json(false);
105 }
106 });
107
108 // self update
109 app.put(`${prefix}/current.json`, access.LOGGEDIN, json, async (req, res) =>
110 {
111 let user = req.user;
112 if (req.body)
113 {
114 try
115 {
116 await updateUserRecord(user, req.body || {}, user, config);
117 res.resolve(config.users.updateRecord(user), 'Done', audit.ACCOUNT_CHANGE);
118 }
119 catch (e)
120 {
121 res.error(e.message, `${audit.ACCOUNT_CHANGE}_FAILURE`)
122 }
123 }
124 });
125
126 // logout
127 app.all(`${prefix}/logout.json`, (req, res) =>
128 {
129 res.success('Logged out', audit.LOGOUT);
130 req.logout();
131 req.session.destroy();
132 });
133}
134
135function setupAccountsAPI(app, config)
136{
137 // ACCOUNT MANAGEMENT API
138 const prefix = config.prefix;
139 const collection = config.users;
140
141 // discover
142 const USER_DATA_ACCESS = access.ROLE_ONE_OF(config.administratorRoles || {});
143
144 app.all(`${prefix}/search.json`, USER_DATA_ACCESS, (req, res) =>
145 {
146 let query = parseFilters(collection.searchMeta, req.query);
147 res.resolve(collection.searchRecords(query), false, audit.ACCOUNT_SEARCH, JSON.stringify(query));
148 });
149
150 app.post(`${prefix}/:user.json`, USER_DATA_ACCESS, json, async (req, res) =>
151 {
152 try
153 {
154 let auth = config.auth.filter(auth => auth.method === req.body.type)[0];
155 let user = auth.findUser(req.body.value);
156 if (user)
157 {
158 throw new Error('user already exists');
159 }
160 let profile = await auth.createProfileFromCredential(req.body.value, req.body);
161 user = auth.createUserFromProfile(profile);
162 user = await config.users.createRecord(user);
163 res.success({success: 'Created!', user}, audit.ACCOUNT_CREATE + audit.SUCCESS);
164 }
165 catch (e)
166 {
167 res.audit(audit.ACCOUNT_CREATE + audit.FAILURE, e.message, JSON.stringify(req.body));
168 res.error('Account creation failed')
169 }
170 });
171
172 // read
173 app.get(`${prefix}/:user.json`, USER_DATA_ACCESS, (req, res) =>
174 {
175 let query = {};
176 query[collection.primaryKey] = req.params.user;
177
178 collection.readRecord(query)
179 .then((user) =>
180 {
181 res.json(summariseUserRecord(user, config.fields, {credentials: true}));
182 res.audit(audit.ACCOUNT_READ + audit.SUCCESS, JSON.stringify(query));
183 }, res.reject(audit.ACCOUNT_READ + audit.FAILURE), JSON.stringify(query));
184 });
185
186 // update
187 app.put(`${prefix}/:user.json`, USER_DATA_ACCESS, json, (req, res) =>
188 {
189 let query = {};
190
191 query[collection.primaryKey] = req.params.user;
192 collection.readRecord(query)
193 .then(async (record) =>
194 {
195 let params = Object.keys(req.body);
196
197 params = JSON.stringify(Object.assign({
198 params
199 }, query));
200 try
201 {
202 await updateUserRecord(record, req.body, req.user, config);
203 res.resolve(collection.updateRecord(record), 'Done', audit.ACCOUNT_UPDATE, params);
204 }
205 catch (e)
206 {
207 res.error(e.message, audit.ACCOUNT_UPDATE + audit.FAILURE, params);
208 }
209 }, res.reject(audit.ACCOUNT_UPDATE + audit.FAILURE, JSON.stringify(query)));
210 });
211
212 // delete
213 app.delete(`${prefix}/:user.json`, USER_DATA_ACCESS, (req, res) =>
214 {
215 let query = {};
216
217 query[collection.primaryKey] = req.params.user;
218 if (req.params.user !== req.user.id)
219 {
220 res.resolve(collection.deleteRecord(query), 'User deleted', audit.ACCOUNT_DELETE, JSON.stringify(req.params));
221 }
222 else
223 {
224 res.error("Can't delete yourself", audit.ACCOUNT_DELETE + audit.FAILURE);
225 }
226 });
227
228}
229
230function summariseUserRecord(user, fields, addiionalToInclude={})
231{
232 const output = {};
233 for (let field in user)
234 {
235 const meta = fields[field];
236 if (meta)
237 {
238 if (meta.mask)
239 {
240 output[field] = true;
241 }
242 else
243 {
244 output[field] = user[field];
245 }
246 }
247 else if (addiionalToInclude[field])
248 {
249 output[field] = user[field];
250 }
251 }
252 output.roles = user.roles;
253 return output;
254}
255
256async function updateUserRecord(user, update, loginUser, config)
257{
258 for (let field in update)
259 {
260 const meta = config.fields[field];
261 if (meta)
262 {
263 const value = update[field];
264
265 if (meta.self === false && loginUser.id === user.id)
266 {
267 throw new Error(`Can't update your own ${field}`);
268 }
269
270 await meta.assign(user, field, value, meta, loginUser, config);
271 continue;
272 }
273 else
274 {
275 throw new Error(`${field} not editable`);
276 }
277 }
278}
279
280function summariseFields(inputFields)
281{
282 let fields = Object.keys(inputFields).map(field =>
283 {
284 return {
285 name: field,
286 order: inputFields[field].order || 10,
287 type: inputFields[field].type || 'string',
288 self: inputFields[field].self !== undefined? inputFields[field].self : true,
289 admin: inputFields[field].admin !== undefined? inputFields[field].admin : true,
290 enabled: inputFields[field].enabled !== undefined? inputFields[field].enabled : true,
291 };
292 });
293 fields.sort(function(a, b)
294 {
295 if (a.order !== b.order)
296 {
297 return a.order - b.order;
298 }
299 else
300 {
301 if (a.name < b.name)
302 {
303 return -1
304 }
305 else
306 {
307 return 1;
308 }
309 }
310 });
311 return fields;
312}
313
314module.exports = setup;