jinaga-server 3.0.1 → 3.1.1
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/dist/http/line-reader.d.ts +2 -0
- package/dist/http/line-reader.js +68 -0
- package/dist/http/line-reader.js.map +1 -0
- package/dist/http/router.d.ts +4 -1
- package/dist/http/router.js +142 -21
- package/dist/http/router.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/jinaga-server.d.ts +1 -0
- package/dist/jinaga-server.js +1 -1
- package/dist/jinaga-server.js.map +1 -1
- package/http/sans-auth/Feeds options.http +1 -0
- package/http/sans-auth/Load json.http +64 -0
- package/http/sans-auth/Save graph.http +6 -0
- package/http/sans-auth/Save json.http +13 -0
- package/http/with-auth/Create post with auth.http +24 -0
- package/http/with-auth/Define authorization rules.http +25 -0
- package/http/with-auth/Define distribution rules.http +30 -0
- package/http/with-auth/Feed sites.http +3 -0
- package/http/with-auth/Feeds options.http +1 -0
- package/http/with-auth/Feeds sites.http +32 -0
- package/http/with-auth/Get posts with auth.http +32 -0
- package/http/with-auth/Load titles.http +21 -0
- package/integration-test/jinaga-test/Dockerfile +1 -1
- package/integration-test/jinaga-test/package-lock.json +143 -55
- package/integration-test/jinaga-test/package.json +2 -2
- package/package.json +3 -3
- package/replicator/index.ts +0 -2
- package/replicator/package-lock.json +157 -86
- package/replicator/package.json +1 -3
- package/src/http/line-reader.ts +69 -0
- package/src/http/router.ts +180 -27
- package/src/index.ts +1 -0
- package/src/jinaga-server.ts +2 -1
package/src/http/router.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Handler, Router } from "express";
|
|
1
|
+
import { Handler, NextFunction, Request, Response, Router } from "express";
|
|
2
2
|
import {
|
|
3
3
|
Authorization,
|
|
4
4
|
Declaration,
|
|
5
|
+
FactEnvelope,
|
|
5
6
|
FactManager,
|
|
6
7
|
FactRecord,
|
|
7
8
|
FactReference,
|
|
@@ -10,12 +11,14 @@ import {
|
|
|
10
11
|
FeedResponse,
|
|
11
12
|
FeedsResponse,
|
|
12
13
|
Forbidden,
|
|
14
|
+
GraphDeserializer,
|
|
15
|
+
GraphSerializer,
|
|
16
|
+
GraphSource,
|
|
13
17
|
LoadMessage,
|
|
14
18
|
LoadResponse,
|
|
15
19
|
ProfileMessage,
|
|
16
20
|
ProjectedResult,
|
|
17
21
|
ReferencesByName,
|
|
18
|
-
SaveMessage,
|
|
19
22
|
Specification,
|
|
20
23
|
SpecificationParser,
|
|
21
24
|
Trace,
|
|
@@ -27,8 +30,8 @@ import {
|
|
|
27
30
|
parseLoadMessage,
|
|
28
31
|
parseSaveMessage
|
|
29
32
|
} from "jinaga";
|
|
30
|
-
|
|
31
33
|
import { Stream } from "./stream";
|
|
34
|
+
import { createLineReader } from "./line-reader";
|
|
32
35
|
|
|
33
36
|
interface ParsedQs { [key: string]: undefined | string | string[] | ParsedQs | ParsedQs[] }
|
|
34
37
|
|
|
@@ -132,7 +135,8 @@ function getAuthenticate<U>(method: ((req: RequestUser, params?: { [key: string]
|
|
|
132
135
|
|
|
133
136
|
function post<T, U>(
|
|
134
137
|
parse: (input: any) => T,
|
|
135
|
-
method: (user: RequestUser, message: T, params?: { [key: string]: string }) => Promise<U
|
|
138
|
+
method: (user: RequestUser, message: T, params?: { [key: string]: string }) => Promise<U>,
|
|
139
|
+
output: (result: U, res: Response, accepts: (type: string) => string | false) => void
|
|
136
140
|
): Handler {
|
|
137
141
|
return (req, res, next) => {
|
|
138
142
|
const user = <RequestUser>req.user;
|
|
@@ -147,8 +151,7 @@ function post<T, U>(
|
|
|
147
151
|
next();
|
|
148
152
|
}
|
|
149
153
|
else {
|
|
150
|
-
res.type
|
|
151
|
-
res.send(JSON.stringify(response));
|
|
154
|
+
output(response, res, (type) => req.accepts(type));
|
|
152
155
|
next();
|
|
153
156
|
}
|
|
154
157
|
})
|
|
@@ -201,15 +204,12 @@ function postString<U>(method: (user: RequestUser, message: string) => Promise<U
|
|
|
201
204
|
}
|
|
202
205
|
|
|
203
206
|
function postCreate<T>(
|
|
204
|
-
parse: (
|
|
207
|
+
parse: (request: Request) => T,
|
|
205
208
|
method: (user: RequestUser, message: T) => Promise<void>
|
|
206
209
|
): Handler {
|
|
207
210
|
return (req, res, next) => {
|
|
208
211
|
const user = <RequestUser>req.user;
|
|
209
|
-
const message = parse(req
|
|
210
|
-
if (!message) {
|
|
211
|
-
throw new Error('Ensure that you have called app.use(express.json()).');
|
|
212
|
-
}
|
|
212
|
+
const message = parse(req);
|
|
213
213
|
method(user, message)
|
|
214
214
|
.then(_ => {
|
|
215
215
|
res.sendStatus(201);
|
|
@@ -272,6 +272,52 @@ function serializeUserIdentity(user: RequestUser | null): UserIdentity | null {
|
|
|
272
272
|
};
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
function inputSaveMessage(req: Request): GraphSource {
|
|
276
|
+
if (req.is('application/x-jinaga-graph-v1')) {
|
|
277
|
+
// Convert the request into a function that reads one line at a time.
|
|
278
|
+
const readLine = createLineReader(req);
|
|
279
|
+
|
|
280
|
+
return new GraphDeserializer(readLine);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
return {
|
|
284
|
+
read: async (onEnvelopes) => {
|
|
285
|
+
const message = parseSaveMessage(req.body);
|
|
286
|
+
if (!message) {
|
|
287
|
+
throw new Error('Ensure that you have called app.use(express.json()).');
|
|
288
|
+
}
|
|
289
|
+
await onEnvelopes(message.facts.map(fact => ({
|
|
290
|
+
fact: fact,
|
|
291
|
+
signatures: []
|
|
292
|
+
})));
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function outputGraph(result: FactEnvelope[], res: Response, accepts: (type: string) => string | false) {
|
|
299
|
+
if (accepts("application/x-jinaga-graph-v1")) {
|
|
300
|
+
res.type("application/x-jinaga-graph-v1");
|
|
301
|
+
const serializer = new GraphSerializer(
|
|
302
|
+
(chunk: string) => res.write(chunk)
|
|
303
|
+
);
|
|
304
|
+
serializer.serialize(result);
|
|
305
|
+
res.end();
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
res.type("json");
|
|
309
|
+
const loadResponse: LoadResponse = {
|
|
310
|
+
facts: result.map(r => r.fact)
|
|
311
|
+
};
|
|
312
|
+
res.send(JSON.stringify(loadResponse));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function outputFeeds(result: FeedsResponse, res: Response, accepts: (type: string) => string | false) {
|
|
317
|
+
res.type("json");
|
|
318
|
+
res.send(JSON.stringify(result));
|
|
319
|
+
}
|
|
320
|
+
|
|
275
321
|
export interface RequestUser {
|
|
276
322
|
provider: string;
|
|
277
323
|
id: string;
|
|
@@ -281,28 +327,59 @@ export interface RequestUser {
|
|
|
281
327
|
export class HttpRouter {
|
|
282
328
|
handler: Handler;
|
|
283
329
|
|
|
284
|
-
constructor(
|
|
330
|
+
constructor(
|
|
331
|
+
private factManager: FactManager,
|
|
332
|
+
private authorization: Authorization,
|
|
333
|
+
private feedCache: FeedCache,
|
|
334
|
+
private allowedOrigin: string | string[] | ((origin: string, callback: (err: Error | null, allow?: boolean) => void) => void)
|
|
335
|
+
) {
|
|
285
336
|
const router = Router();
|
|
286
337
|
router.get('/login', getAuthenticate(user => this.login(user)));
|
|
287
338
|
router.post('/load', post(
|
|
288
339
|
parseLoadMessage,
|
|
289
|
-
(user, loadMessage) => this.load(user, loadMessage)
|
|
340
|
+
(user, loadMessage) => this.load(user, loadMessage),
|
|
341
|
+
outputGraph
|
|
290
342
|
));
|
|
291
343
|
router.post('/save', postCreate(
|
|
292
|
-
|
|
293
|
-
(user,
|
|
344
|
+
inputSaveMessage,
|
|
345
|
+
(user, graphSource) => this.save(user, graphSource)
|
|
294
346
|
));
|
|
295
347
|
|
|
296
348
|
router.post('/read', postString((user, input: string) => this.read(user, input)));
|
|
297
349
|
router.post('/write', postStringCreate((user, input: string) => this.write(user, input)));
|
|
298
350
|
router.post('/feeds', post(
|
|
299
351
|
parseString,
|
|
300
|
-
(user, input: string) => this.feeds(user, input)
|
|
352
|
+
(user, input: string, accepts) => this.feeds(user, input),
|
|
353
|
+
outputFeeds
|
|
301
354
|
));
|
|
302
355
|
router.get('/feeds/:hash', getOrStream<FeedResponse>(
|
|
303
356
|
(user, params, query) => this.feed(user, params, query),
|
|
304
357
|
(user, params, query) => this.streamFeed(user, params, query)));
|
|
305
358
|
|
|
359
|
+
// Respond to OPTIONS requests to describe the methods and content types
|
|
360
|
+
// that are supported.
|
|
361
|
+
this.setOptions(router, '/login')
|
|
362
|
+
.intendedForGet()
|
|
363
|
+
.returningContent();
|
|
364
|
+
this.setOptions(router, '/load')
|
|
365
|
+
.intendedForPost('application/json')
|
|
366
|
+
.returningContent();
|
|
367
|
+
this.setOptions(router, '/save')
|
|
368
|
+
.intendedForPost('application/json', 'application/x-jinaga-graph-v1')
|
|
369
|
+
.returningNoContent();
|
|
370
|
+
this.setOptions(router, '/read')
|
|
371
|
+
.intendedForPost('text/plain')
|
|
372
|
+
.returningContent();
|
|
373
|
+
this.setOptions(router, '/write')
|
|
374
|
+
.intendedForPost('text/plain')
|
|
375
|
+
.returningNoContent();
|
|
376
|
+
this.setOptions(router, '/feeds')
|
|
377
|
+
.intendedForPost('text/plain')
|
|
378
|
+
.returningContent();
|
|
379
|
+
this.setOptions(router, '/feeds/:hash')
|
|
380
|
+
.intendedForGet()
|
|
381
|
+
.returningContent();
|
|
382
|
+
|
|
306
383
|
this.handler = router;
|
|
307
384
|
}
|
|
308
385
|
|
|
@@ -317,22 +394,17 @@ export class HttpRouter {
|
|
|
317
394
|
};
|
|
318
395
|
}
|
|
319
396
|
|
|
320
|
-
private async load(user: RequestUser, loadMessage: LoadMessage): Promise<
|
|
397
|
+
private async load(user: RequestUser, loadMessage: LoadMessage): Promise<FactEnvelope[]> {
|
|
321
398
|
const userIdentity = serializeUserIdentity(user);
|
|
322
399
|
const result = await this.authorization.load(userIdentity, loadMessage.references);
|
|
323
|
-
|
|
324
|
-
return {
|
|
325
|
-
facts
|
|
326
|
-
};
|
|
400
|
+
return result;
|
|
327
401
|
}
|
|
328
402
|
|
|
329
|
-
private async save(user: RequestUser | null,
|
|
403
|
+
private async save(user: RequestUser | null, graphSource: GraphSource): Promise<void> {
|
|
330
404
|
const userIdentity = serializeUserIdentity(user);
|
|
331
|
-
await
|
|
332
|
-
.
|
|
333
|
-
|
|
334
|
-
signatures: []
|
|
335
|
-
})));
|
|
405
|
+
await graphSource.read(async (envelopes) => {
|
|
406
|
+
await this.authorization.save(userIdentity, envelopes);
|
|
407
|
+
});
|
|
336
408
|
}
|
|
337
409
|
|
|
338
410
|
private async read(user: RequestUser | null, input: string): Promise<any[]> {
|
|
@@ -525,6 +597,77 @@ export class HttpRouter {
|
|
|
525
597
|
return declaredFact.declared.reference;
|
|
526
598
|
});
|
|
527
599
|
}
|
|
600
|
+
|
|
601
|
+
private setOptions(router: Router, path: string): OptionsConfiguration {
|
|
602
|
+
const addOptions = (allowedMethods: string[], allowedHeaders: string[], allowedTypes: string[]) => {
|
|
603
|
+
router.options(path, this.applyAllowOrigin((req, res) => {
|
|
604
|
+
res.set('Allow', allowedMethods.join(', '));
|
|
605
|
+
res.set('Access-Control-Allow-Methods', allowedMethods.join(', '));
|
|
606
|
+
res.set('Access-Control-Allow-Headers', allowedHeaders.join(', '));
|
|
607
|
+
if (allowedTypes.length > 0) {
|
|
608
|
+
res.set('Accept-Post', allowedTypes.join(', '));
|
|
609
|
+
}
|
|
610
|
+
res.status(204).send();
|
|
611
|
+
}));
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return {
|
|
615
|
+
intendedForGet: () => {
|
|
616
|
+
addOptions(['GET', 'OPTIONS'], ['Accept', 'Authorization'], []);
|
|
617
|
+
return {
|
|
618
|
+
returningContent: () => { },
|
|
619
|
+
returningNoContent: () => { }
|
|
620
|
+
};
|
|
621
|
+
},
|
|
622
|
+
intendedForPost: (...contentTypes: string[]) => {
|
|
623
|
+
return {
|
|
624
|
+
returningContent: () => {
|
|
625
|
+
addOptions(
|
|
626
|
+
['POST', 'OPTIONS'],
|
|
627
|
+
['Content-Type', 'Accept', 'Authorization'],
|
|
628
|
+
contentTypes);
|
|
629
|
+
},
|
|
630
|
+
returningNoContent: () => {
|
|
631
|
+
addOptions(
|
|
632
|
+
['POST', 'OPTIONS'],
|
|
633
|
+
['Content-Type', 'Authorization'],
|
|
634
|
+
contentTypes);
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
private applyAllowOrigin(sendResponse: (req: Request, res: Response) => void) {
|
|
642
|
+
// Specify the allowed origins.
|
|
643
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
644
|
+
let requestOrigin = req.get('Origin');
|
|
645
|
+
|
|
646
|
+
if (typeof this.allowedOrigin === 'string') {
|
|
647
|
+
// If allowedOrigin is a string, use it as the value of the Access-Control-Allow-Origin header
|
|
648
|
+
res.set('Access-Control-Allow-Origin', this.allowedOrigin);
|
|
649
|
+
sendResponse(req, res);
|
|
650
|
+
} else if (Array.isArray(this.allowedOrigin)) {
|
|
651
|
+
// If allowedOrigin is an array, check if the request's origin is in the array
|
|
652
|
+
if (requestOrigin && this.allowedOrigin.includes(requestOrigin)) {
|
|
653
|
+
res.set('Access-Control-Allow-Origin', requestOrigin);
|
|
654
|
+
}
|
|
655
|
+
sendResponse(req, res);
|
|
656
|
+
} else if (typeof this.allowedOrigin === 'function' && requestOrigin) {
|
|
657
|
+
// If allowedOrigin is a function, call it with the request's origin
|
|
658
|
+
this.allowedOrigin(requestOrigin, (err, allow) => {
|
|
659
|
+
if (err) {
|
|
660
|
+
next(err);
|
|
661
|
+
} else if (allow) {
|
|
662
|
+
res.set('Access-Control-Allow-Origin', requestOrigin);
|
|
663
|
+
}
|
|
664
|
+
sendResponse(req, res);
|
|
665
|
+
});
|
|
666
|
+
} else {
|
|
667
|
+
sendResponse(req, res);
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
}
|
|
528
671
|
}
|
|
529
672
|
|
|
530
673
|
function parseString(input: any): string {
|
|
@@ -548,4 +691,14 @@ function extractResults(obj: any): any {
|
|
|
548
691
|
else {
|
|
549
692
|
return obj;
|
|
550
693
|
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
interface OptionsConfiguration {
|
|
697
|
+
intendedForGet(): ResponseConfiguration;
|
|
698
|
+
intendedForPost(...contentTypes: string[]): ResponseConfiguration;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
interface ResponseConfiguration {
|
|
702
|
+
returningContent(): void;
|
|
703
|
+
returningNoContent(): void;
|
|
551
704
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { AuthenticationDevice } from "./authentication/authentication-device";
|
|
|
2
2
|
export { AuthenticationSession } from "./authentication/authentication-session";
|
|
3
3
|
export { AuthorizationKeystore } from "./authorization/authorization-keystore";
|
|
4
4
|
export { DistributedFactCache } from "./authorization/distributed-fact-cache";
|
|
5
|
+
export { createLineReader } from "./http/line-reader";
|
|
5
6
|
export { HttpRouter, RequestUser } from "./http/router";
|
|
6
7
|
export { Stream } from "./http/stream";
|
|
7
8
|
export { JinagaServer, JinagaServerConfig, JinagaServerInstance, tracePool } from "./jinaga-server";
|
package/src/jinaga-server.ts
CHANGED
|
@@ -38,6 +38,7 @@ export type JinagaServerConfig = {
|
|
|
38
38
|
model?: Model,
|
|
39
39
|
authorization?: (a: AuthorizationRules) => AuthorizationRules,
|
|
40
40
|
distribution?: (d: DistributionRules) => DistributionRules,
|
|
41
|
+
origin?: string | string[] | ((origin: string, callback: (err: Error | null, allow?: boolean) => void) => void),
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
export type JinagaServerInstance = {
|
|
@@ -67,7 +68,7 @@ export class JinagaServer {
|
|
|
67
68
|
const network = new NetworkNoOp();
|
|
68
69
|
const factManager = new FactManager(fork, source, store, network);
|
|
69
70
|
const authorization = createAuthorization(authorizationRules, distributionRules, factManager, store, keystore);
|
|
70
|
-
const router = new HttpRouter(factManager, authorization, feedCache);
|
|
71
|
+
const router = new HttpRouter(factManager, authorization, feedCache, config.origin || '*');
|
|
71
72
|
const j: Jinaga = new Jinaga(authentication, factManager, syncStatusNotifier);
|
|
72
73
|
|
|
73
74
|
async function close() {
|