@powerhousedao/reactor-api 6.2.0-dev.2 → 6.2.0-dev.20
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 +0 -2
- package/dist/index.d.mts +148 -178
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +536 -524
- package/dist/index.mjs.map +1 -1
- package/dist/src/packages/vite-loader.mjs +3 -3
- package/dist/src/packages/vite-loader.mjs.map +1 -1
- package/dist/utils-Dh9tl892.mjs +530 -0
- package/dist/utils-Dh9tl892.mjs.map +1 -0
- package/package.json +13 -13
- package/dist/utils-BFkbSO_H.mjs +0 -296
- package/dist/utils-BFkbSO_H.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
3
|
-
import { a as isSubgraphClass, c as loadDocumentModels, d as BaseSubgraph, i as buildGraphqlOperations, l as loadProcessors, n as buildGraphQlDriveDocument, o as debounce, r as buildGraphqlOperation, t as buildGraphQlDocument, u as loadSubgraphs } from "./utils-
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="cae89b22-c317-5acb-b1ec-dc325f23f6cf")}catch(e){}}();
|
|
3
|
+
import { a as isSubgraphClass, c as loadDocumentModels, d as BaseSubgraph, f as AuthorizationPolicy, i as buildGraphqlOperations, l as loadProcessors, m as createAuthorizationService, n as buildGraphQlDriveDocument, o as debounce, p as AuthorizedDocumentHandle, r as buildGraphqlOperation, t as buildGraphQlDocument, u as loadSubgraphs } from "./utils-Dh9tl892.mjs";
|
|
4
4
|
import { AnalyticsQueryEngine } from "@powerhousedao/analytics-engine-core";
|
|
5
5
|
import { AnalyticsModel, AnalyticsResolvers, typedefs } from "@powerhousedao/analytics-engine-graphql";
|
|
6
6
|
import { gql } from "graphql-tag";
|
|
@@ -8,13 +8,12 @@ import { GraphQLError, Kind, parse, print } from "graphql";
|
|
|
8
8
|
import { ConsoleLogger, childLogger, documentModelDocumentModelModule } from "document-model";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { match } from "path-to-regexp";
|
|
11
|
-
import { verifyAuthBearerToken } from "@renown/sdk";
|
|
12
11
|
import { buildSubgraphSchema } from "@apollo/subgraph";
|
|
13
12
|
import { typeDefs } from "@powerhousedao/document-engineering/graphql";
|
|
14
13
|
import { camelCase, kebabCase, pascalCase } from "change-case";
|
|
15
14
|
import { GraphQLJSONObject } from "graphql-type-json";
|
|
16
15
|
import { setName } from "@powerhousedao/shared/document-model";
|
|
17
|
-
import { DEFAULT_DRIVE_CONTAINER_TYPES, PropagationMode as PropagationMode$1, consolidateSyncOperations, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl } from "@powerhousedao/reactor";
|
|
16
|
+
import { DEFAULT_DRIVE_CONTAINER_TYPES, DriveCollectionId, PropagationMode as PropagationMode$1, consolidateSyncOperations, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl } from "@powerhousedao/reactor";
|
|
18
17
|
import * as z$1 from "zod";
|
|
19
18
|
import { z } from "zod";
|
|
20
19
|
import { createHandler } from "graphql-sse/lib/use/fetch";
|
|
@@ -35,6 +34,7 @@ import { tmpdir } from "node:os";
|
|
|
35
34
|
import { WebSocketServer } from "ws";
|
|
36
35
|
import { createRelationalDb } from "@powerhousedao/shared/processors";
|
|
37
36
|
import { Kysely, Migrator, sql } from "kysely";
|
|
37
|
+
import { verifyAuthBearerToken } from "@renown/sdk";
|
|
38
38
|
import { PGlite } from "@electric-sql/pglite";
|
|
39
39
|
import { AtomicNodeFs } from "@powerhousedao/pglite-fs";
|
|
40
40
|
import knex from "knex";
|
|
@@ -85,18 +85,14 @@ async function documentAccess(service, args) {
|
|
|
85
85
|
async function userDocumentPermissions(service, userAddress) {
|
|
86
86
|
return service.getUserDocuments(userAddress);
|
|
87
87
|
}
|
|
88
|
-
async function grantDocumentPermission(service, args, grantedByAddress
|
|
88
|
+
async function grantDocumentPermission(service, authorizationService, args, grantedByAddress) {
|
|
89
89
|
if (!grantedByAddress) throw new GraphQLError("Authentication required");
|
|
90
|
-
if (!
|
|
91
|
-
if (!await service.canManageDocument(args.documentId, grantedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to grant permissions");
|
|
92
|
-
}
|
|
90
|
+
if (!await authorizationService.canManage(args.documentId, grantedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to grant permissions");
|
|
93
91
|
return service.grantPermission(args.documentId, args.userAddress, args.permission, grantedByAddress);
|
|
94
92
|
}
|
|
95
|
-
async function revokeDocumentPermission(service, args, revokedByAddress
|
|
93
|
+
async function revokeDocumentPermission(service, authorizationService, args, revokedByAddress) {
|
|
96
94
|
if (!revokedByAddress) throw new GraphQLError("Authentication required");
|
|
97
|
-
if (!
|
|
98
|
-
if (!await service.canManageDocument(args.documentId, revokedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to revoke permissions");
|
|
99
|
-
}
|
|
95
|
+
if (!await authorizationService.canManage(args.documentId, revokedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to revoke permissions");
|
|
100
96
|
await service.revokePermission(args.documentId, args.userAddress);
|
|
101
97
|
return true;
|
|
102
98
|
}
|
|
@@ -127,18 +123,14 @@ async function removeUserFromGroup(service, args) {
|
|
|
127
123
|
async function getGroupMembers(service, groupId) {
|
|
128
124
|
return service.getGroupMembers(groupId);
|
|
129
125
|
}
|
|
130
|
-
async function grantGroupPermission(service, args, grantedByAddress
|
|
126
|
+
async function grantGroupPermission(service, authorizationService, args, grantedByAddress) {
|
|
131
127
|
if (!grantedByAddress) throw new GraphQLError("Authentication required");
|
|
132
|
-
if (!
|
|
133
|
-
if (!await service.canManageDocument(args.documentId, grantedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to grant permissions");
|
|
134
|
-
}
|
|
128
|
+
if (!await authorizationService.canManage(args.documentId, grantedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to grant permissions");
|
|
135
129
|
return service.grantGroupPermission(args.documentId, args.groupId, args.permission, grantedByAddress);
|
|
136
130
|
}
|
|
137
|
-
async function revokeGroupPermission(service, args, revokedByAddress
|
|
131
|
+
async function revokeGroupPermission(service, authorizationService, args, revokedByAddress) {
|
|
138
132
|
if (!revokedByAddress) throw new GraphQLError("Authentication required");
|
|
139
|
-
if (!
|
|
140
|
-
if (!await service.canManageDocument(args.documentId, revokedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to revoke permissions");
|
|
141
|
-
}
|
|
133
|
+
if (!await authorizationService.canManage(args.documentId, revokedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to revoke permissions");
|
|
142
134
|
await service.revokeGroupPermission(args.documentId, args.groupId);
|
|
143
135
|
return true;
|
|
144
136
|
}
|
|
@@ -152,59 +144,43 @@ async function operationPermissions(service, args) {
|
|
|
152
144
|
groupPermissions
|
|
153
145
|
};
|
|
154
146
|
}
|
|
155
|
-
async function canExecuteOperation(
|
|
156
|
-
return
|
|
147
|
+
async function canExecuteOperation(authorizationService, args, userAddress) {
|
|
148
|
+
return authorizationService.canMutate(args.documentId, args.operationType, userAddress);
|
|
157
149
|
}
|
|
158
|
-
async function grantOperationPermission(service, args, grantedByAddress
|
|
150
|
+
async function grantOperationPermission(service, authorizationService, args, grantedByAddress) {
|
|
159
151
|
if (!grantedByAddress) throw new GraphQLError("Authentication required");
|
|
160
|
-
if (!
|
|
161
|
-
if (!await service.canManageDocument(args.documentId, grantedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to grant operation permissions");
|
|
162
|
-
}
|
|
152
|
+
if (!await authorizationService.canManage(args.documentId, grantedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to grant operation permissions");
|
|
163
153
|
return service.grantOperationPermission(args.documentId, args.operationType, args.userAddress, grantedByAddress);
|
|
164
154
|
}
|
|
165
|
-
async function revokeOperationPermission(service, args, revokedByAddress
|
|
155
|
+
async function revokeOperationPermission(service, authorizationService, args, revokedByAddress) {
|
|
166
156
|
if (!revokedByAddress) throw new GraphQLError("Authentication required");
|
|
167
|
-
if (!
|
|
168
|
-
if (!await service.canManageDocument(args.documentId, revokedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to revoke operation permissions");
|
|
169
|
-
}
|
|
157
|
+
if (!await authorizationService.canManage(args.documentId, revokedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to revoke operation permissions");
|
|
170
158
|
await service.revokeOperationPermission(args.documentId, args.operationType, args.userAddress);
|
|
171
159
|
return true;
|
|
172
160
|
}
|
|
173
|
-
async function grantGroupOperationPermission(service, args, grantedByAddress
|
|
161
|
+
async function grantGroupOperationPermission(service, authorizationService, args, grantedByAddress) {
|
|
174
162
|
if (!grantedByAddress) throw new GraphQLError("Authentication required");
|
|
175
|
-
if (!
|
|
176
|
-
if (!await service.canManageDocument(args.documentId, grantedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to grant operation permissions");
|
|
177
|
-
}
|
|
163
|
+
if (!await authorizationService.canManage(args.documentId, grantedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to grant operation permissions");
|
|
178
164
|
return service.grantGroupOperationPermission(args.documentId, args.operationType, args.groupId, grantedByAddress);
|
|
179
165
|
}
|
|
180
|
-
async function revokeGroupOperationPermission(service, args, revokedByAddress
|
|
166
|
+
async function revokeGroupOperationPermission(service, authorizationService, args, revokedByAddress) {
|
|
181
167
|
if (!revokedByAddress) throw new GraphQLError("Authentication required");
|
|
182
|
-
if (!
|
|
183
|
-
if (!await service.canManageDocument(args.documentId, revokedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to revoke operation permissions");
|
|
184
|
-
}
|
|
168
|
+
if (!await authorizationService.canManage(args.documentId, revokedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to revoke operation permissions");
|
|
185
169
|
await service.revokeGroupOperationPermission(args.documentId, args.operationType, args.groupId);
|
|
186
170
|
return true;
|
|
187
171
|
}
|
|
188
172
|
async function documentProtection(service, args) {
|
|
189
173
|
return service.getDocumentProtection(args.documentId);
|
|
190
174
|
}
|
|
191
|
-
async function setDocumentProtection(service, authorizationService, args, userAddress
|
|
175
|
+
async function setDocumentProtection(service, authorizationService, args, userAddress) {
|
|
192
176
|
if (!userAddress) throw new GraphQLError("Authentication required");
|
|
193
|
-
if (!
|
|
194
|
-
if (authorizationService) {
|
|
195
|
-
if (!await authorizationService.canManage(args.documentId, userAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to change protection");
|
|
196
|
-
} else if (!await service.canManageDocument(args.documentId, userAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to change protection");
|
|
197
|
-
}
|
|
177
|
+
if (!await authorizationService.canManage(args.documentId, userAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to change protection");
|
|
198
178
|
await service.setDocumentProtection(args.documentId, args.protected);
|
|
199
179
|
return service.getDocumentProtection(args.documentId);
|
|
200
180
|
}
|
|
201
|
-
async function transferDocumentOwnership(service, authorizationService, args, userAddress
|
|
181
|
+
async function transferDocumentOwnership(service, authorizationService, args, userAddress) {
|
|
202
182
|
if (!userAddress) throw new GraphQLError("Authentication required");
|
|
203
|
-
if (!
|
|
204
|
-
if (authorizationService) {
|
|
205
|
-
if (!await authorizationService.canManage(args.documentId, userAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to transfer ownership");
|
|
206
|
-
} else if (!await service.canManageDocument(args.documentId, userAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to transfer ownership");
|
|
207
|
-
}
|
|
183
|
+
if (!await authorizationService.canManage(args.documentId, userAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to transfer ownership");
|
|
208
184
|
const previousOwner = await service.getDocumentOwner(args.documentId);
|
|
209
185
|
if (previousOwner) await service.revokePermission(args.documentId, previousOwner);
|
|
210
186
|
await service.setDocumentOwner(args.documentId, args.newOwnerAddress);
|
|
@@ -297,9 +273,8 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
297
273
|
},
|
|
298
274
|
canExecuteOperation: async (_parent, args, ctx) => {
|
|
299
275
|
this.logger.debug("canExecuteOperation(@args)", args);
|
|
300
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
301
276
|
try {
|
|
302
|
-
return await canExecuteOperation(this.
|
|
277
|
+
return await canExecuteOperation(this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
303
278
|
} catch (error) {
|
|
304
279
|
this.logger.error("Error in canExecuteOperation: @error", error);
|
|
305
280
|
throw error;
|
|
@@ -322,8 +297,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
322
297
|
this.logger.debug("setDocumentProtection(@args)", args);
|
|
323
298
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
324
299
|
try {
|
|
325
|
-
|
|
326
|
-
return await setDocumentProtection(this.documentPermissionService, this.authorizationService, args, ctx.user?.address, isGlobalAdmin);
|
|
300
|
+
return await setDocumentProtection(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
327
301
|
} catch (error) {
|
|
328
302
|
this.logger.error("Error in setDocumentProtection: @error", error);
|
|
329
303
|
throw error;
|
|
@@ -333,8 +307,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
333
307
|
this.logger.debug("transferDocumentOwnership(@args)", args);
|
|
334
308
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
335
309
|
try {
|
|
336
|
-
|
|
337
|
-
return await transferDocumentOwnership(this.documentPermissionService, this.authorizationService, args, ctx.user?.address, isGlobalAdmin);
|
|
310
|
+
return await transferDocumentOwnership(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
338
311
|
} catch (error) {
|
|
339
312
|
this.logger.error("Error in transferDocumentOwnership: @error", error);
|
|
340
313
|
throw error;
|
|
@@ -344,8 +317,11 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
344
317
|
this.logger.debug("grantDocumentPermission(@args)", args);
|
|
345
318
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
346
319
|
try {
|
|
347
|
-
const
|
|
348
|
-
return await grantDocumentPermission(this.documentPermissionService,
|
|
320
|
+
const resolved = await this.withCanonicalDocumentId(args, ctx);
|
|
321
|
+
return await grantDocumentPermission(this.documentPermissionService, this.authorizationService, {
|
|
322
|
+
...resolved,
|
|
323
|
+
permission: resolved.permission
|
|
324
|
+
}, ctx.user?.address);
|
|
349
325
|
} catch (error) {
|
|
350
326
|
this.logger.error("Error in grantDocumentPermission: @error", error);
|
|
351
327
|
throw error;
|
|
@@ -355,8 +331,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
355
331
|
this.logger.debug("revokeDocumentPermission(@args)", args);
|
|
356
332
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
357
333
|
try {
|
|
358
|
-
|
|
359
|
-
return await revokeDocumentPermission(this.documentPermissionService, args, ctx.user?.address, isGlobalAdmin);
|
|
334
|
+
return await revokeDocumentPermission(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
360
335
|
} catch (error) {
|
|
361
336
|
this.logger.error("Error in revokeDocumentPermission: @error", error);
|
|
362
337
|
throw error;
|
|
@@ -406,8 +381,11 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
406
381
|
this.logger.debug("grantGroupPermission(@args)", args);
|
|
407
382
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
408
383
|
try {
|
|
409
|
-
const
|
|
410
|
-
return await grantGroupPermission(this.documentPermissionService,
|
|
384
|
+
const resolved = await this.withCanonicalDocumentId(args, ctx);
|
|
385
|
+
return await grantGroupPermission(this.documentPermissionService, this.authorizationService, {
|
|
386
|
+
...resolved,
|
|
387
|
+
permission: resolved.permission
|
|
388
|
+
}, ctx.user?.address);
|
|
411
389
|
} catch (error) {
|
|
412
390
|
this.logger.error("Error in grantGroupPermission: @error", error);
|
|
413
391
|
throw error;
|
|
@@ -417,8 +395,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
417
395
|
this.logger.debug("revokeGroupPermission(@args)", args);
|
|
418
396
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
419
397
|
try {
|
|
420
|
-
|
|
421
|
-
return await revokeGroupPermission(this.documentPermissionService, args, ctx.user?.address, isGlobalAdmin);
|
|
398
|
+
return await revokeGroupPermission(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
422
399
|
} catch (error) {
|
|
423
400
|
this.logger.error("Error in revokeGroupPermission: @error", error);
|
|
424
401
|
throw error;
|
|
@@ -428,8 +405,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
428
405
|
this.logger.debug("grantOperationPermission(@args)", args);
|
|
429
406
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
430
407
|
try {
|
|
431
|
-
|
|
432
|
-
return await grantOperationPermission(this.documentPermissionService, args, ctx.user?.address, isGlobalAdmin);
|
|
408
|
+
return await grantOperationPermission(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
433
409
|
} catch (error) {
|
|
434
410
|
this.logger.error("Error in grantOperationPermission: @error", error);
|
|
435
411
|
throw error;
|
|
@@ -439,8 +415,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
439
415
|
this.logger.debug("revokeOperationPermission(@args)", args);
|
|
440
416
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
441
417
|
try {
|
|
442
|
-
|
|
443
|
-
return await revokeOperationPermission(this.documentPermissionService, args, ctx.user?.address, isGlobalAdmin);
|
|
418
|
+
return await revokeOperationPermission(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
444
419
|
} catch (error) {
|
|
445
420
|
this.logger.error("Error in revokeOperationPermission: @error", error);
|
|
446
421
|
throw error;
|
|
@@ -450,8 +425,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
450
425
|
this.logger.debug("grantGroupOperationPermission(@args)", args);
|
|
451
426
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
452
427
|
try {
|
|
453
|
-
|
|
454
|
-
return await grantGroupOperationPermission(this.documentPermissionService, args, ctx.user?.address, isGlobalAdmin);
|
|
428
|
+
return await grantGroupOperationPermission(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
455
429
|
} catch (error) {
|
|
456
430
|
this.logger.error("Error in grantGroupOperationPermission: @error", error);
|
|
457
431
|
throw error;
|
|
@@ -461,8 +435,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
461
435
|
this.logger.debug("revokeGroupOperationPermission(@args)", args);
|
|
462
436
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
463
437
|
try {
|
|
464
|
-
|
|
465
|
-
return await revokeGroupOperationPermission(this.documentPermissionService, args, ctx.user?.address, isGlobalAdmin);
|
|
438
|
+
return await revokeGroupOperationPermission(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
466
439
|
} catch (error) {
|
|
467
440
|
this.logger.error("Error in revokeGroupOperationPermission: @error", error);
|
|
468
441
|
throw error;
|
|
@@ -533,133 +506,6 @@ async function createHttpAdapter(type) {
|
|
|
533
506
|
}
|
|
534
507
|
}
|
|
535
508
|
//#endregion
|
|
536
|
-
//#region src/services/auth.service.ts
|
|
537
|
-
var AuthService = class {
|
|
538
|
-
config;
|
|
539
|
-
constructor(config) {
|
|
540
|
-
this.config = config;
|
|
541
|
-
}
|
|
542
|
-
async authenticateRequest(request) {
|
|
543
|
-
if (!this.config.enabled) return {
|
|
544
|
-
user: void 0,
|
|
545
|
-
admins: [],
|
|
546
|
-
auth_enabled: false
|
|
547
|
-
};
|
|
548
|
-
const method = request.method;
|
|
549
|
-
if (method === "OPTIONS" || method === "GET") return {
|
|
550
|
-
user: void 0,
|
|
551
|
-
admins: this.config.admins,
|
|
552
|
-
auth_enabled: true
|
|
553
|
-
};
|
|
554
|
-
return this.verifyBearer(request.headers.get("authorization") ?? void 0);
|
|
555
|
-
}
|
|
556
|
-
/**
|
|
557
|
-
* Verify a Bearer token regardless of HTTP method. Use this from non-GraphQL
|
|
558
|
-
* middleware that must enforce authentication on every request.
|
|
559
|
-
*/
|
|
560
|
-
async verifyBearer(authorization) {
|
|
561
|
-
if (!this.config.enabled) return {
|
|
562
|
-
user: void 0,
|
|
563
|
-
admins: [],
|
|
564
|
-
auth_enabled: false
|
|
565
|
-
};
|
|
566
|
-
const token = authorization?.split(" ")[1];
|
|
567
|
-
if (!token) return {
|
|
568
|
-
user: void 0,
|
|
569
|
-
admins: this.config.admins,
|
|
570
|
-
auth_enabled: true
|
|
571
|
-
};
|
|
572
|
-
try {
|
|
573
|
-
const verified = await this.verifyToken(token);
|
|
574
|
-
if (!verified) return new Response(JSON.stringify({ error: "Verification failed" }), { status: 401 });
|
|
575
|
-
const user = this.extractUserFromVerification(verified);
|
|
576
|
-
if (!user) return new Response(JSON.stringify({ error: "Missing credentials" }), { status: 401 });
|
|
577
|
-
if (!this.config.skipCredentialVerification) {
|
|
578
|
-
if (!await this.verifyCredentialExists(user.address, user.chainId, verified.issuer)) return new Response(JSON.stringify({ error: "Credentials no longer valid" }), { status: 401 });
|
|
579
|
-
}
|
|
580
|
-
return {
|
|
581
|
-
user,
|
|
582
|
-
admins: this.config.admins,
|
|
583
|
-
auth_enabled: true
|
|
584
|
-
};
|
|
585
|
-
} catch {
|
|
586
|
-
return new Response(JSON.stringify({ error: "Authentication failed" }), { status: 401 });
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
async authenticateWebSocketConnection(connectionParams) {
|
|
590
|
-
if (!this.config.enabled) return null;
|
|
591
|
-
const authHeader = connectionParams.authorization;
|
|
592
|
-
if (!authHeader) throw new Error("Missing authorization in connection parameters");
|
|
593
|
-
const token = authHeader.split(" ")[1];
|
|
594
|
-
if (!token) throw new Error("Invalid authorization format");
|
|
595
|
-
const verified = await this.verifyToken(token);
|
|
596
|
-
if (!verified) throw new Error("Token verification failed");
|
|
597
|
-
const user = this.extractUserFromVerification(verified);
|
|
598
|
-
if (!user) throw new Error("Invalid credentials");
|
|
599
|
-
if (!this.config.skipCredentialVerification) {
|
|
600
|
-
if (!await this.verifyCredentialExists(user.address, user.chainId, verified.issuer)) throw new Error("Credentials no longer valid");
|
|
601
|
-
}
|
|
602
|
-
return user;
|
|
603
|
-
}
|
|
604
|
-
/**
|
|
605
|
-
* Verify the auth bearer token
|
|
606
|
-
*/
|
|
607
|
-
async verifyToken(token) {
|
|
608
|
-
return await verifyAuthBearerToken(token);
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Extract user information from verification result
|
|
612
|
-
*/
|
|
613
|
-
extractUserFromVerification(verified) {
|
|
614
|
-
try {
|
|
615
|
-
const { address, chainId, networkId } = verified.verifiableCredential.credentialSubject;
|
|
616
|
-
if (!address || !chainId || !networkId) return null;
|
|
617
|
-
return {
|
|
618
|
-
address,
|
|
619
|
-
chainId,
|
|
620
|
-
networkId
|
|
621
|
-
};
|
|
622
|
-
} catch {
|
|
623
|
-
return null;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
/**
|
|
627
|
-
* Get additional context fields for GraphQL
|
|
628
|
-
*/
|
|
629
|
-
getAdditionalContextFields() {
|
|
630
|
-
if (!this.config.enabled) return { isAdmin: () => true };
|
|
631
|
-
return { isAdmin: (address) => this.config.enabled && this.config.admins?.includes(address.toLowerCase()) };
|
|
632
|
-
}
|
|
633
|
-
/**
|
|
634
|
-
* Get user context for GraphQL
|
|
635
|
-
*/
|
|
636
|
-
getUserContext(user) {
|
|
637
|
-
if (!user) return {};
|
|
638
|
-
return { user: {
|
|
639
|
-
address: user.address.toLowerCase(),
|
|
640
|
-
chainId: user.chainId,
|
|
641
|
-
networkId: user.networkId
|
|
642
|
-
} };
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* Verify that the credential still exists on the Renown API
|
|
646
|
-
*/
|
|
647
|
-
async verifyCredentialExists(address, chainId, appId) {
|
|
648
|
-
const url = `https://www.renown.id/api/auth/credential?address=${address}&chainId=${chainId}&connectId=${appId}&appId=${appId}`;
|
|
649
|
-
try {
|
|
650
|
-
const response = await fetch(url, { method: "GET" });
|
|
651
|
-
const credential = (await response.json()).credential;
|
|
652
|
-
const appIdVerfied = credential.credentialSubject.id;
|
|
653
|
-
const addressVerfied = credential.issuer.id.split(":")[4];
|
|
654
|
-
const chainIdVerfied = credential.issuer.id.split(":")[3];
|
|
655
|
-
if (response.status !== 200) return false;
|
|
656
|
-
return appIdVerfied === appId && addressVerfied.toLocaleLowerCase() === address.toLocaleLowerCase() && chainIdVerfied === chainId.toString();
|
|
657
|
-
} catch {
|
|
658
|
-
return false;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
};
|
|
662
|
-
//#endregion
|
|
663
509
|
//#region src/utils/create-schema.ts
|
|
664
510
|
const logger = childLogger(["reactor-api", "create-schema"]);
|
|
665
511
|
/**
|
|
@@ -1246,7 +1092,6 @@ function ActionContextInputSchema() {
|
|
|
1246
1092
|
}
|
|
1247
1093
|
function ActionInputSchema() {
|
|
1248
1094
|
return z$1.object({
|
|
1249
|
-
attachments: z$1.array(z$1.lazy(() => AttachmentInputSchema())).nullish(),
|
|
1250
1095
|
context: z$1.lazy(() => ActionContextInputSchema().nullish()),
|
|
1251
1096
|
id: z$1.string(),
|
|
1252
1097
|
input: z$1.custom((v) => v != null),
|
|
@@ -1255,15 +1100,6 @@ function ActionInputSchema() {
|
|
|
1255
1100
|
type: z$1.string()
|
|
1256
1101
|
});
|
|
1257
1102
|
}
|
|
1258
|
-
function AttachmentInputSchema() {
|
|
1259
|
-
return z$1.object({
|
|
1260
|
-
data: z$1.string(),
|
|
1261
|
-
extension: z$1.string().nullish(),
|
|
1262
|
-
fileName: z$1.string().nullish(),
|
|
1263
|
-
hash: z$1.string(),
|
|
1264
|
-
mimeType: z$1.string()
|
|
1265
|
-
});
|
|
1266
|
-
}
|
|
1267
1103
|
function ChannelMetaInputSchema() {
|
|
1268
1104
|
return z$1.object({ id: z$1.string() });
|
|
1269
1105
|
}
|
|
@@ -1454,13 +1290,6 @@ const GetDocumentWithOperationsDocument = gql`
|
|
|
1454
1290
|
timestampUtcMs
|
|
1455
1291
|
input
|
|
1456
1292
|
scope
|
|
1457
|
-
attachments {
|
|
1458
|
-
data
|
|
1459
|
-
mimeType
|
|
1460
|
-
hash
|
|
1461
|
-
extension
|
|
1462
|
-
fileName
|
|
1463
|
-
}
|
|
1464
1293
|
context {
|
|
1465
1294
|
signer {
|
|
1466
1295
|
user {
|
|
@@ -1573,13 +1402,6 @@ const GetDocumentOperationsDocument = gql`
|
|
|
1573
1402
|
timestampUtcMs
|
|
1574
1403
|
input
|
|
1575
1404
|
scope
|
|
1576
|
-
attachments {
|
|
1577
|
-
data
|
|
1578
|
-
mimeType
|
|
1579
|
-
hash
|
|
1580
|
-
extension
|
|
1581
|
-
fileName
|
|
1582
|
-
}
|
|
1583
1405
|
context {
|
|
1584
1406
|
signer {
|
|
1585
1407
|
user {
|
|
@@ -1830,13 +1652,6 @@ const PollSyncEnvelopesDocument = gql`
|
|
|
1830
1652
|
timestampUtcMs
|
|
1831
1653
|
input
|
|
1832
1654
|
scope
|
|
1833
|
-
attachments {
|
|
1834
|
-
data
|
|
1835
|
-
mimeType
|
|
1836
|
-
hash
|
|
1837
|
-
extension
|
|
1838
|
-
fileName
|
|
1839
|
-
}
|
|
1840
1655
|
context {
|
|
1841
1656
|
signer {
|
|
1842
1657
|
user {
|
|
@@ -2153,6 +1968,17 @@ function toGqlDocumentChangeEvent(event) {
|
|
|
2153
1968
|
} : null
|
|
2154
1969
|
};
|
|
2155
1970
|
}
|
|
1971
|
+
/**
|
|
1972
|
+
* Distinct document ids a change event references: affected documents plus the
|
|
1973
|
+
* parent/child ids in `context` (delete/relationship events carry only context).
|
|
1974
|
+
*/
|
|
1975
|
+
function collectChangedDocumentIds(event) {
|
|
1976
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1977
|
+
for (const doc of event.documents) if (doc.header.id) ids.add(doc.header.id);
|
|
1978
|
+
if (event.context?.parentId) ids.add(event.context.parentId);
|
|
1979
|
+
if (event.context?.childId) ids.add(event.context.childId);
|
|
1980
|
+
return [...ids];
|
|
1981
|
+
}
|
|
2156
1982
|
function matchesSearchFilter(event, search) {
|
|
2157
1983
|
if (search.type) {
|
|
2158
1984
|
if (!event.documents.some((doc) => doc.header.documentType === search.type)) return false;
|
|
@@ -2624,7 +2450,7 @@ async function touchChannel(syncManager, args) {
|
|
|
2624
2450
|
};
|
|
2625
2451
|
const options = { sinceTimestampUtcMs: args.input.sinceTimestampUtcMs };
|
|
2626
2452
|
try {
|
|
2627
|
-
await syncManager.add(args.input.name, args.input.collectionId, {
|
|
2453
|
+
await syncManager.add(args.input.name, DriveCollectionId.fromKey(args.input.collectionId), {
|
|
2628
2454
|
type: "polling",
|
|
2629
2455
|
parameters: {}
|
|
2630
2456
|
}, filter, options, args.input.id);
|
|
@@ -2647,14 +2473,14 @@ async function touchChannel(syncManager, args) {
|
|
|
2647
2473
|
* client ordinal the switchboard has successfully applied, so the client
|
|
2648
2474
|
* can trim its own outbox)
|
|
2649
2475
|
*/
|
|
2650
|
-
function pollSyncEnvelopes(syncManager, args) {
|
|
2476
|
+
function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new Set()) {
|
|
2651
2477
|
let remote;
|
|
2652
2478
|
try {
|
|
2653
2479
|
remote = syncManager.getById(args.channelId);
|
|
2654
2480
|
} catch (error) {
|
|
2655
2481
|
throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2656
2482
|
}
|
|
2657
|
-
const deadLetters = remote.channel.deadLetter.items.map((syncOp) => ({
|
|
2483
|
+
const deadLetters = remote.channel.deadLetter.items.filter((syncOp) => !forbiddenIds.has(syncOp.documentId)).map((syncOp) => ({
|
|
2658
2484
|
documentId: syncOp.documentId,
|
|
2659
2485
|
error: syncOp.error?.message ?? "Unknown error",
|
|
2660
2486
|
jobId: syncOp.jobId,
|
|
@@ -2678,6 +2504,12 @@ function pollSyncEnvelopes(syncManager, args) {
|
|
|
2678
2504
|
}
|
|
2679
2505
|
}
|
|
2680
2506
|
const operations = remote.channel.outbox.items;
|
|
2507
|
+
if (forbiddenIds.size > 0) {
|
|
2508
|
+
for (const syncOp of operations) if (forbiddenIds.has(syncOp.documentId)) {
|
|
2509
|
+
syncOp.deliveredCount = syncOp.operations.length;
|
|
2510
|
+
syncOp.emittedCount = syncOp.operations.length;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2681
2513
|
if (operations.length === 0) return {
|
|
2682
2514
|
envelopes: [],
|
|
2683
2515
|
ackOrdinal: remote.channel.inbox.ackOrdinal,
|
|
@@ -2869,7 +2701,7 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
2869
2701
|
view
|
|
2870
2702
|
});
|
|
2871
2703
|
if (result.document.documentType !== documentType) throw new GraphQLError(`Document with id ${identifier} is not of type ${documentType}`);
|
|
2872
|
-
await this.
|
|
2704
|
+
await this.assertCanReadCanonical(result.document.id, ctx);
|
|
2873
2705
|
return result;
|
|
2874
2706
|
},
|
|
2875
2707
|
documents: async (_, args, ctx) => {
|
|
@@ -2878,7 +2710,7 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
2878
2710
|
search: { type: documentType },
|
|
2879
2711
|
paging
|
|
2880
2712
|
});
|
|
2881
|
-
if (!this.
|
|
2713
|
+
if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
|
|
2882
2714
|
const filteredItems = [];
|
|
2883
2715
|
for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
|
|
2884
2716
|
return {
|
|
@@ -2899,7 +2731,7 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
2899
2731
|
view,
|
|
2900
2732
|
paging
|
|
2901
2733
|
});
|
|
2902
|
-
if (!this.
|
|
2734
|
+
if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
|
|
2903
2735
|
const filteredItems = [];
|
|
2904
2736
|
for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
|
|
2905
2737
|
return {
|
|
@@ -2911,10 +2743,10 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
2911
2743
|
return result;
|
|
2912
2744
|
},
|
|
2913
2745
|
documentOutgoingRelationships: async (_, args, ctx) => {
|
|
2914
|
-
const {
|
|
2915
|
-
await this.assertCanRead(sourceIdentifier, ctx);
|
|
2746
|
+
const { relationshipType, view, paging } = args;
|
|
2747
|
+
const handle = await this.assertCanRead(args.sourceIdentifier, ctx);
|
|
2916
2748
|
const result = await documentOutgoingRelationships(this.reactorClient, {
|
|
2917
|
-
sourceIdentifier,
|
|
2749
|
+
sourceIdentifier: handle.fetchIdentifier,
|
|
2918
2750
|
relationshipType,
|
|
2919
2751
|
view,
|
|
2920
2752
|
paging
|
|
@@ -2927,10 +2759,10 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
2927
2759
|
};
|
|
2928
2760
|
},
|
|
2929
2761
|
documentIncomingRelationships: async (_, args, ctx) => {
|
|
2930
|
-
const {
|
|
2931
|
-
await this.assertCanRead(targetIdentifier, ctx);
|
|
2762
|
+
const { relationshipType, view, paging } = args;
|
|
2763
|
+
const handle = await this.assertCanRead(args.targetIdentifier, ctx);
|
|
2932
2764
|
return documentIncomingRelationships(this.reactorClient, {
|
|
2933
|
-
targetIdentifier,
|
|
2765
|
+
targetIdentifier: handle.fetchIdentifier,
|
|
2934
2766
|
relationshipType,
|
|
2935
2767
|
view,
|
|
2936
2768
|
paging
|
|
@@ -2940,11 +2772,10 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
2940
2772
|
Mutation: { [documentName]: () => ({}) },
|
|
2941
2773
|
[`${documentName}Mutations`]: {
|
|
2942
2774
|
createDocument: async (_, args, ctx) => {
|
|
2943
|
-
const {
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
} else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
|
|
2775
|
+
const { name, slug, preferredEditor, initialState } = args;
|
|
2776
|
+
let parentIdentifier = args.parentIdentifier;
|
|
2777
|
+
if (parentIdentifier) parentIdentifier = (await this.assertCanWrite(parentIdentifier, ctx)).fetchIdentifier;
|
|
2778
|
+
else this.assertCanCreate(ctx);
|
|
2948
2779
|
let createdDoc;
|
|
2949
2780
|
if (initialState || preferredEditor) createdDoc = await createDocumentWithInitialState(this.reactorClient, {
|
|
2950
2781
|
documentType,
|
|
@@ -2959,46 +2790,42 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
2959
2790
|
parentIdentifier,
|
|
2960
2791
|
name
|
|
2961
2792
|
}, this.graphqlManager.reactorDriveClient);
|
|
2962
|
-
if (
|
|
2793
|
+
if (ctx.user?.address && createdDoc?.id) await this.documentPermissionService?.initializeDocumentProtection(createdDoc.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
2963
2794
|
if (!initialState && !preferredEditor && name && createdDoc.name !== name) return toGqlPhDocument(await this.reactorClient.execute(createdDoc.id, "main", [setName(name)]));
|
|
2964
2795
|
return createdDoc;
|
|
2965
2796
|
},
|
|
2966
2797
|
createEmptyDocument: async (_, args, ctx) => {
|
|
2967
|
-
|
|
2968
|
-
if (parentIdentifier) await this.assertCanWrite(parentIdentifier, ctx);
|
|
2969
|
-
else
|
|
2970
|
-
if (!ctx.user?.address) throw new GraphQLError("Forbidden: authentication required to create documents");
|
|
2971
|
-
} else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
|
|
2798
|
+
let parentIdentifier = args.parentIdentifier;
|
|
2799
|
+
if (parentIdentifier) parentIdentifier = (await this.assertCanWrite(parentIdentifier, ctx)).fetchIdentifier;
|
|
2800
|
+
else this.assertCanCreate(ctx);
|
|
2972
2801
|
const result = await createEmptyDocument(this.reactorClient, {
|
|
2973
2802
|
documentType,
|
|
2974
2803
|
parentIdentifier
|
|
2975
2804
|
}, this.graphqlManager.reactorDriveClient);
|
|
2976
|
-
if (
|
|
2805
|
+
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
2977
2806
|
return result;
|
|
2978
2807
|
},
|
|
2979
2808
|
...operations.reduce((mutations, op) => {
|
|
2980
2809
|
mutations[camelCase(op.name)] = async (_, args, ctx) => {
|
|
2981
2810
|
const { docId, input } = args;
|
|
2982
|
-
|
|
2983
|
-
await this.
|
|
2984
|
-
if ((await this.reactorClient.get(docId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
|
|
2811
|
+
const effectiveDocId = (await this.assertCanExecuteOperation(docId, op.name, ctx)).fetchIdentifier;
|
|
2812
|
+
if ((await this.reactorClient.get(effectiveDocId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
|
|
2985
2813
|
const action = this.documentModel.actions[camelCase(op.name)];
|
|
2986
2814
|
if (!action) throw new GraphQLError(`Action ${op.name} not found`);
|
|
2987
2815
|
try {
|
|
2988
|
-
return toGqlPhDocument(await this.reactorClient.execute(
|
|
2816
|
+
return toGqlPhDocument(await this.reactorClient.execute(effectiveDocId, "main", [action(input)]));
|
|
2989
2817
|
} catch (error) {
|
|
2990
2818
|
throw new GraphQLError(error instanceof Error ? error.message : `Failed to ${op.name}`);
|
|
2991
2819
|
}
|
|
2992
2820
|
};
|
|
2993
2821
|
mutations[`${camelCase(op.name)}Async`] = async (_, args, ctx) => {
|
|
2994
2822
|
const { docId, input } = args;
|
|
2995
|
-
|
|
2996
|
-
await this.
|
|
2997
|
-
if ((await this.reactorClient.get(docId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
|
|
2823
|
+
const effectiveDocId = (await this.assertCanExecuteOperation(docId, op.name, ctx)).fetchIdentifier;
|
|
2824
|
+
if ((await this.reactorClient.get(effectiveDocId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
|
|
2998
2825
|
const action = this.documentModel.actions[camelCase(op.name)];
|
|
2999
2826
|
if (!action) throw new GraphQLError(`Action ${op.name} not found`);
|
|
3000
2827
|
try {
|
|
3001
|
-
return (await this.reactorClient.executeAsync(
|
|
2828
|
+
return (await this.reactorClient.executeAsync(effectiveDocId, "main", [action(input)])).id;
|
|
3002
2829
|
} catch (error) {
|
|
3003
2830
|
throw new GraphQLError(error instanceof Error ? error.message : `Failed to ${op.name}`);
|
|
3004
2831
|
}
|
|
@@ -3177,7 +3004,6 @@ var GraphQLManager = class {
|
|
|
3177
3004
|
coreSubgraphsMap = /* @__PURE__ */ new Map();
|
|
3178
3005
|
contextFields = {};
|
|
3179
3006
|
subgraphs = /* @__PURE__ */ new Map();
|
|
3180
|
-
authService = null;
|
|
3181
3007
|
subgraphWsDisposers = /* @__PURE__ */ new Map();
|
|
3182
3008
|
#authMiddleware;
|
|
3183
3009
|
#driveMiddleware;
|
|
@@ -3191,7 +3017,8 @@ var GraphQLManager = class {
|
|
|
3191
3017
|
* it for reactor-drive parents.
|
|
3192
3018
|
*/
|
|
3193
3019
|
reactorDriveClient;
|
|
3194
|
-
|
|
3020
|
+
authorizationService;
|
|
3021
|
+
constructor(path, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, logger, httpAdapter, gatewayAdapter, authService, documentPermissionService, featureFlags = DefaultFeatureFlags, port = 4001, authorizationService, reactorDriveClient) {
|
|
3195
3022
|
this.path = path;
|
|
3196
3023
|
this.httpServer = httpServer;
|
|
3197
3024
|
this.wsServer = wsServer;
|
|
@@ -3202,13 +3029,13 @@ var GraphQLManager = class {
|
|
|
3202
3029
|
this.logger = logger;
|
|
3203
3030
|
this.httpAdapter = httpAdapter;
|
|
3204
3031
|
this.gatewayAdapter = gatewayAdapter;
|
|
3205
|
-
this.
|
|
3032
|
+
this.authService = authService;
|
|
3206
3033
|
this.documentPermissionService = documentPermissionService;
|
|
3207
3034
|
this.featureFlags = featureFlags;
|
|
3208
3035
|
this.port = port;
|
|
3036
|
+
if (!authorizationService) throw new Error("GraphQLManager requires an authorizationService");
|
|
3209
3037
|
this.authorizationService = authorizationService;
|
|
3210
3038
|
this.reactorDriveClient = reactorDriveClient;
|
|
3211
|
-
if (this.authConfig) this.authService = new AuthService(this.authConfig);
|
|
3212
3039
|
this.driveOwnershipCache = new DriveOwnershipCache(this.reactorClient);
|
|
3213
3040
|
this.wsServer.setMaxListeners(0);
|
|
3214
3041
|
}
|
|
@@ -3328,6 +3155,14 @@ var GraphQLManager = class {
|
|
|
3328
3155
|
getBasePath() {
|
|
3329
3156
|
return this.path;
|
|
3330
3157
|
}
|
|
3158
|
+
/**
|
|
3159
|
+
* Get the authorization service shared with subgraphs. Use this when
|
|
3160
|
+
* constructing a subgraph instance externally for
|
|
3161
|
+
* {@link registerSubgraphInstance}.
|
|
3162
|
+
*/
|
|
3163
|
+
getAuthorizationService() {
|
|
3164
|
+
return this.authorizationService;
|
|
3165
|
+
}
|
|
3331
3166
|
async registerSubgraph(subgraph, supergraph = "", core = false) {
|
|
3332
3167
|
const subgraphInstance = new subgraph({
|
|
3333
3168
|
relationalDb: this.relationalDb,
|
|
@@ -3387,8 +3222,7 @@ var GraphQLManager = class {
|
|
|
3387
3222
|
db: this.relationalDb,
|
|
3388
3223
|
...this.getAdditionalContextFields(),
|
|
3389
3224
|
driveId,
|
|
3390
|
-
user: authCtx?.user
|
|
3391
|
-
isAdmin: authCtx ? (addr) => !authCtx.auth_enabled ? true : authCtx.admins.includes(addr.toLowerCase()) : () => true
|
|
3225
|
+
user: authCtx?.user
|
|
3392
3226
|
});
|
|
3393
3227
|
};
|
|
3394
3228
|
}
|
|
@@ -3532,8 +3366,8 @@ var GraphQLManager = class {
|
|
|
3532
3366
|
};
|
|
3533
3367
|
//#endregion
|
|
3534
3368
|
//#region src/graphql/packages/resolvers.ts
|
|
3535
|
-
function requireAdmin(ctx) {
|
|
3536
|
-
if (!
|
|
3369
|
+
function requireAdmin(authorizationService, ctx) {
|
|
3370
|
+
if (!authorizationService.isSupremeAdmin(ctx.user?.address)) throw new GraphQLError("Admin access required");
|
|
3537
3371
|
}
|
|
3538
3372
|
function formatPackageInfo(info) {
|
|
3539
3373
|
return {
|
|
@@ -3551,16 +3385,16 @@ async function installedPackage(service, args) {
|
|
|
3551
3385
|
const pkg = await service.getInstalledPackage(args.name);
|
|
3552
3386
|
return pkg ? formatPackageInfo(pkg) : null;
|
|
3553
3387
|
}
|
|
3554
|
-
async function installPackage(service, args, ctx) {
|
|
3555
|
-
requireAdmin(ctx);
|
|
3388
|
+
async function installPackage(service, authorizationService, args, ctx) {
|
|
3389
|
+
requireAdmin(authorizationService, ctx);
|
|
3556
3390
|
const result = await service.installPackage(args.name, args.registryUrl ?? void 0);
|
|
3557
3391
|
return {
|
|
3558
3392
|
package: formatPackageInfo(result.package),
|
|
3559
3393
|
documentModelsLoaded: result.documentModelsLoaded
|
|
3560
3394
|
};
|
|
3561
3395
|
}
|
|
3562
|
-
async function uninstallPackage(service, args, ctx) {
|
|
3563
|
-
requireAdmin(ctx);
|
|
3396
|
+
async function uninstallPackage(service, authorizationService, args, ctx) {
|
|
3397
|
+
requireAdmin(authorizationService, ctx);
|
|
3564
3398
|
return service.uninstallPackage(args.name);
|
|
3565
3399
|
}
|
|
3566
3400
|
//#endregion
|
|
@@ -3606,7 +3440,7 @@ var PackagesSubgraph = class extends BaseSubgraph {
|
|
|
3606
3440
|
installPackage: async (_parent, args, ctx) => {
|
|
3607
3441
|
this.logger.debug("installPackage(@args)", args);
|
|
3608
3442
|
try {
|
|
3609
|
-
return await installPackage(this.packageManagementService, args, ctx);
|
|
3443
|
+
return await installPackage(this.packageManagementService, this.authorizationService, args, ctx);
|
|
3610
3444
|
} catch (error) {
|
|
3611
3445
|
this.logger.error("Error in installPackage: @error", error);
|
|
3612
3446
|
throw error;
|
|
@@ -3615,7 +3449,7 @@ var PackagesSubgraph = class extends BaseSubgraph {
|
|
|
3615
3449
|
uninstallPackage: async (_parent, args, ctx) => {
|
|
3616
3450
|
this.logger.debug("uninstallPackage(@args)", args);
|
|
3617
3451
|
try {
|
|
3618
|
-
return await uninstallPackage(this.packageManagementService, args, ctx);
|
|
3452
|
+
return await uninstallPackage(this.packageManagementService, this.authorizationService, args, ctx);
|
|
3619
3453
|
} catch (error) {
|
|
3620
3454
|
this.logger.error("Error in uninstallPackage: @error", error);
|
|
3621
3455
|
throw error;
|
|
@@ -3801,20 +3635,12 @@ const ActionSignerDTO = z.object({
|
|
|
3801
3635
|
app: ActionSignerAppDTO.nullable().optional()
|
|
3802
3636
|
}).strip();
|
|
3803
3637
|
const ActionContextDTO = z.object({ signer: ActionSignerDTO.nullable().optional() }).strip();
|
|
3804
|
-
const AttachmentDTO = z.object({
|
|
3805
|
-
data: z.string(),
|
|
3806
|
-
mimeType: z.string(),
|
|
3807
|
-
hash: z.string(),
|
|
3808
|
-
extension: z.string().nullable().optional(),
|
|
3809
|
-
fileName: z.string().nullable().optional()
|
|
3810
|
-
}).strip();
|
|
3811
3638
|
const OperationActionDTO = z.object({
|
|
3812
3639
|
id: z.string(),
|
|
3813
3640
|
type: z.string(),
|
|
3814
3641
|
timestampUtcMs: z.string(),
|
|
3815
3642
|
input: z.unknown(),
|
|
3816
3643
|
scope: z.string(),
|
|
3817
|
-
attachments: z.array(AttachmentDTO).nullable().optional(),
|
|
3818
3644
|
context: ActionContextDTO.nullable().optional()
|
|
3819
3645
|
}).strip();
|
|
3820
3646
|
const OperationDTO = z.object({
|
|
@@ -3941,7 +3767,7 @@ function createReactorGraphQLClient(url, fetchImpl = fetch, headers) {
|
|
|
3941
3767
|
}
|
|
3942
3768
|
//#endregion
|
|
3943
3769
|
//#region src/graphql/reactor/schema.graphql
|
|
3944
|
-
var schema_default = "# Scalar types (for codegen - also defined in create-schema.ts)\nscalar JSONObject\nscalar DateTime\n\n# Input types\ninput PagingInput {\n limit: Int\n offset: Int\n cursor: String\n}\n\ninput ViewFilterInput {\n branch: String\n scopes: [String!]\n}\n\ninput SearchFilterInput {\n type: String\n parentId: String\n identifiers: [String!]\n}\n\ninput OperationsFilterInput {\n documentId: String!\n branch: String\n scopes: [String!]\n actionTypes: [String!]\n sinceRevision: Int\n timestampFrom: String\n timestampTo: String\n}\n\ninput DocumentOperationsFilterInput {\n branch: String\n scopes: [String!]\n actionTypes: [String!]\n sinceRevision: Int\n timestampFrom: String\n timestampTo: String\n}\n\n# Enums\nenum PropagationMode {\n CASCADE\n ORPHAN\n}\n\nenum DocumentChangeType {\n CREATED\n DELETED\n UPDATED\n PARENT_ADDED\n PARENT_REMOVED\n CHILD_ADDED\n CHILD_REMOVED\n}\n\n# Object types\ntype DocumentModelGlobalState {\n id: String!\n name: String!\n namespace: String\n version: String\n specification: JSONObject!\n}\n\ntype DocumentModelResultPage {\n items: [DocumentModelGlobalState!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype Revision {\n scope: String!\n revision: Int!\n}\n\ntype PHDocument {\n id: String!\n slug: String\n preferredEditor: String\n name: String!\n documentType: String!\n state: JSONObject!\n revisionsList: [Revision!]!\n createdAtUtcIso: DateTime!\n lastModifiedAtUtcIso: DateTime!\n operations(\n filter: DocumentOperationsFilterInput\n paging: PagingInput\n ): ReactorOperationResultPage\n}\n\ntype PHDocumentResultPage {\n items: [PHDocument!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype ReactorOperationResultPage {\n items: [ReactorOperation!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype DeadLetterInfo {\n documentId: String!\n error: String!\n jobId: String!\n branch: String!\n scopes: [String!]!\n operationCount: Int!\n}\n\ntype PollSyncEnvelopesResult {\n envelopes: [SyncEnvelope!]!\n ackOrdinal: Int!\n deadLetters: [DeadLetterInfo!]!\n hasMore: Boolean!\n}\n\ntype DocumentWithChildren {\n document: PHDocument!\n childIds: [String!]!\n}\n\ntype MoveRelationshipResult {\n source: PHDocument!\n target: PHDocument!\n}\n\ntype JobInfo {\n id: String!\n status: String!\n result: JSONObject!\n error: String\n createdAt: DateTime!\n completedAt: DateTime\n}\n\ntype DocumentChangeEvent {\n type: DocumentChangeType!\n documents: [PHDocument!]!\n context: DocumentChangeContext\n}\n\ntype DocumentChangeContext {\n parentId: String\n childId: String\n}\n\ntype JobChangeEvent {\n jobId: String!\n status: String!\n result: JSONObject!\n error: String\n}\n\ntype ReactorSignerUser {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ntype ReactorSignerApp {\n name: String!\n key: String!\n}\n\ntype ReactorSigner {\n user: ReactorSignerUser\n app: ReactorSignerApp\n signatures: [String!]!\n}\n\ntype ActionContext {\n signer: ReactorSigner\n}\n\ntype Action {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n
|
|
3770
|
+
var schema_default = "# Scalar types (for codegen - also defined in create-schema.ts)\nscalar JSONObject\nscalar DateTime\n\n# Input types\ninput PagingInput {\n limit: Int\n offset: Int\n cursor: String\n}\n\ninput ViewFilterInput {\n branch: String\n scopes: [String!]\n}\n\ninput SearchFilterInput {\n type: String\n parentId: String\n identifiers: [String!]\n}\n\ninput OperationsFilterInput {\n documentId: String!\n branch: String\n scopes: [String!]\n actionTypes: [String!]\n sinceRevision: Int\n timestampFrom: String\n timestampTo: String\n}\n\ninput DocumentOperationsFilterInput {\n branch: String\n scopes: [String!]\n actionTypes: [String!]\n sinceRevision: Int\n timestampFrom: String\n timestampTo: String\n}\n\n# Enums\nenum PropagationMode {\n CASCADE\n ORPHAN\n}\n\nenum DocumentChangeType {\n CREATED\n DELETED\n UPDATED\n PARENT_ADDED\n PARENT_REMOVED\n CHILD_ADDED\n CHILD_REMOVED\n}\n\n# Object types\ntype DocumentModelGlobalState {\n id: String!\n name: String!\n namespace: String\n version: String\n specification: JSONObject!\n}\n\ntype DocumentModelResultPage {\n items: [DocumentModelGlobalState!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype Revision {\n scope: String!\n revision: Int!\n}\n\ntype PHDocument {\n id: String!\n slug: String\n preferredEditor: String\n name: String!\n documentType: String!\n state: JSONObject!\n revisionsList: [Revision!]!\n createdAtUtcIso: DateTime!\n lastModifiedAtUtcIso: DateTime!\n operations(\n filter: DocumentOperationsFilterInput\n paging: PagingInput\n ): ReactorOperationResultPage\n}\n\ntype PHDocumentResultPage {\n items: [PHDocument!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype ReactorOperationResultPage {\n items: [ReactorOperation!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype DeadLetterInfo {\n documentId: String!\n error: String!\n jobId: String!\n branch: String!\n scopes: [String!]!\n operationCount: Int!\n}\n\ntype PollSyncEnvelopesResult {\n envelopes: [SyncEnvelope!]!\n ackOrdinal: Int!\n deadLetters: [DeadLetterInfo!]!\n hasMore: Boolean!\n}\n\ntype DocumentWithChildren {\n document: PHDocument!\n childIds: [String!]!\n}\n\ntype MoveRelationshipResult {\n source: PHDocument!\n target: PHDocument!\n}\n\ntype JobInfo {\n id: String!\n status: String!\n result: JSONObject!\n error: String\n createdAt: DateTime!\n completedAt: DateTime\n}\n\ntype DocumentChangeEvent {\n type: DocumentChangeType!\n documents: [PHDocument!]!\n context: DocumentChangeContext\n}\n\ntype DocumentChangeContext {\n parentId: String\n childId: String\n}\n\ntype JobChangeEvent {\n jobId: String!\n status: String!\n result: JSONObject!\n error: String\n}\n\ntype ReactorSignerUser {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ntype ReactorSignerApp {\n name: String!\n key: String!\n}\n\ntype ReactorSigner {\n user: ReactorSignerUser\n app: ReactorSignerApp\n signatures: [String!]!\n}\n\ntype ActionContext {\n signer: ReactorSigner\n}\n\ntype Action {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n context: ActionContext\n}\n\n# Input types for sync operations\ninput ActionContextInput {\n signer: ReactorSignerInput\n}\n\ninput ReactorSignerInput {\n user: ReactorSignerUserInput\n app: ReactorSignerAppInput\n signatures: [String!]!\n}\n\ninput ReactorSignerUserInput {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ninput ReactorSignerAppInput {\n name: String!\n key: String!\n}\n\ninput ActionInput {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n context: ActionContextInput\n}\n\n# Synchronization types\ntype ReactorOperation {\n index: Int!\n timestampUtcMs: String!\n hash: String!\n skip: Int!\n error: String\n id: String\n action: Action!\n}\n\ninput OperationInput {\n index: Int!\n timestampUtcMs: String!\n hash: String!\n skip: Int!\n error: String\n id: String\n action: ActionInput!\n}\n\ntype OperationContext {\n documentId: String!\n documentType: String!\n scope: String!\n branch: String!\n ordinal: Int!\n}\n\ninput OperationContextInput {\n documentId: String!\n documentType: String!\n scope: String!\n branch: String!\n ordinal: Int!\n}\n\ntype OperationWithContext {\n operation: ReactorOperation!\n context: OperationContext!\n}\n\ninput OperationWithContextInput {\n operation: OperationInput!\n context: OperationContextInput!\n}\n\ntype ChannelMeta {\n id: String!\n}\n\ninput ChannelMetaInput {\n id: String!\n}\n\ntype RemoteCursor {\n remoteName: String!\n cursorOrdinal: Int!\n lastSyncedAtUtcMs: String\n}\n\ninput RemoteCursorInput {\n remoteName: String!\n cursorOrdinal: Int!\n lastSyncedAtUtcMs: String\n}\n\nenum SyncEnvelopeType {\n OPERATIONS\n ACK\n}\n\ntype SyncEnvelope {\n type: SyncEnvelopeType!\n channelMeta: ChannelMeta!\n operations: [OperationWithContext!]\n cursor: RemoteCursor\n key: String\n dependsOn: [String!]\n}\n\ninput SyncEnvelopeInput {\n type: SyncEnvelopeType!\n channelMeta: ChannelMetaInput!\n operations: [OperationWithContextInput!]\n cursor: RemoteCursorInput\n key: String\n dependsOn: [String!]\n}\n\ninput RemoteFilterInput {\n documentId: [String!]!\n scope: [String!]!\n branch: String!\n}\n\ninput TouchChannelInput {\n id: String!\n name: String!\n collectionId: String!\n filter: RemoteFilterInput!\n sinceTimestampUtcMs: String!\n}\n\ntype TouchChannelResult {\n success: Boolean!\n ackOrdinal: Int!\n}\n\ntype Query {\n # Get document models for a namespace\n documentModels(\n namespace: String\n paging: PagingInput\n ): DocumentModelResultPage!\n\n # Get a specific document by ID or slug\n document(identifier: String!, view: ViewFilterInput): DocumentWithChildren\n\n # Get outgoing relationships of a given type from a source document\n documentOutgoingRelationships(\n sourceIdentifier: String!\n relationshipType: String!\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Get incoming relationships of a given type to a target document\n documentIncomingRelationships(\n targetIdentifier: String!\n relationshipType: String!\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Find documents by search criteria\n findDocuments(\n search: SearchFilterInput\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Get job status\n jobStatus(jobId: String!): JobInfo\n\n # Get operations for a document with filtering and pagination\n documentOperations(\n filter: OperationsFilterInput!\n paging: PagingInput\n ): ReactorOperationResultPage!\n\n # Poll for sync envelopes from a channel\n pollSyncEnvelopes(\n channelId: String!\n outboxAck: Int!\n outboxLatest: Int!\n ): PollSyncEnvelopesResult!\n}\n\ntype Mutation {\n # Create a new document\n createDocument(document: JSONObject!, parentIdentifier: String): PHDocument!\n\n # Create an empty document of specified type\n createEmptyDocument(\n documentType: String!\n parentIdentifier: String\n ): PHDocument!\n\n # Apply actions to a document (synchronous)\n mutateDocument(\n documentIdentifier: String!\n actions: [JSONObject!]!\n view: ViewFilterInput\n ): PHDocument!\n\n # Submit actions to a document (asynchronous)\n mutateDocumentAsync(\n documentIdentifier: String!\n actions: [JSONObject!]!\n view: ViewFilterInput\n ): String!\n\n # Rename a document\n renameDocument(\n documentIdentifier: String!\n name: String!\n branch: String\n ): PHDocument!\n\n # Update the preferred editor recorded in the document header meta.\n # Pass null/omit to clear it.\n setPreferredEditor(\n documentIdentifier: String!\n preferredEditor: String\n branch: String\n ): PHDocument!\n\n # Add a relationship between two documents\n addRelationship(\n sourceIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n branch: String\n ): PHDocument!\n\n # Remove a relationship between two documents\n removeRelationship(\n sourceIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n branch: String\n ): PHDocument!\n\n # Move a relationship from one source to another\n moveRelationship(\n sourceParentIdentifier: String!\n targetParentIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n branch: String\n ): MoveRelationshipResult!\n\n # Delete a single document\n deleteDocument(identifier: String!, propagate: PropagationMode): Boolean!\n\n # Delete multiple documents\n deleteDocuments(identifiers: [String!]!, propagate: PropagationMode): Boolean!\n\n # Touch (create or update) a channel for sync\n touchChannel(input: TouchChannelInput!): TouchChannelResult!\n\n # Push sync envelopes to a channel\n pushSyncEnvelopes(envelopes: [SyncEnvelopeInput!]!): Boolean!\n}\n\ntype Subscription {\n # Subscribe to document changes\n documentChanges(\n search: SearchFilterInput\n view: ViewFilterInput\n ): DocumentChangeEvent!\n\n # Subscribe to job changes\n jobChanges(jobId: String!): JobChangeEvent!\n}\n";
|
|
3945
3771
|
//#endregion
|
|
3946
3772
|
//#region src/graphql/reactor/pubsub.ts
|
|
3947
3773
|
const pubSub = new PubSub();
|
|
@@ -3990,7 +3816,8 @@ function ensureJobSubscription(reactorClient, jobId) {
|
|
|
3990
3816
|
error: jobInfo.error?.message ?? null,
|
|
3991
3817
|
result: jobInfo.result ?? {}
|
|
3992
3818
|
},
|
|
3993
|
-
jobId
|
|
3819
|
+
jobId,
|
|
3820
|
+
documentId: jobInfo.documentId
|
|
3994
3821
|
};
|
|
3995
3822
|
pubSub.publish(SUBSCRIPTION_TRIGGERS.JOB_CHANGES, payload);
|
|
3996
3823
|
const isTerminal = String(jobInfo.status) === "FAILED" || String(jobInfo.status) === "READ_MODELS_READY" || jobInfo.completedAtUtcIso !== void 0;
|
|
@@ -4040,12 +3867,14 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4040
3867
|
* Delegates to base assertCanExecuteOperation for each action.
|
|
4041
3868
|
*/
|
|
4042
3869
|
async assertCanExecuteOperations(documentId, actions, ctx) {
|
|
3870
|
+
let handle = AuthorizedDocumentHandle.skipped(documentId);
|
|
4043
3871
|
for (const action of actions) {
|
|
4044
3872
|
if (!action || typeof action !== "object") continue;
|
|
4045
3873
|
const operationType = action.type;
|
|
4046
3874
|
if (typeof operationType !== "string") continue;
|
|
4047
|
-
await this.assertCanExecuteOperation(documentId, operationType, ctx);
|
|
3875
|
+
handle = await this.assertCanExecuteOperation(documentId, operationType, ctx);
|
|
4048
3876
|
}
|
|
3877
|
+
return handle;
|
|
4049
3878
|
}
|
|
4050
3879
|
/**
|
|
4051
3880
|
* Returns the drive id when the given identifier (id or slug) refers
|
|
@@ -4062,11 +3891,25 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4062
3891
|
return;
|
|
4063
3892
|
}
|
|
4064
3893
|
}
|
|
3894
|
+
/**
|
|
3895
|
+
* Adds to `forbidden` the canonical document ids in `syncOps` that the caller
|
|
3896
|
+
* cannot read, checking each distinct id once. Sync operation document ids are
|
|
3897
|
+
* canonical (never slugs), so no resolution is needed.
|
|
3898
|
+
*/
|
|
3899
|
+
async #collectForbiddenDocuments(syncOps, forbidden, ctx) {
|
|
3900
|
+
const checked = /* @__PURE__ */ new Set();
|
|
3901
|
+
for (const syncOp of syncOps) {
|
|
3902
|
+
const documentId = syncOp.documentId;
|
|
3903
|
+
if (checked.has(documentId) || forbidden.has(documentId)) continue;
|
|
3904
|
+
checked.add(documentId);
|
|
3905
|
+
if (!await this.canReadDocument(documentId, ctx)) forbidden.add(documentId);
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
4065
3908
|
typeDefs = gql(schema_default);
|
|
4066
3909
|
resolvers = {
|
|
4067
3910
|
PHDocument: { operations: async (parent, args, ctx) => {
|
|
4068
3911
|
this.logger.debug("PHDocument.operations(@parent.id, @args)", parent.id, args);
|
|
4069
|
-
await this.
|
|
3912
|
+
await this.assertCanReadCanonical(parent.id, ctx);
|
|
4070
3913
|
try {
|
|
4071
3914
|
const filter = {
|
|
4072
3915
|
documentId: parent.id,
|
|
@@ -4099,8 +3942,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4099
3942
|
document: async (_parent, args, ctx) => {
|
|
4100
3943
|
this.logger.debug("document(@args)", args);
|
|
4101
3944
|
try {
|
|
4102
|
-
await this.assertCanRead(args.identifier, ctx);
|
|
4103
|
-
return await document(this.reactorClient,
|
|
3945
|
+
const handle = await this.assertCanRead(args.identifier, ctx);
|
|
3946
|
+
return await document(this.reactorClient, {
|
|
3947
|
+
...args,
|
|
3948
|
+
identifier: handle.fetchIdentifier
|
|
3949
|
+
});
|
|
4104
3950
|
} catch (error) {
|
|
4105
3951
|
this.logger.error("Error in document: @Error", error);
|
|
4106
3952
|
throw error;
|
|
@@ -4109,8 +3955,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4109
3955
|
documentOutgoingRelationships: async (_parent, args, ctx) => {
|
|
4110
3956
|
this.logger.debug("documentOutgoingRelationships(@args)", args);
|
|
4111
3957
|
try {
|
|
4112
|
-
await this.assertCanRead(args.sourceIdentifier, ctx);
|
|
4113
|
-
return await documentOutgoingRelationships(this.reactorClient,
|
|
3958
|
+
const handle = await this.assertCanRead(args.sourceIdentifier, ctx);
|
|
3959
|
+
return await documentOutgoingRelationships(this.reactorClient, {
|
|
3960
|
+
...args,
|
|
3961
|
+
sourceIdentifier: handle.fetchIdentifier
|
|
3962
|
+
});
|
|
4114
3963
|
} catch (error) {
|
|
4115
3964
|
this.logger.error("Error in documentOutgoingRelationships: @Error", error);
|
|
4116
3965
|
throw error;
|
|
@@ -4119,9 +3968,12 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4119
3968
|
documentIncomingRelationships: async (_parent, args, ctx) => {
|
|
4120
3969
|
this.logger.debug("documentIncomingRelationships(@args)", args);
|
|
4121
3970
|
try {
|
|
4122
|
-
await this.assertCanRead(args.targetIdentifier, ctx);
|
|
4123
|
-
const result = await documentIncomingRelationships(this.reactorClient,
|
|
4124
|
-
|
|
3971
|
+
const handle = await this.assertCanRead(args.targetIdentifier, ctx);
|
|
3972
|
+
const result = await documentIncomingRelationships(this.reactorClient, {
|
|
3973
|
+
...args,
|
|
3974
|
+
targetIdentifier: handle.fetchIdentifier
|
|
3975
|
+
});
|
|
3976
|
+
if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
|
|
4125
3977
|
const filteredItems = [];
|
|
4126
3978
|
for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
|
|
4127
3979
|
return {
|
|
@@ -4142,7 +3994,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4142
3994
|
...args,
|
|
4143
3995
|
search: args.search ?? {}
|
|
4144
3996
|
});
|
|
4145
|
-
if (!this.
|
|
3997
|
+
if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
|
|
4146
3998
|
const filteredItems = [];
|
|
4147
3999
|
for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
|
|
4148
4000
|
return {
|
|
@@ -4168,17 +4020,37 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4168
4020
|
documentOperations: async (_parent, args, ctx) => {
|
|
4169
4021
|
this.logger.debug("documentOperations(@args)", args);
|
|
4170
4022
|
try {
|
|
4171
|
-
await this.assertCanRead(args.filter.documentId, ctx);
|
|
4172
|
-
return await documentOperations(this.reactorClient,
|
|
4023
|
+
const handle = await this.assertCanRead(args.filter.documentId, ctx);
|
|
4024
|
+
return await documentOperations(this.reactorClient, {
|
|
4025
|
+
...args,
|
|
4026
|
+
filter: {
|
|
4027
|
+
...args.filter,
|
|
4028
|
+
documentId: handle.fetchIdentifier
|
|
4029
|
+
}
|
|
4030
|
+
});
|
|
4173
4031
|
} catch (error) {
|
|
4174
4032
|
this.logger.error("Error in documentOperations: @Error", error);
|
|
4175
4033
|
throw error;
|
|
4176
4034
|
}
|
|
4177
4035
|
},
|
|
4178
|
-
pollSyncEnvelopes: (_parent, args) => {
|
|
4036
|
+
pollSyncEnvelopes: async (_parent, args, ctx) => {
|
|
4179
4037
|
this.logger.debug("pollSyncEnvelopes(@args)", args);
|
|
4180
4038
|
try {
|
|
4181
|
-
|
|
4039
|
+
let remote;
|
|
4040
|
+
try {
|
|
4041
|
+
remote = this.syncManager.getById(args.channelId);
|
|
4042
|
+
} catch (error) {
|
|
4043
|
+
throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4044
|
+
}
|
|
4045
|
+
const driveId = remote.collectionId.driveId;
|
|
4046
|
+
const isAdmin = this.authorizationService.isSupremeAdmin(ctx.user?.address);
|
|
4047
|
+
if (!isAdmin) await this.assertCanReadCanonical(driveId, ctx);
|
|
4048
|
+
const forbiddenIds = /* @__PURE__ */ new Set();
|
|
4049
|
+
if (!isAdmin) {
|
|
4050
|
+
if (await this.authorizationService.canRead(driveId, void 0)) await this.#collectForbiddenDocuments(remote.channel.outbox.items, forbiddenIds, ctx);
|
|
4051
|
+
await this.#collectForbiddenDocuments(remote.channel.deadLetter.items, forbiddenIds, ctx);
|
|
4052
|
+
}
|
|
4053
|
+
const { envelopes, ackOrdinal, deadLetters, hasMore } = pollSyncEnvelopes(this.syncManager, args, forbiddenIds);
|
|
4182
4054
|
return {
|
|
4183
4055
|
envelopes,
|
|
4184
4056
|
ackOrdinal,
|
|
@@ -4197,13 +4069,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4197
4069
|
try {
|
|
4198
4070
|
if (args.parentIdentifier) {
|
|
4199
4071
|
const parent = await document(this.reactorClient, { identifier: args.parentIdentifier });
|
|
4200
|
-
await this.
|
|
4201
|
-
} else
|
|
4202
|
-
if (!ctx.user?.address) throw new GraphQLError("Forbidden: authentication required to create documents");
|
|
4203
|
-
} else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
|
|
4072
|
+
await this.assertCanWriteCanonical(parent.document.id, ctx);
|
|
4073
|
+
} else this.assertCanCreate(ctx);
|
|
4204
4074
|
const result = await createDocument(this.reactorClient, args, this.graphqlManager.reactorDriveClient);
|
|
4205
4075
|
if (result?.id && isDriveContainerType(result.documentType)) this.graphqlManager.driveOwnershipCache.add(result.id);
|
|
4206
|
-
if (
|
|
4076
|
+
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
4207
4077
|
return result;
|
|
4208
4078
|
} catch (error) {
|
|
4209
4079
|
this.logger.error("Error in createDocument(@args): @Error", args, error);
|
|
@@ -4215,13 +4085,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4215
4085
|
try {
|
|
4216
4086
|
if (args.parentIdentifier) {
|
|
4217
4087
|
const parent = await document(this.reactorClient, { identifier: args.parentIdentifier });
|
|
4218
|
-
await this.
|
|
4219
|
-
} else
|
|
4220
|
-
if (!ctx.user?.address) throw new GraphQLError("Forbidden: authentication required to create documents");
|
|
4221
|
-
} else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
|
|
4088
|
+
await this.assertCanWriteCanonical(parent.document.id, ctx);
|
|
4089
|
+
} else this.assertCanCreate(ctx);
|
|
4222
4090
|
const result = await createEmptyDocument(this.reactorClient, args, this.graphqlManager.reactorDriveClient);
|
|
4223
4091
|
if (result?.id && isDriveContainerType(result.documentType)) this.graphqlManager.driveOwnershipCache.add(result.id);
|
|
4224
|
-
if (
|
|
4092
|
+
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
4225
4093
|
return result;
|
|
4226
4094
|
} catch (error) {
|
|
4227
4095
|
this.logger.error("Error in createEmptyDocument(@args): @Error", args, error);
|
|
@@ -4231,9 +4099,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4231
4099
|
mutateDocument: async (_parent, args, ctx) => {
|
|
4232
4100
|
this.logger.debug("mutateDocument(@args)", args);
|
|
4233
4101
|
try {
|
|
4234
|
-
|
|
4235
|
-
await this.
|
|
4236
|
-
|
|
4102
|
+
const handle = await this.assertCanExecuteOperations(args.documentIdentifier, args.actions, ctx);
|
|
4103
|
+
return await mutateDocument(this.reactorClient, {
|
|
4104
|
+
...args,
|
|
4105
|
+
documentIdentifier: handle.fetchIdentifier
|
|
4106
|
+
});
|
|
4237
4107
|
} catch (error) {
|
|
4238
4108
|
this.logger.error("Error in mutateDocument(@args): @Error", args, error);
|
|
4239
4109
|
throw error;
|
|
@@ -4242,9 +4112,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4242
4112
|
mutateDocumentAsync: async (_parent, args, ctx) => {
|
|
4243
4113
|
this.logger.debug("mutateDocumentAsync(@args)", args);
|
|
4244
4114
|
try {
|
|
4245
|
-
|
|
4246
|
-
await this.
|
|
4247
|
-
|
|
4115
|
+
const handle = await this.assertCanExecuteOperations(args.documentIdentifier, args.actions, ctx);
|
|
4116
|
+
return await mutateDocumentAsync(this.reactorClient, {
|
|
4117
|
+
...args,
|
|
4118
|
+
documentIdentifier: handle.fetchIdentifier
|
|
4119
|
+
});
|
|
4248
4120
|
} catch (error) {
|
|
4249
4121
|
this.logger.error("Error in mutateDocumentAsync(@args): @Error", args, error);
|
|
4250
4122
|
throw error;
|
|
@@ -4253,8 +4125,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4253
4125
|
renameDocument: async (_parent, args, ctx) => {
|
|
4254
4126
|
this.logger.debug("renameDocument(@args)", args);
|
|
4255
4127
|
try {
|
|
4256
|
-
await this.assertCanWrite(args.documentIdentifier, ctx);
|
|
4257
|
-
return await renameDocument(this.reactorClient,
|
|
4128
|
+
const handle = await this.assertCanWrite(args.documentIdentifier, ctx);
|
|
4129
|
+
return await renameDocument(this.reactorClient, {
|
|
4130
|
+
...args,
|
|
4131
|
+
documentIdentifier: handle.fetchIdentifier
|
|
4132
|
+
});
|
|
4258
4133
|
} catch (error) {
|
|
4259
4134
|
this.logger.error("Error in renameDocument(@args): @Error", args, error);
|
|
4260
4135
|
throw error;
|
|
@@ -4263,8 +4138,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4263
4138
|
setPreferredEditor: async (_parent, args, ctx) => {
|
|
4264
4139
|
this.logger.debug("setPreferredEditor(@args)", args);
|
|
4265
4140
|
try {
|
|
4266
|
-
await this.assertCanWrite(args.documentIdentifier, ctx);
|
|
4267
|
-
return await setPreferredEditor(this.reactorClient,
|
|
4141
|
+
const handle = await this.assertCanWrite(args.documentIdentifier, ctx);
|
|
4142
|
+
return await setPreferredEditor(this.reactorClient, {
|
|
4143
|
+
...args,
|
|
4144
|
+
documentIdentifier: handle.fetchIdentifier
|
|
4145
|
+
});
|
|
4268
4146
|
} catch (error) {
|
|
4269
4147
|
this.logger.error("Error in setPreferredEditor(@args): @Error", args, error);
|
|
4270
4148
|
throw error;
|
|
@@ -4273,8 +4151,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4273
4151
|
addRelationship: async (_parent, args, ctx) => {
|
|
4274
4152
|
this.logger.debug("addRelationship(@args)", args);
|
|
4275
4153
|
try {
|
|
4276
|
-
await this.assertCanWrite(args.sourceIdentifier, ctx);
|
|
4277
|
-
return await addRelationship(this.reactorClient,
|
|
4154
|
+
const handle = await this.assertCanWrite(args.sourceIdentifier, ctx);
|
|
4155
|
+
return await addRelationship(this.reactorClient, {
|
|
4156
|
+
...args,
|
|
4157
|
+
sourceIdentifier: handle.fetchIdentifier
|
|
4158
|
+
});
|
|
4278
4159
|
} catch (error) {
|
|
4279
4160
|
this.logger.error("Error in addRelationship(@args): @Error", args, error);
|
|
4280
4161
|
throw error;
|
|
@@ -4283,8 +4164,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4283
4164
|
removeRelationship: async (_parent, args, ctx) => {
|
|
4284
4165
|
this.logger.debug("removeRelationship(@args)", args);
|
|
4285
4166
|
try {
|
|
4286
|
-
await this.assertCanWrite(args.sourceIdentifier, ctx);
|
|
4287
|
-
return await removeRelationship(this.reactorClient,
|
|
4167
|
+
const handle = await this.assertCanWrite(args.sourceIdentifier, ctx);
|
|
4168
|
+
return await removeRelationship(this.reactorClient, {
|
|
4169
|
+
...args,
|
|
4170
|
+
sourceIdentifier: handle.fetchIdentifier
|
|
4171
|
+
});
|
|
4288
4172
|
} catch (error) {
|
|
4289
4173
|
this.logger.error("Error in removeRelationship(@args): @Error", args, error);
|
|
4290
4174
|
throw error;
|
|
@@ -4293,9 +4177,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4293
4177
|
moveRelationship: async (_parent, args, ctx) => {
|
|
4294
4178
|
this.logger.debug("moveRelationship(@args)", args);
|
|
4295
4179
|
try {
|
|
4296
|
-
await this.assertCanWrite(args.sourceParentIdentifier, ctx);
|
|
4297
|
-
await this.assertCanWrite(args.targetParentIdentifier, ctx);
|
|
4298
|
-
return await moveRelationship(this.reactorClient,
|
|
4180
|
+
const sourceHandle = await this.assertCanWrite(args.sourceParentIdentifier, ctx);
|
|
4181
|
+
const targetHandle = await this.assertCanWrite(args.targetParentIdentifier, ctx);
|
|
4182
|
+
return await moveRelationship(this.reactorClient, {
|
|
4183
|
+
...args,
|
|
4184
|
+
sourceParentIdentifier: sourceHandle.fetchIdentifier,
|
|
4185
|
+
targetParentIdentifier: targetHandle.fetchIdentifier
|
|
4186
|
+
});
|
|
4299
4187
|
} catch (error) {
|
|
4300
4188
|
this.logger.error("Error in moveRelationship(@args): @Error @args", error, args);
|
|
4301
4189
|
throw error;
|
|
@@ -4304,9 +4192,12 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4304
4192
|
deleteDocument: async (_parent, args, ctx) => {
|
|
4305
4193
|
this.logger.debug("deleteDocument(@args)", args);
|
|
4306
4194
|
try {
|
|
4307
|
-
await this.assertCanWrite(args.identifier, ctx);
|
|
4308
|
-
const driveIdToInvalidate = await this.#resolveDriveId(
|
|
4309
|
-
const result = await deleteDocument(this.reactorClient,
|
|
4195
|
+
const identifier = (await this.assertCanWrite(args.identifier, ctx)).fetchIdentifier;
|
|
4196
|
+
const driveIdToInvalidate = await this.#resolveDriveId(identifier);
|
|
4197
|
+
const result = await deleteDocument(this.reactorClient, {
|
|
4198
|
+
...args,
|
|
4199
|
+
identifier
|
|
4200
|
+
}, this.graphqlManager.reactorDriveClient);
|
|
4310
4201
|
if (result && driveIdToInvalidate) this.graphqlManager.driveOwnershipCache.remove(driveIdToInvalidate);
|
|
4311
4202
|
return result;
|
|
4312
4203
|
} catch (error) {
|
|
@@ -4317,25 +4208,49 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4317
4208
|
deleteDocuments: async (_parent, args, ctx) => {
|
|
4318
4209
|
this.logger.debug("deleteDocuments(@args)", args);
|
|
4319
4210
|
try {
|
|
4320
|
-
|
|
4321
|
-
|
|
4211
|
+
const identifiers = [];
|
|
4212
|
+
for (const identifier of args.identifiers) {
|
|
4213
|
+
const handle = await this.assertCanWrite(identifier, ctx);
|
|
4214
|
+
identifiers.push(handle.fetchIdentifier);
|
|
4215
|
+
}
|
|
4216
|
+
return await deleteDocuments(this.reactorClient, {
|
|
4217
|
+
...args,
|
|
4218
|
+
identifiers
|
|
4219
|
+
});
|
|
4322
4220
|
} catch (error) {
|
|
4323
4221
|
this.logger.error("Error in deleteDocuments(@args): @Error", args, error);
|
|
4324
4222
|
throw error;
|
|
4325
4223
|
}
|
|
4326
4224
|
},
|
|
4327
|
-
touchChannel: async (_parent, args) => {
|
|
4225
|
+
touchChannel: async (_parent, args, ctx) => {
|
|
4328
4226
|
this.logger.debug("touchChannel(@args)", args);
|
|
4329
4227
|
try {
|
|
4228
|
+
if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
|
|
4229
|
+
const driveId = DriveCollectionId.fromKey(args.input.collectionId).driveId;
|
|
4230
|
+
await this.assertCanReadCanonical(driveId, ctx);
|
|
4231
|
+
}
|
|
4330
4232
|
return await touchChannel(this.syncManager, args);
|
|
4331
4233
|
} catch (error) {
|
|
4332
4234
|
this.logger.error("Error in touchChannel(@args): @Error", args, error);
|
|
4333
4235
|
throw error;
|
|
4334
4236
|
}
|
|
4335
4237
|
},
|
|
4336
|
-
pushSyncEnvelopes: async (_parent, args) => {
|
|
4238
|
+
pushSyncEnvelopes: async (_parent, args, ctx) => {
|
|
4337
4239
|
this.logger.debug("pushSyncEnvelopes(@args)", args);
|
|
4338
4240
|
try {
|
|
4241
|
+
const checkedOperations = /* @__PURE__ */ new Map();
|
|
4242
|
+
for (const envelope of args.envelopes) for (const op of envelope.operations ?? []) {
|
|
4243
|
+
const documentId = op.context.documentId;
|
|
4244
|
+
const operationType = op.operation.action.type;
|
|
4245
|
+
let checkedTypes = checkedOperations.get(documentId);
|
|
4246
|
+
if (!checkedTypes) {
|
|
4247
|
+
checkedTypes = /* @__PURE__ */ new Set();
|
|
4248
|
+
checkedOperations.set(documentId, checkedTypes);
|
|
4249
|
+
}
|
|
4250
|
+
if (checkedTypes.has(operationType)) continue;
|
|
4251
|
+
checkedTypes.add(operationType);
|
|
4252
|
+
await this.assertCanExecuteOperationCanonical(documentId, operationType, ctx);
|
|
4253
|
+
}
|
|
4339
4254
|
const mutableArgs = { envelopes: args.envelopes.map((envelope) => ({
|
|
4340
4255
|
type: envelope.type,
|
|
4341
4256
|
channelMeta: { id: envelope.channelMeta.id },
|
|
@@ -4366,31 +4281,43 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4366
4281
|
},
|
|
4367
4282
|
Subscription: {
|
|
4368
4283
|
documentChanges: {
|
|
4369
|
-
subscribe:
|
|
4284
|
+
subscribe: (rootValue, args, ctx, info) => {
|
|
4370
4285
|
this.logger.debug("documentChanges subscription started");
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4286
|
+
return withFilter(() => {
|
|
4287
|
+
ensureGlobalDocumentSubscription(this.reactorClient);
|
|
4288
|
+
return getPubSub().asyncIterableIterator(SUBSCRIPTION_TRIGGERS.DOCUMENT_CHANGES);
|
|
4289
|
+
}, async (payload, filterArgs, filterCtx) => {
|
|
4290
|
+
if (!payload) return false;
|
|
4291
|
+
const search = {
|
|
4292
|
+
type: filterArgs?.search?.type ?? void 0,
|
|
4293
|
+
parentId: filterArgs?.search?.parentId ?? void 0
|
|
4294
|
+
};
|
|
4295
|
+
if (!matchesSearchFilter(payload.documentChanges, search)) return false;
|
|
4296
|
+
if (this.authorizationService.isSupremeAdmin(filterCtx?.user?.address)) return true;
|
|
4297
|
+
const documentIds = collectChangedDocumentIds(payload.documentChanges);
|
|
4298
|
+
if (documentIds.length === 0) return false;
|
|
4299
|
+
for (const documentId of documentIds) if (!await this.canReadDocument(documentId, filterCtx)) return false;
|
|
4300
|
+
return true;
|
|
4301
|
+
})(rootValue, args, ctx, info);
|
|
4302
|
+
},
|
|
4381
4303
|
resolve: (payload) => {
|
|
4382
4304
|
return toGqlDocumentChangeEvent(payload.documentChanges);
|
|
4383
4305
|
}
|
|
4384
4306
|
},
|
|
4385
4307
|
jobChanges: {
|
|
4386
|
-
subscribe:
|
|
4308
|
+
subscribe: (rootValue, args, ctx, info) => {
|
|
4387
4309
|
this.logger.debug("jobChanges(@args) subscription started", args);
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4310
|
+
return withFilter(() => {
|
|
4311
|
+
ensureJobSubscription(this.reactorClient, args.jobId);
|
|
4312
|
+
return getPubSub().asyncIterableIterator(SUBSCRIPTION_TRIGGERS.JOB_CHANGES);
|
|
4313
|
+
}, async (payload, filterArgs, filterCtx) => {
|
|
4314
|
+
if (!payload || !filterArgs) return false;
|
|
4315
|
+
if (!matchesJobFilter(payload, filterArgs)) return false;
|
|
4316
|
+
if (this.authorizationService.isSupremeAdmin(filterCtx?.user?.address)) return true;
|
|
4317
|
+
if (!payload.documentId) return false;
|
|
4318
|
+
return this.canReadDocument(payload.documentId, filterCtx);
|
|
4319
|
+
})(rootValue, args, ctx, info);
|
|
4320
|
+
},
|
|
4394
4321
|
resolve: (payload) => {
|
|
4395
4322
|
return payload.jobChanges;
|
|
4396
4323
|
}
|
|
@@ -4414,10 +4341,10 @@ const ADMIN_USERS = getAdminUsers();
|
|
|
4414
4341
|
//#endregion
|
|
4415
4342
|
//#region src/graphql/system/version.ts
|
|
4416
4343
|
function getVersion() {
|
|
4417
|
-
return "6.2.0-dev.
|
|
4344
|
+
return "6.2.0-dev.20";
|
|
4418
4345
|
}
|
|
4419
4346
|
function getGitHash() {
|
|
4420
|
-
return "
|
|
4347
|
+
return "69a4177a74abeda6e2812c8c19f79e3defc14df0";
|
|
4421
4348
|
}
|
|
4422
4349
|
function getGitUrl() {
|
|
4423
4350
|
return buildTreeUrl(getGitHash());
|
|
@@ -5038,104 +4965,159 @@ async function runMigrations(db) {
|
|
|
5038
4965
|
}
|
|
5039
4966
|
}
|
|
5040
4967
|
//#endregion
|
|
5041
|
-
//#region src/services/
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
* Authorization model:
|
|
5046
|
-
* 1. Supreme admin (ADMINS env) → ALLOW ALL
|
|
5047
|
-
* 2. Is document protected?
|
|
5048
|
-
* a. NOT protected:
|
|
5049
|
-
* - READ: anyone (even anonymous) → ALLOW
|
|
5050
|
-
* - WRITE: authenticated user → ALLOW
|
|
5051
|
-
* b. PROTECTED:
|
|
5052
|
-
* - READ: requires explicit READ/WRITE/ADMIN grant (direct or via group/parent)
|
|
5053
|
-
* - WRITE: requires explicit WRITE/ADMIN grant (direct or via group/parent)
|
|
5054
|
-
* 3. Operation restricted? → Check OperationUserPermission
|
|
5055
|
-
* 4. Document owner = implicit ADMIN
|
|
5056
|
-
* 5. Drive protected = all children effectively protected
|
|
5057
|
-
*/
|
|
5058
|
-
var AuthorizationService = class {
|
|
4968
|
+
//#region src/services/auth.service.ts
|
|
4969
|
+
const DEFAULT_CREDENTIAL_CACHE_TTL_MS = 6e4;
|
|
4970
|
+
const CREDENTIAL_CACHE_MAX_ENTRIES = 1e3;
|
|
4971
|
+
var AuthService = class {
|
|
5059
4972
|
config;
|
|
5060
|
-
|
|
5061
|
-
|
|
4973
|
+
credentialCache = /* @__PURE__ */ new Map();
|
|
4974
|
+
constructor(config) {
|
|
5062
4975
|
this.config = config;
|
|
5063
4976
|
}
|
|
4977
|
+
async authenticateRequest(request) {
|
|
4978
|
+
if (!this.config.enabled) return {
|
|
4979
|
+
user: void 0,
|
|
4980
|
+
admins: [],
|
|
4981
|
+
auth_enabled: false
|
|
4982
|
+
};
|
|
4983
|
+
const method = request.method;
|
|
4984
|
+
if (method === "OPTIONS" || method === "GET") return {
|
|
4985
|
+
user: void 0,
|
|
4986
|
+
admins: this.config.admins,
|
|
4987
|
+
auth_enabled: true
|
|
4988
|
+
};
|
|
4989
|
+
return this.verifyBearer(request.headers.get("authorization") ?? void 0);
|
|
4990
|
+
}
|
|
5064
4991
|
/**
|
|
5065
|
-
*
|
|
4992
|
+
* Verify a Bearer token regardless of HTTP method. Use this from non-GraphQL
|
|
4993
|
+
* middleware that must enforce authentication on every request.
|
|
5066
4994
|
*/
|
|
5067
|
-
|
|
5068
|
-
if (!
|
|
5069
|
-
|
|
4995
|
+
async verifyBearer(authorization) {
|
|
4996
|
+
if (!this.config.enabled) return {
|
|
4997
|
+
user: void 0,
|
|
4998
|
+
admins: [],
|
|
4999
|
+
auth_enabled: false
|
|
5000
|
+
};
|
|
5001
|
+
const token = authorization?.split(" ")[1];
|
|
5002
|
+
if (!token) return {
|
|
5003
|
+
user: void 0,
|
|
5004
|
+
admins: this.config.admins,
|
|
5005
|
+
auth_enabled: true
|
|
5006
|
+
};
|
|
5007
|
+
try {
|
|
5008
|
+
const verified = await this.verifyToken(token);
|
|
5009
|
+
if (!verified) return new Response(JSON.stringify({ error: "Verification failed" }), { status: 401 });
|
|
5010
|
+
const user = this.extractUserFromVerification(verified);
|
|
5011
|
+
if (!user) return new Response(JSON.stringify({ error: "Missing credentials" }), { status: 401 });
|
|
5012
|
+
if (!this.config.skipCredentialVerification) {
|
|
5013
|
+
if (!await this.verifyCredentialExists(user.address, user.chainId, verified.issuer)) return new Response(JSON.stringify({ error: "Credentials no longer valid" }), { status: 401 });
|
|
5014
|
+
}
|
|
5015
|
+
return {
|
|
5016
|
+
user,
|
|
5017
|
+
admins: this.config.admins,
|
|
5018
|
+
auth_enabled: true
|
|
5019
|
+
};
|
|
5020
|
+
} catch {
|
|
5021
|
+
return new Response(JSON.stringify({ error: "Authentication failed" }), { status: 401 });
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
async authenticateWebSocketConnection(connectionParams) {
|
|
5025
|
+
if (!this.config.enabled) return null;
|
|
5026
|
+
const authHeader = connectionParams.authorization;
|
|
5027
|
+
if (!authHeader) throw new Error("Missing authorization in connection parameters");
|
|
5028
|
+
const token = authHeader.split(" ")[1];
|
|
5029
|
+
if (!token) throw new Error("Invalid authorization format");
|
|
5030
|
+
const verified = await this.verifyToken(token);
|
|
5031
|
+
if (!verified) throw new Error("Token verification failed");
|
|
5032
|
+
const user = this.extractUserFromVerification(verified);
|
|
5033
|
+
if (!user) throw new Error("Invalid credentials");
|
|
5034
|
+
if (!this.config.skipCredentialVerification) {
|
|
5035
|
+
if (!await this.verifyCredentialExists(user.address, user.chainId, verified.issuer)) throw new Error("Credentials no longer valid");
|
|
5036
|
+
}
|
|
5037
|
+
return user;
|
|
5070
5038
|
}
|
|
5071
5039
|
/**
|
|
5072
|
-
*
|
|
5073
|
-
*
|
|
5074
|
-
* - Supreme admin → yes
|
|
5075
|
-
* - Not protected → anyone can read (even anonymous)
|
|
5076
|
-
* - Protected → requires READ/WRITE/ADMIN grant (direct, group, or parent inheritance)
|
|
5077
|
-
* - Owner → yes (implicit ADMIN)
|
|
5040
|
+
* Verify the auth bearer token
|
|
5078
5041
|
*/
|
|
5079
|
-
async
|
|
5080
|
-
|
|
5081
|
-
if (!(getParentIds ? await this.documentPermissionService.isProtectedWithAncestors(documentId, getParentIds) : await this.documentPermissionService.isDocumentProtected(documentId))) return true;
|
|
5082
|
-
if (!userAddress) return false;
|
|
5083
|
-
const owner = await this.documentPermissionService.getDocumentOwner(documentId);
|
|
5084
|
-
if (owner && owner === userAddress.toLowerCase()) return true;
|
|
5085
|
-
if (getParentIds) return this.documentPermissionService.canRead(documentId, userAddress, getParentIds);
|
|
5086
|
-
return this.documentPermissionService.canReadDocument(documentId, userAddress);
|
|
5042
|
+
async verifyToken(token) {
|
|
5043
|
+
return await verifyAuthBearerToken(token);
|
|
5087
5044
|
}
|
|
5088
5045
|
/**
|
|
5089
|
-
*
|
|
5090
|
-
*
|
|
5091
|
-
* - Supreme admin → yes
|
|
5092
|
-
* - Not protected → anyone can write (even anonymous)
|
|
5093
|
-
* - Protected → requires authentication + WRITE/ADMIN grant
|
|
5094
|
-
* - Owner → yes (implicit ADMIN)
|
|
5046
|
+
* Extract user information from verification result
|
|
5095
5047
|
*/
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5048
|
+
extractUserFromVerification(verified) {
|
|
5049
|
+
try {
|
|
5050
|
+
const { address, chainId, networkId } = verified.verifiableCredential.credentialSubject;
|
|
5051
|
+
if (!address || !chainId || !networkId) return null;
|
|
5052
|
+
return {
|
|
5053
|
+
address,
|
|
5054
|
+
chainId,
|
|
5055
|
+
networkId
|
|
5056
|
+
};
|
|
5057
|
+
} catch {
|
|
5058
|
+
return null;
|
|
5059
|
+
}
|
|
5104
5060
|
}
|
|
5105
5061
|
/**
|
|
5106
|
-
*
|
|
5062
|
+
* Verify that the credential still exists on the Renown API.
|
|
5107
5063
|
*
|
|
5108
|
-
*
|
|
5109
|
-
* -
|
|
5110
|
-
*
|
|
5064
|
+
* Results are cached per (address, chainId, issuer) for a short TTL so the
|
|
5065
|
+
* blocking external round-trip is not paid on every request. Concurrent
|
|
5066
|
+
* checks for the same key share a single in-flight request, and entries
|
|
5067
|
+
* that resolve to false are evicted immediately so failed or revoked
|
|
5068
|
+
* credentials are re-checked on the next request.
|
|
5111
5069
|
*/
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
if (
|
|
5115
|
-
const
|
|
5116
|
-
|
|
5117
|
-
|
|
5070
|
+
verifyCredentialExists(address, chainId, appId) {
|
|
5071
|
+
const ttlMs = this.config.credentialVerificationCacheTtlMs ?? DEFAULT_CREDENTIAL_CACHE_TTL_MS;
|
|
5072
|
+
if (ttlMs <= 0) return this.fetchCredentialExists(address, chainId, appId);
|
|
5073
|
+
const key = `${address.toLowerCase()}:${chainId}:${appId}`;
|
|
5074
|
+
const now = Date.now();
|
|
5075
|
+
const cached = this.credentialCache.get(key);
|
|
5076
|
+
if (cached && cached.expiresAt > now) return cached.exists;
|
|
5077
|
+
this.pruneCredentialCache(now);
|
|
5078
|
+
const entry = {
|
|
5079
|
+
exists: this.fetchCredentialExists(address, chainId, appId).then((exists) => {
|
|
5080
|
+
if (!exists && this.credentialCache.get(key) === entry) this.credentialCache.delete(key);
|
|
5081
|
+
return exists;
|
|
5082
|
+
}),
|
|
5083
|
+
expiresAt: now + ttlMs
|
|
5084
|
+
};
|
|
5085
|
+
this.credentialCache.set(key, entry);
|
|
5086
|
+
return entry.exists;
|
|
5118
5087
|
}
|
|
5119
5088
|
/**
|
|
5120
|
-
*
|
|
5121
|
-
*
|
|
5122
|
-
*
|
|
5089
|
+
* Enforce the cache size cap before inserting a new entry: drop expired
|
|
5090
|
+
* entries first, then evict oldest-inserted entries (insertion order
|
|
5091
|
+
* matches expiry order since the TTL is constant) until under the cap, so
|
|
5092
|
+
* a flood of distinct keys cannot grow the map without bound.
|
|
5123
5093
|
*/
|
|
5124
|
-
|
|
5125
|
-
if (this.
|
|
5126
|
-
|
|
5127
|
-
|
|
5094
|
+
pruneCredentialCache(now) {
|
|
5095
|
+
if (this.credentialCache.size < CREDENTIAL_CACHE_MAX_ENTRIES) return;
|
|
5096
|
+
for (const [key, entry] of this.credentialCache) if (entry.expiresAt <= now) this.credentialCache.delete(key);
|
|
5097
|
+
while (this.credentialCache.size >= CREDENTIAL_CACHE_MAX_ENTRIES) {
|
|
5098
|
+
const oldestKey = this.credentialCache.keys().next().value;
|
|
5099
|
+
if (oldestKey === void 0) break;
|
|
5100
|
+
this.credentialCache.delete(oldestKey);
|
|
5101
|
+
}
|
|
5128
5102
|
}
|
|
5129
5103
|
/**
|
|
5130
|
-
*
|
|
5131
|
-
*
|
|
5132
|
-
*
|
|
5133
|
-
* allowing READ-only users with an explicit operation grant to execute that operation.
|
|
5104
|
+
* Fetch the credential from the Renown API and validate it against the
|
|
5105
|
+
* expected address, chainId and issuer. Never throws; returns false on any
|
|
5106
|
+
* network or validation failure.
|
|
5134
5107
|
*/
|
|
5135
|
-
async
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5108
|
+
async fetchCredentialExists(address, chainId, appId) {
|
|
5109
|
+
const url = `https://www.renown.id/api/auth/credential?address=${address}&chainId=${chainId}&connectId=${appId}&appId=${appId}`;
|
|
5110
|
+
try {
|
|
5111
|
+
const response = await fetch(url, { method: "GET" });
|
|
5112
|
+
if (response.status !== 200) return false;
|
|
5113
|
+
const credential = (await response.json()).credential;
|
|
5114
|
+
const appIdVerfied = credential.credentialSubject.id;
|
|
5115
|
+
const addressVerfied = credential.issuer.id.split(":")[4];
|
|
5116
|
+
const chainIdVerfied = credential.issuer.id.split(":")[3];
|
|
5117
|
+
return appIdVerfied === appId && addressVerfied.toLocaleLowerCase() === address.toLocaleLowerCase() && chainIdVerfied === chainId.toString();
|
|
5118
|
+
} catch {
|
|
5119
|
+
return false;
|
|
5120
|
+
}
|
|
5139
5121
|
}
|
|
5140
5122
|
};
|
|
5141
5123
|
//#endregion
|
|
@@ -5233,63 +5215,6 @@ var DocumentPermissionService = class {
|
|
|
5233
5215
|
await this.db.deleteFrom("OperationGroupPermission").where("documentId", "=", documentId).execute();
|
|
5234
5216
|
}
|
|
5235
5217
|
/**
|
|
5236
|
-
* Check if a user can read a document.
|
|
5237
|
-
* Returns true if user has READ, WRITE, or ADMIN permission (direct or via group)
|
|
5238
|
-
*/
|
|
5239
|
-
async canReadDocument(documentId, userAddress) {
|
|
5240
|
-
if (!userAddress) return false;
|
|
5241
|
-
if (await this.getUserPermission(documentId, userAddress) !== null) return true;
|
|
5242
|
-
return await this.getUserGroupPermission(documentId, userAddress) !== null;
|
|
5243
|
-
}
|
|
5244
|
-
/**
|
|
5245
|
-
* Check if a user can write to a document.
|
|
5246
|
-
* Returns true if user has WRITE or ADMIN permission (direct or via group)
|
|
5247
|
-
*/
|
|
5248
|
-
async canWriteDocument(documentId, userAddress) {
|
|
5249
|
-
if (!userAddress) return false;
|
|
5250
|
-
const directPermission = await this.getUserPermission(documentId, userAddress);
|
|
5251
|
-
if (directPermission === "WRITE" || directPermission === "ADMIN") return true;
|
|
5252
|
-
const groupPermission = await this.getUserGroupPermission(documentId, userAddress);
|
|
5253
|
-
return groupPermission === "WRITE" || groupPermission === "ADMIN";
|
|
5254
|
-
}
|
|
5255
|
-
/**
|
|
5256
|
-
* Check if a user can manage a document (change permissions, settings).
|
|
5257
|
-
* Returns true if user has ADMIN permission (direct or via group)
|
|
5258
|
-
*/
|
|
5259
|
-
async canManageDocument(documentId, userAddress) {
|
|
5260
|
-
if (!userAddress) return false;
|
|
5261
|
-
if (await this.getUserPermission(documentId, userAddress) === "ADMIN") return true;
|
|
5262
|
-
return await this.getUserGroupPermission(documentId, userAddress) === "ADMIN";
|
|
5263
|
-
}
|
|
5264
|
-
/**
|
|
5265
|
-
* Check if a user can read a document, including parent permission inheritance.
|
|
5266
|
-
* Returns true if user has permission on the document OR any parent in the hierarchy.
|
|
5267
|
-
*/
|
|
5268
|
-
async canRead(documentId, userAddress, getParentIds) {
|
|
5269
|
-
if (await this.canReadDocument(documentId, userAddress)) return true;
|
|
5270
|
-
const parentIds = await getParentIds(documentId);
|
|
5271
|
-
for (const parentId of parentIds) if (await this.canRead(parentId, userAddress, getParentIds)) return true;
|
|
5272
|
-
return false;
|
|
5273
|
-
}
|
|
5274
|
-
/**
|
|
5275
|
-
* Check if a user can write to a document, including parent permission inheritance.
|
|
5276
|
-
* Returns true if user has write permission on the document OR any parent in the hierarchy.
|
|
5277
|
-
*/
|
|
5278
|
-
async canWrite(documentId, userAddress, getParentIds) {
|
|
5279
|
-
if (await this.canWriteDocument(documentId, userAddress)) return true;
|
|
5280
|
-
const parentIds = await getParentIds(documentId);
|
|
5281
|
-
for (const parentId of parentIds) if (await this.canWrite(parentId, userAddress, getParentIds)) return true;
|
|
5282
|
-
return false;
|
|
5283
|
-
}
|
|
5284
|
-
/**
|
|
5285
|
-
* Filter a list of document IDs to only include those the user can read.
|
|
5286
|
-
*/
|
|
5287
|
-
async filterReadableDocuments(documentIds, userAddress, getParentIds) {
|
|
5288
|
-
const results = [];
|
|
5289
|
-
for (const docId of documentIds) if (await this.canRead(docId, userAddress, getParentIds)) results.push(docId);
|
|
5290
|
-
return results;
|
|
5291
|
-
}
|
|
5292
|
-
/**
|
|
5293
5218
|
* Create a new group
|
|
5294
5219
|
*/
|
|
5295
5220
|
async createGroup(name, description) {
|
|
@@ -5518,11 +5443,10 @@ var DocumentPermissionService = class {
|
|
|
5518
5443
|
]).where("documentId", "=", documentId).where("operationType", "=", operationType).execute();
|
|
5519
5444
|
}
|
|
5520
5445
|
/**
|
|
5521
|
-
*
|
|
5522
|
-
*
|
|
5446
|
+
* Whether an operation-permission row exists for the user on this
|
|
5447
|
+
* operation, either directly or via a group the user belongs to.
|
|
5523
5448
|
*/
|
|
5524
|
-
async
|
|
5525
|
-
if (!userAddress) return false;
|
|
5449
|
+
async hasOperationGrant(documentId, operationType, userAddress) {
|
|
5526
5450
|
const normalizedAddress = userAddress.toLowerCase();
|
|
5527
5451
|
if (await this.db.selectFrom("OperationUserPermission").select("userAddress").where("documentId", "=", documentId).where("operationType", "=", operationType).where("userAddress", "=", normalizedAddress).executeTakeFirst()) return true;
|
|
5528
5452
|
return !!await this.db.selectFrom("OperationGroupPermission").innerJoin("UserGroup", "UserGroup.groupId", "OperationGroupPermission.groupId").select("OperationGroupPermission.groupId").where("OperationGroupPermission.documentId", "=", documentId).where("OperationGroupPermission.operationType", "=", operationType).where("UserGroup.userAddress", "=", normalizedAddress).executeTakeFirst();
|
|
@@ -5649,6 +5573,60 @@ var DocumentPermissionService = class {
|
|
|
5649
5573
|
}
|
|
5650
5574
|
};
|
|
5651
5575
|
//#endregion
|
|
5576
|
+
//#region src/services/get-parent-ids.ts
|
|
5577
|
+
/**
|
|
5578
|
+
* The canonical parent-document resolver used for permission inheritance:
|
|
5579
|
+
* a document's parents are the sources of its incoming "child"
|
|
5580
|
+
* relationships. Lookup failures resolve to "no parents" so a relationship
|
|
5581
|
+
* store outage degrades to no inherited permissions rather than an error.
|
|
5582
|
+
*/
|
|
5583
|
+
function createGetParentIdsFn(reactorClient) {
|
|
5584
|
+
return async (documentId) => {
|
|
5585
|
+
try {
|
|
5586
|
+
return (await reactorClient.getIncomingRelationships(documentId, "child")).results.map((doc) => doc.header.id);
|
|
5587
|
+
} catch {
|
|
5588
|
+
return [];
|
|
5589
|
+
}
|
|
5590
|
+
};
|
|
5591
|
+
}
|
|
5592
|
+
//#endregion
|
|
5593
|
+
//#region src/services/mcp-request-authorizer.ts
|
|
5594
|
+
/**
|
|
5595
|
+
* Authorization gate for the /mcp endpoint (AUTH_REVIEW S-C1). MCP tools have
|
|
5596
|
+
* unrestricted reactor access, so access is limited to supreme admins when
|
|
5597
|
+
* auth is enabled. OPEN policy is public; any other policy without an
|
|
5598
|
+
* AuthService fails closed.
|
|
5599
|
+
*/
|
|
5600
|
+
function createMcpRequestAuthorizer(authService, authorizationService) {
|
|
5601
|
+
return async (req) => {
|
|
5602
|
+
if (!authService) {
|
|
5603
|
+
if (authorizationService.config.policy === AuthorizationPolicy.OPEN) return { authorized: true };
|
|
5604
|
+
return {
|
|
5605
|
+
authorized: false,
|
|
5606
|
+
status: 401,
|
|
5607
|
+
message: "Authentication required"
|
|
5608
|
+
};
|
|
5609
|
+
}
|
|
5610
|
+
const context = await authService.verifyBearer(req.headers.authorization);
|
|
5611
|
+
if (context instanceof Response) return {
|
|
5612
|
+
authorized: false,
|
|
5613
|
+
status: context.status,
|
|
5614
|
+
message: "Authentication failed"
|
|
5615
|
+
};
|
|
5616
|
+
if (!context.user) return {
|
|
5617
|
+
authorized: false,
|
|
5618
|
+
status: 401,
|
|
5619
|
+
message: "Authentication required"
|
|
5620
|
+
};
|
|
5621
|
+
if (!authorizationService.isSupremeAdmin(context.user.address)) return {
|
|
5622
|
+
authorized: false,
|
|
5623
|
+
status: 403,
|
|
5624
|
+
message: "Forbidden: MCP access requires an administrator"
|
|
5625
|
+
};
|
|
5626
|
+
return { authorized: true };
|
|
5627
|
+
};
|
|
5628
|
+
}
|
|
5629
|
+
//#endregion
|
|
5652
5630
|
//#region src/utils/db.ts
|
|
5653
5631
|
function isPG(connectionString) {
|
|
5654
5632
|
if (connectionString.startsWith("postgresql://") || connectionString.startsWith("postgres://")) return true;
|
|
@@ -5774,6 +5752,33 @@ const initAnalyticsStoreSql = [
|
|
|
5774
5752
|
//#region src/server.ts
|
|
5775
5753
|
const defaultLogger = childLogger(["reactor-api", "server"]);
|
|
5776
5754
|
const DEFAULT_PORT = 4e3;
|
|
5755
|
+
/**
|
|
5756
|
+
* Doc-perms require auth: with auth off no `user` is ever resolved, so every
|
|
5757
|
+
* authorization check fails closed. Refuse to boot rather than run broken.
|
|
5758
|
+
*/
|
|
5759
|
+
function assertAuthRequiredForDocumentPermissions(authEnabled, documentPermissionsRequested) {
|
|
5760
|
+
if (!authEnabled && documentPermissionsRequested) throw new Error("Document permissions require authentication: AUTH_ENABLED is false but document permissions were requested (DOCUMENT_PERMISSIONS_ENABLED=true or a documentPermissionService was provided). Enable authentication (AUTH_ENABLED=true, or auth.enabled in the config file) or disable document permissions.");
|
|
5761
|
+
}
|
|
5762
|
+
/**
|
|
5763
|
+
* Refuses SKIP_CREDENTIAL_VERIFICATION at boot outside tests or an explicit
|
|
5764
|
+
* opt-in: it removes the only binding between a token's claimed address and its
|
|
5765
|
+
* signing key. Fail-closed — unset NODE_ENV counts as production.
|
|
5766
|
+
*/
|
|
5767
|
+
function assertSkipCredentialVerificationAllowed(authEnabled, skipCredentialVerification, env) {
|
|
5768
|
+
if (!authEnabled || !skipCredentialVerification) return;
|
|
5769
|
+
const inAutomatedTest = env.VITEST === "true" || env.NODE_ENV === "test";
|
|
5770
|
+
const acknowledged = env.ALLOW_INSECURE_SKIP_CREDENTIAL_VERIFICATION === "true";
|
|
5771
|
+
if (!inAutomatedTest && !acknowledged) throw new Error("SKIP_CREDENTIAL_VERIFICATION is set but refused: it disables the live Renown credential check — the only check binding a token's claimed address to the key that signed it — so honoring it allows identity spoofing, including of admins. It is never safe in production. For local or sandbox use, also set ALLOW_INSECURE_SKIP_CREDENTIAL_VERIFICATION=true to acknowledge the risk; automated test runs (VITEST=true or NODE_ENV=test) are exempt.");
|
|
5772
|
+
}
|
|
5773
|
+
function createReadinessGate() {
|
|
5774
|
+
let ready = false;
|
|
5775
|
+
return {
|
|
5776
|
+
isReady: () => ready,
|
|
5777
|
+
markReady: () => {
|
|
5778
|
+
ready = true;
|
|
5779
|
+
}
|
|
5780
|
+
};
|
|
5781
|
+
}
|
|
5777
5782
|
function resolveAttachmentStoragePath(options) {
|
|
5778
5783
|
if (options.attachmentStoragePath) return options.attachmentStoragePath;
|
|
5779
5784
|
if (options.dbPath && !options.dbPath.startsWith("postgres")) return path.resolve(options.dbPath, "..", "attachments");
|
|
@@ -5812,11 +5817,8 @@ function makeDbClosers(knexInstance, pglite) {
|
|
|
5812
5817
|
/**
|
|
5813
5818
|
* Sets up the subgraph manager and registers subgraphs
|
|
5814
5819
|
*/
|
|
5815
|
-
async function setupGraphQLManager(httpAdapter, authFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger,
|
|
5816
|
-
const graphqlManager = new GraphQLManager(config.basePath, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, logger, httpAdapter, await createGatewayAdapter("apollo", logger), {
|
|
5817
|
-
enabled: auth?.enabled ?? false,
|
|
5818
|
-
admins: auth?.admins ?? []
|
|
5819
|
-
}, documentPermissionService, { enableDocumentModelSubgraphs }, port, authorizationService, reactorDriveClient);
|
|
5820
|
+
async function setupGraphQLManager(httpAdapter, authFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, authorizationService, authService, documentPermissionService, enableDocumentModelSubgraphs, port, reactorDriveClient) {
|
|
5821
|
+
const graphqlManager = new GraphQLManager(config.basePath, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, logger, httpAdapter, await createGatewayAdapter("apollo", logger), authService, documentPermissionService, { enableDocumentModelSubgraphs }, port, authorizationService, reactorDriveClient);
|
|
5820
5822
|
await graphqlManager.init(subgraphs.core, authFetchMiddleware);
|
|
5821
5823
|
for (const [, collection] of subgraphs.extended.entries()) for (const subgraph of collection) await graphqlManager.registerSubgraph(subgraph, "graphql");
|
|
5822
5824
|
await graphqlManager.updateRouter();
|
|
@@ -5890,6 +5892,7 @@ async function startServer(httpAdapter, port, httpsOptions, logger) {
|
|
|
5890
5892
|
async function _setupCommonInfrastructure(options) {
|
|
5891
5893
|
const port = options.port ?? DEFAULT_PORT;
|
|
5892
5894
|
const { adapter: httpAdapter } = await createHttpAdapter("express");
|
|
5895
|
+
const logger = options.logger ?? defaultLogger;
|
|
5893
5896
|
let admins = [];
|
|
5894
5897
|
let authEnabled = false;
|
|
5895
5898
|
if (options.configFile) {
|
|
@@ -5900,17 +5903,26 @@ async function _setupCommonInfrastructure(options) {
|
|
|
5900
5903
|
admins = options.auth.admins.map((a) => a.toLowerCase());
|
|
5901
5904
|
authEnabled = options.auth.enabled;
|
|
5902
5905
|
}
|
|
5903
|
-
const { AUTH_ENABLED, ADMINS, DEFAULT_PROTECTION, DOCUMENT_PERMISSIONS_ENABLED, SKIP_CREDENTIAL_VERIFICATION } = process.env;
|
|
5906
|
+
const { AUTH_ENABLED, ADMINS, DEFAULT_PROTECTION, DOCUMENT_PERMISSIONS_ENABLED, SKIP_CREDENTIAL_VERIFICATION, CREDENTIAL_VERIFICATION_CACHE_TTL_MS } = process.env;
|
|
5904
5907
|
if (AUTH_ENABLED !== void 0) authEnabled = AUTH_ENABLED === "true";
|
|
5905
5908
|
if (ADMINS !== void 0) admins = ADMINS.split(",").map((a) => a.toLowerCase());
|
|
5906
5909
|
let defaultProtection = false;
|
|
5907
5910
|
if (DEFAULT_PROTECTION !== void 0) defaultProtection = DEFAULT_PROTECTION.toLowerCase() === "true";
|
|
5908
|
-
const { USERS, GUESTS, FREE_ENTRY } = process.env;
|
|
5909
|
-
if (USERS || GUESTS || FREE_ENTRY) console.warn("[DEPRECATION WARNING] The USERS, GUESTS, and FREE_ENTRY environment variables are no longer supported. Access control is now managed per-document via the DocumentProtection system. Use DEFAULT_PROTECTION=true for strict mode, or manage protection per document via the GraphQL API. See the auth documentation for migration guidance.");
|
|
5910
5911
|
let skipCredentialVerification = false;
|
|
5911
5912
|
if (SKIP_CREDENTIAL_VERIFICATION !== void 0) skipCredentialVerification = SKIP_CREDENTIAL_VERIFICATION === "true";
|
|
5912
|
-
|
|
5913
|
+
let credentialVerificationCacheTtlMs;
|
|
5914
|
+
if (CREDENTIAL_VERIFICATION_CACHE_TTL_MS !== void 0) {
|
|
5915
|
+
const parsed = Number(CREDENTIAL_VERIFICATION_CACHE_TTL_MS);
|
|
5916
|
+
if (CREDENTIAL_VERIFICATION_CACHE_TTL_MS.trim() !== "" && Number.isFinite(parsed) && parsed >= 0) credentialVerificationCacheTtlMs = parsed;
|
|
5917
|
+
else logger.warn(`Ignoring invalid CREDENTIAL_VERIFICATION_CACHE_TTL_MS="${CREDENTIAL_VERIFICATION_CACHE_TTL_MS}" (expected a non-negative number of milliseconds; 0 disables caching) — using the default TTL`);
|
|
5918
|
+
}
|
|
5919
|
+
const documentPermissionsRequested = options.documentPermissionService !== void 0 || DOCUMENT_PERMISSIONS_ENABLED === "true";
|
|
5920
|
+
assertAuthRequiredForDocumentPermissions(authEnabled, documentPermissionsRequested);
|
|
5921
|
+
assertSkipCredentialVerificationAllowed(authEnabled, skipCredentialVerification, process.env);
|
|
5922
|
+
if (authEnabled && skipCredentialVerification) logger.warn("SECURITY: SKIP_CREDENTIAL_VERIFICATION is enabled — Renown credential verification is disabled and a bearer token's claimed address is NOT cryptographically bound to its signing key. Identity is unverifiable; use only in development or test.");
|
|
5913
5923
|
httpAdapter.getRoute("/health", () => new Response("OK", { status: 200 }));
|
|
5924
|
+
const readiness = createReadinessGate();
|
|
5925
|
+
httpAdapter.getRoute("/ready", () => readiness.isReady() ? new Response("OK", { status: 200 }) : new Response("starting", { status: 503 }));
|
|
5914
5926
|
const explorerPrefix = `${config.basePath}/explorer`;
|
|
5915
5927
|
httpAdapter.getRoute(`${explorerPrefix}/:endpoint?`, (request) => {
|
|
5916
5928
|
const url = new URL(request.url);
|
|
@@ -5926,7 +5938,8 @@ async function _setupCommonInfrastructure(options) {
|
|
|
5926
5938
|
authService = new AuthService({
|
|
5927
5939
|
enabled: authEnabled,
|
|
5928
5940
|
admins,
|
|
5929
|
-
skipCredentialVerification
|
|
5941
|
+
skipCredentialVerification,
|
|
5942
|
+
credentialVerificationCacheTtlMs
|
|
5930
5943
|
});
|
|
5931
5944
|
authFetchMiddleware = createAuthFetchMiddleware(authService);
|
|
5932
5945
|
}
|
|
@@ -5942,14 +5955,12 @@ async function _setupCommonInfrastructure(options) {
|
|
|
5942
5955
|
documentPermissionService = new DocumentPermissionService(db, { defaultProtection });
|
|
5943
5956
|
logger.info("Document permission service initialized");
|
|
5944
5957
|
}
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
logger.info("Authorization service initialized");
|
|
5952
|
-
}
|
|
5958
|
+
const policy = documentPermissionService ? AuthorizationPolicy.DOCUMENT_PERMISSIONS : authEnabled ? AuthorizationPolicy.ADMIN_ONLY : AuthorizationPolicy.OPEN;
|
|
5959
|
+
const authorizationConfig = {
|
|
5960
|
+
admins,
|
|
5961
|
+
defaultProtection,
|
|
5962
|
+
policy
|
|
5963
|
+
};
|
|
5953
5964
|
const attachmentStoragePath = resolveAttachmentStoragePath(options);
|
|
5954
5965
|
await mkdir(attachmentStoragePath, { recursive: true });
|
|
5955
5966
|
const { db: attachmentDb, knex: attachmentKnex, pglite: attachmentPglite } = getDbClient(options.dbPath, options.pgliteFactory);
|
|
@@ -5969,23 +5980,20 @@ async function _setupCommonInfrastructure(options) {
|
|
|
5969
5980
|
httpAdapter,
|
|
5970
5981
|
authFetchMiddleware,
|
|
5971
5982
|
authService,
|
|
5972
|
-
auth: {
|
|
5973
|
-
enabled: authEnabled,
|
|
5974
|
-
admins
|
|
5975
|
-
},
|
|
5976
5983
|
relationalDb,
|
|
5977
5984
|
analyticsStore,
|
|
5978
5985
|
documentPermissionService,
|
|
5979
|
-
|
|
5986
|
+
authorizationConfig,
|
|
5980
5987
|
attachments,
|
|
5981
5988
|
packages,
|
|
5982
|
-
dbClosers
|
|
5989
|
+
dbClosers,
|
|
5990
|
+
readiness
|
|
5983
5991
|
};
|
|
5984
5992
|
}
|
|
5985
5993
|
/**
|
|
5986
5994
|
* Private helper function containing common setup logic for API initialization
|
|
5987
5995
|
*/
|
|
5988
|
-
async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options,
|
|
5996
|
+
async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, readModels, attachments, authorizationConfig, documentModelRegistry, dbClosers = [], reactorDriveClient) {
|
|
5989
5997
|
const hostModule = {
|
|
5990
5998
|
relationalDb,
|
|
5991
5999
|
analyticsStore,
|
|
@@ -6030,6 +6038,8 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6030
6038
|
}))).flat());
|
|
6031
6039
|
}
|
|
6032
6040
|
const { httpServer, wsServer } = await startServer(httpAdapter, port, options.https, logger);
|
|
6041
|
+
const authorizationService = createAuthorizationService(authorizationConfig, documentPermissionService, createGetParentIdsFn(reactorClient));
|
|
6042
|
+
logger.info(`Authorization service initialized (policy: ${authorizationConfig.policy})`);
|
|
6033
6043
|
const coreSubgraphs = DefaultCoreSubgraphs.slice();
|
|
6034
6044
|
coreSubgraphs.push(ReactorSubgraph);
|
|
6035
6045
|
if (documentPermissionService) {
|
|
@@ -6039,12 +6049,13 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6039
6049
|
const graphqlManager = await setupGraphQLManager(httpAdapter, authFetchMiddleware, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, {
|
|
6040
6050
|
extended: subgraphs,
|
|
6041
6051
|
core: coreSubgraphs
|
|
6042
|
-
}, logger.child(["graphql-manager"]),
|
|
6052
|
+
}, logger.child(["graphql-manager"]), authorizationService, authService, documentPermissionService, options.enableDocumentModelSubgraphs, port, reactorDriveClient);
|
|
6043
6053
|
setupEventListeners(packages, graphqlManager, reactorProcessorManager, hostModule, documentModelRegistry);
|
|
6044
6054
|
if (mcpServerEnabled) {
|
|
6045
6055
|
await setupMcpServer({
|
|
6046
6056
|
client: reactorClient,
|
|
6047
|
-
syncManager
|
|
6057
|
+
syncManager,
|
|
6058
|
+
authorizeRequest: createMcpRequestAuthorizer(authService, authorizationService)
|
|
6048
6059
|
}, httpAdapter);
|
|
6049
6060
|
logger.info(`MCP server available at http://localhost:${port}/mcp`);
|
|
6050
6061
|
}
|
|
@@ -6101,7 +6112,7 @@ function buildApiDispose(args) {
|
|
|
6101
6112
|
};
|
|
6102
6113
|
}
|
|
6103
6114
|
async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
6104
|
-
const { port, httpAdapter, authFetchMiddleware, authService,
|
|
6115
|
+
const { port, httpAdapter, authFetchMiddleware, authService, relationalDb, analyticsStore, documentPermissionService, authorizationConfig, attachments, packages, dbClosers, readiness } = await _setupCommonInfrastructure(options);
|
|
6105
6116
|
const { documentModels, processors, subgraphs } = await packages.init();
|
|
6106
6117
|
const { module: reactorClientModule, reactorDriveClient } = await clientInitializer(documentModels);
|
|
6107
6118
|
const reactorClient = reactorClientModule.client;
|
|
@@ -6112,10 +6123,11 @@ async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
|
6112
6123
|
const documentModelRegistry = reactorClientModule.reactorModule?.documentModelRegistry;
|
|
6113
6124
|
if (!documentModelRegistry) throw new Error("DocumentModelRegistry not available from ReactorClientModule");
|
|
6114
6125
|
return {
|
|
6115
|
-
...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options,
|
|
6126
|
+
...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, (reactorClientModule.reactorModule?.readModelCoordinator)?.readModels ?? [], attachments, authorizationConfig, documentModelRegistry, dbClosers, reactorDriveClient),
|
|
6116
6127
|
client: reactorClient,
|
|
6117
6128
|
syncManager,
|
|
6118
|
-
documentModelRegistry
|
|
6129
|
+
documentModelRegistry,
|
|
6130
|
+
readiness
|
|
6119
6131
|
};
|
|
6120
6132
|
}
|
|
6121
6133
|
//#endregion
|
|
@@ -6219,7 +6231,7 @@ var PackageManagementService = class {
|
|
|
6219
6231
|
}
|
|
6220
6232
|
};
|
|
6221
6233
|
//#endregion
|
|
6222
|
-
export { ADMIN_USERS, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph,
|
|
6234
|
+
export { ADMIN_USERS, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AuthService, AuthSubgraph, AuthorizationPolicy, AuthorizedDocumentHandle, BaseSubgraph, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, FindDocumentsDocument, GetDocumentDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, ImportPackageLoader, InMemoryPackageStorage, JobChangesDocument, MoveRelationshipDocument, MutateDocumentAsyncDocument, MutateDocumentDocument, OperationContextInputSchema, OperationInputSchema, OperationWithContextInputSchema, OperationsFilterInputSchema, PackageManagementService, PackageManager, PackagesSubgraph, PagingInputSchema, PhDocumentFieldsFragmentDoc, PollSyncEnvelopesDocument, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RemoteCursorInputSchema, RemoteFilterInputSchema, RemoveRelationshipDocument, RenameDocumentDocument, SearchFilterInputSchema, SetPreferredEditorDocument, SyncEnvelopeInputSchema, SyncEnvelopeType, SyncEnvelopeTypeSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, ViewFilterInputSchema, assertAuthRequiredForDocumentPermissions, assertSkipCredentialVerificationAllowed, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, createAuthFetchMiddleware, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createSchema, definedNonNullAnySchema, driveIdFromUrl, extractSubgraphsFromModule, generateDocumentModelSchema, getAuthContext, getDbClient, getDocumentModelSchemaName, getDocumentModelTypeDefs, getGitHash, getGitUrl, getSdk, getUniqueDocumentModels, getVersion, initAnalyticsStoreSql, initializeAndStartAPI, isDefinedNonNullAny, isExpectedLoaderMiss, isSubgraphClass, parseDriveUrl, renderGraphqlPlayground };
|
|
6223
6235
|
|
|
6224
6236
|
//# sourceMappingURL=index.mjs.map
|
|
6225
|
-
//# debugId=
|
|
6237
|
+
//# debugId=cae89b22-c317-5acb-b1ec-dc325f23f6cf
|