@degenerate/endpoints 0.1.7 → 0.2.0

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 (43) hide show
  1. package/dist/cors/authority/jwt/build-validate-jwt-for-audience.d.ts +41 -0
  2. package/dist/cors/authority/jwt/build-validate-jwt-for-audience.d.ts.map +1 -0
  3. package/dist/cors/authority/jwt/build-validate-jwt-for-audience.js +75 -0
  4. package/dist/cors/authority/jwt/validate-jwt-authorities.d.ts +5 -13
  5. package/dist/cors/authority/jwt/validate-jwt-authorities.d.ts.map +1 -1
  6. package/dist/cors/authority/jwt/validate-jwt-authorities.js +7 -27
  7. package/dist/cors/authority/jwt/validate-jwt-endpoints.d.ts +5 -13
  8. package/dist/cors/authority/jwt/validate-jwt-endpoints.d.ts.map +1 -1
  9. package/dist/cors/authority/jwt/validate-jwt-endpoints.js +7 -27
  10. package/dist/cors/authority/jwt/validate-jwt-events.d.ts +5 -13
  11. package/dist/cors/authority/jwt/validate-jwt-events.d.ts.map +1 -1
  12. package/dist/cors/authority/jwt/validate-jwt-events.js +7 -27
  13. package/dist/cors/authority/jwt/validate-jwt-functions.d.ts +5 -13
  14. package/dist/cors/authority/jwt/validate-jwt-functions.d.ts.map +1 -1
  15. package/dist/cors/authority/jwt/validate-jwt-functions.js +7 -27
  16. package/dist/cors/authority/jwt/validate-jwt-rules.d.ts +5 -13
  17. package/dist/cors/authority/jwt/validate-jwt-rules.d.ts.map +1 -1
  18. package/dist/cors/authority/jwt/validate-jwt-rules.js +7 -27
  19. package/dist/cors/authority/jwt/validate-jwt-share-data.d.ts +5 -13
  20. package/dist/cors/authority/jwt/validate-jwt-share-data.d.ts.map +1 -1
  21. package/dist/cors/authority/jwt/validate-jwt-share-data.js +7 -27
  22. package/dist/cors/endpoints/helpers/validate-incoming-request-data.d.ts.map +1 -1
  23. package/dist/cors/endpoints/helpers/validate-incoming-request-data.js +1 -1
  24. package/dist/cors/index.d.ts +1 -0
  25. package/dist/cors/index.d.ts.map +1 -1
  26. package/dist/cors/index.js +1 -0
  27. package/dist/examples/express-server/app.d.ts +20 -2
  28. package/dist/examples/express-server/app.d.ts.map +1 -1
  29. package/dist/examples/express-server/app.js +34 -21
  30. package/dist/examples/express-server/authenticate-realtime.d.ts +27 -0
  31. package/dist/examples/express-server/authenticate-realtime.d.ts.map +1 -0
  32. package/dist/examples/express-server/authenticate-realtime.js +23 -0
  33. package/dist/examples/express-server/server.d.ts +32 -1
  34. package/dist/examples/express-server/server.d.ts.map +1 -1
  35. package/dist/examples/express-server/server.js +61 -7
  36. package/dist/routes/events/v1.routes.d.ts.map +1 -1
  37. package/dist/routes/events/v1.routes.js +5 -3
  38. package/dist/routes/rules/v1/rule-set.routes.js +3 -3
  39. package/dist/routes/share-data.routes.d.ts.map +1 -1
  40. package/dist/routes/share-data.routes.js +17 -59
  41. package/dist/routes/sharedata/v1.routes.d.ts.map +1 -1
  42. package/dist/routes/sharedata/v1.routes.js +182 -9
  43. package/package.json +28 -15
@@ -3,7 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createApp = createApp;
6
7
  const express_1 = __importDefault(require("express"));
8
+ const generic_1 = require("@degenerate/generic");
9
+ const sharedata_1 = require("@degenerate/sharedata");
7
10
  //#region Route Imports
8
11
  const authorities_routes_1 = require("../../routes/authorities.routes");
9
12
  const events_routes_1 = require("../../routes/events.routes");
@@ -11,25 +14,35 @@ const functions_routes_1 = require("../../routes/functions.routes");
11
14
  const rules_routes_1 = require("../../routes/rules.routes");
12
15
  const share_data_routes_1 = require("../../routes/share-data.routes");
13
16
  const validation_routes_1 = require("../../routes/validation.routes");
14
- const generic_1 = require("@degenerate/generic");
15
17
  const cors_1 = require("../../cors");
16
- //#endregion Route Imports
17
- // create the express application
18
- const app = (0, express_1.default)();
19
- app.use(express_1.default.json());
20
- const authorizedAuthorities = new generic_1.AuthorizedAuthorities();
21
- // Initialize the authorized authorities here if needed
22
- // e.g., authorizedAuthorities.addAuthorizedJWTAuthority(...);
23
- // Make the instance available throughout the app
24
- app.set('authorizedAuthorities', authorizedAuthorities);
25
- // Make sure you enable whatever middleware you need here
26
- app.use('/authorities', authorities_routes_1.authorities); // add the authorities routes
27
- app.use('/events', events_routes_1.events); // add the events routes
28
- app.use('/functions', functions_routes_1.functions); // add the functions routes
29
- app.use('/rules', rules_routes_1.rules); // add the rules routes
30
- app.use('/share-data', share_data_routes_1.shareData); // add the share data routes
31
- app.use('/validation', validation_routes_1.validation); // add the validation routes
32
- app.use(cors_1.unauthorizedHandler); // the unauthorized handler should be the first error handler
33
- app.use(cors_1.badRequestHandler); // the bad request handler should be the second error handler
34
- app.use(cors_1.allErrorHandler); // the all error handler catches all remaining errors
35
- exports.default = app;
18
+ /**
19
+ * Builds a fresh Express app with all SAFE routes wired in. Returns the app
20
+ * along with the shared `ShareDataManager` instance — handed back so callers
21
+ * (e.g. `createSafeServer` for realtime attachment) can subscribe to its
22
+ * events without reaching into `app.locals`.
23
+ *
24
+ * Route handlers access the manager via `req.app.get('shareDataManager')`,
25
+ * matching the existing `authorizedAuthorities` pattern.
26
+ */
27
+ function createApp() {
28
+ const app = (0, express_1.default)();
29
+ app.use(express_1.default.json());
30
+ const authorizedAuthorities = new generic_1.AuthorizedAuthorities();
31
+ const shareDataManager = new sharedata_1.ShareDataManager();
32
+ app.set('authorizedAuthorities', authorizedAuthorities);
33
+ app.set('shareDataManager', shareDataManager);
34
+ app.use('/authorities', authorities_routes_1.authorities);
35
+ app.use('/events', events_routes_1.events);
36
+ app.use('/functions', functions_routes_1.functions);
37
+ app.use('/rules', rules_routes_1.rules);
38
+ app.use('/share-data', share_data_routes_1.shareData);
39
+ app.use('/validation', validation_routes_1.validation);
40
+ app.use(cors_1.unauthorizedHandler);
41
+ app.use(cors_1.badRequestHandler);
42
+ app.use(cors_1.allErrorHandler);
43
+ return { app, authorizedAuthorities, shareDataManager };
44
+ }
45
+ // Back-compat: tests and existing callers import the default app singleton.
46
+ // New code should prefer `createApp()` so each instance is isolated.
47
+ const defaultInstance = createApp();
48
+ exports.default = defaultInstance.app;
@@ -0,0 +1,27 @@
1
+ import type { AuthenticateUpgrade } from '@degenerate/realtime';
2
+ /**
3
+ * Context attached to every accepted WebSocket connection. Replace the
4
+ * placeholder fields with whatever your application needs to authorize
5
+ * subsequent messages (subject, scopes, tenant id, etc.).
6
+ */
7
+ export interface ExampleRealtimeContext {
8
+ subject: string;
9
+ }
10
+ /**
11
+ * Example upgrade authenticator. Accepts every connection with a synthetic
12
+ * subject — adequate for demos and integration tests, not for production.
13
+ *
14
+ * To wire real JWT auth, replace the body with something like:
15
+ *
16
+ * import { ... } from '@degenerate/generic';
17
+ * const token = extractTokenFromHeaderOrQuery(req);
18
+ * if (!token) return null;
19
+ * const payload = await verifyJwt(token, authorizedAuthorities);
20
+ * return payload ? { subject: payload.sub } : null;
21
+ *
22
+ * `validateJWTShareData` in `cors/authority/jwt/` is the HTTP-side equivalent
23
+ * — reuse the same key resolution + claim checks here so HTTP and WS share an
24
+ * authorization model.
25
+ */
26
+ export declare const exampleAuthenticate: AuthenticateUpgrade<ExampleRealtimeContext>;
27
+ //# sourceMappingURL=authenticate-realtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authenticate-realtime.d.ts","sourceRoot":"","sources":["../../../src/examples/express-server/authenticate-realtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACnC,OAAO,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,EAAE,mBAAmB,CAAC,sBAAsB,CAI3E,CAAC"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.exampleAuthenticate = void 0;
4
+ /**
5
+ * Example upgrade authenticator. Accepts every connection with a synthetic
6
+ * subject — adequate for demos and integration tests, not for production.
7
+ *
8
+ * To wire real JWT auth, replace the body with something like:
9
+ *
10
+ * import { ... } from '@degenerate/generic';
11
+ * const token = extractTokenFromHeaderOrQuery(req);
12
+ * if (!token) return null;
13
+ * const payload = await verifyJwt(token, authorizedAuthorities);
14
+ * return payload ? { subject: payload.sub } : null;
15
+ *
16
+ * `validateJWTShareData` in `cors/authority/jwt/` is the HTTP-side equivalent
17
+ * — reuse the same key resolution + claim checks here so HTTP and WS share an
18
+ * authorization model.
19
+ */
20
+ const exampleAuthenticate = (_req) => {
21
+ return { subject: 'example-anonymous' };
22
+ };
23
+ exports.exampleAuthenticate = exampleAuthenticate;
@@ -1,2 +1,33 @@
1
- export {};
1
+ import { type Server as HttpServer } from 'http';
2
+ import type { Express } from 'express';
3
+ import { ShareDataManager } from '@degenerate/sharedata';
4
+ import { type RealtimeServerHandle } from '@degenerate/realtime';
5
+ export interface SafeServerHandle {
6
+ /** The Express application — the same instance imported from `./app`. */
7
+ readonly app: Express;
8
+ /** The underlying Node HTTP server. Call `.listen()` to start accepting. */
9
+ readonly httpServer: HttpServer;
10
+ /** ShareDataManager broadcasting to subscribed WebSocket clients. */
11
+ readonly manager: ShareDataManager;
12
+ /** Realtime websocket handle. Call `.close()` during shutdown. */
13
+ readonly realtime: RealtimeServerHandle;
14
+ /** Convenience: cleanly stop realtime + close the HTTP server. */
15
+ close(): Promise<void>;
16
+ }
17
+ /**
18
+ * Builds the example SAFE server: the existing Express app wrapped in a Node
19
+ * HTTP server, with the realtime WebSocket transport attached at `/realtime`.
20
+ * Returns handles for caller-controlled `listen()` and shutdown so this is
21
+ * safe to use from tests as well as a `bin/` entry point.
22
+ *
23
+ * The returned `manager` is the one wired to the realtime broadcast loop and
24
+ * also the one routes resolve via `req.app.get('shareDataManager')`. HTTP
25
+ * route mutations therefore appear as `transaction` frames on the websocket.
26
+ *
27
+ * `propose` is wired to the id-based function resolver: any `IShareFunction`
28
+ * registered on a ShareData with an `id` becomes addressable by clients via
29
+ * the `functionId` field in a `propose` message. Functions without an `id`
30
+ * remain invisible to the wire — useful for purely-internal functions.
31
+ */
32
+ export declare function createSafeServer(): SafeServerHandle;
2
33
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/examples/express-server/server.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/examples/express-server/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AAC/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAGH,KAAK,oBAAoB,EAC5B,MAAM,sBAAsB,CAAC;AAI9B,MAAM,WAAW,gBAAgB;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,kEAAkE;IAClE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,IAAI,gBAAgB,CA6BnD"}
@@ -1,9 +1,63 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const app_1 = __importDefault(require("./app"));
7
- app_1.default.listen(3000, () => {
8
- console.log('Server is running on port 3000');
9
- });
3
+ exports.createSafeServer = createSafeServer;
4
+ const http_1 = require("http");
5
+ const realtime_1 = require("@degenerate/realtime");
6
+ const app_1 = require("./app");
7
+ const authenticate_realtime_1 = require("./authenticate-realtime");
8
+ /**
9
+ * Builds the example SAFE server: the existing Express app wrapped in a Node
10
+ * HTTP server, with the realtime WebSocket transport attached at `/realtime`.
11
+ * Returns handles for caller-controlled `listen()` and shutdown so this is
12
+ * safe to use from tests as well as a `bin/` entry point.
13
+ *
14
+ * The returned `manager` is the one wired to the realtime broadcast loop and
15
+ * also the one routes resolve via `req.app.get('shareDataManager')`. HTTP
16
+ * route mutations therefore appear as `transaction` frames on the websocket.
17
+ *
18
+ * `propose` is wired to the id-based function resolver: any `IShareFunction`
19
+ * registered on a ShareData with an `id` becomes addressable by clients via
20
+ * the `functionId` field in a `propose` message. Functions without an `id`
21
+ * remain invisible to the wire — useful for purely-internal functions.
22
+ */
23
+ function createSafeServer() {
24
+ const { app, shareDataManager: manager } = (0, app_1.createApp)();
25
+ const httpServer = (0, http_1.createServer)(app);
26
+ const realtime = (0, realtime_1.attachRealtimeServer)({
27
+ server: httpServer,
28
+ manager,
29
+ authenticate: authenticate_realtime_1.exampleAuthenticate,
30
+ path: '/realtime',
31
+ resolveFunction: (0, realtime_1.createIdBasedFunctionResolver)(manager),
32
+ });
33
+ let closed = false;
34
+ return {
35
+ app,
36
+ httpServer,
37
+ manager,
38
+ realtime,
39
+ async close() {
40
+ if (closed)
41
+ return;
42
+ closed = true;
43
+ await realtime.close();
44
+ // `httpServer.close()` errors if the server is already closed.
45
+ // The realtime handle may have already terminated all WS sockets,
46
+ // but the HTTP listener still needs to be stopped. Swallow the
47
+ // "not running" error so close() is idempotent.
48
+ await new Promise((resolve) => {
49
+ httpServer.close(() => resolve());
50
+ });
51
+ },
52
+ };
53
+ }
54
+ // When run directly (e.g. `node dist/examples/express-server/server.js`),
55
+ // start the server on port 3000. Tests should call `createSafeServer()` and
56
+ // drive `.httpServer.listen(0, ...)` themselves.
57
+ if (require.main === module) {
58
+ const handle = createSafeServer();
59
+ handle.httpServer.listen(3000, () => {
60
+ // eslint-disable-next-line no-console
61
+ console.log('SAFE example server listening on :3000 (WS at /realtime)');
62
+ });
63
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"v1.routes.d.ts","sourceRoot":"","sources":["../../../src/routes/events/v1.routes.ts"],"names":[],"mappings":"AAkEA,eAAO,MAAM,EAAE,4CAAS,CAAC"}
1
+ {"version":3,"file":"v1.routes.d.ts","sourceRoot":"","sources":["../../../src/routes/events/v1.routes.ts"],"names":[],"mappings":"AAqEA,eAAO,MAAM,EAAE,4CAAS,CAAC"}
@@ -47,9 +47,6 @@ const generic_1 = require("@degenerate/generic");
47
47
  const router = express.Router({ mergeParams: true });
48
48
  router.use(add_unique_tracking_1.addUniqueTracking); // add the unique tracking id to the request
49
49
  router.use(validate_jwt_events_1.validateJWTEvents); // validate the JWT token
50
- router.use(unauthorized_handler_1.unauthorizedHandler); // the unauthorized handler should be the first error handler
51
- router.use(bad_request_handler_1.badRequestHandler); // the bad request handler should be the second error handler
52
- router.use(all_error_handler_1.allErrorHandler); // the all error handler catches all remaining errors
53
50
  /**
54
51
  * This creates a new event object.
55
52
  */
@@ -82,4 +79,9 @@ router.delete(// https://{hostname}/events/v1
82
79
  (0, validate_incoming_request_data_1.validateIncomingRequestData)(generic_1.ISAFETransactionEventDeleteSchema), async (req, res) => {
83
80
  return res.status(200).send({ successful: true });
84
81
  });
82
+ // Error handlers must be registered AFTER routes — express only routes
83
+ // errors to handlers that follow the throwing middleware in the chain.
84
+ router.use(unauthorized_handler_1.unauthorizedHandler); // the unauthorized handler should be the first error handler
85
+ router.use(bad_request_handler_1.badRequestHandler); // the bad request handler should be the second error handler
86
+ router.use(all_error_handler_1.allErrorHandler); // the all error handler catches all remaining errors
85
87
  exports.v1 = router; // https://{hostname}/events/v1
@@ -85,7 +85,7 @@ router.get('/', async (req, res, next) => {
85
85
  */
86
86
  router.get('/:rulesetsid', async (req, res, next) => {
87
87
  try {
88
- const rulesSetsId = (0, generic_1.getParamRuleSetsId)(req); // get the rules id.
88
+ const rulesSetsId = (0, generic_1.getRuleSetsId)(req); // get the rules id.
89
89
  const ruleSets = ruleSetManager.getRuleSetById(rulesSetsId); // add the rule set to the rule set manager
90
90
  const returnItem = { successful: true };
91
91
  if (ruleSets) {
@@ -103,7 +103,7 @@ router.get('/:rulesetsid', async (req, res, next) => {
103
103
  router.patch('/:rulesetsid', validate_request_body_1.validateRequestBody, // make sure this is a valid request body
104
104
  (0, validate_incoming_request_data_1.validateIncomingRequestData)(generic_1.ISAFETransactionRuleSetUpdateSchema), async (req, res, next) => {
105
105
  try {
106
- const rulesId = (0, generic_1.getParamRuleSetsId)(req); // get the rules id.
106
+ const rulesId = (0, generic_1.getRuleSetsId)(req); // get the rules id.
107
107
  const ruleSet = req.body.transaction.data.ruleSet; // get the rule set;
108
108
  if (ruleSet.id !== rulesId) {
109
109
  throw new Error('Rule set id does not match');
@@ -121,7 +121,7 @@ router.patch('/:rulesetsid', validate_request_body_1.validateRequestBody, // mak
121
121
  router.delete('/:rulesetsid', validate_request_body_1.validateRequestBody, // make sure this is a valid request body
122
122
  (0, validate_incoming_request_data_1.validateIncomingRequestData)(generic_1.ISAFETransactionRuleSetDeleteSchema), async (req, res, next) => {
123
123
  try {
124
- const rulesId = (0, generic_1.getParamRuleSetsId)(req); // get the rules id.
124
+ const rulesId = (0, generic_1.getRuleSetsId)(req); // get the rules id.
125
125
  const ruleSet = req.body.transaction.data.ruleSet; // get the rule set;
126
126
  if (ruleSet.id !== rulesId) {
127
127
  throw new Error('Rule set id does not match');
@@ -1 +1 @@
1
- {"version":3,"file":"share-data.routes.d.ts","sourceRoot":"","sources":["../../src/routes/share-data.routes.ts"],"names":[],"mappings":"AAmCA,eAAO,MAAM,SAAS,4CAAS,CAAC"}
1
+ {"version":3,"file":"share-data.routes.d.ts","sourceRoot":"","sources":["../../src/routes/share-data.routes.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,SAAS,4CAAS,CAAC"}
@@ -1,66 +1,24 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
35
5
  Object.defineProperty(exports, "__esModule", { value: true });
36
6
  exports.shareData = void 0;
37
- const express = __importStar(require("express"));
7
+ const express_1 = __importDefault(require("express"));
38
8
  const get_set_audiences_1 = require("../cors/authority/audience/get-set-audiences");
9
+ const request_logging_1 = require("../cors/logging/request-logging");
10
+ const v1_routes_1 = require("./sharedata/v1.routes");
11
+ const validate_jwt_share_data_1 = require("../cors/authority/jwt/validate-jwt-share-data");
12
+ const all_error_handler_1 = require("../cors/errors/all-error-handler");
13
+ const bad_request_handler_1 = require("../cors/errors/bad-request-handler");
14
+ const unauthorized_handler_1 = require("../cors/errors/unauthorized-handler");
39
15
  // eslint-disable-next-line new-cap
40
- const router = express.Router({ mergeParams: true });
16
+ const router = express_1.default.Router({ mergeParams: true });
17
+ router.use(request_logging_1.requestLogging); // log the request
41
18
  router.use(get_set_audiences_1.setAudienceShareData); // set the audience to share data (this will throw an error if the audience is not set)
42
- /**
43
- * This creates a new sharedata object.
44
- */
45
- router.post('/', async (req, res) => {
46
- return res.status(200).send({ successful: true });
47
- });
48
- /**
49
- * This gets the sharedata object.
50
- */
51
- router.get('/', async (req, res) => {
52
- return res.status(200).send({ successful: true });
53
- });
54
- /**
55
- * This updates the sharedata object.
56
- */
57
- router.patch('/', async (req, res) => {
58
- return res.status(200).send({ successful: true });
59
- });
60
- /**
61
- * This deletes the sharedata object.
62
- */
63
- router.delete('/', async (req, res) => {
64
- return res.status(200).send({ successful: true });
65
- });
19
+ router.use(validate_jwt_share_data_1.validateJWTShareData); // get the audience authority
20
+ router.use('/v1', v1_routes_1.v1); // use the v1 routes
21
+ router.use(unauthorized_handler_1.unauthorizedHandler); // the unauthorized handler should be the first error handler
22
+ router.use(bad_request_handler_1.badRequestHandler); // the bad request handler should be the second error handler
23
+ router.use(all_error_handler_1.allErrorHandler); // the all error handler catches all remaining errors
66
24
  exports.shareData = router;
@@ -1 +1 @@
1
- {"version":3,"file":"v1.routes.d.ts","sourceRoot":"","sources":["../../../src/routes/sharedata/v1.routes.ts"],"names":[],"mappings":"AAqEA,eAAO,MAAM,EAAE,4CAAS,CAAC"}
1
+ {"version":3,"file":"v1.routes.d.ts","sourceRoot":"","sources":["../../../src/routes/sharedata/v1.routes.ts"],"names":[],"mappings":"AA0QA,eAAO,MAAM,EAAE,4CAAS,CAAC"}
@@ -43,21 +43,140 @@ const all_error_handler_1 = require("../../cors/errors/all-error-handler");
43
43
  const bad_request_handler_1 = require("../../cors/errors/bad-request-handler");
44
44
  const unauthorized_handler_1 = require("../../cors/errors/unauthorized-handler");
45
45
  const generic_1 = require("@degenerate/generic");
46
+ const shareblock_1 = require("@degenerate/shareblock");
46
47
  // eslint-disable-next-line new-cap
47
48
  const router = express.Router({ mergeParams: true });
49
+ // The ShareDataManager is provided by the host application via
50
+ // `app.set('shareDataManager', manager)`. Sharing the same instance across
51
+ // HTTP and realtime is what makes route mutations broadcast to subscribed
52
+ // WebSocket clients. Tests must `app.set('shareDataManager', new ShareDataManager())`
53
+ // in their setup before mounting this router.
54
+ const getManager = (req) => {
55
+ const manager = req.app.get('shareDataManager');
56
+ if (!manager) {
57
+ throw new Error('shareDataManager is not registered on the app — see createApp()');
58
+ }
59
+ return manager;
60
+ };
61
+ // Authorization claims ride on the verified JWT as loosely-typed custom
62
+ // claims. Treat `allowedSigningKeys` as authoritative only when it is
63
+ // genuinely an array of strings — a malformed token that supplied a bare
64
+ // string would otherwise fall into String.prototype.includes substring
65
+ // semantics and could wrongly authorize a partially-matching key. Both the
66
+ // create and delete gates parse through here so they can't drift.
67
+ const getAllowedSigningKeys = (req) => {
68
+ const raw = req.auth?.customClaims?.allowedSigningKeys;
69
+ return Array.isArray(raw) && raw.every((k) => typeof k === 'string')
70
+ ? raw
71
+ : undefined;
72
+ };
48
73
  router.use(add_unique_tracking_1.addUniqueTracking); // add the unique tracking id to the request
49
74
  router.use(validate_jwt_share_data_1.validateJWTShareData); // validate the JWT token
50
- router.use(unauthorized_handler_1.unauthorizedHandler); // the unauthorized handler should be the first error handler
51
- router.use(bad_request_handler_1.badRequestHandler); // the bad request handler should be the second error handler
52
- router.use(all_error_handler_1.allErrorHandler); // the all error handler catches all remaining errors
53
75
  /**
54
76
  * This creates a new sharedata object.
55
77
  */
56
78
  router.post(// https://{hostname}/sharedata/v1
57
79
  '/', validate_request_body_1.validateRequestBody, // make sure this is a valid request body
58
80
  (0, validate_incoming_request_data_1.validateIncomingRequestData)(generic_1.ISAFETransactionShareDataAddSchema), async (req, res) => {
59
- return res.status(200).send({ successful: true });
81
+ try {
82
+ const { metadata, initialState, initialCrypto, proofs } = req.body.transaction.data;
83
+ const creationPublicKey = initialCrypto.publicKey;
84
+ // Cross-check: the verified JWT must authorize the caller to sign
85
+ // as `creationPublicKey`. Token issuers attach
86
+ // `customClaims.allowedSigningKeys: string[]` listing the public
87
+ // keys the subject is vouched for — without this check, anyone
88
+ // holding a valid SHAREDATA JWT could create a block attributed to
89
+ // any key (since they generate the proofs themselves with their
90
+ // own keypair). The shared validateJWTShareData middleware has
91
+ // already verified signature/issuer/audience/expiry; this gate
92
+ // closes the subject → signing-key binding.
93
+ const allowedSigningKeys = getAllowedSigningKeys(req);
94
+ if (!allowedSigningKeys || !allowedSigningKeys.includes(creationPublicKey)) {
95
+ return res.status(403).send({
96
+ successful: false,
97
+ error: `creation-public-key-mismatch: JWT does not authorize signing as ${creationPublicKey}`,
98
+ });
99
+ }
100
+ // Cross-check: every proof in the bundle must be signed by the same
101
+ // key the initialCrypto attributes the block to. A mismatch means
102
+ // the client either swapped a proof in or supplied a forged
103
+ // creationPublicKey — reject early with one clear error rather
104
+ // than letting it surface as a verifyStructuralProof failure inside
105
+ // the factory.
106
+ for (const [purpose, proof] of Object.entries(proofs)) {
107
+ if (proof.publicKey !== creationPublicKey) {
108
+ return res.status(400).send({
109
+ successful: false,
110
+ error: `proof-public-key-mismatch: proof for '${purpose}' signed by ${proof.publicKey}, but initialCrypto attributes block to ${creationPublicKey}`,
111
+ });
112
+ }
113
+ }
114
+ // Cross-check: bundle covers every purpose the factory will request.
115
+ // Surfaces a missing proof once, here, instead of as the first
116
+ // MissingRequiredProofError thrown mid-construction.
117
+ const requiredPurposes = REQUIRED_PURPOSES_BY_BLOCK_TYPE[metadata.blockType];
118
+ for (const purpose of requiredPurposes) {
119
+ if (!(purpose in proofs)) {
120
+ return res.status(400).send({
121
+ successful: false,
122
+ error: `missing-proof: bundle is missing required proof for purpose '${purpose}' on ${metadata.blockType} ShareBlock`,
123
+ });
124
+ }
125
+ }
126
+ // Schema's literal union and the enum are the same string values —
127
+ // narrow once and reuse so we don't repeat the cast.
128
+ const blockType = metadata.blockType;
129
+ const proofFor = (0, shareblock_1.buildLookupProofFor)({
130
+ proofs,
131
+ initialCrypto,
132
+ blockType,
133
+ });
134
+ const factoryArgs = { metadata, creationPublicKey, initialState, proofFor };
135
+ let shareData;
136
+ switch (blockType) {
137
+ case generic_1.ShareBlockType.Clearing:
138
+ shareData = (0, shareblock_1.createClearingShareBlock)(factoryArgs);
139
+ break;
140
+ case generic_1.ShareBlockType.Funding:
141
+ shareData = (0, shareblock_1.createFundingShareBlock)(factoryArgs);
142
+ break;
143
+ case generic_1.ShareBlockType.Distribution:
144
+ shareData = (0, shareblock_1.createDistributionShareBlock)(factoryArgs);
145
+ break;
146
+ default: {
147
+ // Exhaustiveness check — schema validation should prevent
148
+ // reaching this, but TS doesn't know that. A runtime fallback
149
+ // makes the intent explicit and silences the unioned-undefined
150
+ // warning on `shareData`.
151
+ const _exhaustive = blockType;
152
+ return res.status(400).send({
153
+ successful: false,
154
+ error: `unknown blockType: ${_exhaustive}`,
155
+ });
156
+ }
157
+ }
158
+ // Carry the creation recipe so a repository-backed manager can persist
159
+ // this block and keep writing through as transactions accrue. The
160
+ // factory was handed exactly `{ metadata, creationPublicKey,
161
+ // initialState }`; the manager re-serializes from those.
162
+ await getManager(req).addShareData(shareData, {
163
+ blockType,
164
+ creationConfig: { metadata, creationPublicKey, initialState },
165
+ });
166
+ return res.status(200).send({ successful: true });
167
+ }
168
+ catch (error) {
169
+ const message = error instanceof Error ? error.message : 'Unknown error';
170
+ return res.status(400).send({ successful: false, error: message });
171
+ }
60
172
  });
173
+ // Coverage-check table — keeps the per-variant required-proof list lookup
174
+ // in one place rather than hard-coding switches in the route body.
175
+ const REQUIRED_PURPOSES_BY_BLOCK_TYPE = {
176
+ [generic_1.ShareBlockType.Clearing]: shareblock_1.CLEARING_REQUIRED_PROOF_PURPOSES,
177
+ [generic_1.ShareBlockType.Funding]: shareblock_1.FUNDING_REQUIRED_PROOF_PURPOSES,
178
+ [generic_1.ShareBlockType.Distribution]: shareblock_1.DISTRIBUTION_REQUIRED_PROOF_PURPOSES,
179
+ };
61
180
  /**
62
181
  * This gets the sharedata objects and their data.
63
182
  */
@@ -68,19 +187,73 @@ async (req, res) => {
68
187
  return;
69
188
  });
70
189
  /**
71
- * This updates the sharedata object.
190
+ * PATCH is intentionally unsupported. The previous handler replaced the
191
+ * stored ShareData wholesale with the request body — bypassing the rule
192
+ * engine and the structural-proof trust chain entirely. That is state
193
+ * forgery, not merely a missing access check: a caller could overwrite a
194
+ * block's canonical state with arbitrary JSON. In the SAFE model, state
195
+ * changes are signed PROPOSALS judged by `startRuleEvaluation` (the
196
+ * `ShareDataManager.applyTransaction` path), never client-uploaded blobs.
197
+ * Until a proposal-over-HTTP endpoint exists, PATCH stays closed. Body
198
+ * shape is still validated first, so malformed requests get 400 before 501.
72
199
  */
73
200
  router.patch(// https://{hostname}/sharedata/v1/{sharedataid}
74
201
  '/:sharedataid', add_unique_tracking_1.addUniqueTracking, // add a unique tracking id to the request
75
- validate_request_body_1.validateRequestBody, (0, validate_incoming_request_data_1.validateIncomingRequestData)(generic_1.ISAFETransactionShareDataUpdateSchema), async (req, res) => {
76
- return res.status(200).send({ successful: true });
202
+ validate_request_body_1.validateRequestBody, (0, validate_incoming_request_data_1.validateIncomingRequestData)(generic_1.ISAFETransactionShareDataUpdateSchema), async (_req, res) => {
203
+ return res.status(501).send({
204
+ successful: false,
205
+ error: 'patch-unsupported: ShareData mutations must go through the rule-engine-verified proposal path, not a wholesale state replacement.',
206
+ });
77
207
  });
78
208
  /**
79
209
  * This deletes the sharedata object.
80
210
  */
81
- router.delete(// https://{hostname}/sharedata/v1
211
+ router.delete(// https://{hostname}/sharedata/v1/{sharedataid}
82
212
  '/:sharedataid', add_unique_tracking_1.addUniqueTracking, // add a unique tracking id to the request
83
213
  validate_request_body_1.validateRequestBody, (0, validate_incoming_request_data_1.validateIncomingRequestData)(generic_1.ISAFETransactionShareDataDeleteSchema), async (req, res) => {
84
- return res.status(200).send({ successful: true });
214
+ try {
215
+ const shareDataId = req.params.sharedataid;
216
+ // Body/URL agreement: the wire payload names a target block; it must
217
+ // match the id in the URL path. A mismatch is a malformed request.
218
+ const bodyId = req.body
219
+ .transaction.data.metadata.shareBlockId;
220
+ if (bodyId !== shareDataId) {
221
+ return res.status(400).send({
222
+ successful: false,
223
+ error: `sharedata-id-mismatch: URL id ${shareDataId} does not match body id ${bodyId}`,
224
+ });
225
+ }
226
+ // Per-resource authorization (BOLA gate). The router-level
227
+ // validateJWTShareData only proves the caller holds a valid SHAREDATA
228
+ // token — it does NOT prove they may act on THIS block. Look up the
229
+ // target and require the verified JWT to vouch for the key that
230
+ // created it. Authorize against the stored block's creationPublicKey,
231
+ // never the client-supplied body. Mirrors the POST create gate.
232
+ const existing = getManager(req).getShareDataById(shareDataId);
233
+ if (!existing) {
234
+ return res.status(404).send({
235
+ successful: false,
236
+ error: `not-found: no ShareData with id ${shareDataId}`,
237
+ });
238
+ }
239
+ const allowedSigningKeys = getAllowedSigningKeys(req);
240
+ if (!allowedSigningKeys || !allowedSigningKeys.includes(existing.creationPublicKey)) {
241
+ return res.status(403).send({
242
+ successful: false,
243
+ error: `creation-public-key-mismatch: JWT does not authorize mutating block created by ${existing.creationPublicKey}`,
244
+ });
245
+ }
246
+ await getManager(req).removeShareDataById(shareDataId);
247
+ return res.status(200).send({ successful: true });
248
+ }
249
+ catch (error) {
250
+ const message = error instanceof Error ? error.message : 'Unknown error';
251
+ return res.status(400).send({ successful: false, error: message });
252
+ }
85
253
  });
254
+ // Error handlers must be registered AFTER routes — express only routes
255
+ // errors to handlers that follow the throwing middleware in the chain.
256
+ router.use(unauthorized_handler_1.unauthorizedHandler); // the unauthorized handler should be the first error handler
257
+ router.use(bad_request_handler_1.badRequestHandler); // the bad request handler should be the second error handler
258
+ router.use(all_error_handler_1.allErrorHandler); // the all error handler catches all remaining errors
86
259
  exports.v1 = router; // https://{hostname}/sharedata/v1