@degenerate/endpoints 0.1.7 → 0.4.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.
- package/README.md +18 -0
- package/dist/cors/authority/jwt/build-validate-jwt-for-audience.d.ts +41 -0
- package/dist/cors/authority/jwt/build-validate-jwt-for-audience.d.ts.map +1 -0
- package/dist/cors/authority/jwt/build-validate-jwt-for-audience.js +75 -0
- package/dist/cors/authority/jwt/validate-jwt-authorities.d.ts +5 -13
- package/dist/cors/authority/jwt/validate-jwt-authorities.d.ts.map +1 -1
- package/dist/cors/authority/jwt/validate-jwt-authorities.js +7 -27
- package/dist/cors/authority/jwt/validate-jwt-endpoints.d.ts +5 -13
- package/dist/cors/authority/jwt/validate-jwt-endpoints.d.ts.map +1 -1
- package/dist/cors/authority/jwt/validate-jwt-endpoints.js +7 -27
- package/dist/cors/authority/jwt/validate-jwt-events.d.ts +5 -13
- package/dist/cors/authority/jwt/validate-jwt-events.d.ts.map +1 -1
- package/dist/cors/authority/jwt/validate-jwt-events.js +7 -27
- package/dist/cors/authority/jwt/validate-jwt-functions.d.ts +5 -13
- package/dist/cors/authority/jwt/validate-jwt-functions.d.ts.map +1 -1
- package/dist/cors/authority/jwt/validate-jwt-functions.js +7 -27
- package/dist/cors/authority/jwt/validate-jwt-rules.d.ts +5 -13
- package/dist/cors/authority/jwt/validate-jwt-rules.d.ts.map +1 -1
- package/dist/cors/authority/jwt/validate-jwt-rules.js +7 -27
- package/dist/cors/authority/jwt/validate-jwt-share-data.d.ts +5 -13
- package/dist/cors/authority/jwt/validate-jwt-share-data.d.ts.map +1 -1
- package/dist/cors/authority/jwt/validate-jwt-share-data.js +7 -27
- package/dist/cors/endpoints/helpers/validate-incoming-request-data.d.ts.map +1 -1
- package/dist/cors/endpoints/helpers/validate-incoming-request-data.js +1 -1
- package/dist/cors/index.d.ts +1 -0
- package/dist/cors/index.d.ts.map +1 -1
- package/dist/cors/index.js +1 -0
- package/dist/examples/express-server/app.d.ts +20 -2
- package/dist/examples/express-server/app.d.ts.map +1 -1
- package/dist/examples/express-server/app.js +42 -21
- package/dist/examples/express-server/authenticate-realtime.d.ts +27 -0
- package/dist/examples/express-server/authenticate-realtime.d.ts.map +1 -0
- package/dist/examples/express-server/authenticate-realtime.js +23 -0
- package/dist/examples/express-server/server.d.ts +32 -1
- package/dist/examples/express-server/server.d.ts.map +1 -1
- package/dist/examples/express-server/server.js +61 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/openapi/build-openapi-document.d.ts +27 -0
- package/dist/openapi/build-openapi-document.d.ts.map +1 -0
- package/dist/openapi/build-openapi-document.js +223 -0
- package/dist/openapi/domain-schemas.d.ts +16 -0
- package/dist/openapi/domain-schemas.d.ts.map +1 -0
- package/dist/openapi/domain-schemas.js +39 -0
- package/dist/openapi/index.d.ts +6 -0
- package/dist/openapi/index.d.ts.map +1 -0
- package/dist/openapi/index.js +21 -0
- package/dist/openapi/operations.d.ts +8 -0
- package/dist/openapi/operations.d.ts.map +1 -0
- package/dist/openapi/operations.js +22 -0
- package/dist/openapi/response-schemas.d.ts +32 -0
- package/dist/openapi/response-schemas.d.ts.map +1 -0
- package/dist/openapi/response-schemas.js +30 -0
- package/dist/openapi/types.d.ts +29 -0
- package/dist/openapi/types.d.ts.map +1 -0
- package/dist/openapi/types.js +2 -0
- package/dist/routes/authorities/v1.openapi.d.ts +4 -0
- package/dist/routes/authorities/v1.openapi.d.ts.map +1 -0
- package/dist/routes/authorities/v1.openapi.js +68 -0
- package/dist/routes/events/v1.openapi.d.ts +4 -0
- package/dist/routes/events/v1.openapi.d.ts.map +1 -0
- package/dist/routes/events/v1.openapi.js +54 -0
- package/dist/routes/events/v1.routes.d.ts.map +1 -1
- package/dist/routes/events/v1.routes.js +5 -3
- package/dist/routes/events.routes.d.ts.map +1 -1
- package/dist/routes/events.routes.js +10 -60
- package/dist/routes/functions/v1.openapi.d.ts +4 -0
- package/dist/routes/functions/v1.openapi.d.ts.map +1 -0
- package/dist/routes/functions/v1.openapi.js +68 -0
- package/dist/routes/functions.routes.d.ts.map +1 -1
- package/dist/routes/functions.routes.js +10 -60
- package/dist/routes/rules/v1/rule-set.openapi.d.ts +4 -0
- package/dist/routes/rules/v1/rule-set.openapi.d.ts.map +1 -0
- package/dist/routes/rules/v1/rule-set.openapi.js +72 -0
- package/dist/routes/rules/v1/rule-set.routes.js +3 -3
- package/dist/routes/share-data.routes.d.ts.map +1 -1
- package/dist/routes/share-data.routes.js +17 -59
- package/dist/routes/sharedata/v1.openapi.d.ts +8 -0
- package/dist/routes/sharedata/v1.openapi.d.ts.map +1 -0
- package/dist/routes/sharedata/v1.openapi.js +60 -0
- package/dist/routes/sharedata/v1.routes.d.ts.map +1 -1
- package/dist/routes/sharedata/v1.routes.js +195 -9
- package/dist/routes/validation/rules/validate-rules.openapi.d.ts +8 -0
- package/dist/routes/validation/rules/validate-rules.openapi.d.ts.map +1 -0
- package/dist/routes/validation/rules/validate-rules.openapi.js +76 -0
- package/package.json +28 -15
|
@@ -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.
|
|
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.
|
|
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.
|
|
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":"
|
|
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
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
router.
|
|
46
|
-
|
|
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;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { OperationDescriptor } from '../../openapi/types';
|
|
2
|
+
/**
|
|
3
|
+
* Operations under `/share-data` — gated by the SHAREDATA JWT audience, plus a
|
|
4
|
+
* per-resource (BOLA) check on create/delete: the verified token's
|
|
5
|
+
* `allowedSigningKeys` claim must vouch for the block's `creationPublicKey`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const shareDataOperations: OperationDescriptor[];
|
|
8
|
+
//# sourceMappingURL=v1.openapi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v1.openapi.d.ts","sourceRoot":"","sources":["../../../src/routes/sharedata/v1.openapi.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG/D;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,mBAAmB,EAiDpD,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.shareDataOperations = void 0;
|
|
4
|
+
const generic_1 = require("@degenerate/generic");
|
|
5
|
+
const response_schemas_1 = require("../../openapi/response-schemas");
|
|
6
|
+
/**
|
|
7
|
+
* Operations under `/share-data` — gated by the SHAREDATA JWT audience, plus a
|
|
8
|
+
* per-resource (BOLA) check on create/delete: the verified token's
|
|
9
|
+
* `allowedSigningKeys` claim must vouch for the block's `creationPublicKey`.
|
|
10
|
+
*/
|
|
11
|
+
exports.shareDataOperations = [
|
|
12
|
+
{
|
|
13
|
+
method: 'post',
|
|
14
|
+
path: '/share-data/v1',
|
|
15
|
+
security: generic_1.AuthorizationAudience.SHAREDATA,
|
|
16
|
+
summary: 'Create a ShareData block. The JWT allowedSigningKeys claim must authorize the block creationPublicKey.',
|
|
17
|
+
request: generic_1.ISAFETransactionShareDataAddSchema,
|
|
18
|
+
response: {
|
|
19
|
+
200: response_schemas_1.SuccessResponseSchema,
|
|
20
|
+
400: response_schemas_1.ErrorResponseSchema,
|
|
21
|
+
403: response_schemas_1.ErrorResponseSchema,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
method: 'get',
|
|
26
|
+
path: '/share-data/v1',
|
|
27
|
+
security: generic_1.AuthorizationAudience.SHAREDATA,
|
|
28
|
+
summary: 'List ShareData blocks.',
|
|
29
|
+
response: {
|
|
30
|
+
200: response_schemas_1.SuccessResponseSchema,
|
|
31
|
+
403: response_schemas_1.ErrorResponseSchema,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
method: 'patch',
|
|
36
|
+
path: '/share-data/v1/{sharedataid}',
|
|
37
|
+
security: generic_1.AuthorizationAudience.SHAREDATA,
|
|
38
|
+
summary: 'Unsupported. ShareData mutations must go through the rule-engine-verified proposal path, never a wholesale state replacement. Always returns 501.',
|
|
39
|
+
request: generic_1.ISAFETransactionShareDataUpdateSchema,
|
|
40
|
+
deprecated: true,
|
|
41
|
+
response: {
|
|
42
|
+
400: response_schemas_1.ErrorResponseSchema,
|
|
43
|
+
403: response_schemas_1.ErrorResponseSchema,
|
|
44
|
+
501: response_schemas_1.ErrorResponseSchema,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
method: 'delete',
|
|
49
|
+
path: '/share-data/v1/{sharedataid}',
|
|
50
|
+
security: generic_1.AuthorizationAudience.SHAREDATA,
|
|
51
|
+
summary: 'Delete a ShareData block by id. The JWT must vouch for the stored block creationPublicKey (BOLA gate).',
|
|
52
|
+
request: generic_1.ISAFETransactionShareDataDeleteSchema,
|
|
53
|
+
response: {
|
|
54
|
+
200: response_schemas_1.SuccessResponseSchema,
|
|
55
|
+
400: response_schemas_1.ErrorResponseSchema,
|
|
56
|
+
403: response_schemas_1.ErrorResponseSchema,
|
|
57
|
+
404: response_schemas_1.ErrorResponseSchema,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v1.routes.d.ts","sourceRoot":"","sources":["../../../src/routes/sharedata/v1.routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"v1.routes.d.ts","sourceRoot":"","sources":["../../../src/routes/sharedata/v1.routes.ts"],"names":[],"mappings":"AAwRA,eAAO,MAAM,EAAE,4CAAS,CAAC"}
|
|
@@ -43,21 +43,153 @@ 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
|
-
|
|
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
|
+
case generic_1.ShareBlockType.GraceRepayment:
|
|
147
|
+
// Not creatable through this generic route: a GraceRepayment
|
|
148
|
+
// block's initialState needs grace-specific fields
|
|
149
|
+
// (outstandingPrincipalCents, per-lender graceDecision) that the
|
|
150
|
+
// shared create payload doesn't carry. Reject explicitly rather
|
|
151
|
+
// than mis-constructing one from a partial payload.
|
|
152
|
+
return res.status(400).send({
|
|
153
|
+
successful: false,
|
|
154
|
+
error: 'unsupported-block-type: GraceRepayment blocks are not created via this route',
|
|
155
|
+
});
|
|
156
|
+
default: {
|
|
157
|
+
// Exhaustiveness check — schema validation should prevent
|
|
158
|
+
// reaching this, but TS doesn't know that. A runtime fallback
|
|
159
|
+
// makes the intent explicit and silences the unioned-undefined
|
|
160
|
+
// warning on `shareData`.
|
|
161
|
+
const _exhaustive = blockType;
|
|
162
|
+
return res.status(400).send({
|
|
163
|
+
successful: false,
|
|
164
|
+
error: `unknown blockType: ${_exhaustive}`,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Carry the creation recipe so a repository-backed manager can persist
|
|
169
|
+
// this block and keep writing through as transactions accrue. The
|
|
170
|
+
// factory was handed exactly `{ metadata, creationPublicKey,
|
|
171
|
+
// initialState }`; the manager re-serializes from those.
|
|
172
|
+
await getManager(req).addShareData(shareData, {
|
|
173
|
+
blockType,
|
|
174
|
+
creationConfig: { metadata, creationPublicKey, initialState },
|
|
175
|
+
});
|
|
176
|
+
return res.status(200).send({ successful: true });
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
180
|
+
return res.status(400).send({ successful: false, error: message });
|
|
181
|
+
}
|
|
60
182
|
});
|
|
183
|
+
// Coverage-check table — keeps the per-variant required-proof list lookup
|
|
184
|
+
// in one place rather than hard-coding switches in the route body.
|
|
185
|
+
const REQUIRED_PURPOSES_BY_BLOCK_TYPE = {
|
|
186
|
+
[generic_1.ShareBlockType.Clearing]: shareblock_1.CLEARING_REQUIRED_PROOF_PURPOSES,
|
|
187
|
+
[generic_1.ShareBlockType.Funding]: shareblock_1.FUNDING_REQUIRED_PROOF_PURPOSES,
|
|
188
|
+
[generic_1.ShareBlockType.Distribution]: shareblock_1.DISTRIBUTION_REQUIRED_PROOF_PURPOSES,
|
|
189
|
+
// Listed for type-completeness and to document the real proof set, even
|
|
190
|
+
// though the route currently rejects GraceRepayment creation above.
|
|
191
|
+
[generic_1.ShareBlockType.GraceRepayment]: shareblock_1.GRACE_REPAYMENT_REQUIRED_PROOF_PURPOSES,
|
|
192
|
+
};
|
|
61
193
|
/**
|
|
62
194
|
* This gets the sharedata objects and their data.
|
|
63
195
|
*/
|
|
@@ -68,19 +200,73 @@ async (req, res) => {
|
|
|
68
200
|
return;
|
|
69
201
|
});
|
|
70
202
|
/**
|
|
71
|
-
*
|
|
203
|
+
* PATCH is intentionally unsupported. The previous handler replaced the
|
|
204
|
+
* stored ShareData wholesale with the request body — bypassing the rule
|
|
205
|
+
* engine and the structural-proof trust chain entirely. That is state
|
|
206
|
+
* forgery, not merely a missing access check: a caller could overwrite a
|
|
207
|
+
* block's canonical state with arbitrary JSON. In the SAFE model, state
|
|
208
|
+
* changes are signed PROPOSALS judged by `startRuleEvaluation` (the
|
|
209
|
+
* `ShareDataManager.applyTransaction` path), never client-uploaded blobs.
|
|
210
|
+
* Until a proposal-over-HTTP endpoint exists, PATCH stays closed. Body
|
|
211
|
+
* shape is still validated first, so malformed requests get 400 before 501.
|
|
72
212
|
*/
|
|
73
213
|
router.patch(// https://{hostname}/sharedata/v1/{sharedataid}
|
|
74
214
|
'/: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 (
|
|
76
|
-
return res.status(
|
|
215
|
+
validate_request_body_1.validateRequestBody, (0, validate_incoming_request_data_1.validateIncomingRequestData)(generic_1.ISAFETransactionShareDataUpdateSchema), async (_req, res) => {
|
|
216
|
+
return res.status(501).send({
|
|
217
|
+
successful: false,
|
|
218
|
+
error: 'patch-unsupported: ShareData mutations must go through the rule-engine-verified proposal path, not a wholesale state replacement.',
|
|
219
|
+
});
|
|
77
220
|
});
|
|
78
221
|
/**
|
|
79
222
|
* This deletes the sharedata object.
|
|
80
223
|
*/
|
|
81
|
-
router.delete(// https://{hostname}/sharedata/v1
|
|
224
|
+
router.delete(// https://{hostname}/sharedata/v1/{sharedataid}
|
|
82
225
|
'/:sharedataid', add_unique_tracking_1.addUniqueTracking, // add a unique tracking id to the request
|
|
83
226
|
validate_request_body_1.validateRequestBody, (0, validate_incoming_request_data_1.validateIncomingRequestData)(generic_1.ISAFETransactionShareDataDeleteSchema), async (req, res) => {
|
|
84
|
-
|
|
227
|
+
try {
|
|
228
|
+
const shareDataId = req.params.sharedataid;
|
|
229
|
+
// Body/URL agreement: the wire payload names a target block; it must
|
|
230
|
+
// match the id in the URL path. A mismatch is a malformed request.
|
|
231
|
+
const bodyId = req.body
|
|
232
|
+
.transaction.data.metadata.shareBlockId;
|
|
233
|
+
if (bodyId !== shareDataId) {
|
|
234
|
+
return res.status(400).send({
|
|
235
|
+
successful: false,
|
|
236
|
+
error: `sharedata-id-mismatch: URL id ${shareDataId} does not match body id ${bodyId}`,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
// Per-resource authorization (BOLA gate). The router-level
|
|
240
|
+
// validateJWTShareData only proves the caller holds a valid SHAREDATA
|
|
241
|
+
// token — it does NOT prove they may act on THIS block. Look up the
|
|
242
|
+
// target and require the verified JWT to vouch for the key that
|
|
243
|
+
// created it. Authorize against the stored block's creationPublicKey,
|
|
244
|
+
// never the client-supplied body. Mirrors the POST create gate.
|
|
245
|
+
const existing = getManager(req).getShareDataById(shareDataId);
|
|
246
|
+
if (!existing) {
|
|
247
|
+
return res.status(404).send({
|
|
248
|
+
successful: false,
|
|
249
|
+
error: `not-found: no ShareData with id ${shareDataId}`,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const allowedSigningKeys = getAllowedSigningKeys(req);
|
|
253
|
+
if (!allowedSigningKeys || !allowedSigningKeys.includes(existing.creationPublicKey)) {
|
|
254
|
+
return res.status(403).send({
|
|
255
|
+
successful: false,
|
|
256
|
+
error: `creation-public-key-mismatch: JWT does not authorize mutating block created by ${existing.creationPublicKey}`,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
await getManager(req).removeShareDataById(shareDataId);
|
|
260
|
+
return res.status(200).send({ successful: true });
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
264
|
+
return res.status(400).send({ successful: false, error: message });
|
|
265
|
+
}
|
|
85
266
|
});
|
|
267
|
+
// Error handlers must be registered AFTER routes — express only routes
|
|
268
|
+
// errors to handlers that follow the throwing middleware in the chain.
|
|
269
|
+
router.use(unauthorized_handler_1.unauthorizedHandler); // the unauthorized handler should be the first error handler
|
|
270
|
+
router.use(bad_request_handler_1.badRequestHandler); // the bad request handler should be the second error handler
|
|
271
|
+
router.use(all_error_handler_1.allErrorHandler); // the all error handler catches all remaining errors
|
|
86
272
|
exports.v1 = router; // https://{hostname}/sharedata/v1
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { OperationDescriptor } from '../../../openapi/types';
|
|
2
|
+
/**
|
|
3
|
+
* Operations under `/validation/v1/validate-rules` — public (no JWT). Each
|
|
4
|
+
* echoes `{ successful: true }` when the posted body parses against its domain
|
|
5
|
+
* schema, otherwise the shared 400 error envelope.
|
|
6
|
+
*/
|
|
7
|
+
export declare const validateRulesOperations: OperationDescriptor[];
|
|
8
|
+
//# sourceMappingURL=validate-rules.openapi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-rules.openapi.d.ts","sourceRoot":"","sources":["../../../../src/routes/validation/rules/validate-rules.openapi.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAGlE;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAAmB,EAiExD,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateRulesOperations = void 0;
|
|
4
|
+
const generic_1 = require("@degenerate/generic");
|
|
5
|
+
const response_schemas_1 = require("../../../openapi/response-schemas");
|
|
6
|
+
/**
|
|
7
|
+
* Operations under `/validation/v1/validate-rules` — public (no JWT). Each
|
|
8
|
+
* echoes `{ successful: true }` when the posted body parses against its domain
|
|
9
|
+
* schema, otherwise the shared 400 error envelope.
|
|
10
|
+
*/
|
|
11
|
+
exports.validateRulesOperations = [
|
|
12
|
+
{
|
|
13
|
+
method: 'get',
|
|
14
|
+
path: '/validation',
|
|
15
|
+
security: 'public',
|
|
16
|
+
summary: 'Liveness probe for the validation service.',
|
|
17
|
+
response: {
|
|
18
|
+
200: response_schemas_1.SuccessResponseSchema,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
method: 'post',
|
|
23
|
+
path: '/validation/v1/validate-rules/rule-set',
|
|
24
|
+
security: 'public',
|
|
25
|
+
summary: 'Validate a rule set against its schema.',
|
|
26
|
+
request: generic_1.IShareRuleSetSchema,
|
|
27
|
+
response: {
|
|
28
|
+
200: response_schemas_1.SuccessResponseSchema,
|
|
29
|
+
400: response_schemas_1.ErrorResponseSchema,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
method: 'post',
|
|
34
|
+
path: '/validation/v1/validate-rules/rule-group',
|
|
35
|
+
security: 'public',
|
|
36
|
+
summary: 'Validate a rule group against its schema.',
|
|
37
|
+
request: generic_1.IShareRuleGroupSchema,
|
|
38
|
+
response: {
|
|
39
|
+
200: response_schemas_1.SuccessResponseSchema,
|
|
40
|
+
400: response_schemas_1.ErrorResponseSchema,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
method: 'post',
|
|
45
|
+
path: '/validation/v1/validate-rules/rule-applied',
|
|
46
|
+
security: 'public',
|
|
47
|
+
summary: 'Validate an applied rule against its schema.',
|
|
48
|
+
request: generic_1.IShareRuleAppliedSchema,
|
|
49
|
+
response: {
|
|
50
|
+
200: response_schemas_1.SuccessResponseSchema,
|
|
51
|
+
400: response_schemas_1.ErrorResponseSchema,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
method: 'post',
|
|
56
|
+
path: '/validation/v1/validate-rules/rule',
|
|
57
|
+
security: 'public',
|
|
58
|
+
summary: 'Validate a rule against its schema.',
|
|
59
|
+
request: generic_1.IShareRuleSchema,
|
|
60
|
+
response: {
|
|
61
|
+
200: response_schemas_1.SuccessResponseSchema,
|
|
62
|
+
400: response_schemas_1.ErrorResponseSchema,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
method: 'post',
|
|
67
|
+
path: '/validation/v1/validate-rules/condition',
|
|
68
|
+
security: 'public',
|
|
69
|
+
summary: 'Validate a condition against its schema.',
|
|
70
|
+
request: generic_1.IConditionSchema,
|
|
71
|
+
response: {
|
|
72
|
+
200: response_schemas_1.SuccessResponseSchema,
|
|
73
|
+
400: response_schemas_1.ErrorResponseSchema,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
];
|
package/package.json
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@degenerate/endpoints",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "This contains the ShareData Adaptive Framework Ecosystem (SAFE) network endpoint code",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
7
16
|
"devDependencies": {
|
|
8
|
-
"@types/
|
|
9
|
-
"@types/
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
17
|
+
"@types/express": "^5.0.6",
|
|
18
|
+
"@types/jest": "^30.0.0",
|
|
19
|
+
"@types/node": "^22.19.18",
|
|
20
|
+
"jest": "^30.4.2",
|
|
21
|
+
"ts-jest": "^29.4.9",
|
|
22
|
+
"typescript": "^6.0.3"
|
|
13
23
|
},
|
|
14
24
|
"scripts": {
|
|
15
25
|
"build": "rm -rf dist/* && tsc -p tsconfig.pkg.json",
|
|
16
26
|
"compile": "rm -rf dist/* && tsc -p tsconfig.pkg.json",
|
|
17
|
-
"test": "jest --forceExit --detectOpenHandles --verbose --silent=false --env node
|
|
18
|
-
"test:coverage": "jest --forceExit --coverage --detectOpenHandles --verbose --silent=false --env node
|
|
27
|
+
"test": "jest --forceExit --detectOpenHandles --verbose --silent=false --env node",
|
|
28
|
+
"test:coverage": "jest --forceExit --coverage --detectOpenHandles --verbose --silent=false --env node ",
|
|
19
29
|
"test:clearCache": "jest --clearCache"
|
|
20
30
|
},
|
|
21
31
|
"repository": {
|
|
@@ -40,14 +50,17 @@
|
|
|
40
50
|
"author": "Degenerate Inc.",
|
|
41
51
|
"license": "ISC",
|
|
42
52
|
"dependencies": {
|
|
43
|
-
"@degenerate/generic": "^0.
|
|
44
|
-
"@
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
53
|
+
"@degenerate/generic": "^0.4.0",
|
|
54
|
+
"@degenerate/realtime": "^0.4.0",
|
|
55
|
+
"@degenerate/shareblock": "^0.4.0",
|
|
56
|
+
"@degenerate/sharedata": "^0.4.0",
|
|
57
|
+
"@types/supertest": "^6.0.3",
|
|
58
|
+
"express": "^5.2.1",
|
|
59
|
+
"supertest": "^7.2.2",
|
|
60
|
+
"zod": "^4.4.3"
|
|
48
61
|
},
|
|
49
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "9453d9ab785e03fd01eb5b408ed0e3ca22baefd5",
|
|
50
63
|
"engines": {
|
|
51
|
-
"node": "22
|
|
64
|
+
"node": ">=22"
|
|
52
65
|
}
|
|
53
66
|
}
|