@nymphjs/server 1.0.0-beta.12 → 1.0.0-beta.120
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 +472 -0
- package/README.md +75 -3
- 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 +298 -119
- 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 +138 -24
- 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 +55 -7
- package/dist/testArtifacts.js +160 -74
- package/dist/testArtifacts.js.map +1 -1
- package/jest.config.js +31 -5
- package/package.json +29 -23
- package/src/cache.test.ts +5 -5
- package/src/createServer.ts +302 -128
- package/src/index.test.ts +171 -27
- 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 { Entity, EntityConflictError, InvalidParametersError, TilmeldAccessLevels, classNamesToEntityConstructors, } 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 {
|
|
@@ -94,6 +105,10 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
94
105
|
let EntityClass;
|
|
95
106
|
try {
|
|
96
107
|
EntityClass = response.locals.nymph.getEntityClass(data[0].class);
|
|
108
|
+
if (!EntityClass.restEnabled) {
|
|
109
|
+
httpError(response, 403);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
97
112
|
}
|
|
98
113
|
catch (e) {
|
|
99
114
|
httpError(response, 400, e);
|
|
@@ -102,7 +117,22 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
102
117
|
options.class = EntityClass;
|
|
103
118
|
options.source = 'client';
|
|
104
119
|
options.skipAc = false;
|
|
105
|
-
|
|
120
|
+
if (options.return === 'object') {
|
|
121
|
+
options.return = 'entity';
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
selectors = classNamesToEntityConstructors(response.locals.nymph, selectors, 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
|
+
}
|
|
135
|
+
}
|
|
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) {
|
|
@@ -191,13 +222,16 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
191
222
|
entity = await loadEntity(entData, response.locals.nymph);
|
|
192
223
|
}
|
|
193
224
|
catch (e) {
|
|
194
|
-
if (e instanceof
|
|
225
|
+
if (e instanceof EntityConflictError) {
|
|
195
226
|
conflict = true;
|
|
196
227
|
}
|
|
228
|
+
else if (e instanceof ForbiddenClassError) {
|
|
229
|
+
forbidden = true;
|
|
230
|
+
}
|
|
197
231
|
else if (e.message === NOT_FOUND_ERROR) {
|
|
198
232
|
notfound = true;
|
|
199
233
|
}
|
|
200
|
-
else if (e instanceof
|
|
234
|
+
else if (e instanceof InvalidParametersError) {
|
|
201
235
|
invalidRequest = true;
|
|
202
236
|
lastException = e;
|
|
203
237
|
}
|
|
@@ -222,7 +256,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
222
256
|
}
|
|
223
257
|
}
|
|
224
258
|
catch (e) {
|
|
225
|
-
if (e instanceof
|
|
259
|
+
if (e instanceof EntityInvalidDataError) {
|
|
226
260
|
invalidRequest = true;
|
|
227
261
|
}
|
|
228
262
|
else {
|
|
@@ -240,6 +274,10 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
240
274
|
httpError(response, 409);
|
|
241
275
|
return;
|
|
242
276
|
}
|
|
277
|
+
else if (forbidden) {
|
|
278
|
+
httpError(response, 403);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
243
281
|
else if (notfound) {
|
|
244
282
|
httpError(response, 404);
|
|
245
283
|
return;
|
|
@@ -263,93 +301,154 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
263
301
|
httpError(response, 400);
|
|
264
302
|
return;
|
|
265
303
|
}
|
|
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;
|
|
304
|
+
try {
|
|
305
|
+
const params = referencesToEntities([...data.params], response.locals.nymph);
|
|
306
|
+
if (data.static) {
|
|
307
|
+
let EntityClass;
|
|
308
|
+
try {
|
|
309
|
+
EntityClass = response.locals.nymph.getEntityClass(data.class);
|
|
310
|
+
if (!EntityClass.restEnabled) {
|
|
311
|
+
httpError(response, 403);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
294
314
|
}
|
|
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);
|
|
315
|
+
catch (e) {
|
|
316
|
+
httpError(response, 400);
|
|
317
|
+
return;
|
|
312
318
|
}
|
|
313
|
-
|
|
314
|
-
httpError(response,
|
|
319
|
+
if (EntityClass.clientEnabledStaticMethods.indexOf(data.method) === -1) {
|
|
320
|
+
httpError(response, 403);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (!(data.method in EntityClass)) {
|
|
324
|
+
httpError(response, 400);
|
|
325
|
+
return;
|
|
315
326
|
}
|
|
316
|
-
|
|
317
|
-
|
|
327
|
+
// @ts-ignore Dynamic methods make TypeScript sad.
|
|
328
|
+
const method = EntityClass[data.method];
|
|
329
|
+
if (typeof method !== 'function') {
|
|
330
|
+
httpError(response, 400);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (data.iterator) {
|
|
334
|
+
// Ping every 15 seconds to keep connection alive.
|
|
335
|
+
const interval = setInterval(() => {
|
|
336
|
+
if (response.headersSent) {
|
|
337
|
+
response.write('event: ping\n');
|
|
338
|
+
response.write(`data: ${new Date().toISOString()}\n\n`);
|
|
339
|
+
}
|
|
340
|
+
}, 15000);
|
|
341
|
+
try {
|
|
342
|
+
response.set({
|
|
343
|
+
'Cache-Control': 'no-cache',
|
|
344
|
+
'Content-Type': 'text/event-stream',
|
|
345
|
+
Connection: 'keep-alive',
|
|
346
|
+
});
|
|
347
|
+
response.flushHeaders();
|
|
348
|
+
const result = method.call(EntityClass, ...params);
|
|
349
|
+
let sequence = result;
|
|
350
|
+
if (result instanceof Promise) {
|
|
351
|
+
sequence = await result;
|
|
352
|
+
}
|
|
353
|
+
let { value, done } = await sequence.next(response.destroyed);
|
|
354
|
+
while (!done) {
|
|
355
|
+
response.write('event: next\n');
|
|
356
|
+
response.write(`data: ${JSON.stringify(value)}\n\n`);
|
|
357
|
+
// Wait for an event loop each iteration, to allow data to flush.
|
|
358
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
359
|
+
({ value, done } = await sequence.next(response.destroyed));
|
|
360
|
+
}
|
|
361
|
+
clearInterval(interval);
|
|
362
|
+
response.write('event: finished\n');
|
|
363
|
+
response.write('data: \n\n');
|
|
364
|
+
response.end();
|
|
365
|
+
}
|
|
366
|
+
catch (e) {
|
|
367
|
+
clearInterval(interval);
|
|
368
|
+
eventStreamError(response, 500, e);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
318
371
|
}
|
|
319
372
|
else {
|
|
320
|
-
|
|
373
|
+
try {
|
|
374
|
+
const result = method.call(EntityClass, ...params);
|
|
375
|
+
let ret = result;
|
|
376
|
+
if (result instanceof Promise) {
|
|
377
|
+
ret = await result;
|
|
378
|
+
}
|
|
379
|
+
response.status(200);
|
|
380
|
+
response.setHeader('Content-Type', 'application/json');
|
|
381
|
+
response.send({ return: ret });
|
|
382
|
+
}
|
|
383
|
+
catch (e) {
|
|
384
|
+
httpError(response, 500, e);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
321
387
|
}
|
|
322
|
-
return;
|
|
323
388
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (entity.$getClientEnabledMethods().indexOf(data.method) === -1) {
|
|
329
|
-
httpError(response, 403);
|
|
330
|
-
return;
|
|
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;
|
|
389
|
+
else {
|
|
390
|
+
let entity;
|
|
391
|
+
try {
|
|
392
|
+
entity = await loadEntity(data.entity, response.locals.nymph);
|
|
342
393
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
394
|
+
catch (e) {
|
|
395
|
+
if (e instanceof EntityConflictError) {
|
|
396
|
+
httpError(response, 409);
|
|
397
|
+
}
|
|
398
|
+
else if (e instanceof ForbiddenClassError) {
|
|
399
|
+
httpError(response, 403);
|
|
400
|
+
}
|
|
401
|
+
else if (e.message === NOT_FOUND_ERROR) {
|
|
402
|
+
httpError(response, 404, e);
|
|
403
|
+
}
|
|
404
|
+
else if (e instanceof InvalidParametersError) {
|
|
405
|
+
httpError(response, 400, e);
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
httpError(response, 500, e);
|
|
409
|
+
}
|
|
410
|
+
return;
|
|
347
411
|
}
|
|
348
|
-
|
|
349
|
-
response
|
|
412
|
+
if (data.entity.guid && !entity.guid) {
|
|
413
|
+
httpError(response, 400);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (entity.$getClientEnabledMethods().indexOf(data.method) === -1) {
|
|
417
|
+
httpError(response, 403);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (!(data.method in entity) ||
|
|
421
|
+
typeof entity[data.method] !== 'function') {
|
|
422
|
+
httpError(response, 400);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
try {
|
|
426
|
+
const result = entity[data.method](...params);
|
|
427
|
+
let ret = result;
|
|
428
|
+
if (result instanceof Promise) {
|
|
429
|
+
ret = await result;
|
|
430
|
+
}
|
|
431
|
+
response.status(200);
|
|
432
|
+
response.setHeader('Content-Type', 'application/json');
|
|
433
|
+
if (data.stateless) {
|
|
434
|
+
response.send({ return: ret });
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
response.send({ entity: entity, return: ret });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
catch (e) {
|
|
441
|
+
httpError(response, 500, e);
|
|
442
|
+
return;
|
|
350
443
|
}
|
|
351
444
|
}
|
|
352
|
-
|
|
445
|
+
}
|
|
446
|
+
catch (e) {
|
|
447
|
+
if (e instanceof ForbiddenClassError) {
|
|
448
|
+
httpError(response, 403);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
353
452
|
httpError(response, 500, e);
|
|
354
453
|
return;
|
|
355
454
|
}
|
|
@@ -361,7 +460,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
361
460
|
return;
|
|
362
461
|
}
|
|
363
462
|
if (response.locals.nymph.tilmeld) {
|
|
364
|
-
if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data,
|
|
463
|
+
if (!(await response.locals.nymph.tilmeld.checkClientUIDPermissions(data, TilmeldAccessLevels.WRITE_ACCESS))) {
|
|
365
464
|
httpError(response, 403);
|
|
366
465
|
return;
|
|
367
466
|
}
|
|
@@ -423,7 +522,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
423
522
|
return;
|
|
424
523
|
}
|
|
425
524
|
if (response.locals.nymph.tilmeld) {
|
|
426
|
-
if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data.name,
|
|
525
|
+
if (!(await response.locals.nymph.tilmeld.checkClientUIDPermissions(data.name, TilmeldAccessLevels.FULL_ACCESS))) {
|
|
427
526
|
httpError(response, 403);
|
|
428
527
|
return;
|
|
429
528
|
}
|
|
@@ -452,6 +551,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
452
551
|
let hadSuccess = false;
|
|
453
552
|
let invalidRequest = false;
|
|
454
553
|
let conflict = false;
|
|
554
|
+
let forbidden = false;
|
|
455
555
|
let notfound = false;
|
|
456
556
|
let lastException = null;
|
|
457
557
|
for (let entData of data) {
|
|
@@ -465,13 +565,16 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
465
565
|
entity = await loadEntity(entData, response.locals.nymph, patch);
|
|
466
566
|
}
|
|
467
567
|
catch (e) {
|
|
468
|
-
if (e instanceof
|
|
568
|
+
if (e instanceof EntityConflictError) {
|
|
469
569
|
conflict = true;
|
|
470
570
|
}
|
|
571
|
+
else if (e instanceof ForbiddenClassError) {
|
|
572
|
+
forbidden = true;
|
|
573
|
+
}
|
|
471
574
|
else if (e.message === NOT_FOUND_ERROR) {
|
|
472
575
|
notfound = true;
|
|
473
576
|
}
|
|
474
|
-
else if (e instanceof
|
|
577
|
+
else if (e instanceof InvalidParametersError) {
|
|
475
578
|
invalidRequest = true;
|
|
476
579
|
lastException = e;
|
|
477
580
|
}
|
|
@@ -496,7 +599,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
496
599
|
}
|
|
497
600
|
}
|
|
498
601
|
catch (e) {
|
|
499
|
-
if (e instanceof
|
|
602
|
+
if (e instanceof EntityInvalidDataError) {
|
|
500
603
|
invalidRequest = true;
|
|
501
604
|
}
|
|
502
605
|
else {
|
|
@@ -509,6 +612,9 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
509
612
|
if (invalidRequest) {
|
|
510
613
|
httpError(response, 400, lastException);
|
|
511
614
|
}
|
|
615
|
+
else if (forbidden) {
|
|
616
|
+
httpError(response, 403);
|
|
617
|
+
}
|
|
512
618
|
else if (conflict) {
|
|
513
619
|
httpError(response, 409);
|
|
514
620
|
}
|
|
@@ -555,6 +661,10 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
555
661
|
let EntityClass;
|
|
556
662
|
try {
|
|
557
663
|
EntityClass = response.locals.nymph.getEntityClass(entData.class);
|
|
664
|
+
if (!EntityClass.restEnabled) {
|
|
665
|
+
httpError(response, 403);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
558
668
|
}
|
|
559
669
|
catch (e) {
|
|
560
670
|
invalidRequest = true;
|
|
@@ -615,7 +725,7 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
615
725
|
return;
|
|
616
726
|
}
|
|
617
727
|
if (response.locals.nymph.tilmeld) {
|
|
618
|
-
if (!response.locals.nymph.tilmeld.checkClientUIDPermissions(data,
|
|
728
|
+
if (!(await response.locals.nymph.tilmeld.checkClientUIDPermissions(data, TilmeldAccessLevels.FULL_ACCESS))) {
|
|
619
729
|
httpError(response, 403);
|
|
620
730
|
return;
|
|
621
731
|
}
|
|
@@ -642,12 +752,17 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
642
752
|
return;
|
|
643
753
|
}
|
|
644
754
|
});
|
|
755
|
+
// Unauthenticate after the request.
|
|
645
756
|
rest.use(unauthenticateTilmeld);
|
|
646
757
|
async function loadEntity(entityData, nymph, patch = false, allowConflict = false) {
|
|
647
758
|
if (entityData.class === 'Entity') {
|
|
648
|
-
|
|
759
|
+
// Don't let clients use the `Entity` class, since it has no validity/AC checks.
|
|
760
|
+
throw new InvalidParametersError("Can't use Entity class directly from the front end.");
|
|
649
761
|
}
|
|
650
762
|
let EntityClass = nymph.getEntityClass(entityData.class);
|
|
763
|
+
if (!EntityClass.restEnabled) {
|
|
764
|
+
throw new ForbiddenClassError('Not accessible.');
|
|
765
|
+
}
|
|
651
766
|
let entity;
|
|
652
767
|
if (entityData.guid) {
|
|
653
768
|
entity = await nymph.getEntity({ class: EntityClass, source: 'client' }, {
|
|
@@ -669,11 +784,25 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
669
784
|
}
|
|
670
785
|
return entity;
|
|
671
786
|
}
|
|
787
|
+
/**
|
|
788
|
+
* Check if an item is a reference, and if it is, convert it to an entity.
|
|
789
|
+
*
|
|
790
|
+
* This function will recurse into deeper arrays and objects.
|
|
791
|
+
*
|
|
792
|
+
* @param item The item to check.
|
|
793
|
+
* @returns The item, converted.
|
|
794
|
+
*/
|
|
672
795
|
function referencesToEntities(item, nymph) {
|
|
673
|
-
if (
|
|
796
|
+
if (item == null) {
|
|
797
|
+
return item;
|
|
798
|
+
}
|
|
799
|
+
else if (Array.isArray(item)) {
|
|
674
800
|
if (item.length === 3 && item[0] === 'nymph_entity_reference') {
|
|
675
801
|
try {
|
|
676
|
-
const EntityClass = nymph.getEntityClass(item[
|
|
802
|
+
const EntityClass = nymph.getEntityClass(item[2]);
|
|
803
|
+
if (!EntityClass.restEnabled) {
|
|
804
|
+
throw new ForbiddenClassError('Not accessible.');
|
|
805
|
+
}
|
|
677
806
|
return EntityClass.factoryReference(item);
|
|
678
807
|
}
|
|
679
808
|
catch (e) {
|
|
@@ -682,7 +811,8 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
682
811
|
}
|
|
683
812
|
return item.map((entry) => referencesToEntities(entry, nymph));
|
|
684
813
|
}
|
|
685
|
-
else if (typeof item === 'object' && !(item instanceof
|
|
814
|
+
else if (typeof item === 'object' && !(item instanceof Entity)) {
|
|
815
|
+
// Only do this for non-entity objects.
|
|
686
816
|
const newItem = {};
|
|
687
817
|
for (let curProperty in item) {
|
|
688
818
|
newItem[curProperty] = referencesToEntities(item[curProperty], nymph);
|
|
@@ -691,20 +821,60 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
691
821
|
}
|
|
692
822
|
return item;
|
|
693
823
|
}
|
|
824
|
+
/**
|
|
825
|
+
* Return the request with an HTTP error response.
|
|
826
|
+
*
|
|
827
|
+
* @param res The server response object.
|
|
828
|
+
* @param defaultStatusCode The HTTP status code to use if none is given in the error object.
|
|
829
|
+
* @param error An optional error object to report.
|
|
830
|
+
*/
|
|
694
831
|
function httpError(res, defaultStatusCode, error) {
|
|
695
832
|
const status = error?.status || defaultStatusCode;
|
|
696
833
|
const statusText = error?.statusText ||
|
|
697
834
|
(error?.status == null
|
|
698
|
-
?
|
|
699
|
-
: error.status in
|
|
700
|
-
|
|
835
|
+
? statusDescriptions[defaultStatusCode]
|
|
836
|
+
: error.status in statusDescriptions &&
|
|
837
|
+
statusDescriptions[error.status]) ||
|
|
701
838
|
'Internal Server Error';
|
|
839
|
+
const errorResponse = error
|
|
840
|
+
? {
|
|
841
|
+
textStatus: `${status} ${statusText}`,
|
|
842
|
+
statusText,
|
|
843
|
+
message: error.message,
|
|
844
|
+
error,
|
|
845
|
+
...(process.env.NODE_ENV !== 'production'
|
|
846
|
+
? { stack: error.stack }
|
|
847
|
+
: {}),
|
|
848
|
+
}
|
|
849
|
+
: {
|
|
850
|
+
textStatus: `${status} ${statusText}`,
|
|
851
|
+
statusText,
|
|
852
|
+
message: statusText,
|
|
853
|
+
};
|
|
702
854
|
if (!res.headersSent) {
|
|
703
855
|
res.status(status);
|
|
704
856
|
res.setHeader('Content-Type', 'application/json');
|
|
705
857
|
}
|
|
706
|
-
|
|
707
|
-
|
|
858
|
+
res.send(errorResponse);
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* End the event stream with an HTTP error response.
|
|
862
|
+
*
|
|
863
|
+
* @param res The server response object.
|
|
864
|
+
* @param defaultStatusCode The HTTP status code to use if none is given in the error object.
|
|
865
|
+
* @param error An optional error object to report.
|
|
866
|
+
*/
|
|
867
|
+
function eventStreamError(res, defaultStatusCode, error) {
|
|
868
|
+
const status = error?.status || defaultStatusCode;
|
|
869
|
+
const statusText = error?.statusText ||
|
|
870
|
+
(error?.status == null
|
|
871
|
+
? statusDescriptions[defaultStatusCode]
|
|
872
|
+
: error.status in statusDescriptions &&
|
|
873
|
+
statusDescriptions[error.status]) ||
|
|
874
|
+
'Internal Server Error';
|
|
875
|
+
const errorResponse = error
|
|
876
|
+
? {
|
|
877
|
+
status,
|
|
708
878
|
textStatus: `${status} ${statusText}`,
|
|
709
879
|
statusText,
|
|
710
880
|
message: error.message,
|
|
@@ -712,17 +882,26 @@ function createServer(nymph, { jsonOptions = {} } = {}) {
|
|
|
712
882
|
...(process.env.NODE_ENV !== 'production'
|
|
713
883
|
? { stack: error.stack }
|
|
714
884
|
: {}),
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
res.send({
|
|
885
|
+
}
|
|
886
|
+
: {
|
|
887
|
+
status,
|
|
719
888
|
textStatus: `${status} ${statusText}`,
|
|
720
889
|
statusText,
|
|
721
890
|
message: statusText,
|
|
722
|
-
}
|
|
891
|
+
};
|
|
892
|
+
if (!res.headersSent) {
|
|
893
|
+
res.status(status);
|
|
894
|
+
res.setHeader('Content-Type', 'application/json');
|
|
895
|
+
res.send(errorResponse);
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
res.write('event: error\n');
|
|
899
|
+
res.write(`data: ${JSON.stringify(errorResponse)}\n\n`);
|
|
900
|
+
res.write('event: finished\n');
|
|
901
|
+
res.write('data: \n\n');
|
|
902
|
+
res.end();
|
|
723
903
|
}
|
|
724
904
|
}
|
|
725
905
|
return rest;
|
|
726
906
|
}
|
|
727
|
-
exports.createServer = createServer;
|
|
728
907
|
//# sourceMappingURL=createServer.js.map
|