1 | "use strict";
|
2 |
|
3 | const audit = require('./helper/audit');
|
4 | const access = require('./helper/access');
|
5 | const bootstrap = require('./helper/bootstrap');
|
6 | const cookieParser = require('cookie-parser');
|
7 | const expressSession = require('express-session');
|
8 | const json = require('body-parser')
|
9 | .json();
|
10 | const parseFilters = require('./filters/parse');
|
11 | const passport = require('passport');
|
12 |
|
13 | const CollectionSessionStore = require('./session/CollectionSessionStore');
|
14 | const MemorySessionStore = require('./session/MemorySessionStore');
|
15 |
|
16 | const passwordField = require('./fields/password');
|
17 | const defaultFields = require('./fields/defaultFields');
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | function setup(app, config)
|
26 | {
|
27 |
|
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 |
|
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 |
|
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 |
|
64 | setupAuthAPI(app, config);
|
65 | setupAccountsAPI(app, config);
|
66 | }
|
67 |
|
68 | function setupAuthAPI(app, config)
|
69 | {
|
70 | const prefix = config.prefix;
|
71 |
|
72 |
|
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 |
|
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 |
|
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 |
|
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 |
|
135 | function setupAccountsAPI(app, config)
|
136 | {
|
137 |
|
138 | const prefix = config.prefix;
|
139 | const collection = config.users;
|
140 |
|
141 |
|
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 |
|
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 |
|
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 |
|
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 |
|
230 | function 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 |
|
256 | async 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 |
|
280 | function 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 |
|
314 | module.exports = setup;
|