@nymphjs/server 1.0.0-beta.13 → 1.0.0-beta.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +520 -0
- package/README.md +78 -4
- package/dist/cache.test.js +14 -18
- package/dist/cache.test.js.map +1 -1
- package/dist/createServer.d.ts +12 -0
- package/dist/createServer.js +317 -167
- package/dist/createServer.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.js +4 -21
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +139 -26
- package/dist/index.test.js.map +1 -1
- package/dist/statusDescriptions.d.ts +3 -0
- package/dist/statusDescriptions.js +4 -4
- package/dist/statusDescriptions.js.map +1 -1
- package/dist/testArtifacts.d.ts +56 -8
- package/dist/testArtifacts.js +160 -74
- package/dist/testArtifacts.js.map +1 -1
- package/jest.config.js +31 -5
- package/package.json +35 -25
- package/src/cache.test.ts +5 -5
- package/src/createServer.ts +370 -184
- package/src/index.test.ts +172 -29
- package/src/index.ts +3 -4
- package/src/testArtifacts.ts +171 -42
- package/tsconfig.json +6 -3
- package/typedoc.json +4 -0
- package/dist/HttpError.d.ts +0 -5
- package/dist/HttpError.js +0 -13
- package/dist/HttpError.js.map +0 -1
- package/src/HttpError.ts +0 -12
package/dist/createServer.js
CHANGED
|
@@ -1,29 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
exports.createServer = void 0;
|
|
7
|
-
const express_1 = __importDefault(require("express"));
|
|
8
|
-
const cookie_parser_1 = __importDefault(require("cookie-parser"));
|
|
9
|
-
const nymph_1 = require("@nymphjs/nymph");
|
|
10
|
-
const nymph_2 = require("@nymphjs/nymph");
|
|
11
|
-
const statusDescriptions_1 = require("./statusDescriptions");
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cookieParser from 'cookie-parser';
|
|
3
|
+
import { EntityConflictError, InvalidParametersError, TilmeldAccessLevels, classNamesToEntityConstructors, referencesToEntities, } from '@nymphjs/nymph';
|
|
4
|
+
import { EntityInvalidDataError } from '@nymphjs/nymph';
|
|
5
|
+
import { statusDescriptions } from './statusDescriptions.js';
|
|
12
6
|
const NOT_FOUND_ERROR = 'Entity is not found.';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
export class ForbiddenClassError extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = 'ForbiddenClassError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A REST server middleware creator for Nymph.
|
|
15
|
+
*
|
|
16
|
+
* Written by Hunter Perrin for SciActive.
|
|
17
|
+
*
|
|
18
|
+
* @author Hunter Perrin <hperrin@port87.com>
|
|
19
|
+
* @copyright SciActive Inc
|
|
20
|
+
* @see http://nymph.io/
|
|
21
|
+
*/
|
|
22
|
+
export function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
23
|
+
const rest = express();
|
|
24
|
+
rest.use(cookieParser());
|
|
25
|
+
rest.use(express.json(jsonOptions || {}));
|
|
17
26
|
function instantiateNymph(_request, response, next) {
|
|
18
27
|
response.locals.nymph = nymph.clone();
|
|
19
28
|
next();
|
|
20
29
|
}
|
|
21
|
-
function authenticateTilmeld(request, response, next) {
|
|
30
|
+
async function authenticateTilmeld(request, response, next) {
|
|
22
31
|
if (response.locals.nymph.tilmeld) {
|
|
23
32
|
response.locals.nymph.tilmeld.request = request;
|
|
24
33
|
response.locals.nymph.tilmeld.response = response;
|
|
25
34
|
try {
|
|
26
|
-
response.locals.nymph.tilmeld.authenticate();
|
|
35
|
+
await response.locals.nymph.tilmeld.authenticate(false, request.header('x-tilmeld-token-renewal') === 'off');
|
|
27
36
|
}
|
|
28
37
|
catch (e) {
|
|
29
38
|
httpError(response, 500, e);
|
|
@@ -67,7 +76,9 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
67
76
|
};
|
|
68
77
|
}
|
|
69
78
|
}
|
|
79
|
+
// Create a new instance of Nymph for the request/response.
|
|
70
80
|
rest.use(instantiateNymph);
|
|
81
|
+
// Authenticate before the request.
|
|
71
82
|
rest.use(authenticateTilmeld);
|
|
72
83
|
rest.get('/', async (request, response) => {
|
|
73
84
|
try {
|
|
@@ -91,18 +102,37 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
91
102
|
return;
|
|
92
103
|
}
|
|
93
104
|
let [options, ...selectors] = data;
|
|
94
|
-
|
|
105
|
+
options.source = 'client';
|
|
106
|
+
options.skipAc = false;
|
|
107
|
+
if (options.return === 'object') {
|
|
108
|
+
options.return = 'entity';
|
|
109
|
+
}
|
|
95
110
|
try {
|
|
96
|
-
|
|
111
|
+
options = classNamesToEntityConstructors(response.locals.nymph, options, true);
|
|
97
112
|
}
|
|
98
113
|
catch (e) {
|
|
99
|
-
|
|
100
|
-
|
|
114
|
+
if (e?.message === 'Not accessible.') {
|
|
115
|
+
httpError(response, 403);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
httpError(response, 500, e);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
selectors = classNamesToEntityConstructors(response.locals.nymph, referencesToEntities(selectors, response.locals.nymph, false, true), true);
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
if (e?.message === 'Not accessible.') {
|
|
128
|
+
httpError(response, 403);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
httpError(response, 500, e);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
101
135
|
}
|
|
102
|
-
options.class = EntityClass;
|
|
103
|
-
options.source = 'client';
|
|
104
|
-
options.skipAc = false;
|
|
105
|
-
selectors = (0, nymph_1.classNamesToEntityConstructors)(response.locals.nymph, selectors);
|
|
106
136
|
let result;
|
|
107
137
|
try {
|
|
108
138
|
if (action === 'entity') {
|
|
@@ -132,7 +162,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
132
162
|
return;
|
|
133
163
|
}
|
|
134
164
|
if (response.locals.nymph.tilmeld) {
|
|
135
|
-
if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data,
|
|
165
|
+
if (!(await response.locals.nymph.tilmeld.checkClientUIDPermissions(data, TilmeldAccessLevels.READ_ACCESS))) {
|
|
136
166
|
httpError(response, 403);
|
|
137
167
|
return;
|
|
138
168
|
}
|
|
@@ -179,6 +209,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
179
209
|
let invalidRequest = false;
|
|
180
210
|
let conflict = false;
|
|
181
211
|
let notfound = false;
|
|
212
|
+
let forbidden = false;
|
|
182
213
|
let lastException = null;
|
|
183
214
|
for (let entData of data) {
|
|
184
215
|
if (entData.guid) {
|
|
@@ -188,19 +219,28 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
188
219
|
}
|
|
189
220
|
let entity;
|
|
190
221
|
try {
|
|
191
|
-
entity = await loadEntity(entData, response.locals.nymph);
|
|
222
|
+
entity = await loadEntity(classNamesToEntityConstructors(response.locals.nymph, referencesToEntities(entData, response.locals.nymph, false, true), true), response.locals.nymph);
|
|
192
223
|
}
|
|
193
224
|
catch (e) {
|
|
194
|
-
if (e instanceof
|
|
225
|
+
if (e instanceof EntityConflictError) {
|
|
195
226
|
conflict = true;
|
|
227
|
+
lastException = e;
|
|
228
|
+
}
|
|
229
|
+
else if (e instanceof ForbiddenClassError) {
|
|
230
|
+
forbidden = true;
|
|
231
|
+
lastException = e;
|
|
196
232
|
}
|
|
197
233
|
else if (e.message === NOT_FOUND_ERROR) {
|
|
198
234
|
notfound = true;
|
|
199
235
|
}
|
|
200
|
-
else if (e instanceof
|
|
236
|
+
else if (e instanceof InvalidParametersError) {
|
|
201
237
|
invalidRequest = true;
|
|
202
238
|
lastException = e;
|
|
203
239
|
}
|
|
240
|
+
else if (e.message === 'Not accessible.') {
|
|
241
|
+
forbidden = true;
|
|
242
|
+
lastException = e;
|
|
243
|
+
}
|
|
204
244
|
else {
|
|
205
245
|
lastException = e;
|
|
206
246
|
}
|
|
@@ -213,16 +253,12 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
213
253
|
continue;
|
|
214
254
|
}
|
|
215
255
|
try {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
created.push(false);
|
|
222
|
-
}
|
|
256
|
+
await entity.$save();
|
|
257
|
+
created.push(entity);
|
|
258
|
+
hadSuccess = true;
|
|
223
259
|
}
|
|
224
260
|
catch (e) {
|
|
225
|
-
if (e instanceof
|
|
261
|
+
if (e instanceof EntityInvalidDataError) {
|
|
226
262
|
invalidRequest = true;
|
|
227
263
|
}
|
|
228
264
|
else {
|
|
@@ -237,7 +273,11 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
237
273
|
return;
|
|
238
274
|
}
|
|
239
275
|
else if (conflict) {
|
|
240
|
-
httpError(response, 409);
|
|
276
|
+
httpError(response, 409, lastException);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
else if (forbidden) {
|
|
280
|
+
httpError(response, 403, lastException);
|
|
241
281
|
return;
|
|
242
282
|
}
|
|
243
283
|
else if (notfound) {
|
|
@@ -263,93 +303,157 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
263
303
|
httpError(response, 400);
|
|
264
304
|
return;
|
|
265
305
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (EntityClass.clientEnabledStaticMethods.indexOf(data.method) === -1) {
|
|
277
|
-
httpError(response, 403);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
if (!(data.method in EntityClass)) {
|
|
281
|
-
httpError(response, 400);
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
const method = EntityClass[data.method];
|
|
285
|
-
if (typeof method !== 'function') {
|
|
286
|
-
httpError(response, 400);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
try {
|
|
290
|
-
const result = method.call(EntityClass, ...params);
|
|
291
|
-
let ret = result;
|
|
292
|
-
if (result instanceof Promise) {
|
|
293
|
-
ret = await result;
|
|
306
|
+
try {
|
|
307
|
+
const params = classNamesToEntityConstructors(response.locals.nymph, referencesToEntities([...data.params], response.locals.nymph, false, true), true);
|
|
308
|
+
if (data.static) {
|
|
309
|
+
let EntityClass;
|
|
310
|
+
try {
|
|
311
|
+
EntityClass = response.locals.nymph.getEntityClass(data.class);
|
|
312
|
+
if (!EntityClass.restEnabled) {
|
|
313
|
+
httpError(response, 403);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
294
316
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
catch (e) {
|
|
300
|
-
httpError(response, 500, e);
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
else {
|
|
305
|
-
let entity;
|
|
306
|
-
try {
|
|
307
|
-
entity = await loadEntity(data.entity, response.locals.nymph);
|
|
308
|
-
}
|
|
309
|
-
catch (e) {
|
|
310
|
-
if (e instanceof nymph_1.EntityConflictError) {
|
|
311
|
-
httpError(response, 409);
|
|
317
|
+
catch (e) {
|
|
318
|
+
httpError(response, 400);
|
|
319
|
+
return;
|
|
312
320
|
}
|
|
313
|
-
|
|
314
|
-
httpError(response,
|
|
321
|
+
if (EntityClass.clientEnabledStaticMethods.indexOf(data.method) === -1) {
|
|
322
|
+
httpError(response, 403);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (!(data.method in EntityClass)) {
|
|
326
|
+
httpError(response, 400);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
// @ts-ignore Dynamic methods make TypeScript sad.
|
|
330
|
+
const method = EntityClass[data.method];
|
|
331
|
+
if (typeof method !== 'function') {
|
|
332
|
+
httpError(response, 400);
|
|
333
|
+
return;
|
|
315
334
|
}
|
|
316
|
-
|
|
317
|
-
|
|
335
|
+
if (data.iterator) {
|
|
336
|
+
// Ping every 15 seconds to keep connection alive.
|
|
337
|
+
const interval = setInterval(() => {
|
|
338
|
+
if (response.headersSent) {
|
|
339
|
+
response.write('event: ping\n');
|
|
340
|
+
response.write(`data: ${new Date().toISOString()}\n\n`);
|
|
341
|
+
}
|
|
342
|
+
}, 15000);
|
|
343
|
+
try {
|
|
344
|
+
response.set({
|
|
345
|
+
'Cache-Control': 'no-cache',
|
|
346
|
+
'Content-Type': 'text/event-stream',
|
|
347
|
+
Connection: 'keep-alive',
|
|
348
|
+
});
|
|
349
|
+
response.flushHeaders();
|
|
350
|
+
const result = method.call(EntityClass, ...params);
|
|
351
|
+
let sequence = result;
|
|
352
|
+
if (result instanceof Promise) {
|
|
353
|
+
sequence = await result;
|
|
354
|
+
}
|
|
355
|
+
let { value, done } = await sequence.next(response.destroyed);
|
|
356
|
+
while (!done) {
|
|
357
|
+
response.write('event: next\n');
|
|
358
|
+
response.write(`data: ${JSON.stringify(value)}\n\n`);
|
|
359
|
+
// Wait for an event loop each iteration, to allow data to flush.
|
|
360
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
361
|
+
({ value, done } = await sequence.next(response.destroyed));
|
|
362
|
+
}
|
|
363
|
+
clearInterval(interval);
|
|
364
|
+
response.write('event: finished\n');
|
|
365
|
+
response.write('data: \n\n');
|
|
366
|
+
response.end();
|
|
367
|
+
}
|
|
368
|
+
catch (e) {
|
|
369
|
+
clearInterval(interval);
|
|
370
|
+
eventStreamError(response, 500, e);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
318
373
|
}
|
|
319
374
|
else {
|
|
320
|
-
|
|
375
|
+
try {
|
|
376
|
+
const result = method.call(EntityClass, ...params);
|
|
377
|
+
let ret = result;
|
|
378
|
+
if (result instanceof Promise) {
|
|
379
|
+
ret = await result;
|
|
380
|
+
}
|
|
381
|
+
response.status(200);
|
|
382
|
+
response.setHeader('Content-Type', 'application/json');
|
|
383
|
+
response.send({ return: ret });
|
|
384
|
+
}
|
|
385
|
+
catch (e) {
|
|
386
|
+
httpError(response, 500, e);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
321
389
|
}
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
if (data.entity.guid && !entity.guid) {
|
|
325
|
-
httpError(response, 400);
|
|
326
|
-
return;
|
|
327
390
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (!(data.method in entity) ||
|
|
333
|
-
typeof entity[data.method] !== 'function') {
|
|
334
|
-
httpError(response, 400);
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
try {
|
|
338
|
-
const result = entity[data.method](...params);
|
|
339
|
-
let ret = result;
|
|
340
|
-
if (result instanceof Promise) {
|
|
341
|
-
ret = await result;
|
|
391
|
+
else {
|
|
392
|
+
let entity;
|
|
393
|
+
try {
|
|
394
|
+
entity = await loadEntity(classNamesToEntityConstructors(response.locals.nymph, referencesToEntities(data.entity, response.locals.nymph, false, true), true), response.locals.nymph);
|
|
342
395
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
396
|
+
catch (e) {
|
|
397
|
+
if (e instanceof EntityConflictError) {
|
|
398
|
+
httpError(response, 409, e);
|
|
399
|
+
}
|
|
400
|
+
else if (e instanceof ForbiddenClassError) {
|
|
401
|
+
httpError(response, 403, e);
|
|
402
|
+
}
|
|
403
|
+
else if (e.message === NOT_FOUND_ERROR) {
|
|
404
|
+
httpError(response, 404, e);
|
|
405
|
+
}
|
|
406
|
+
else if (e instanceof InvalidParametersError) {
|
|
407
|
+
httpError(response, 400, e);
|
|
408
|
+
}
|
|
409
|
+
else if (e.message === 'Not accessible.') {
|
|
410
|
+
httpError(response, 403);
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
httpError(response, 500, e);
|
|
414
|
+
}
|
|
415
|
+
return;
|
|
347
416
|
}
|
|
348
|
-
|
|
349
|
-
response
|
|
417
|
+
if (data.entity.guid && !entity.guid) {
|
|
418
|
+
httpError(response, 400);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (entity.$getClientEnabledMethods().indexOf(data.method) === -1) {
|
|
422
|
+
httpError(response, 403);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (!(data.method in entity) ||
|
|
426
|
+
typeof entity[data.method] !== 'function') {
|
|
427
|
+
httpError(response, 400);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
try {
|
|
431
|
+
const result = entity[data.method](...params);
|
|
432
|
+
let ret = result;
|
|
433
|
+
if (result instanceof Promise) {
|
|
434
|
+
ret = await result;
|
|
435
|
+
}
|
|
436
|
+
response.status(200);
|
|
437
|
+
response.setHeader('Content-Type', 'application/json');
|
|
438
|
+
if (data.stateless) {
|
|
439
|
+
response.send({ return: ret });
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
response.send({ entity: entity, return: ret });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
catch (e) {
|
|
446
|
+
httpError(response, 500, e);
|
|
447
|
+
return;
|
|
350
448
|
}
|
|
351
449
|
}
|
|
352
|
-
|
|
450
|
+
}
|
|
451
|
+
catch (e) {
|
|
452
|
+
if (e instanceof ForbiddenClassError) {
|
|
453
|
+
httpError(response, 403, e);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
353
457
|
httpError(response, 500, e);
|
|
354
458
|
return;
|
|
355
459
|
}
|
|
@@ -361,7 +465,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
361
465
|
return;
|
|
362
466
|
}
|
|
363
467
|
if (response.locals.nymph.tilmeld) {
|
|
364
|
-
if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data,
|
|
468
|
+
if (!(await response.locals.nymph.tilmeld.checkClientUIDPermissions(data, TilmeldAccessLevels.WRITE_ACCESS))) {
|
|
365
469
|
httpError(response, 403);
|
|
366
470
|
return;
|
|
367
471
|
}
|
|
@@ -423,7 +527,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
423
527
|
return;
|
|
424
528
|
}
|
|
425
529
|
if (response.locals.nymph.tilmeld) {
|
|
426
|
-
if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data.name,
|
|
530
|
+
if (!(await response.locals.nymph.tilmeld.checkClientUIDPermissions(data.name, TilmeldAccessLevels.FULL_ACCESS))) {
|
|
427
531
|
httpError(response, 403);
|
|
428
532
|
return;
|
|
429
533
|
}
|
|
@@ -452,6 +556,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
452
556
|
let hadSuccess = false;
|
|
453
557
|
let invalidRequest = false;
|
|
454
558
|
let conflict = false;
|
|
559
|
+
let forbidden = false;
|
|
455
560
|
let notfound = false;
|
|
456
561
|
let lastException = null;
|
|
457
562
|
for (let entData of data) {
|
|
@@ -462,19 +567,28 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
462
567
|
}
|
|
463
568
|
let entity;
|
|
464
569
|
try {
|
|
465
|
-
entity = await loadEntity(entData, response.locals.nymph, patch);
|
|
570
|
+
entity = await loadEntity(classNamesToEntityConstructors(response.locals.nymph, referencesToEntities(entData, response.locals.nymph, false, true), true), response.locals.nymph, patch);
|
|
466
571
|
}
|
|
467
572
|
catch (e) {
|
|
468
|
-
if (e instanceof
|
|
573
|
+
if (e instanceof EntityConflictError) {
|
|
469
574
|
conflict = true;
|
|
575
|
+
lastException = e;
|
|
576
|
+
}
|
|
577
|
+
else if (e instanceof ForbiddenClassError) {
|
|
578
|
+
forbidden = true;
|
|
579
|
+
lastException = e;
|
|
470
580
|
}
|
|
471
581
|
else if (e.message === NOT_FOUND_ERROR) {
|
|
472
582
|
notfound = true;
|
|
473
583
|
}
|
|
474
|
-
else if (e instanceof
|
|
584
|
+
else if (e instanceof InvalidParametersError) {
|
|
475
585
|
invalidRequest = true;
|
|
476
586
|
lastException = e;
|
|
477
587
|
}
|
|
588
|
+
else if (e.message === 'Not accessible.') {
|
|
589
|
+
forbidden = true;
|
|
590
|
+
lastException = e;
|
|
591
|
+
}
|
|
478
592
|
else {
|
|
479
593
|
lastException = e;
|
|
480
594
|
}
|
|
@@ -487,16 +601,12 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
487
601
|
continue;
|
|
488
602
|
}
|
|
489
603
|
try {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
else {
|
|
495
|
-
saved.push(false);
|
|
496
|
-
}
|
|
604
|
+
await entity.$save();
|
|
605
|
+
saved.push(entity);
|
|
606
|
+
hadSuccess = true;
|
|
497
607
|
}
|
|
498
608
|
catch (e) {
|
|
499
|
-
if (e instanceof
|
|
609
|
+
if (e instanceof EntityInvalidDataError) {
|
|
500
610
|
invalidRequest = true;
|
|
501
611
|
}
|
|
502
612
|
else {
|
|
@@ -509,8 +619,11 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
509
619
|
if (invalidRequest) {
|
|
510
620
|
httpError(response, 400, lastException);
|
|
511
621
|
}
|
|
622
|
+
else if (forbidden) {
|
|
623
|
+
httpError(response, 403, lastException);
|
|
624
|
+
}
|
|
512
625
|
else if (conflict) {
|
|
513
|
-
httpError(response, 409);
|
|
626
|
+
httpError(response, 409, lastException);
|
|
514
627
|
}
|
|
515
628
|
else if (notfound) {
|
|
516
629
|
httpError(response, 404);
|
|
@@ -555,6 +668,10 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
555
668
|
let EntityClass;
|
|
556
669
|
try {
|
|
557
670
|
EntityClass = response.locals.nymph.getEntityClass(entData.class);
|
|
671
|
+
if (!EntityClass.restEnabled) {
|
|
672
|
+
httpError(response, 403);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
558
675
|
}
|
|
559
676
|
catch (e) {
|
|
560
677
|
invalidRequest = true;
|
|
@@ -576,10 +693,11 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
576
693
|
continue;
|
|
577
694
|
}
|
|
578
695
|
try {
|
|
579
|
-
|
|
696
|
+
try {
|
|
697
|
+
await entity.$delete();
|
|
580
698
|
deleted.push(entData.guid);
|
|
581
699
|
}
|
|
582
|
-
|
|
700
|
+
catch (e) {
|
|
583
701
|
failures = true;
|
|
584
702
|
}
|
|
585
703
|
}
|
|
@@ -615,7 +733,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
615
733
|
return;
|
|
616
734
|
}
|
|
617
735
|
if (response.locals.nymph.tilmeld) {
|
|
618
|
-
if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data,
|
|
736
|
+
if (!(await response.locals.nymph.tilmeld.checkClientUIDPermissions(data, TilmeldAccessLevels.FULL_ACCESS))) {
|
|
619
737
|
httpError(response, 403);
|
|
620
738
|
return;
|
|
621
739
|
}
|
|
@@ -642,12 +760,17 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
642
760
|
return;
|
|
643
761
|
}
|
|
644
762
|
});
|
|
763
|
+
// Unauthenticate after the request.
|
|
645
764
|
rest.use(unauthenticateTilmeld);
|
|
646
765
|
async function loadEntity(entityData, nymph, patch = false, allowConflict = false) {
|
|
647
|
-
|
|
648
|
-
|
|
766
|
+
let EntityClass = entityData.class;
|
|
767
|
+
if (EntityClass.class === 'Entity') {
|
|
768
|
+
// Don't let clients use the `Entity` class, since it has no validity/AC checks.
|
|
769
|
+
throw new ForbiddenClassError("Can't use Entity class directly from the front end.");
|
|
770
|
+
}
|
|
771
|
+
if (!EntityClass.restEnabled) {
|
|
772
|
+
throw new ForbiddenClassError('Not accessible.');
|
|
649
773
|
}
|
|
650
|
-
let EntityClass = nymph.getEntityClass(entityData.class);
|
|
651
774
|
let entity;
|
|
652
775
|
if (entityData.guid) {
|
|
653
776
|
entity = await nymph.getEntity({ class: EntityClass, source: 'client' }, {
|
|
@@ -669,42 +792,60 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
669
792
|
}
|
|
670
793
|
return entity;
|
|
671
794
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
catch (e) {
|
|
680
|
-
return item;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
return item.map((entry) => referencesToEntities(entry, nymph));
|
|
684
|
-
}
|
|
685
|
-
else if (typeof item === 'object' && !(item instanceof nymph_1.Entity)) {
|
|
686
|
-
const newItem = {};
|
|
687
|
-
for (let curProperty in item) {
|
|
688
|
-
newItem[curProperty] = referencesToEntities(item[curProperty], nymph);
|
|
689
|
-
}
|
|
690
|
-
return newItem;
|
|
691
|
-
}
|
|
692
|
-
return item;
|
|
693
|
-
}
|
|
795
|
+
/**
|
|
796
|
+
* Return the request with an HTTP error response.
|
|
797
|
+
*
|
|
798
|
+
* @param res The server response object.
|
|
799
|
+
* @param defaultStatusCode The HTTP status code to use if none is given in the error object.
|
|
800
|
+
* @param error An optional error object to report.
|
|
801
|
+
*/
|
|
694
802
|
function httpError(res, defaultStatusCode, error) {
|
|
695
803
|
const status = error?.status || defaultStatusCode;
|
|
696
804
|
const statusText = error?.statusText ||
|
|
697
805
|
(error?.status == null
|
|
698
|
-
?
|
|
699
|
-
: error.status in
|
|
700
|
-
|
|
806
|
+
? statusDescriptions[defaultStatusCode]
|
|
807
|
+
: error.status in statusDescriptions &&
|
|
808
|
+
statusDescriptions[error.status]) ||
|
|
701
809
|
'Internal Server Error';
|
|
810
|
+
const errorResponse = error
|
|
811
|
+
? {
|
|
812
|
+
textStatus: `${status} ${statusText}`,
|
|
813
|
+
statusText,
|
|
814
|
+
message: error.message,
|
|
815
|
+
error,
|
|
816
|
+
...(process.env.NODE_ENV !== 'production'
|
|
817
|
+
? { stack: error.stack }
|
|
818
|
+
: {}),
|
|
819
|
+
}
|
|
820
|
+
: {
|
|
821
|
+
textStatus: `${status} ${statusText}`,
|
|
822
|
+
statusText,
|
|
823
|
+
message: statusText,
|
|
824
|
+
};
|
|
702
825
|
if (!res.headersSent) {
|
|
703
826
|
res.status(status);
|
|
704
827
|
res.setHeader('Content-Type', 'application/json');
|
|
705
828
|
}
|
|
706
|
-
|
|
707
|
-
|
|
829
|
+
res.send(errorResponse);
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* End the event stream with an HTTP error response.
|
|
833
|
+
*
|
|
834
|
+
* @param res The server response object.
|
|
835
|
+
* @param defaultStatusCode The HTTP status code to use if none is given in the error object.
|
|
836
|
+
* @param error An optional error object to report.
|
|
837
|
+
*/
|
|
838
|
+
function eventStreamError(res, defaultStatusCode, error) {
|
|
839
|
+
const status = error?.status || defaultStatusCode;
|
|
840
|
+
const statusText = error?.statusText ||
|
|
841
|
+
(error?.status == null
|
|
842
|
+
? statusDescriptions[defaultStatusCode]
|
|
843
|
+
: error.status in statusDescriptions &&
|
|
844
|
+
statusDescriptions[error.status]) ||
|
|
845
|
+
'Internal Server Error';
|
|
846
|
+
const errorResponse = error
|
|
847
|
+
? {
|
|
848
|
+
status,
|
|
708
849
|
textStatus: `${status} ${statusText}`,
|
|
709
850
|
statusText,
|
|
710
851
|
message: error.message,
|
|
@@ -712,17 +853,26 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
712
853
|
...(process.env.NODE_ENV !== 'production'
|
|
713
854
|
? { stack: error.stack }
|
|
714
855
|
: {}),
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
res.send({
|
|
856
|
+
}
|
|
857
|
+
: {
|
|
858
|
+
status,
|
|
719
859
|
textStatus: `${status} ${statusText}`,
|
|
720
860
|
statusText,
|
|
721
861
|
message: statusText,
|
|
722
|
-
}
|
|
862
|
+
};
|
|
863
|
+
if (!res.headersSent) {
|
|
864
|
+
res.status(status);
|
|
865
|
+
res.setHeader('Content-Type', 'application/json');
|
|
866
|
+
res.send(errorResponse);
|
|
867
|
+
}
|
|
868
|
+
else {
|
|
869
|
+
res.write('event: error\n');
|
|
870
|
+
res.write(`data: ${JSON.stringify(errorResponse)}\n\n`);
|
|
871
|
+
res.write('event: finished\n');
|
|
872
|
+
res.write('data: \n\n');
|
|
873
|
+
res.end();
|
|
723
874
|
}
|
|
724
875
|
}
|
|
725
876
|
return rest;
|
|
726
877
|
}
|
|
727
|
-
exports.createServer = createServer;
|
|
728
878
|
//# sourceMappingURL=createServer.js.map
|