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.
Files changed (35) hide show
  1. package/dist/http/line-reader.d.ts +2 -0
  2. package/dist/http/line-reader.js +68 -0
  3. package/dist/http/line-reader.js.map +1 -0
  4. package/dist/http/router.d.ts +4 -1
  5. package/dist/http/router.js +142 -21
  6. package/dist/http/router.js.map +1 -1
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +3 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/jinaga-server.d.ts +1 -0
  11. package/dist/jinaga-server.js +1 -1
  12. package/dist/jinaga-server.js.map +1 -1
  13. package/http/sans-auth/Feeds options.http +1 -0
  14. package/http/sans-auth/Load json.http +64 -0
  15. package/http/sans-auth/Save graph.http +6 -0
  16. package/http/sans-auth/Save json.http +13 -0
  17. package/http/with-auth/Create post with auth.http +24 -0
  18. package/http/with-auth/Define authorization rules.http +25 -0
  19. package/http/with-auth/Define distribution rules.http +30 -0
  20. package/http/with-auth/Feed sites.http +3 -0
  21. package/http/with-auth/Feeds options.http +1 -0
  22. package/http/with-auth/Feeds sites.http +32 -0
  23. package/http/with-auth/Get posts with auth.http +32 -0
  24. package/http/with-auth/Load titles.http +21 -0
  25. package/integration-test/jinaga-test/Dockerfile +1 -1
  26. package/integration-test/jinaga-test/package-lock.json +143 -55
  27. package/integration-test/jinaga-test/package.json +2 -2
  28. package/package.json +3 -3
  29. package/replicator/index.ts +0 -2
  30. package/replicator/package-lock.json +157 -86
  31. package/replicator/package.json +1 -3
  32. package/src/http/line-reader.ts +69 -0
  33. package/src/http/router.ts +180 -27
  34. package/src/index.ts +1 -0
  35. package/src/jinaga-server.ts +2 -1
@@ -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("json");
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: (input: any) => T,
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.body);
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(private factManager: FactManager, private authorization: Authorization, private feedCache: FeedCache) {
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
- parseSaveMessage,
293
- (user, saveMessage) => this.save(user, saveMessage)
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<LoadResponse> {
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
- const facts = result.map(r => r.fact);
324
- return {
325
- facts
326
- };
400
+ return result;
327
401
  }
328
402
 
329
- private async save(user: RequestUser | null, saveMessage: SaveMessage): Promise<void> {
403
+ private async save(user: RequestUser | null, graphSource: GraphSource): Promise<void> {
330
404
  const userIdentity = serializeUserIdentity(user);
331
- await this.authorization.save(userIdentity, saveMessage.facts
332
- .map(fact => ({
333
- fact: fact,
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";
@@ -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() {