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 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | function setup(app, config)
|
23 | {
|
24 |
|
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 |
|
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 |
|
50 | config.prefix = config.prefix || '/api/accounts';
|
51 |
|
52 |
|
53 | setupAuthAPI(app, config);
|
54 | setupAccountsAPI(app, config);
|
55 | }
|
56 |
|
57 | function setupAuthAPI(app, config)
|
58 | {
|
59 | config.custom = config.custom || {};
|
60 |
|
61 | const prefix = config.prefix;
|
62 |
|
63 |
|
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 |
|
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 |
|
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 |
|
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 |
|
141 | function setupAccountsAPI(app, config)
|
142 | {
|
143 |
|
144 | const prefix = config.prefix;
|
145 | const collection = config.users;
|
146 |
|
147 |
|
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 |
|
158 | |
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
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 |
|
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 |
|
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 |
|
229 | function 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 |
|
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 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 | default:
|
280 | throw new Error(`${field} not editable`);
|
281 | }
|
282 | }
|
283 | }
|
284 |
|
285 | module.exports = setup;
|