@powerhousedao/reactor-api 6.2.0-dev.3 → 6.2.0-dev.30
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/{adapter-gateway-apollo-IZuGzFaY.mjs → adapter-gateway-apollo-CxV_hMTv.mjs} +27 -5
- package/dist/adapter-gateway-apollo-CxV_hMTv.mjs.map +1 -0
- package/dist/index.d.mts +158 -400
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +667 -911
- 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-iibOQ50e.mjs +543 -0
- package/dist/utils-iibOQ50e.mjs.map +1 -0
- package/package.json +28 -16
- package/dist/adapter-gateway-apollo-IZuGzFaY.mjs.map +0 -1
- package/dist/utils-BFkbSO_H.mjs +0 -296
- package/dist/utils-BFkbSO_H.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
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]="88c66104-0cca-57b8-b3ad-93532b51c2b3")}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-iibOQ50e.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";
|
|
7
7
|
import { GraphQLError, Kind, parse, print } from "graphql";
|
|
8
|
+
import { DEFAULT_DRIVE_CONTAINER_TYPES, DriveCollectionId, PropagationMode as PropagationMode$1, consolidateSyncOperations, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl } from "@powerhousedao/reactor";
|
|
8
9
|
import { ConsoleLogger, childLogger, documentModelDocumentModelModule } from "document-model";
|
|
9
10
|
import path from "node:path";
|
|
10
11
|
import { match } from "path-to-regexp";
|
|
11
|
-
import { verifyAuthBearerToken } from "@renown/sdk";
|
|
12
12
|
import { buildSubgraphSchema } from "@apollo/subgraph";
|
|
13
13
|
import { typeDefs } from "@powerhousedao/document-engineering/graphql";
|
|
14
14
|
import { camelCase, kebabCase, pascalCase } from "change-case";
|
|
15
15
|
import { GraphQLJSONObject } from "graphql-type-json";
|
|
16
16
|
import { setName } from "@powerhousedao/shared/document-model";
|
|
17
|
-
import { DEFAULT_DRIVE_CONTAINER_TYPES, 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";
|
|
@@ -70,141 +70,70 @@ var AnalyticsSubgraph = class extends BaseSubgraph {
|
|
|
70
70
|
};
|
|
71
71
|
//#endregion
|
|
72
72
|
//#region src/graphql/auth/schema.graphql
|
|
73
|
-
var schema_default$2 = "# Auth Subgraph Schema\n# Contains all document permission and authorization related types\n\nscalar DateTime\n\n# Permission levels for documents\nenum DocumentPermissionLevel {\n # Can fetch and read the document\n READ\n # Can push updates and modify the document\n WRITE\n # Can manage document permissions and settings\n ADMIN\n}\n\n# Document permission entry\ntype DocumentPermissionEntry {\n documentId: String!\n userAddress: String!\n permission: DocumentPermissionLevel!\n grantedBy: String!\n createdAt: DateTime!\n updatedAt: DateTime!\n}\n\n#
|
|
73
|
+
var schema_default$2 = "# Auth Subgraph Schema\n# Contains all document permission and authorization related types\n\nscalar DateTime\n\n# Permission levels for documents\nenum DocumentPermissionLevel {\n # Can fetch and read the document\n READ\n # Can push updates and modify the document\n WRITE\n # Can manage document permissions and settings\n ADMIN\n}\n\n# Document permission entry\ntype DocumentPermissionEntry {\n documentId: String!\n userAddress: String!\n permission: DocumentPermissionLevel!\n grantedBy: String!\n createdAt: DateTime!\n updatedAt: DateTime!\n}\n\n# Operation user permission entry\ntype OperationUserPermission {\n documentId: String!\n operationType: String!\n userAddress: String!\n grantedBy: String!\n createdAt: DateTime!\n}\n\n# Combined document access info\ntype DocumentAccessInfo {\n documentId: String!\n permissions: [DocumentPermissionEntry!]!\n}\n\n# Operation permissions info\ntype OperationPermissionsInfo {\n documentId: String!\n operationType: String!\n userPermissions: [OperationUserPermission!]!\n}\n\n# Document protection info\ntype DocumentProtectionInfo {\n documentId: String!\n protected: Boolean!\n ownerAddress: String\n}\n\ntype Query {\n # Get permissions for a document (requires ADMIN permission or global admin)\n documentAccess(documentId: String!): DocumentAccessInfo!\n\n # Get protection status for a document (requires ADMIN permission or global admin)\n documentProtection(documentId: String!): DocumentProtectionInfo!\n\n # Get all documents the current user has explicit access to\n userDocumentPermissions: [DocumentPermissionEntry!]!\n\n # Get operation permissions for a document and operation type\n # (requires ADMIN permission or global admin)\n operationPermissions(\n documentId: String!\n operationType: String!\n ): OperationPermissionsInfo!\n\n # Check if the current user can execute a specific operation on a document\n canExecuteOperation(documentId: String!, operationType: String!): Boolean!\n}\n\ntype Mutation {\n # Set document protection status (requires ADMIN permission or global admin)\n setDocumentProtection(\n documentId: String!\n protected: Boolean!\n ): DocumentProtectionInfo!\n\n # Transfer document ownership (requires ADMIN permission or global admin)\n transferDocumentOwnership(\n documentId: String!\n newOwnerAddress: String!\n ): DocumentProtectionInfo!\n\n # Grant a user permission on a document (requires ADMIN permission or global admin)\n grantDocumentPermission(\n documentId: String!\n userAddress: String!\n permission: DocumentPermissionLevel!\n ): DocumentPermissionEntry!\n\n # Revoke a user's permission on a document (requires ADMIN permission or global admin)\n revokeDocumentPermission(documentId: String!, userAddress: String!): Boolean!\n\n # --- Operation Permissions ---\n\n # Grant a user permission to execute an operation on a document\n grantOperationPermission(\n documentId: String!\n operationType: String!\n userAddress: String!\n ): OperationUserPermission!\n\n # Revoke a user's permission to execute an operation\n revokeOperationPermission(\n documentId: String!\n operationType: String!\n userAddress: String!\n ): Boolean!\n}\n";
|
|
74
74
|
//#endregion
|
|
75
75
|
//#region src/graphql/auth/resolvers.ts
|
|
76
|
-
async function documentAccess(service, args) {
|
|
76
|
+
async function documentAccess(service, authorizationService, args, userAddress) {
|
|
77
|
+
if (!userAddress) throw new GraphQLError("Authentication required");
|
|
78
|
+
if (!await authorizationService.canManage(args.documentId, userAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to view its permissions");
|
|
77
79
|
const permissions = await service.getDocumentPermissions(args.documentId);
|
|
78
|
-
const groupPermissions = await service.getDocumentGroupPermissions(args.documentId);
|
|
79
80
|
return {
|
|
80
81
|
documentId: args.documentId,
|
|
81
|
-
permissions
|
|
82
|
-
groupPermissions
|
|
82
|
+
permissions
|
|
83
83
|
};
|
|
84
84
|
}
|
|
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
|
}
|
|
103
|
-
async function
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
async function group(service, args) {
|
|
107
|
-
return service.getGroup(args.id);
|
|
108
|
-
}
|
|
109
|
-
async function userGroups(service, args) {
|
|
110
|
-
return service.getUserGroups(args.userAddress);
|
|
111
|
-
}
|
|
112
|
-
async function createGroup(service, args) {
|
|
113
|
-
return service.createGroup(args.name, args.description ?? void 0);
|
|
114
|
-
}
|
|
115
|
-
async function deleteGroup(service, args) {
|
|
116
|
-
await service.deleteGroup(args.id);
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
async function addUserToGroup(service, args) {
|
|
120
|
-
await service.addUserToGroup(args.userAddress, args.groupId);
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
async function removeUserFromGroup(service, args) {
|
|
124
|
-
await service.removeUserFromGroup(args.userAddress, args.groupId);
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
async function getGroupMembers(service, groupId) {
|
|
128
|
-
return service.getGroupMembers(groupId);
|
|
129
|
-
}
|
|
130
|
-
async function grantGroupPermission(service, args, grantedByAddress, isGlobalAdmin) {
|
|
131
|
-
if (!grantedByAddress) throw new GraphQLError("Authentication required");
|
|
132
|
-
if (!isGlobalAdmin) {
|
|
133
|
-
if (!await service.canManageDocument(args.documentId, grantedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to grant permissions");
|
|
134
|
-
}
|
|
135
|
-
return service.grantGroupPermission(args.documentId, args.groupId, args.permission, grantedByAddress);
|
|
136
|
-
}
|
|
137
|
-
async function revokeGroupPermission(service, args, revokedByAddress, isGlobalAdmin) {
|
|
138
|
-
if (!revokedByAddress) throw new GraphQLError("Authentication required");
|
|
139
|
-
if (!isGlobalAdmin) {
|
|
140
|
-
if (!await service.canManageDocument(args.documentId, revokedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to revoke permissions");
|
|
141
|
-
}
|
|
142
|
-
await service.revokeGroupPermission(args.documentId, args.groupId);
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
async function operationPermissions(service, args) {
|
|
99
|
+
async function operationPermissions(service, authorizationService, args, userAddress) {
|
|
100
|
+
if (!userAddress) throw new GraphQLError("Authentication required");
|
|
101
|
+
if (!await authorizationService.canManage(args.documentId, userAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to view its operation permissions");
|
|
146
102
|
const userPermissions = await service.getOperationUserPermissions(args.documentId, args.operationType);
|
|
147
|
-
const groupPermissions = await service.getOperationGroupPermissions(args.documentId, args.operationType);
|
|
148
103
|
return {
|
|
149
104
|
documentId: args.documentId,
|
|
150
105
|
operationType: args.operationType,
|
|
151
|
-
userPermissions
|
|
152
|
-
groupPermissions
|
|
106
|
+
userPermissions
|
|
153
107
|
};
|
|
154
108
|
}
|
|
155
|
-
async function canExecuteOperation(
|
|
156
|
-
return
|
|
109
|
+
async function canExecuteOperation(authorizationService, args, userAddress) {
|
|
110
|
+
return authorizationService.canMutate(args.documentId, args.operationType, userAddress);
|
|
157
111
|
}
|
|
158
|
-
async function grantOperationPermission(service, args, grantedByAddress
|
|
112
|
+
async function grantOperationPermission(service, authorizationService, args, grantedByAddress) {
|
|
159
113
|
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
|
-
}
|
|
114
|
+
if (!await authorizationService.canManage(args.documentId, grantedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to grant operation permissions");
|
|
163
115
|
return service.grantOperationPermission(args.documentId, args.operationType, args.userAddress, grantedByAddress);
|
|
164
116
|
}
|
|
165
|
-
async function revokeOperationPermission(service, args, revokedByAddress
|
|
117
|
+
async function revokeOperationPermission(service, authorizationService, args, revokedByAddress) {
|
|
166
118
|
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
|
-
}
|
|
119
|
+
if (!await authorizationService.canManage(args.documentId, revokedByAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to revoke operation permissions");
|
|
170
120
|
await service.revokeOperationPermission(args.documentId, args.operationType, args.userAddress);
|
|
171
121
|
return true;
|
|
172
122
|
}
|
|
173
|
-
async function
|
|
174
|
-
if (!
|
|
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
|
-
}
|
|
178
|
-
return service.grantGroupOperationPermission(args.documentId, args.operationType, args.groupId, grantedByAddress);
|
|
179
|
-
}
|
|
180
|
-
async function revokeGroupOperationPermission(service, args, revokedByAddress, isGlobalAdmin) {
|
|
181
|
-
if (!revokedByAddress) throw new GraphQLError("Authentication required");
|
|
182
|
-
if (!isGlobalAdmin) {
|
|
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
|
-
}
|
|
185
|
-
await service.revokeGroupOperationPermission(args.documentId, args.operationType, args.groupId);
|
|
186
|
-
return true;
|
|
187
|
-
}
|
|
188
|
-
async function documentProtection(service, args) {
|
|
123
|
+
async function documentProtection(service, authorizationService, args, userAddress) {
|
|
124
|
+
if (!userAddress) throw new GraphQLError("Authentication required");
|
|
125
|
+
if (!await authorizationService.canManage(args.documentId, userAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to view its protection info");
|
|
189
126
|
return service.getDocumentProtection(args.documentId);
|
|
190
127
|
}
|
|
191
|
-
async function setDocumentProtection(service, authorizationService, args, userAddress
|
|
128
|
+
async function setDocumentProtection(service, authorizationService, args, userAddress) {
|
|
192
129
|
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
|
-
}
|
|
130
|
+
if (!await authorizationService.canManage(args.documentId, userAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to change protection");
|
|
198
131
|
await service.setDocumentProtection(args.documentId, args.protected);
|
|
199
132
|
return service.getDocumentProtection(args.documentId);
|
|
200
133
|
}
|
|
201
|
-
async function transferDocumentOwnership(service, authorizationService, args, userAddress
|
|
134
|
+
async function transferDocumentOwnership(service, authorizationService, args, userAddress) {
|
|
202
135
|
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
|
-
}
|
|
136
|
+
if (!await authorizationService.canManage(args.documentId, userAddress)) throw new GraphQLError("Forbidden: You must be an admin of this document to transfer ownership");
|
|
208
137
|
const previousOwner = await service.getDocumentOwner(args.documentId);
|
|
209
138
|
if (previousOwner) await service.revokePermission(args.documentId, previousOwner);
|
|
210
139
|
await service.setDocumentOwner(args.documentId, args.newOwnerAddress);
|
|
@@ -219,8 +148,7 @@ async function transferDocumentOwnership(service, authorizationService, args, us
|
|
|
219
148
|
* This subgraph is conditionally registered based on the DOCUMENT_PERMISSIONS_ENABLED
|
|
220
149
|
* feature flag. When enabled, it provides GraphQL operations for:
|
|
221
150
|
* - Document permissions (grant/revoke user access)
|
|
222
|
-
* -
|
|
223
|
-
* - Group document permissions (grant/revoke group access)
|
|
151
|
+
* - Document protection and ownership
|
|
224
152
|
* - Operation-level permissions (fine-grained operation control)
|
|
225
153
|
*/
|
|
226
154
|
var AuthSubgraph = class extends BaseSubgraph {
|
|
@@ -234,11 +162,11 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
234
162
|
typeDefs = gql(schema_default$2);
|
|
235
163
|
resolvers = {
|
|
236
164
|
Query: {
|
|
237
|
-
documentAccess: async (_parent, args) => {
|
|
165
|
+
documentAccess: async (_parent, args, ctx) => {
|
|
238
166
|
this.logger.debug("documentAccess(@args)", args);
|
|
239
167
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
240
168
|
try {
|
|
241
|
-
return await documentAccess(this.documentPermissionService, args);
|
|
169
|
+
return await documentAccess(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
242
170
|
} catch (error) {
|
|
243
171
|
this.logger.error("Error in documentAccess: @error", error);
|
|
244
172
|
throw error;
|
|
@@ -255,41 +183,11 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
255
183
|
throw error;
|
|
256
184
|
}
|
|
257
185
|
},
|
|
258
|
-
|
|
259
|
-
this.logger.debug("groups");
|
|
260
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
261
|
-
try {
|
|
262
|
-
return await groups(this.documentPermissionService);
|
|
263
|
-
} catch (error) {
|
|
264
|
-
this.logger.error("Error in groups: @error", error);
|
|
265
|
-
throw error;
|
|
266
|
-
}
|
|
267
|
-
},
|
|
268
|
-
group: async (_parent, args) => {
|
|
269
|
-
this.logger.debug("group(@args)", args);
|
|
270
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
271
|
-
try {
|
|
272
|
-
return await group(this.documentPermissionService, args);
|
|
273
|
-
} catch (error) {
|
|
274
|
-
this.logger.error("Error in group: @error", error);
|
|
275
|
-
throw error;
|
|
276
|
-
}
|
|
277
|
-
},
|
|
278
|
-
userGroups: async (_parent, args) => {
|
|
279
|
-
this.logger.debug("userGroups(@args)", args);
|
|
280
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
281
|
-
try {
|
|
282
|
-
return await userGroups(this.documentPermissionService, args);
|
|
283
|
-
} catch (error) {
|
|
284
|
-
this.logger.error("Error in userGroups: @error", error);
|
|
285
|
-
throw error;
|
|
286
|
-
}
|
|
287
|
-
},
|
|
288
|
-
operationPermissions: async (_parent, args) => {
|
|
186
|
+
operationPermissions: async (_parent, args, ctx) => {
|
|
289
187
|
this.logger.debug("operationPermissions(@args)", args);
|
|
290
188
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
291
189
|
try {
|
|
292
|
-
return await operationPermissions(this.documentPermissionService, args);
|
|
190
|
+
return await operationPermissions(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
293
191
|
} catch (error) {
|
|
294
192
|
this.logger.error("Error in operationPermissions: @error", error);
|
|
295
193
|
throw error;
|
|
@@ -297,9 +195,8 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
297
195
|
},
|
|
298
196
|
canExecuteOperation: async (_parent, args, ctx) => {
|
|
299
197
|
this.logger.debug("canExecuteOperation(@args)", args);
|
|
300
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
301
198
|
try {
|
|
302
|
-
return await canExecuteOperation(this.
|
|
199
|
+
return await canExecuteOperation(this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
303
200
|
} catch (error) {
|
|
304
201
|
this.logger.error("Error in canExecuteOperation: @error", error);
|
|
305
202
|
throw error;
|
|
@@ -308,9 +205,8 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
308
205
|
documentProtection: async (_parent, args, ctx) => {
|
|
309
206
|
this.logger.debug("documentProtection(@args)", args);
|
|
310
207
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
311
|
-
if (!ctx.user?.address) throw new GraphQLError("Authentication required to view document protection info");
|
|
312
208
|
try {
|
|
313
|
-
return await documentProtection(this.documentPermissionService, args);
|
|
209
|
+
return await documentProtection(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
314
210
|
} catch (error) {
|
|
315
211
|
this.logger.error("Error in documentProtection: @error", error);
|
|
316
212
|
throw error;
|
|
@@ -322,8 +218,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
322
218
|
this.logger.debug("setDocumentProtection(@args)", args);
|
|
323
219
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
324
220
|
try {
|
|
325
|
-
|
|
326
|
-
return await setDocumentProtection(this.documentPermissionService, this.authorizationService, args, ctx.user?.address, isGlobalAdmin);
|
|
221
|
+
return await setDocumentProtection(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
327
222
|
} catch (error) {
|
|
328
223
|
this.logger.error("Error in setDocumentProtection: @error", error);
|
|
329
224
|
throw error;
|
|
@@ -333,8 +228,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
333
228
|
this.logger.debug("transferDocumentOwnership(@args)", args);
|
|
334
229
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
335
230
|
try {
|
|
336
|
-
|
|
337
|
-
return await transferDocumentOwnership(this.documentPermissionService, this.authorizationService, args, ctx.user?.address, isGlobalAdmin);
|
|
231
|
+
return await transferDocumentOwnership(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
338
232
|
} catch (error) {
|
|
339
233
|
this.logger.error("Error in transferDocumentOwnership: @error", error);
|
|
340
234
|
throw error;
|
|
@@ -344,8 +238,11 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
344
238
|
this.logger.debug("grantDocumentPermission(@args)", args);
|
|
345
239
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
346
240
|
try {
|
|
347
|
-
const
|
|
348
|
-
return await grantDocumentPermission(this.documentPermissionService,
|
|
241
|
+
const resolved = await this.withCanonicalDocumentId(args, ctx);
|
|
242
|
+
return await grantDocumentPermission(this.documentPermissionService, this.authorizationService, {
|
|
243
|
+
...resolved,
|
|
244
|
+
permission: resolved.permission
|
|
245
|
+
}, ctx.user?.address);
|
|
349
246
|
} catch (error) {
|
|
350
247
|
this.logger.error("Error in grantDocumentPermission: @error", error);
|
|
351
248
|
throw error;
|
|
@@ -355,81 +252,17 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
355
252
|
this.logger.debug("revokeDocumentPermission(@args)", args);
|
|
356
253
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
357
254
|
try {
|
|
358
|
-
|
|
359
|
-
return await revokeDocumentPermission(this.documentPermissionService, args, ctx.user?.address, isGlobalAdmin);
|
|
255
|
+
return await revokeDocumentPermission(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
360
256
|
} catch (error) {
|
|
361
257
|
this.logger.error("Error in revokeDocumentPermission: @error", error);
|
|
362
258
|
throw error;
|
|
363
259
|
}
|
|
364
260
|
},
|
|
365
|
-
createGroup: async (_parent, args) => {
|
|
366
|
-
this.logger.debug("createGroup(@args)", args);
|
|
367
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
368
|
-
try {
|
|
369
|
-
return await createGroup(this.documentPermissionService, args);
|
|
370
|
-
} catch (error) {
|
|
371
|
-
this.logger.error("Error in createGroup: @error", error);
|
|
372
|
-
throw error;
|
|
373
|
-
}
|
|
374
|
-
},
|
|
375
|
-
deleteGroup: async (_parent, args) => {
|
|
376
|
-
this.logger.debug("deleteGroup(@args)", args);
|
|
377
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
378
|
-
try {
|
|
379
|
-
return await deleteGroup(this.documentPermissionService, args);
|
|
380
|
-
} catch (error) {
|
|
381
|
-
this.logger.error("Error in deleteGroup: @error", error);
|
|
382
|
-
throw error;
|
|
383
|
-
}
|
|
384
|
-
},
|
|
385
|
-
addUserToGroup: async (_parent, args) => {
|
|
386
|
-
this.logger.debug("addUserToGroup(@args)", args);
|
|
387
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
388
|
-
try {
|
|
389
|
-
return await addUserToGroup(this.documentPermissionService, args);
|
|
390
|
-
} catch (error) {
|
|
391
|
-
this.logger.error("Error in addUserToGroup: @error", error);
|
|
392
|
-
throw error;
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
|
-
removeUserFromGroup: async (_parent, args) => {
|
|
396
|
-
this.logger.debug("removeUserFromGroup(@args)", args);
|
|
397
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
398
|
-
try {
|
|
399
|
-
return await removeUserFromGroup(this.documentPermissionService, args);
|
|
400
|
-
} catch (error) {
|
|
401
|
-
this.logger.error("Error in removeUserFromGroup: @error", error);
|
|
402
|
-
throw error;
|
|
403
|
-
}
|
|
404
|
-
},
|
|
405
|
-
grantGroupPermission: async (_parent, args, ctx) => {
|
|
406
|
-
this.logger.debug("grantGroupPermission(@args)", args);
|
|
407
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
408
|
-
try {
|
|
409
|
-
const isGlobalAdmin = ctx.isAdmin?.(ctx.user?.address ?? "") ?? false;
|
|
410
|
-
return await grantGroupPermission(this.documentPermissionService, args, ctx.user?.address, isGlobalAdmin);
|
|
411
|
-
} catch (error) {
|
|
412
|
-
this.logger.error("Error in grantGroupPermission: @error", error);
|
|
413
|
-
throw error;
|
|
414
|
-
}
|
|
415
|
-
},
|
|
416
|
-
revokeGroupPermission: async (_parent, args, ctx) => {
|
|
417
|
-
this.logger.debug("revokeGroupPermission(@args)", args);
|
|
418
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
419
|
-
try {
|
|
420
|
-
const isGlobalAdmin = ctx.isAdmin?.(ctx.user?.address ?? "") ?? false;
|
|
421
|
-
return await revokeGroupPermission(this.documentPermissionService, args, ctx.user?.address, isGlobalAdmin);
|
|
422
|
-
} catch (error) {
|
|
423
|
-
this.logger.error("Error in revokeGroupPermission: @error", error);
|
|
424
|
-
throw error;
|
|
425
|
-
}
|
|
426
|
-
},
|
|
427
261
|
grantOperationPermission: async (_parent, args, ctx) => {
|
|
428
262
|
this.logger.debug("grantOperationPermission(@args)", args);
|
|
429
263
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
430
264
|
try {
|
|
431
|
-
|
|
432
|
-
return await grantOperationPermission(this.documentPermissionService, args, ctx.user?.address, isGlobalAdmin);
|
|
265
|
+
return await grantOperationPermission(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
433
266
|
} catch (error) {
|
|
434
267
|
this.logger.error("Error in grantOperationPermission: @error", error);
|
|
435
268
|
throw error;
|
|
@@ -439,52 +272,13 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
439
272
|
this.logger.debug("revokeOperationPermission(@args)", args);
|
|
440
273
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
441
274
|
try {
|
|
442
|
-
|
|
443
|
-
return await revokeOperationPermission(this.documentPermissionService, args, ctx.user?.address, isGlobalAdmin);
|
|
275
|
+
return await revokeOperationPermission(this.documentPermissionService, this.authorizationService, await this.withCanonicalDocumentId(args, ctx), ctx.user?.address);
|
|
444
276
|
} catch (error) {
|
|
445
277
|
this.logger.error("Error in revokeOperationPermission: @error", error);
|
|
446
278
|
throw error;
|
|
447
279
|
}
|
|
448
|
-
},
|
|
449
|
-
grantGroupOperationPermission: async (_parent, args, ctx) => {
|
|
450
|
-
this.logger.debug("grantGroupOperationPermission(@args)", args);
|
|
451
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
452
|
-
try {
|
|
453
|
-
const isGlobalAdmin = ctx.isAdmin?.(ctx.user?.address ?? "") ?? false;
|
|
454
|
-
return await grantGroupOperationPermission(this.documentPermissionService, args, ctx.user?.address, isGlobalAdmin);
|
|
455
|
-
} catch (error) {
|
|
456
|
-
this.logger.error("Error in grantGroupOperationPermission: @error", error);
|
|
457
|
-
throw error;
|
|
458
|
-
}
|
|
459
|
-
},
|
|
460
|
-
revokeGroupOperationPermission: async (_parent, args, ctx) => {
|
|
461
|
-
this.logger.debug("revokeGroupOperationPermission(@args)", args);
|
|
462
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
463
|
-
try {
|
|
464
|
-
const isGlobalAdmin = ctx.isAdmin?.(ctx.user?.address ?? "") ?? false;
|
|
465
|
-
return await revokeGroupOperationPermission(this.documentPermissionService, args, ctx.user?.address, isGlobalAdmin);
|
|
466
|
-
} catch (error) {
|
|
467
|
-
this.logger.error("Error in revokeGroupOperationPermission: @error", error);
|
|
468
|
-
throw error;
|
|
469
|
-
}
|
|
470
280
|
}
|
|
471
|
-
}
|
|
472
|
-
Group: { members: async (parent) => {
|
|
473
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
474
|
-
return getGroupMembers(this.documentPermissionService, parent.id);
|
|
475
|
-
} },
|
|
476
|
-
DocumentGroupPermission: { group: async (parent) => {
|
|
477
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
478
|
-
const grp = await group(this.documentPermissionService, { id: parent.groupId });
|
|
479
|
-
if (!grp) throw new GraphQLError(`Group not found: ${parent.groupId}`);
|
|
480
|
-
return grp;
|
|
481
|
-
} },
|
|
482
|
-
OperationGroupPermission: { group: async (parent) => {
|
|
483
|
-
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
484
|
-
const grp = await group(this.documentPermissionService, { id: parent.groupId });
|
|
485
|
-
if (!grp) throw new GraphQLError(`Group not found: ${parent.groupId}`);
|
|
486
|
-
return grp;
|
|
487
|
-
} }
|
|
281
|
+
}
|
|
488
282
|
};
|
|
489
283
|
onSetup() {
|
|
490
284
|
this.logger.debug("Setting up AuthSubgraph");
|
|
@@ -511,7 +305,7 @@ function createAuthFetchMiddleware(authService) {
|
|
|
511
305
|
async function createGatewayAdapter(type, logger) {
|
|
512
306
|
switch (type) {
|
|
513
307
|
case "apollo": {
|
|
514
|
-
const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-
|
|
308
|
+
const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-CxV_hMTv.mjs");
|
|
515
309
|
return new ApolloGatewayAdapter(logger);
|
|
516
310
|
}
|
|
517
311
|
case "mercurius": {
|
|
@@ -533,133 +327,6 @@ async function createHttpAdapter(type) {
|
|
|
533
327
|
}
|
|
534
328
|
}
|
|
535
329
|
//#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
330
|
//#region src/utils/create-schema.ts
|
|
664
331
|
const logger = childLogger(["reactor-api", "create-schema"]);
|
|
665
332
|
/**
|
|
@@ -683,6 +350,49 @@ const stripScalarDefinitions = (doc) => {
|
|
|
683
350
|
definitions: filteredDefinitions
|
|
684
351
|
});
|
|
685
352
|
};
|
|
353
|
+
/**
|
|
354
|
+
* Type-system definition kinds that GraphQL requires to be uniquely named.
|
|
355
|
+
* A subgraph SDL with two definitions sharing a name fails federation
|
|
356
|
+
* composition ("There can be only one type named X"), which is fatal to the
|
|
357
|
+
* whole gateway. We dedupe these by name; field/operation/extension nodes are
|
|
358
|
+
* left untouched.
|
|
359
|
+
*/
|
|
360
|
+
const TYPE_DEFINITION_KINDS = new Set([
|
|
361
|
+
Kind.OBJECT_TYPE_DEFINITION,
|
|
362
|
+
Kind.ENUM_TYPE_DEFINITION,
|
|
363
|
+
Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
|
364
|
+
Kind.INTERFACE_TYPE_DEFINITION,
|
|
365
|
+
Kind.UNION_TYPE_DEFINITION,
|
|
366
|
+
Kind.SCALAR_TYPE_DEFINITION
|
|
367
|
+
]);
|
|
368
|
+
/**
|
|
369
|
+
* Drop duplicate type-system definitions by name, keeping the first occurrence.
|
|
370
|
+
* Deduping is by name across ALL kinds (a `type` and an `enum` sharing a name
|
|
371
|
+
* collide in GraphQL too), so the first definition of a given name wins
|
|
372
|
+
* regardless of kind. State types are emitted before operation types, so
|
|
373
|
+
* keep-first preserves the authoritative state definition. This heals a document
|
|
374
|
+
* model that defines the same name twice (global+local, state+operation, or
|
|
375
|
+
* twice in one scope) so the assembled subgraph SDL composes instead of crashing
|
|
376
|
+
* the gateway (Sentry #917).
|
|
377
|
+
*/
|
|
378
|
+
const dedupeTypeDefinitions = (doc) => {
|
|
379
|
+
const seen = /* @__PURE__ */ new Set();
|
|
380
|
+
const definitions = doc.definitions.filter((def) => {
|
|
381
|
+
if (!TYPE_DEFINITION_KINDS.has(def.kind)) return true;
|
|
382
|
+
const name = def.name?.value;
|
|
383
|
+
if (!name) return true;
|
|
384
|
+
if (seen.has(name)) {
|
|
385
|
+
logger.debug(`Dropping duplicate type definition: ${name}`);
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
seen.add(name);
|
|
389
|
+
return true;
|
|
390
|
+
});
|
|
391
|
+
return {
|
|
392
|
+
kind: Kind.DOCUMENT,
|
|
393
|
+
definitions
|
|
394
|
+
};
|
|
395
|
+
};
|
|
686
396
|
const buildSubgraphSchemaModule = (documentModels, resolvers, typeDefs) => {
|
|
687
397
|
const newResolvers = {
|
|
688
398
|
...resolvers,
|
|
@@ -748,7 +458,7 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
|
|
|
748
458
|
stateJSON: JSONObject
|
|
749
459
|
}\n`;
|
|
750
460
|
});
|
|
751
|
-
return gql`
|
|
461
|
+
return dedupeTypeDefinitions(gql`
|
|
752
462
|
scalar JSONObject
|
|
753
463
|
${typeDefs.join("\n").replaceAll(";", "")}
|
|
754
464
|
|
|
@@ -820,7 +530,7 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
|
|
|
820
530
|
}
|
|
821
531
|
|
|
822
532
|
${stripScalarDefinitions(typeDefs$1)}
|
|
823
|
-
|
|
533
|
+
`);
|
|
824
534
|
};
|
|
825
535
|
/**
|
|
826
536
|
* Extract type names from a GraphQL schema.
|
|
@@ -1246,7 +956,6 @@ function ActionContextInputSchema() {
|
|
|
1246
956
|
}
|
|
1247
957
|
function ActionInputSchema() {
|
|
1248
958
|
return z$1.object({
|
|
1249
|
-
attachments: z$1.array(z$1.lazy(() => AttachmentInputSchema())).nullish(),
|
|
1250
959
|
context: z$1.lazy(() => ActionContextInputSchema().nullish()),
|
|
1251
960
|
id: z$1.string(),
|
|
1252
961
|
input: z$1.custom((v) => v != null),
|
|
@@ -1255,15 +964,6 @@ function ActionInputSchema() {
|
|
|
1255
964
|
type: z$1.string()
|
|
1256
965
|
});
|
|
1257
966
|
}
|
|
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
967
|
function ChannelMetaInputSchema() {
|
|
1268
968
|
return z$1.object({ id: z$1.string() });
|
|
1269
969
|
}
|
|
@@ -1454,13 +1154,6 @@ const GetDocumentWithOperationsDocument = gql`
|
|
|
1454
1154
|
timestampUtcMs
|
|
1455
1155
|
input
|
|
1456
1156
|
scope
|
|
1457
|
-
attachments {
|
|
1458
|
-
data
|
|
1459
|
-
mimeType
|
|
1460
|
-
hash
|
|
1461
|
-
extension
|
|
1462
|
-
fileName
|
|
1463
|
-
}
|
|
1464
1157
|
context {
|
|
1465
1158
|
signer {
|
|
1466
1159
|
user {
|
|
@@ -1573,13 +1266,6 @@ const GetDocumentOperationsDocument = gql`
|
|
|
1573
1266
|
timestampUtcMs
|
|
1574
1267
|
input
|
|
1575
1268
|
scope
|
|
1576
|
-
attachments {
|
|
1577
|
-
data
|
|
1578
|
-
mimeType
|
|
1579
|
-
hash
|
|
1580
|
-
extension
|
|
1581
|
-
fileName
|
|
1582
|
-
}
|
|
1583
1269
|
context {
|
|
1584
1270
|
signer {
|
|
1585
1271
|
user {
|
|
@@ -1830,13 +1516,6 @@ const PollSyncEnvelopesDocument = gql`
|
|
|
1830
1516
|
timestampUtcMs
|
|
1831
1517
|
input
|
|
1832
1518
|
scope
|
|
1833
|
-
attachments {
|
|
1834
|
-
data
|
|
1835
|
-
mimeType
|
|
1836
|
-
hash
|
|
1837
|
-
extension
|
|
1838
|
-
fileName
|
|
1839
|
-
}
|
|
1840
1519
|
context {
|
|
1841
1520
|
signer {
|
|
1842
1521
|
user {
|
|
@@ -2153,6 +1832,17 @@ function toGqlDocumentChangeEvent(event) {
|
|
|
2153
1832
|
} : null
|
|
2154
1833
|
};
|
|
2155
1834
|
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Distinct document ids a change event references: affected documents plus the
|
|
1837
|
+
* parent/child ids in `context` (delete/relationship events carry only context).
|
|
1838
|
+
*/
|
|
1839
|
+
function collectChangedDocumentIds(event) {
|
|
1840
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1841
|
+
for (const doc of event.documents) if (doc.header.id) ids.add(doc.header.id);
|
|
1842
|
+
if (event.context?.parentId) ids.add(event.context.parentId);
|
|
1843
|
+
if (event.context?.childId) ids.add(event.context.childId);
|
|
1844
|
+
return [...ids];
|
|
1845
|
+
}
|
|
2156
1846
|
function matchesSearchFilter(event, search) {
|
|
2157
1847
|
if (search.type) {
|
|
2158
1848
|
if (!event.documents.some((doc) => doc.header.documentType === search.type)) return false;
|
|
@@ -2624,7 +2314,7 @@ async function touchChannel(syncManager, args) {
|
|
|
2624
2314
|
};
|
|
2625
2315
|
const options = { sinceTimestampUtcMs: args.input.sinceTimestampUtcMs };
|
|
2626
2316
|
try {
|
|
2627
|
-
await syncManager.add(args.input.name, args.input.collectionId, {
|
|
2317
|
+
await syncManager.add(args.input.name, DriveCollectionId.fromKey(args.input.collectionId), {
|
|
2628
2318
|
type: "polling",
|
|
2629
2319
|
parameters: {}
|
|
2630
2320
|
}, filter, options, args.input.id);
|
|
@@ -2647,14 +2337,14 @@ async function touchChannel(syncManager, args) {
|
|
|
2647
2337
|
* client ordinal the switchboard has successfully applied, so the client
|
|
2648
2338
|
* can trim its own outbox)
|
|
2649
2339
|
*/
|
|
2650
|
-
function pollSyncEnvelopes(syncManager, args) {
|
|
2340
|
+
function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new Set()) {
|
|
2651
2341
|
let remote;
|
|
2652
2342
|
try {
|
|
2653
2343
|
remote = syncManager.getById(args.channelId);
|
|
2654
2344
|
} catch (error) {
|
|
2655
2345
|
throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2656
2346
|
}
|
|
2657
|
-
const deadLetters = remote.channel.deadLetter.items.map((syncOp) => ({
|
|
2347
|
+
const deadLetters = remote.channel.deadLetter.items.filter((syncOp) => !forbiddenIds.has(syncOp.documentId)).map((syncOp) => ({
|
|
2658
2348
|
documentId: syncOp.documentId,
|
|
2659
2349
|
error: syncOp.error?.message ?? "Unknown error",
|
|
2660
2350
|
jobId: syncOp.jobId,
|
|
@@ -2678,6 +2368,12 @@ function pollSyncEnvelopes(syncManager, args) {
|
|
|
2678
2368
|
}
|
|
2679
2369
|
}
|
|
2680
2370
|
const operations = remote.channel.outbox.items;
|
|
2371
|
+
if (forbiddenIds.size > 0) {
|
|
2372
|
+
for (const syncOp of operations) if (forbiddenIds.has(syncOp.documentId)) {
|
|
2373
|
+
syncOp.deliveredCount = syncOp.operations.length;
|
|
2374
|
+
syncOp.emittedCount = syncOp.operations.length;
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2681
2377
|
if (operations.length === 0) return {
|
|
2682
2378
|
envelopes: [],
|
|
2683
2379
|
ackOrdinal: remote.channel.inbox.ackOrdinal,
|
|
@@ -2869,7 +2565,7 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
2869
2565
|
view
|
|
2870
2566
|
});
|
|
2871
2567
|
if (result.document.documentType !== documentType) throw new GraphQLError(`Document with id ${identifier} is not of type ${documentType}`);
|
|
2872
|
-
await this.
|
|
2568
|
+
await this.assertCanReadCanonical(result.document.id, ctx);
|
|
2873
2569
|
return result;
|
|
2874
2570
|
},
|
|
2875
2571
|
documents: async (_, args, ctx) => {
|
|
@@ -2878,7 +2574,7 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
2878
2574
|
search: { type: documentType },
|
|
2879
2575
|
paging
|
|
2880
2576
|
});
|
|
2881
|
-
if (!this.
|
|
2577
|
+
if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
|
|
2882
2578
|
const filteredItems = [];
|
|
2883
2579
|
for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
|
|
2884
2580
|
return {
|
|
@@ -2899,7 +2595,7 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
2899
2595
|
view,
|
|
2900
2596
|
paging
|
|
2901
2597
|
});
|
|
2902
|
-
if (!this.
|
|
2598
|
+
if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
|
|
2903
2599
|
const filteredItems = [];
|
|
2904
2600
|
for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
|
|
2905
2601
|
return {
|
|
@@ -2911,10 +2607,10 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
2911
2607
|
return result;
|
|
2912
2608
|
},
|
|
2913
2609
|
documentOutgoingRelationships: async (_, args, ctx) => {
|
|
2914
|
-
const {
|
|
2915
|
-
await this.assertCanRead(sourceIdentifier, ctx);
|
|
2610
|
+
const { relationshipType, view, paging } = args;
|
|
2611
|
+
const handle = await this.assertCanRead(args.sourceIdentifier, ctx);
|
|
2916
2612
|
const result = await documentOutgoingRelationships(this.reactorClient, {
|
|
2917
|
-
sourceIdentifier,
|
|
2613
|
+
sourceIdentifier: handle.fetchIdentifier,
|
|
2918
2614
|
relationshipType,
|
|
2919
2615
|
view,
|
|
2920
2616
|
paging
|
|
@@ -2927,10 +2623,10 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
2927
2623
|
};
|
|
2928
2624
|
},
|
|
2929
2625
|
documentIncomingRelationships: async (_, args, ctx) => {
|
|
2930
|
-
const {
|
|
2931
|
-
await this.assertCanRead(targetIdentifier, ctx);
|
|
2626
|
+
const { relationshipType, view, paging } = args;
|
|
2627
|
+
const handle = await this.assertCanRead(args.targetIdentifier, ctx);
|
|
2932
2628
|
return documentIncomingRelationships(this.reactorClient, {
|
|
2933
|
-
targetIdentifier,
|
|
2629
|
+
targetIdentifier: handle.fetchIdentifier,
|
|
2934
2630
|
relationshipType,
|
|
2935
2631
|
view,
|
|
2936
2632
|
paging
|
|
@@ -2940,11 +2636,10 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
2940
2636
|
Mutation: { [documentName]: () => ({}) },
|
|
2941
2637
|
[`${documentName}Mutations`]: {
|
|
2942
2638
|
createDocument: async (_, args, ctx) => {
|
|
2943
|
-
const {
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
} else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
|
|
2639
|
+
const { name, slug, preferredEditor, initialState } = args;
|
|
2640
|
+
let parentIdentifier = args.parentIdentifier;
|
|
2641
|
+
if (parentIdentifier) parentIdentifier = (await this.assertCanWrite(parentIdentifier, ctx)).fetchIdentifier;
|
|
2642
|
+
else this.assertCanCreate(ctx);
|
|
2948
2643
|
let createdDoc;
|
|
2949
2644
|
if (initialState || preferredEditor) createdDoc = await createDocumentWithInitialState(this.reactorClient, {
|
|
2950
2645
|
documentType,
|
|
@@ -2959,46 +2654,42 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
2959
2654
|
parentIdentifier,
|
|
2960
2655
|
name
|
|
2961
2656
|
}, this.graphqlManager.reactorDriveClient);
|
|
2962
|
-
if (
|
|
2657
|
+
if (ctx.user?.address && createdDoc?.id) await this.documentPermissionService?.initializeDocumentProtection(createdDoc.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
2963
2658
|
if (!initialState && !preferredEditor && name && createdDoc.name !== name) return toGqlPhDocument(await this.reactorClient.execute(createdDoc.id, "main", [setName(name)]));
|
|
2964
2659
|
return createdDoc;
|
|
2965
2660
|
},
|
|
2966
2661
|
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");
|
|
2662
|
+
let parentIdentifier = args.parentIdentifier;
|
|
2663
|
+
if (parentIdentifier) parentIdentifier = (await this.assertCanWrite(parentIdentifier, ctx)).fetchIdentifier;
|
|
2664
|
+
else this.assertCanCreate(ctx);
|
|
2972
2665
|
const result = await createEmptyDocument(this.reactorClient, {
|
|
2973
2666
|
documentType,
|
|
2974
2667
|
parentIdentifier
|
|
2975
2668
|
}, this.graphqlManager.reactorDriveClient);
|
|
2976
|
-
if (
|
|
2669
|
+
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
2977
2670
|
return result;
|
|
2978
2671
|
},
|
|
2979
2672
|
...operations.reduce((mutations, op) => {
|
|
2980
2673
|
mutations[camelCase(op.name)] = async (_, args, ctx) => {
|
|
2981
2674
|
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}`);
|
|
2675
|
+
const effectiveDocId = (await this.assertCanExecuteOperation(docId, op.name, ctx)).fetchIdentifier;
|
|
2676
|
+
if ((await this.reactorClient.get(effectiveDocId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
|
|
2985
2677
|
const action = this.documentModel.actions[camelCase(op.name)];
|
|
2986
2678
|
if (!action) throw new GraphQLError(`Action ${op.name} not found`);
|
|
2987
2679
|
try {
|
|
2988
|
-
return toGqlPhDocument(await this.reactorClient.execute(
|
|
2680
|
+
return toGqlPhDocument(await this.reactorClient.execute(effectiveDocId, "main", [action(input)]));
|
|
2989
2681
|
} catch (error) {
|
|
2990
2682
|
throw new GraphQLError(error instanceof Error ? error.message : `Failed to ${op.name}`);
|
|
2991
2683
|
}
|
|
2992
2684
|
};
|
|
2993
2685
|
mutations[`${camelCase(op.name)}Async`] = async (_, args, ctx) => {
|
|
2994
2686
|
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}`);
|
|
2687
|
+
const effectiveDocId = (await this.assertCanExecuteOperation(docId, op.name, ctx)).fetchIdentifier;
|
|
2688
|
+
if ((await this.reactorClient.get(effectiveDocId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
|
|
2998
2689
|
const action = this.documentModel.actions[camelCase(op.name)];
|
|
2999
2690
|
if (!action) throw new GraphQLError(`Action ${op.name} not found`);
|
|
3000
2691
|
try {
|
|
3001
|
-
return (await this.reactorClient.executeAsync(
|
|
2692
|
+
return (await this.reactorClient.executeAsync(effectiveDocId, "main", [action(input)])).id;
|
|
3002
2693
|
} catch (error) {
|
|
3003
2694
|
throw new GraphQLError(error instanceof Error ? error.message : `Failed to ${op.name}`);
|
|
3004
2695
|
}
|
|
@@ -3177,7 +2868,6 @@ var GraphQLManager = class {
|
|
|
3177
2868
|
coreSubgraphsMap = /* @__PURE__ */ new Map();
|
|
3178
2869
|
contextFields = {};
|
|
3179
2870
|
subgraphs = /* @__PURE__ */ new Map();
|
|
3180
|
-
authService = null;
|
|
3181
2871
|
subgraphWsDisposers = /* @__PURE__ */ new Map();
|
|
3182
2872
|
#authMiddleware;
|
|
3183
2873
|
#driveMiddleware;
|
|
@@ -3191,7 +2881,8 @@ var GraphQLManager = class {
|
|
|
3191
2881
|
* it for reactor-drive parents.
|
|
3192
2882
|
*/
|
|
3193
2883
|
reactorDriveClient;
|
|
3194
|
-
|
|
2884
|
+
authorizationService;
|
|
2885
|
+
constructor(path, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, logger, httpAdapter, gatewayAdapter, authService, documentPermissionService, featureFlags = DefaultFeatureFlags, port = 4001, authorizationService, reactorDriveClient) {
|
|
3195
2886
|
this.path = path;
|
|
3196
2887
|
this.httpServer = httpServer;
|
|
3197
2888
|
this.wsServer = wsServer;
|
|
@@ -3202,13 +2893,13 @@ var GraphQLManager = class {
|
|
|
3202
2893
|
this.logger = logger;
|
|
3203
2894
|
this.httpAdapter = httpAdapter;
|
|
3204
2895
|
this.gatewayAdapter = gatewayAdapter;
|
|
3205
|
-
this.
|
|
2896
|
+
this.authService = authService;
|
|
3206
2897
|
this.documentPermissionService = documentPermissionService;
|
|
3207
2898
|
this.featureFlags = featureFlags;
|
|
3208
2899
|
this.port = port;
|
|
2900
|
+
if (!authorizationService) throw new Error("GraphQLManager requires an authorizationService");
|
|
3209
2901
|
this.authorizationService = authorizationService;
|
|
3210
2902
|
this.reactorDriveClient = reactorDriveClient;
|
|
3211
|
-
if (this.authConfig) this.authService = new AuthService(this.authConfig);
|
|
3212
2903
|
this.driveOwnershipCache = new DriveOwnershipCache(this.reactorClient);
|
|
3213
2904
|
this.wsServer.setMaxListeners(0);
|
|
3214
2905
|
}
|
|
@@ -3328,6 +3019,14 @@ var GraphQLManager = class {
|
|
|
3328
3019
|
getBasePath() {
|
|
3329
3020
|
return this.path;
|
|
3330
3021
|
}
|
|
3022
|
+
/**
|
|
3023
|
+
* Get the authorization service shared with subgraphs. Use this when
|
|
3024
|
+
* constructing a subgraph instance externally for
|
|
3025
|
+
* {@link registerSubgraphInstance}.
|
|
3026
|
+
*/
|
|
3027
|
+
getAuthorizationService() {
|
|
3028
|
+
return this.authorizationService;
|
|
3029
|
+
}
|
|
3331
3030
|
async registerSubgraph(subgraph, supergraph = "", core = false) {
|
|
3332
3031
|
const subgraphInstance = new subgraph({
|
|
3333
3032
|
relationalDb: this.relationalDb,
|
|
@@ -3387,8 +3086,7 @@ var GraphQLManager = class {
|
|
|
3387
3086
|
db: this.relationalDb,
|
|
3388
3087
|
...this.getAdditionalContextFields(),
|
|
3389
3088
|
driveId,
|
|
3390
|
-
user: authCtx?.user
|
|
3391
|
-
isAdmin: authCtx ? (addr) => !authCtx.auth_enabled ? true : authCtx.admins.includes(addr.toLowerCase()) : () => true
|
|
3089
|
+
user: authCtx?.user
|
|
3392
3090
|
});
|
|
3393
3091
|
};
|
|
3394
3092
|
}
|
|
@@ -3532,8 +3230,8 @@ var GraphQLManager = class {
|
|
|
3532
3230
|
};
|
|
3533
3231
|
//#endregion
|
|
3534
3232
|
//#region src/graphql/packages/resolvers.ts
|
|
3535
|
-
function requireAdmin(ctx) {
|
|
3536
|
-
if (!
|
|
3233
|
+
function requireAdmin(authorizationService, ctx) {
|
|
3234
|
+
if (!authorizationService.isSupremeAdmin(ctx.user?.address)) throw new GraphQLError("Admin access required");
|
|
3537
3235
|
}
|
|
3538
3236
|
function formatPackageInfo(info) {
|
|
3539
3237
|
return {
|
|
@@ -3551,16 +3249,16 @@ async function installedPackage(service, args) {
|
|
|
3551
3249
|
const pkg = await service.getInstalledPackage(args.name);
|
|
3552
3250
|
return pkg ? formatPackageInfo(pkg) : null;
|
|
3553
3251
|
}
|
|
3554
|
-
async function installPackage(service, args, ctx) {
|
|
3555
|
-
requireAdmin(ctx);
|
|
3252
|
+
async function installPackage(service, authorizationService, args, ctx) {
|
|
3253
|
+
requireAdmin(authorizationService, ctx);
|
|
3556
3254
|
const result = await service.installPackage(args.name, args.registryUrl ?? void 0);
|
|
3557
3255
|
return {
|
|
3558
3256
|
package: formatPackageInfo(result.package),
|
|
3559
3257
|
documentModelsLoaded: result.documentModelsLoaded
|
|
3560
3258
|
};
|
|
3561
3259
|
}
|
|
3562
|
-
async function uninstallPackage(service, args, ctx) {
|
|
3563
|
-
requireAdmin(ctx);
|
|
3260
|
+
async function uninstallPackage(service, authorizationService, args, ctx) {
|
|
3261
|
+
requireAdmin(authorizationService, ctx);
|
|
3564
3262
|
return service.uninstallPackage(args.name);
|
|
3565
3263
|
}
|
|
3566
3264
|
//#endregion
|
|
@@ -3606,7 +3304,7 @@ var PackagesSubgraph = class extends BaseSubgraph {
|
|
|
3606
3304
|
installPackage: async (_parent, args, ctx) => {
|
|
3607
3305
|
this.logger.debug("installPackage(@args)", args);
|
|
3608
3306
|
try {
|
|
3609
|
-
return await installPackage(this.packageManagementService, args, ctx);
|
|
3307
|
+
return await installPackage(this.packageManagementService, this.authorizationService, args, ctx);
|
|
3610
3308
|
} catch (error) {
|
|
3611
3309
|
this.logger.error("Error in installPackage: @error", error);
|
|
3612
3310
|
throw error;
|
|
@@ -3615,7 +3313,7 @@ var PackagesSubgraph = class extends BaseSubgraph {
|
|
|
3615
3313
|
uninstallPackage: async (_parent, args, ctx) => {
|
|
3616
3314
|
this.logger.debug("uninstallPackage(@args)", args);
|
|
3617
3315
|
try {
|
|
3618
|
-
return await uninstallPackage(this.packageManagementService, args, ctx);
|
|
3316
|
+
return await uninstallPackage(this.packageManagementService, this.authorizationService, args, ctx);
|
|
3619
3317
|
} catch (error) {
|
|
3620
3318
|
this.logger.error("Error in uninstallPackage: @error", error);
|
|
3621
3319
|
throw error;
|
|
@@ -3801,20 +3499,12 @@ const ActionSignerDTO = z.object({
|
|
|
3801
3499
|
app: ActionSignerAppDTO.nullable().optional()
|
|
3802
3500
|
}).strip();
|
|
3803
3501
|
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
3502
|
const OperationActionDTO = z.object({
|
|
3812
3503
|
id: z.string(),
|
|
3813
3504
|
type: z.string(),
|
|
3814
3505
|
timestampUtcMs: z.string(),
|
|
3815
3506
|
input: z.unknown(),
|
|
3816
3507
|
scope: z.string(),
|
|
3817
|
-
attachments: z.array(AttachmentDTO).nullable().optional(),
|
|
3818
3508
|
context: ActionContextDTO.nullable().optional()
|
|
3819
3509
|
}).strip();
|
|
3820
3510
|
const OperationDTO = z.object({
|
|
@@ -3941,7 +3631,7 @@ function createReactorGraphQLClient(url, fetchImpl = fetch, headers) {
|
|
|
3941
3631
|
}
|
|
3942
3632
|
//#endregion
|
|
3943
3633
|
//#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
|
|
3634
|
+
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
3635
|
//#endregion
|
|
3946
3636
|
//#region src/graphql/reactor/pubsub.ts
|
|
3947
3637
|
const pubSub = new PubSub();
|
|
@@ -3990,7 +3680,8 @@ function ensureJobSubscription(reactorClient, jobId) {
|
|
|
3990
3680
|
error: jobInfo.error?.message ?? null,
|
|
3991
3681
|
result: jobInfo.result ?? {}
|
|
3992
3682
|
},
|
|
3993
|
-
jobId
|
|
3683
|
+
jobId,
|
|
3684
|
+
documentId: jobInfo.documentId
|
|
3994
3685
|
};
|
|
3995
3686
|
pubSub.publish(SUBSCRIPTION_TRIGGERS.JOB_CHANGES, payload);
|
|
3996
3687
|
const isTerminal = String(jobInfo.status) === "FAILED" || String(jobInfo.status) === "READ_MODELS_READY" || jobInfo.completedAtUtcIso !== void 0;
|
|
@@ -4040,12 +3731,14 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4040
3731
|
* Delegates to base assertCanExecuteOperation for each action.
|
|
4041
3732
|
*/
|
|
4042
3733
|
async assertCanExecuteOperations(documentId, actions, ctx) {
|
|
3734
|
+
let handle = AuthorizedDocumentHandle.skipped(documentId);
|
|
4043
3735
|
for (const action of actions) {
|
|
4044
3736
|
if (!action || typeof action !== "object") continue;
|
|
4045
3737
|
const operationType = action.type;
|
|
4046
3738
|
if (typeof operationType !== "string") continue;
|
|
4047
|
-
await this.assertCanExecuteOperation(documentId, operationType, ctx);
|
|
3739
|
+
handle = await this.assertCanExecuteOperation(documentId, operationType, ctx);
|
|
4048
3740
|
}
|
|
3741
|
+
return handle;
|
|
4049
3742
|
}
|
|
4050
3743
|
/**
|
|
4051
3744
|
* Returns the drive id when the given identifier (id or slug) refers
|
|
@@ -4062,11 +3755,25 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4062
3755
|
return;
|
|
4063
3756
|
}
|
|
4064
3757
|
}
|
|
3758
|
+
/**
|
|
3759
|
+
* Adds to `forbidden` the canonical document ids in `syncOps` that the caller
|
|
3760
|
+
* cannot read, checking each distinct id once. Sync operation document ids are
|
|
3761
|
+
* canonical (never slugs), so no resolution is needed.
|
|
3762
|
+
*/
|
|
3763
|
+
async #collectForbiddenDocuments(syncOps, forbidden, ctx) {
|
|
3764
|
+
const checked = /* @__PURE__ */ new Set();
|
|
3765
|
+
for (const syncOp of syncOps) {
|
|
3766
|
+
const documentId = syncOp.documentId;
|
|
3767
|
+
if (checked.has(documentId) || forbidden.has(documentId)) continue;
|
|
3768
|
+
checked.add(documentId);
|
|
3769
|
+
if (!await this.canReadDocument(documentId, ctx)) forbidden.add(documentId);
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
4065
3772
|
typeDefs = gql(schema_default);
|
|
4066
3773
|
resolvers = {
|
|
4067
3774
|
PHDocument: { operations: async (parent, args, ctx) => {
|
|
4068
3775
|
this.logger.debug("PHDocument.operations(@parent.id, @args)", parent.id, args);
|
|
4069
|
-
await this.
|
|
3776
|
+
await this.assertCanReadCanonical(parent.id, ctx);
|
|
4070
3777
|
try {
|
|
4071
3778
|
const filter = {
|
|
4072
3779
|
documentId: parent.id,
|
|
@@ -4099,8 +3806,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4099
3806
|
document: async (_parent, args, ctx) => {
|
|
4100
3807
|
this.logger.debug("document(@args)", args);
|
|
4101
3808
|
try {
|
|
4102
|
-
await this.assertCanRead(args.identifier, ctx);
|
|
4103
|
-
return await document(this.reactorClient,
|
|
3809
|
+
const handle = await this.assertCanRead(args.identifier, ctx);
|
|
3810
|
+
return await document(this.reactorClient, {
|
|
3811
|
+
...args,
|
|
3812
|
+
identifier: handle.fetchIdentifier
|
|
3813
|
+
});
|
|
4104
3814
|
} catch (error) {
|
|
4105
3815
|
this.logger.error("Error in document: @Error", error);
|
|
4106
3816
|
throw error;
|
|
@@ -4109,8 +3819,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4109
3819
|
documentOutgoingRelationships: async (_parent, args, ctx) => {
|
|
4110
3820
|
this.logger.debug("documentOutgoingRelationships(@args)", args);
|
|
4111
3821
|
try {
|
|
4112
|
-
await this.assertCanRead(args.sourceIdentifier, ctx);
|
|
4113
|
-
return await documentOutgoingRelationships(this.reactorClient,
|
|
3822
|
+
const handle = await this.assertCanRead(args.sourceIdentifier, ctx);
|
|
3823
|
+
return await documentOutgoingRelationships(this.reactorClient, {
|
|
3824
|
+
...args,
|
|
3825
|
+
sourceIdentifier: handle.fetchIdentifier
|
|
3826
|
+
});
|
|
4114
3827
|
} catch (error) {
|
|
4115
3828
|
this.logger.error("Error in documentOutgoingRelationships: @Error", error);
|
|
4116
3829
|
throw error;
|
|
@@ -4119,9 +3832,12 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4119
3832
|
documentIncomingRelationships: async (_parent, args, ctx) => {
|
|
4120
3833
|
this.logger.debug("documentIncomingRelationships(@args)", args);
|
|
4121
3834
|
try {
|
|
4122
|
-
await this.assertCanRead(args.targetIdentifier, ctx);
|
|
4123
|
-
const result = await documentIncomingRelationships(this.reactorClient,
|
|
4124
|
-
|
|
3835
|
+
const handle = await this.assertCanRead(args.targetIdentifier, ctx);
|
|
3836
|
+
const result = await documentIncomingRelationships(this.reactorClient, {
|
|
3837
|
+
...args,
|
|
3838
|
+
targetIdentifier: handle.fetchIdentifier
|
|
3839
|
+
});
|
|
3840
|
+
if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
|
|
4125
3841
|
const filteredItems = [];
|
|
4126
3842
|
for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
|
|
4127
3843
|
return {
|
|
@@ -4142,7 +3858,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4142
3858
|
...args,
|
|
4143
3859
|
search: args.search ?? {}
|
|
4144
3860
|
});
|
|
4145
|
-
if (!this.
|
|
3861
|
+
if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
|
|
4146
3862
|
const filteredItems = [];
|
|
4147
3863
|
for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
|
|
4148
3864
|
return {
|
|
@@ -4168,17 +3884,37 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4168
3884
|
documentOperations: async (_parent, args, ctx) => {
|
|
4169
3885
|
this.logger.debug("documentOperations(@args)", args);
|
|
4170
3886
|
try {
|
|
4171
|
-
await this.assertCanRead(args.filter.documentId, ctx);
|
|
4172
|
-
return await documentOperations(this.reactorClient,
|
|
3887
|
+
const handle = await this.assertCanRead(args.filter.documentId, ctx);
|
|
3888
|
+
return await documentOperations(this.reactorClient, {
|
|
3889
|
+
...args,
|
|
3890
|
+
filter: {
|
|
3891
|
+
...args.filter,
|
|
3892
|
+
documentId: handle.fetchIdentifier
|
|
3893
|
+
}
|
|
3894
|
+
});
|
|
4173
3895
|
} catch (error) {
|
|
4174
3896
|
this.logger.error("Error in documentOperations: @Error", error);
|
|
4175
3897
|
throw error;
|
|
4176
3898
|
}
|
|
4177
3899
|
},
|
|
4178
|
-
pollSyncEnvelopes: (_parent, args) => {
|
|
3900
|
+
pollSyncEnvelopes: async (_parent, args, ctx) => {
|
|
4179
3901
|
this.logger.debug("pollSyncEnvelopes(@args)", args);
|
|
4180
3902
|
try {
|
|
4181
|
-
|
|
3903
|
+
let remote;
|
|
3904
|
+
try {
|
|
3905
|
+
remote = this.syncManager.getById(args.channelId);
|
|
3906
|
+
} catch (error) {
|
|
3907
|
+
throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3908
|
+
}
|
|
3909
|
+
const driveId = remote.collectionId.driveId;
|
|
3910
|
+
const isAdmin = this.authorizationService.isSupremeAdmin(ctx.user?.address);
|
|
3911
|
+
if (!isAdmin) await this.assertCanReadCanonical(driveId, ctx);
|
|
3912
|
+
const forbiddenIds = /* @__PURE__ */ new Set();
|
|
3913
|
+
if (!isAdmin) {
|
|
3914
|
+
if (await this.authorizationService.canRead(driveId, void 0)) await this.#collectForbiddenDocuments(remote.channel.outbox.items, forbiddenIds, ctx);
|
|
3915
|
+
await this.#collectForbiddenDocuments(remote.channel.deadLetter.items, forbiddenIds, ctx);
|
|
3916
|
+
}
|
|
3917
|
+
const { envelopes, ackOrdinal, deadLetters, hasMore } = pollSyncEnvelopes(this.syncManager, args, forbiddenIds);
|
|
4182
3918
|
return {
|
|
4183
3919
|
envelopes,
|
|
4184
3920
|
ackOrdinal,
|
|
@@ -4197,13 +3933,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4197
3933
|
try {
|
|
4198
3934
|
if (args.parentIdentifier) {
|
|
4199
3935
|
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");
|
|
3936
|
+
await this.assertCanWriteCanonical(parent.document.id, ctx);
|
|
3937
|
+
} else this.assertCanCreate(ctx);
|
|
4204
3938
|
const result = await createDocument(this.reactorClient, args, this.graphqlManager.reactorDriveClient);
|
|
4205
3939
|
if (result?.id && isDriveContainerType(result.documentType)) this.graphqlManager.driveOwnershipCache.add(result.id);
|
|
4206
|
-
if (
|
|
3940
|
+
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
4207
3941
|
return result;
|
|
4208
3942
|
} catch (error) {
|
|
4209
3943
|
this.logger.error("Error in createDocument(@args): @Error", args, error);
|
|
@@ -4215,13 +3949,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4215
3949
|
try {
|
|
4216
3950
|
if (args.parentIdentifier) {
|
|
4217
3951
|
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");
|
|
3952
|
+
await this.assertCanWriteCanonical(parent.document.id, ctx);
|
|
3953
|
+
} else this.assertCanCreate(ctx);
|
|
4222
3954
|
const result = await createEmptyDocument(this.reactorClient, args, this.graphqlManager.reactorDriveClient);
|
|
4223
3955
|
if (result?.id && isDriveContainerType(result.documentType)) this.graphqlManager.driveOwnershipCache.add(result.id);
|
|
4224
|
-
if (
|
|
3956
|
+
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
4225
3957
|
return result;
|
|
4226
3958
|
} catch (error) {
|
|
4227
3959
|
this.logger.error("Error in createEmptyDocument(@args): @Error", args, error);
|
|
@@ -4231,9 +3963,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4231
3963
|
mutateDocument: async (_parent, args, ctx) => {
|
|
4232
3964
|
this.logger.debug("mutateDocument(@args)", args);
|
|
4233
3965
|
try {
|
|
4234
|
-
|
|
4235
|
-
await this.
|
|
4236
|
-
|
|
3966
|
+
const handle = await this.assertCanExecuteOperations(args.documentIdentifier, args.actions, ctx);
|
|
3967
|
+
return await mutateDocument(this.reactorClient, {
|
|
3968
|
+
...args,
|
|
3969
|
+
documentIdentifier: handle.fetchIdentifier
|
|
3970
|
+
});
|
|
4237
3971
|
} catch (error) {
|
|
4238
3972
|
this.logger.error("Error in mutateDocument(@args): @Error", args, error);
|
|
4239
3973
|
throw error;
|
|
@@ -4242,9 +3976,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4242
3976
|
mutateDocumentAsync: async (_parent, args, ctx) => {
|
|
4243
3977
|
this.logger.debug("mutateDocumentAsync(@args)", args);
|
|
4244
3978
|
try {
|
|
4245
|
-
|
|
4246
|
-
await this.
|
|
4247
|
-
|
|
3979
|
+
const handle = await this.assertCanExecuteOperations(args.documentIdentifier, args.actions, ctx);
|
|
3980
|
+
return await mutateDocumentAsync(this.reactorClient, {
|
|
3981
|
+
...args,
|
|
3982
|
+
documentIdentifier: handle.fetchIdentifier
|
|
3983
|
+
});
|
|
4248
3984
|
} catch (error) {
|
|
4249
3985
|
this.logger.error("Error in mutateDocumentAsync(@args): @Error", args, error);
|
|
4250
3986
|
throw error;
|
|
@@ -4253,8 +3989,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4253
3989
|
renameDocument: async (_parent, args, ctx) => {
|
|
4254
3990
|
this.logger.debug("renameDocument(@args)", args);
|
|
4255
3991
|
try {
|
|
4256
|
-
await this.assertCanWrite(args.documentIdentifier, ctx);
|
|
4257
|
-
return await renameDocument(this.reactorClient,
|
|
3992
|
+
const handle = await this.assertCanWrite(args.documentIdentifier, ctx);
|
|
3993
|
+
return await renameDocument(this.reactorClient, {
|
|
3994
|
+
...args,
|
|
3995
|
+
documentIdentifier: handle.fetchIdentifier
|
|
3996
|
+
});
|
|
4258
3997
|
} catch (error) {
|
|
4259
3998
|
this.logger.error("Error in renameDocument(@args): @Error", args, error);
|
|
4260
3999
|
throw error;
|
|
@@ -4263,8 +4002,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4263
4002
|
setPreferredEditor: async (_parent, args, ctx) => {
|
|
4264
4003
|
this.logger.debug("setPreferredEditor(@args)", args);
|
|
4265
4004
|
try {
|
|
4266
|
-
await this.assertCanWrite(args.documentIdentifier, ctx);
|
|
4267
|
-
return await setPreferredEditor(this.reactorClient,
|
|
4005
|
+
const handle = await this.assertCanWrite(args.documentIdentifier, ctx);
|
|
4006
|
+
return await setPreferredEditor(this.reactorClient, {
|
|
4007
|
+
...args,
|
|
4008
|
+
documentIdentifier: handle.fetchIdentifier
|
|
4009
|
+
});
|
|
4268
4010
|
} catch (error) {
|
|
4269
4011
|
this.logger.error("Error in setPreferredEditor(@args): @Error", args, error);
|
|
4270
4012
|
throw error;
|
|
@@ -4273,8 +4015,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4273
4015
|
addRelationship: async (_parent, args, ctx) => {
|
|
4274
4016
|
this.logger.debug("addRelationship(@args)", args);
|
|
4275
4017
|
try {
|
|
4276
|
-
await this.assertCanWrite(args.sourceIdentifier, ctx);
|
|
4277
|
-
return await addRelationship(this.reactorClient,
|
|
4018
|
+
const handle = await this.assertCanWrite(args.sourceIdentifier, ctx);
|
|
4019
|
+
return await addRelationship(this.reactorClient, {
|
|
4020
|
+
...args,
|
|
4021
|
+
sourceIdentifier: handle.fetchIdentifier
|
|
4022
|
+
});
|
|
4278
4023
|
} catch (error) {
|
|
4279
4024
|
this.logger.error("Error in addRelationship(@args): @Error", args, error);
|
|
4280
4025
|
throw error;
|
|
@@ -4283,8 +4028,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4283
4028
|
removeRelationship: async (_parent, args, ctx) => {
|
|
4284
4029
|
this.logger.debug("removeRelationship(@args)", args);
|
|
4285
4030
|
try {
|
|
4286
|
-
await this.assertCanWrite(args.sourceIdentifier, ctx);
|
|
4287
|
-
return await removeRelationship(this.reactorClient,
|
|
4031
|
+
const handle = await this.assertCanWrite(args.sourceIdentifier, ctx);
|
|
4032
|
+
return await removeRelationship(this.reactorClient, {
|
|
4033
|
+
...args,
|
|
4034
|
+
sourceIdentifier: handle.fetchIdentifier
|
|
4035
|
+
});
|
|
4288
4036
|
} catch (error) {
|
|
4289
4037
|
this.logger.error("Error in removeRelationship(@args): @Error", args, error);
|
|
4290
4038
|
throw error;
|
|
@@ -4293,9 +4041,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4293
4041
|
moveRelationship: async (_parent, args, ctx) => {
|
|
4294
4042
|
this.logger.debug("moveRelationship(@args)", args);
|
|
4295
4043
|
try {
|
|
4296
|
-
await this.assertCanWrite(args.sourceParentIdentifier, ctx);
|
|
4297
|
-
await this.assertCanWrite(args.targetParentIdentifier, ctx);
|
|
4298
|
-
return await moveRelationship(this.reactorClient,
|
|
4044
|
+
const sourceHandle = await this.assertCanWrite(args.sourceParentIdentifier, ctx);
|
|
4045
|
+
const targetHandle = await this.assertCanWrite(args.targetParentIdentifier, ctx);
|
|
4046
|
+
return await moveRelationship(this.reactorClient, {
|
|
4047
|
+
...args,
|
|
4048
|
+
sourceParentIdentifier: sourceHandle.fetchIdentifier,
|
|
4049
|
+
targetParentIdentifier: targetHandle.fetchIdentifier
|
|
4050
|
+
});
|
|
4299
4051
|
} catch (error) {
|
|
4300
4052
|
this.logger.error("Error in moveRelationship(@args): @Error @args", error, args);
|
|
4301
4053
|
throw error;
|
|
@@ -4304,9 +4056,12 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4304
4056
|
deleteDocument: async (_parent, args, ctx) => {
|
|
4305
4057
|
this.logger.debug("deleteDocument(@args)", args);
|
|
4306
4058
|
try {
|
|
4307
|
-
await this.assertCanWrite(args.identifier, ctx);
|
|
4308
|
-
const driveIdToInvalidate = await this.#resolveDriveId(
|
|
4309
|
-
const result = await deleteDocument(this.reactorClient,
|
|
4059
|
+
const identifier = (await this.assertCanWrite(args.identifier, ctx)).fetchIdentifier;
|
|
4060
|
+
const driveIdToInvalidate = await this.#resolveDriveId(identifier);
|
|
4061
|
+
const result = await deleteDocument(this.reactorClient, {
|
|
4062
|
+
...args,
|
|
4063
|
+
identifier
|
|
4064
|
+
}, this.graphqlManager.reactorDriveClient);
|
|
4310
4065
|
if (result && driveIdToInvalidate) this.graphqlManager.driveOwnershipCache.remove(driveIdToInvalidate);
|
|
4311
4066
|
return result;
|
|
4312
4067
|
} catch (error) {
|
|
@@ -4317,25 +4072,49 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4317
4072
|
deleteDocuments: async (_parent, args, ctx) => {
|
|
4318
4073
|
this.logger.debug("deleteDocuments(@args)", args);
|
|
4319
4074
|
try {
|
|
4320
|
-
|
|
4321
|
-
|
|
4075
|
+
const identifiers = [];
|
|
4076
|
+
for (const identifier of args.identifiers) {
|
|
4077
|
+
const handle = await this.assertCanWrite(identifier, ctx);
|
|
4078
|
+
identifiers.push(handle.fetchIdentifier);
|
|
4079
|
+
}
|
|
4080
|
+
return await deleteDocuments(this.reactorClient, {
|
|
4081
|
+
...args,
|
|
4082
|
+
identifiers
|
|
4083
|
+
});
|
|
4322
4084
|
} catch (error) {
|
|
4323
4085
|
this.logger.error("Error in deleteDocuments(@args): @Error", args, error);
|
|
4324
4086
|
throw error;
|
|
4325
4087
|
}
|
|
4326
4088
|
},
|
|
4327
|
-
touchChannel: async (_parent, args) => {
|
|
4089
|
+
touchChannel: async (_parent, args, ctx) => {
|
|
4328
4090
|
this.logger.debug("touchChannel(@args)", args);
|
|
4329
4091
|
try {
|
|
4092
|
+
if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
|
|
4093
|
+
const driveId = DriveCollectionId.fromKey(args.input.collectionId).driveId;
|
|
4094
|
+
await this.assertCanReadCanonical(driveId, ctx);
|
|
4095
|
+
}
|
|
4330
4096
|
return await touchChannel(this.syncManager, args);
|
|
4331
4097
|
} catch (error) {
|
|
4332
4098
|
this.logger.error("Error in touchChannel(@args): @Error", args, error);
|
|
4333
4099
|
throw error;
|
|
4334
4100
|
}
|
|
4335
4101
|
},
|
|
4336
|
-
pushSyncEnvelopes: async (_parent, args) => {
|
|
4102
|
+
pushSyncEnvelopes: async (_parent, args, ctx) => {
|
|
4337
4103
|
this.logger.debug("pushSyncEnvelopes(@args)", args);
|
|
4338
4104
|
try {
|
|
4105
|
+
const checkedOperations = /* @__PURE__ */ new Map();
|
|
4106
|
+
for (const envelope of args.envelopes) for (const op of envelope.operations ?? []) {
|
|
4107
|
+
const documentId = op.context.documentId;
|
|
4108
|
+
const operationType = op.operation.action.type;
|
|
4109
|
+
let checkedTypes = checkedOperations.get(documentId);
|
|
4110
|
+
if (!checkedTypes) {
|
|
4111
|
+
checkedTypes = /* @__PURE__ */ new Set();
|
|
4112
|
+
checkedOperations.set(documentId, checkedTypes);
|
|
4113
|
+
}
|
|
4114
|
+
if (checkedTypes.has(operationType)) continue;
|
|
4115
|
+
checkedTypes.add(operationType);
|
|
4116
|
+
await this.assertCanExecuteOperationCanonical(documentId, operationType, ctx);
|
|
4117
|
+
}
|
|
4339
4118
|
const mutableArgs = { envelopes: args.envelopes.map((envelope) => ({
|
|
4340
4119
|
type: envelope.type,
|
|
4341
4120
|
channelMeta: { id: envelope.channelMeta.id },
|
|
@@ -4366,31 +4145,43 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
4366
4145
|
},
|
|
4367
4146
|
Subscription: {
|
|
4368
4147
|
documentChanges: {
|
|
4369
|
-
subscribe:
|
|
4148
|
+
subscribe: (rootValue, args, ctx, info) => {
|
|
4370
4149
|
this.logger.debug("documentChanges subscription started");
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4150
|
+
return withFilter(() => {
|
|
4151
|
+
ensureGlobalDocumentSubscription(this.reactorClient);
|
|
4152
|
+
return getPubSub().asyncIterableIterator(SUBSCRIPTION_TRIGGERS.DOCUMENT_CHANGES);
|
|
4153
|
+
}, async (payload, filterArgs, filterCtx) => {
|
|
4154
|
+
if (!payload) return false;
|
|
4155
|
+
const search = {
|
|
4156
|
+
type: filterArgs?.search?.type ?? void 0,
|
|
4157
|
+
parentId: filterArgs?.search?.parentId ?? void 0
|
|
4158
|
+
};
|
|
4159
|
+
if (!matchesSearchFilter(payload.documentChanges, search)) return false;
|
|
4160
|
+
if (this.authorizationService.isSupremeAdmin(filterCtx?.user?.address)) return true;
|
|
4161
|
+
const documentIds = collectChangedDocumentIds(payload.documentChanges);
|
|
4162
|
+
if (documentIds.length === 0) return false;
|
|
4163
|
+
for (const documentId of documentIds) if (!await this.canReadDocument(documentId, filterCtx)) return false;
|
|
4164
|
+
return true;
|
|
4165
|
+
})(rootValue, args, ctx, info);
|
|
4166
|
+
},
|
|
4381
4167
|
resolve: (payload) => {
|
|
4382
4168
|
return toGqlDocumentChangeEvent(payload.documentChanges);
|
|
4383
4169
|
}
|
|
4384
4170
|
},
|
|
4385
4171
|
jobChanges: {
|
|
4386
|
-
subscribe:
|
|
4172
|
+
subscribe: (rootValue, args, ctx, info) => {
|
|
4387
4173
|
this.logger.debug("jobChanges(@args) subscription started", args);
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4174
|
+
return withFilter(() => {
|
|
4175
|
+
ensureJobSubscription(this.reactorClient, args.jobId);
|
|
4176
|
+
return getPubSub().asyncIterableIterator(SUBSCRIPTION_TRIGGERS.JOB_CHANGES);
|
|
4177
|
+
}, async (payload, filterArgs, filterCtx) => {
|
|
4178
|
+
if (!payload || !filterArgs) return false;
|
|
4179
|
+
if (!matchesJobFilter(payload, filterArgs)) return false;
|
|
4180
|
+
if (this.authorizationService.isSupremeAdmin(filterCtx?.user?.address)) return true;
|
|
4181
|
+
if (!payload.documentId) return false;
|
|
4182
|
+
return this.canReadDocument(payload.documentId, filterCtx);
|
|
4183
|
+
})(rootValue, args, ctx, info);
|
|
4184
|
+
},
|
|
4394
4185
|
resolve: (payload) => {
|
|
4395
4186
|
return payload.jobChanges;
|
|
4396
4187
|
}
|
|
@@ -4414,10 +4205,10 @@ const ADMIN_USERS = getAdminUsers();
|
|
|
4414
4205
|
//#endregion
|
|
4415
4206
|
//#region src/graphql/system/version.ts
|
|
4416
4207
|
function getVersion() {
|
|
4417
|
-
return "6.2.0-dev.
|
|
4208
|
+
return "6.2.0-dev.30";
|
|
4418
4209
|
}
|
|
4419
4210
|
function getGitHash() {
|
|
4420
|
-
return "
|
|
4211
|
+
return "a2faa630dc17a7ee1ea7b4720567ed2bbbd58537";
|
|
4421
4212
|
}
|
|
4422
4213
|
function getGitUrl() {
|
|
4423
4214
|
return buildTreeUrl(getGitHash());
|
|
@@ -4900,10 +4691,10 @@ const DefaultCoreSubgraphs = [AnalyticsSubgraph, SystemSubgraph];
|
|
|
4900
4691
|
//#endregion
|
|
4901
4692
|
//#region src/migrations/001_create_document_permissions.ts
|
|
4902
4693
|
var _001_create_document_permissions_exports = /* @__PURE__ */ __exportAll({
|
|
4903
|
-
down: () => down$
|
|
4904
|
-
up: () => up$
|
|
4694
|
+
down: () => down$2,
|
|
4695
|
+
up: () => up$2
|
|
4905
4696
|
});
|
|
4906
|
-
async function up$
|
|
4697
|
+
async function up$2(db) {
|
|
4907
4698
|
await sql`
|
|
4908
4699
|
CREATE TABLE IF NOT EXISTS "DocumentPermission" (
|
|
4909
4700
|
"id" SERIAL PRIMARY KEY,
|
|
@@ -4977,7 +4768,7 @@ async function up$1(db) {
|
|
|
4977
4768
|
await sql`CREATE INDEX IF NOT EXISTS "operationgrouppermission_documentid_index" ON "OperationGroupPermission" ("documentId")`.execute(db);
|
|
4978
4769
|
await sql`CREATE INDEX IF NOT EXISTS "operationgrouppermission_groupid_index" ON "OperationGroupPermission" ("groupId")`.execute(db);
|
|
4979
4770
|
}
|
|
4980
|
-
async function down$
|
|
4771
|
+
async function down$2(db) {
|
|
4981
4772
|
await sql`DROP TABLE IF EXISTS "OperationGroupPermission"`.execute(db);
|
|
4982
4773
|
await sql`DROP TABLE IF EXISTS "OperationUserPermission"`.execute(db);
|
|
4983
4774
|
await sql`DROP TABLE IF EXISTS "DocumentGroupPermission"`.execute(db);
|
|
@@ -4988,10 +4779,10 @@ async function down$1(db) {
|
|
|
4988
4779
|
//#endregion
|
|
4989
4780
|
//#region src/migrations/002_add_document_protection.ts
|
|
4990
4781
|
var _002_add_document_protection_exports = /* @__PURE__ */ __exportAll({
|
|
4991
|
-
down: () => down,
|
|
4992
|
-
up: () => up
|
|
4782
|
+
down: () => down$1,
|
|
4783
|
+
up: () => up$1
|
|
4993
4784
|
});
|
|
4994
|
-
async function up(db) {
|
|
4785
|
+
async function up$1(db) {
|
|
4995
4786
|
await sql`
|
|
4996
4787
|
CREATE TABLE IF NOT EXISTS "DocumentProtection" (
|
|
4997
4788
|
"documentId" VARCHAR(255) PRIMARY KEY,
|
|
@@ -5004,10 +4795,80 @@ async function up(db) {
|
|
|
5004
4795
|
await sql`CREATE INDEX IF NOT EXISTS "documentprotection_owneraddress_index" ON "DocumentProtection" ("ownerAddress")`.execute(db);
|
|
5005
4796
|
await sql`CREATE INDEX IF NOT EXISTS "documentprotection_protected_index" ON "DocumentProtection" ("protected")`.execute(db);
|
|
5006
4797
|
}
|
|
5007
|
-
async function down(db) {
|
|
4798
|
+
async function down$1(db) {
|
|
5008
4799
|
await sql`DROP TABLE IF EXISTS "DocumentProtection"`.execute(db);
|
|
5009
4800
|
}
|
|
5010
4801
|
//#endregion
|
|
4802
|
+
//#region src/migrations/003_remove_group_tables.ts
|
|
4803
|
+
var _003_remove_group_tables_exports = /* @__PURE__ */ __exportAll({
|
|
4804
|
+
down: () => down,
|
|
4805
|
+
up: () => up
|
|
4806
|
+
});
|
|
4807
|
+
/**
|
|
4808
|
+
* Removes the group-permission feature: the Group, UserGroup,
|
|
4809
|
+
* DocumentGroupPermission, and OperationGroupPermission tables. The feature had
|
|
4810
|
+
* no consumers and disclosed ACL/membership metadata (AUTH_REVIEW S-H3/S-H5);
|
|
4811
|
+
* it is retired in favor of direct user grants, ownership, and protection.
|
|
4812
|
+
*
|
|
4813
|
+
* Tables are dropped in reverse dependency order. There are no DB-enforced
|
|
4814
|
+
* foreign keys, so the drops cannot violate referential integrity and do not
|
|
4815
|
+
* touch the surviving DocumentPermission / OperationUserPermission /
|
|
4816
|
+
* DocumentProtection tables.
|
|
4817
|
+
*/
|
|
4818
|
+
async function up(db) {
|
|
4819
|
+
await sql`DROP TABLE IF EXISTS "OperationGroupPermission"`.execute(db);
|
|
4820
|
+
await sql`DROP TABLE IF EXISTS "DocumentGroupPermission"`.execute(db);
|
|
4821
|
+
await sql`DROP TABLE IF EXISTS "UserGroup"`.execute(db);
|
|
4822
|
+
await sql`DROP TABLE IF EXISTS "Group"`.execute(db);
|
|
4823
|
+
}
|
|
4824
|
+
async function down(db) {
|
|
4825
|
+
await sql`
|
|
4826
|
+
CREATE TABLE IF NOT EXISTS "Group" (
|
|
4827
|
+
"id" SERIAL PRIMARY KEY,
|
|
4828
|
+
"name" VARCHAR(255) NOT NULL UNIQUE,
|
|
4829
|
+
"description" TEXT,
|
|
4830
|
+
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
4831
|
+
"updatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
4832
|
+
)
|
|
4833
|
+
`.execute(db);
|
|
4834
|
+
await sql`
|
|
4835
|
+
CREATE TABLE IF NOT EXISTS "UserGroup" (
|
|
4836
|
+
"userAddress" VARCHAR(255) NOT NULL,
|
|
4837
|
+
"groupId" INTEGER NOT NULL,
|
|
4838
|
+
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
4839
|
+
PRIMARY KEY ("userAddress", "groupId")
|
|
4840
|
+
)
|
|
4841
|
+
`.execute(db);
|
|
4842
|
+
await sql`CREATE INDEX IF NOT EXISTS "usergroup_groupid_index" ON "UserGroup" ("groupId")`.execute(db);
|
|
4843
|
+
await sql`
|
|
4844
|
+
CREATE TABLE IF NOT EXISTS "DocumentGroupPermission" (
|
|
4845
|
+
"id" SERIAL PRIMARY KEY,
|
|
4846
|
+
"documentId" VARCHAR(255) NOT NULL,
|
|
4847
|
+
"groupId" INTEGER NOT NULL,
|
|
4848
|
+
"permission" VARCHAR(20) NOT NULL CHECK ("permission" IN ('READ', 'WRITE', 'ADMIN')),
|
|
4849
|
+
"grantedBy" VARCHAR(255) NOT NULL,
|
|
4850
|
+
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
4851
|
+
"updatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
4852
|
+
UNIQUE ("documentId", "groupId")
|
|
4853
|
+
)
|
|
4854
|
+
`.execute(db);
|
|
4855
|
+
await sql`CREATE INDEX IF NOT EXISTS "documentgrouppermission_documentid_index" ON "DocumentGroupPermission" ("documentId")`.execute(db);
|
|
4856
|
+
await sql`CREATE INDEX IF NOT EXISTS "documentgrouppermission_groupid_index" ON "DocumentGroupPermission" ("groupId")`.execute(db);
|
|
4857
|
+
await sql`
|
|
4858
|
+
CREATE TABLE IF NOT EXISTS "OperationGroupPermission" (
|
|
4859
|
+
"id" SERIAL PRIMARY KEY,
|
|
4860
|
+
"documentId" VARCHAR(255) NOT NULL,
|
|
4861
|
+
"operationType" VARCHAR(255) NOT NULL,
|
|
4862
|
+
"groupId" INTEGER NOT NULL,
|
|
4863
|
+
"grantedBy" VARCHAR(255) NOT NULL,
|
|
4864
|
+
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
4865
|
+
UNIQUE ("documentId", "operationType", "groupId")
|
|
4866
|
+
)
|
|
4867
|
+
`.execute(db);
|
|
4868
|
+
await sql`CREATE INDEX IF NOT EXISTS "operationgrouppermission_documentid_index" ON "OperationGroupPermission" ("documentId")`.execute(db);
|
|
4869
|
+
await sql`CREATE INDEX IF NOT EXISTS "operationgrouppermission_groupid_index" ON "OperationGroupPermission" ("groupId")`.execute(db);
|
|
4870
|
+
}
|
|
4871
|
+
//#endregion
|
|
5011
4872
|
//#region src/migrations/index.ts
|
|
5012
4873
|
/**
|
|
5013
4874
|
* Custom migration provider that loads migrations from imported modules
|
|
@@ -5016,7 +4877,8 @@ var StaticMigrationProvider = class {
|
|
|
5016
4877
|
getMigrations() {
|
|
5017
4878
|
return Promise.resolve({
|
|
5018
4879
|
"001_create_document_permissions": _001_create_document_permissions_exports,
|
|
5019
|
-
"002_add_document_protection": _002_add_document_protection_exports
|
|
4880
|
+
"002_add_document_protection": _002_add_document_protection_exports,
|
|
4881
|
+
"003_remove_group_tables": _003_remove_group_tables_exports
|
|
5020
4882
|
});
|
|
5021
4883
|
}
|
|
5022
4884
|
};
|
|
@@ -5038,104 +4900,159 @@ async function runMigrations(db) {
|
|
|
5038
4900
|
}
|
|
5039
4901
|
}
|
|
5040
4902
|
//#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 {
|
|
4903
|
+
//#region src/services/auth.service.ts
|
|
4904
|
+
const DEFAULT_CREDENTIAL_CACHE_TTL_MS = 6e4;
|
|
4905
|
+
const CREDENTIAL_CACHE_MAX_ENTRIES = 1e3;
|
|
4906
|
+
var AuthService = class {
|
|
5059
4907
|
config;
|
|
5060
|
-
|
|
5061
|
-
|
|
4908
|
+
credentialCache = /* @__PURE__ */ new Map();
|
|
4909
|
+
constructor(config) {
|
|
5062
4910
|
this.config = config;
|
|
5063
4911
|
}
|
|
4912
|
+
async authenticateRequest(request) {
|
|
4913
|
+
if (!this.config.enabled) return {
|
|
4914
|
+
user: void 0,
|
|
4915
|
+
admins: [],
|
|
4916
|
+
auth_enabled: false
|
|
4917
|
+
};
|
|
4918
|
+
const method = request.method;
|
|
4919
|
+
if (method === "OPTIONS" || method === "GET") return {
|
|
4920
|
+
user: void 0,
|
|
4921
|
+
admins: this.config.admins,
|
|
4922
|
+
auth_enabled: true
|
|
4923
|
+
};
|
|
4924
|
+
return this.verifyBearer(request.headers.get("authorization") ?? void 0);
|
|
4925
|
+
}
|
|
5064
4926
|
/**
|
|
5065
|
-
*
|
|
4927
|
+
* Verify a Bearer token regardless of HTTP method. Use this from non-GraphQL
|
|
4928
|
+
* middleware that must enforce authentication on every request.
|
|
5066
4929
|
*/
|
|
5067
|
-
|
|
5068
|
-
if (!
|
|
5069
|
-
|
|
4930
|
+
async verifyBearer(authorization) {
|
|
4931
|
+
if (!this.config.enabled) return {
|
|
4932
|
+
user: void 0,
|
|
4933
|
+
admins: [],
|
|
4934
|
+
auth_enabled: false
|
|
4935
|
+
};
|
|
4936
|
+
const token = authorization?.split(" ")[1];
|
|
4937
|
+
if (!token) return {
|
|
4938
|
+
user: void 0,
|
|
4939
|
+
admins: this.config.admins,
|
|
4940
|
+
auth_enabled: true
|
|
4941
|
+
};
|
|
4942
|
+
try {
|
|
4943
|
+
const verified = await this.verifyToken(token);
|
|
4944
|
+
if (!verified) return new Response(JSON.stringify({ error: "Verification failed" }), { status: 401 });
|
|
4945
|
+
const user = this.extractUserFromVerification(verified);
|
|
4946
|
+
if (!user) return new Response(JSON.stringify({ error: "Missing credentials" }), { status: 401 });
|
|
4947
|
+
if (!this.config.skipCredentialVerification) {
|
|
4948
|
+
if (!await this.verifyCredentialExists(user.address, user.chainId, verified.issuer)) return new Response(JSON.stringify({ error: "Credentials no longer valid" }), { status: 401 });
|
|
4949
|
+
}
|
|
4950
|
+
return {
|
|
4951
|
+
user,
|
|
4952
|
+
admins: this.config.admins,
|
|
4953
|
+
auth_enabled: true
|
|
4954
|
+
};
|
|
4955
|
+
} catch {
|
|
4956
|
+
return new Response(JSON.stringify({ error: "Authentication failed" }), { status: 401 });
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
async authenticateWebSocketConnection(connectionParams) {
|
|
4960
|
+
if (!this.config.enabled) return null;
|
|
4961
|
+
const authHeader = connectionParams.authorization;
|
|
4962
|
+
if (!authHeader) throw new Error("Missing authorization in connection parameters");
|
|
4963
|
+
const token = authHeader.split(" ")[1];
|
|
4964
|
+
if (!token) throw new Error("Invalid authorization format");
|
|
4965
|
+
const verified = await this.verifyToken(token);
|
|
4966
|
+
if (!verified) throw new Error("Token verification failed");
|
|
4967
|
+
const user = this.extractUserFromVerification(verified);
|
|
4968
|
+
if (!user) throw new Error("Invalid credentials");
|
|
4969
|
+
if (!this.config.skipCredentialVerification) {
|
|
4970
|
+
if (!await this.verifyCredentialExists(user.address, user.chainId, verified.issuer)) throw new Error("Credentials no longer valid");
|
|
4971
|
+
}
|
|
4972
|
+
return user;
|
|
5070
4973
|
}
|
|
5071
4974
|
/**
|
|
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)
|
|
4975
|
+
* Verify the auth bearer token
|
|
5078
4976
|
*/
|
|
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);
|
|
4977
|
+
async verifyToken(token) {
|
|
4978
|
+
return await verifyAuthBearerToken(token);
|
|
5087
4979
|
}
|
|
5088
4980
|
/**
|
|
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)
|
|
4981
|
+
* Extract user information from verification result
|
|
5095
4982
|
*/
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
4983
|
+
extractUserFromVerification(verified) {
|
|
4984
|
+
try {
|
|
4985
|
+
const { address, chainId, networkId } = verified.verifiableCredential.credentialSubject;
|
|
4986
|
+
if (!address || !chainId || !networkId) return null;
|
|
4987
|
+
return {
|
|
4988
|
+
address,
|
|
4989
|
+
chainId,
|
|
4990
|
+
networkId
|
|
4991
|
+
};
|
|
4992
|
+
} catch {
|
|
4993
|
+
return null;
|
|
4994
|
+
}
|
|
5104
4995
|
}
|
|
5105
4996
|
/**
|
|
5106
|
-
*
|
|
4997
|
+
* Verify that the credential still exists on the Renown API.
|
|
5107
4998
|
*
|
|
5108
|
-
*
|
|
5109
|
-
* -
|
|
5110
|
-
*
|
|
4999
|
+
* Results are cached per (address, chainId, issuer) for a short TTL so the
|
|
5000
|
+
* blocking external round-trip is not paid on every request. Concurrent
|
|
5001
|
+
* checks for the same key share a single in-flight request, and entries
|
|
5002
|
+
* that resolve to false are evicted immediately so failed or revoked
|
|
5003
|
+
* credentials are re-checked on the next request.
|
|
5111
5004
|
*/
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
if (
|
|
5115
|
-
const
|
|
5116
|
-
|
|
5117
|
-
|
|
5005
|
+
verifyCredentialExists(address, chainId, appId) {
|
|
5006
|
+
const ttlMs = this.config.credentialVerificationCacheTtlMs ?? DEFAULT_CREDENTIAL_CACHE_TTL_MS;
|
|
5007
|
+
if (ttlMs <= 0) return this.fetchCredentialExists(address, chainId, appId);
|
|
5008
|
+
const key = `${address.toLowerCase()}:${chainId}:${appId}`;
|
|
5009
|
+
const now = Date.now();
|
|
5010
|
+
const cached = this.credentialCache.get(key);
|
|
5011
|
+
if (cached && cached.expiresAt > now) return cached.exists;
|
|
5012
|
+
this.pruneCredentialCache(now);
|
|
5013
|
+
const entry = {
|
|
5014
|
+
exists: this.fetchCredentialExists(address, chainId, appId).then((exists) => {
|
|
5015
|
+
if (!exists && this.credentialCache.get(key) === entry) this.credentialCache.delete(key);
|
|
5016
|
+
return exists;
|
|
5017
|
+
}),
|
|
5018
|
+
expiresAt: now + ttlMs
|
|
5019
|
+
};
|
|
5020
|
+
this.credentialCache.set(key, entry);
|
|
5021
|
+
return entry.exists;
|
|
5118
5022
|
}
|
|
5119
5023
|
/**
|
|
5120
|
-
*
|
|
5121
|
-
*
|
|
5122
|
-
*
|
|
5024
|
+
* Enforce the cache size cap before inserting a new entry: drop expired
|
|
5025
|
+
* entries first, then evict oldest-inserted entries (insertion order
|
|
5026
|
+
* matches expiry order since the TTL is constant) until under the cap, so
|
|
5027
|
+
* a flood of distinct keys cannot grow the map without bound.
|
|
5123
5028
|
*/
|
|
5124
|
-
|
|
5125
|
-
if (this.
|
|
5126
|
-
|
|
5127
|
-
|
|
5029
|
+
pruneCredentialCache(now) {
|
|
5030
|
+
if (this.credentialCache.size < CREDENTIAL_CACHE_MAX_ENTRIES) return;
|
|
5031
|
+
for (const [key, entry] of this.credentialCache) if (entry.expiresAt <= now) this.credentialCache.delete(key);
|
|
5032
|
+
while (this.credentialCache.size >= CREDENTIAL_CACHE_MAX_ENTRIES) {
|
|
5033
|
+
const oldestKey = this.credentialCache.keys().next().value;
|
|
5034
|
+
if (oldestKey === void 0) break;
|
|
5035
|
+
this.credentialCache.delete(oldestKey);
|
|
5036
|
+
}
|
|
5128
5037
|
}
|
|
5129
5038
|
/**
|
|
5130
|
-
*
|
|
5131
|
-
*
|
|
5132
|
-
*
|
|
5133
|
-
* allowing READ-only users with an explicit operation grant to execute that operation.
|
|
5039
|
+
* Fetch the credential from the Renown API and validate it against the
|
|
5040
|
+
* expected address, chainId and issuer. Never throws; returns false on any
|
|
5041
|
+
* network or validation failure.
|
|
5134
5042
|
*/
|
|
5135
|
-
async
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5043
|
+
async fetchCredentialExists(address, chainId, appId) {
|
|
5044
|
+
const url = `https://www.renown.id/api/auth/credential?address=${address}&chainId=${chainId}&connectId=${appId}&appId=${appId}`;
|
|
5045
|
+
try {
|
|
5046
|
+
const response = await fetch(url, { method: "GET" });
|
|
5047
|
+
if (response.status !== 200) return false;
|
|
5048
|
+
const credential = (await response.json()).credential;
|
|
5049
|
+
const appIdVerfied = credential.credentialSubject.id;
|
|
5050
|
+
const addressVerfied = credential.issuer.id.split(":")[4];
|
|
5051
|
+
const chainIdVerfied = credential.issuer.id.split(":")[3];
|
|
5052
|
+
return appIdVerfied === appId && addressVerfied.toLocaleLowerCase() === address.toLocaleLowerCase() && chainIdVerfied === chainId.toString();
|
|
5053
|
+
} catch {
|
|
5054
|
+
return false;
|
|
5055
|
+
}
|
|
5139
5056
|
}
|
|
5140
5057
|
};
|
|
5141
5058
|
//#endregion
|
|
@@ -5149,7 +5066,7 @@ var AuthorizationService = class {
|
|
|
5149
5066
|
* - ADMIN: Can manage document permissions and settings
|
|
5150
5067
|
*
|
|
5151
5068
|
* Operation permissions:
|
|
5152
|
-
* - Users
|
|
5069
|
+
* - Users can be granted permission to execute specific operations
|
|
5153
5070
|
*/
|
|
5154
5071
|
var DocumentPermissionService = class {
|
|
5155
5072
|
config;
|
|
@@ -5228,209 +5145,7 @@ var DocumentPermissionService = class {
|
|
|
5228
5145
|
*/
|
|
5229
5146
|
async deleteAllDocumentPermissions(documentId) {
|
|
5230
5147
|
await this.db.deleteFrom("DocumentPermission").where("documentId", "=", documentId).execute();
|
|
5231
|
-
await this.db.deleteFrom("DocumentGroupPermission").where("documentId", "=", documentId).execute();
|
|
5232
5148
|
await this.db.deleteFrom("OperationUserPermission").where("documentId", "=", documentId).execute();
|
|
5233
|
-
await this.db.deleteFrom("OperationGroupPermission").where("documentId", "=", documentId).execute();
|
|
5234
|
-
}
|
|
5235
|
-
/**
|
|
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
|
-
* Create a new group
|
|
5294
|
-
*/
|
|
5295
|
-
async createGroup(name, description) {
|
|
5296
|
-
const now = /* @__PURE__ */ new Date();
|
|
5297
|
-
await this.db.insertInto("Group").values({
|
|
5298
|
-
name,
|
|
5299
|
-
description: description ?? null,
|
|
5300
|
-
createdAt: now,
|
|
5301
|
-
updatedAt: now
|
|
5302
|
-
}).execute();
|
|
5303
|
-
return await this.db.selectFrom("Group").select([
|
|
5304
|
-
"id",
|
|
5305
|
-
"name",
|
|
5306
|
-
"description",
|
|
5307
|
-
"createdAt",
|
|
5308
|
-
"updatedAt"
|
|
5309
|
-
]).where("name", "=", name).executeTakeFirstOrThrow();
|
|
5310
|
-
}
|
|
5311
|
-
/**
|
|
5312
|
-
* Delete a group and all its associations
|
|
5313
|
-
*/
|
|
5314
|
-
async deleteGroup(groupId) {
|
|
5315
|
-
await this.db.deleteFrom("OperationGroupPermission").where("groupId", "=", groupId).execute();
|
|
5316
|
-
await this.db.deleteFrom("DocumentGroupPermission").where("groupId", "=", groupId).execute();
|
|
5317
|
-
await this.db.deleteFrom("UserGroup").where("groupId", "=", groupId).execute();
|
|
5318
|
-
await this.db.deleteFrom("Group").where("id", "=", groupId).execute();
|
|
5319
|
-
}
|
|
5320
|
-
/**
|
|
5321
|
-
* Get a group by ID
|
|
5322
|
-
*/
|
|
5323
|
-
async getGroup(groupId) {
|
|
5324
|
-
return await this.db.selectFrom("Group").select([
|
|
5325
|
-
"id",
|
|
5326
|
-
"name",
|
|
5327
|
-
"description",
|
|
5328
|
-
"createdAt",
|
|
5329
|
-
"updatedAt"
|
|
5330
|
-
]).where("id", "=", groupId).executeTakeFirst() ?? null;
|
|
5331
|
-
}
|
|
5332
|
-
/**
|
|
5333
|
-
* List all groups
|
|
5334
|
-
*/
|
|
5335
|
-
async listGroups() {
|
|
5336
|
-
return this.db.selectFrom("Group").select([
|
|
5337
|
-
"id",
|
|
5338
|
-
"name",
|
|
5339
|
-
"description",
|
|
5340
|
-
"createdAt",
|
|
5341
|
-
"updatedAt"
|
|
5342
|
-
]).execute();
|
|
5343
|
-
}
|
|
5344
|
-
/**
|
|
5345
|
-
* Add a user to a group
|
|
5346
|
-
*/
|
|
5347
|
-
async addUserToGroup(userAddress, groupId) {
|
|
5348
|
-
const now = /* @__PURE__ */ new Date();
|
|
5349
|
-
const normalizedAddress = userAddress.toLowerCase();
|
|
5350
|
-
await this.db.insertInto("UserGroup").values({
|
|
5351
|
-
userAddress: normalizedAddress,
|
|
5352
|
-
groupId,
|
|
5353
|
-
createdAt: now
|
|
5354
|
-
}).onConflict((oc) => oc.columns(["userAddress", "groupId"]).doNothing()).execute();
|
|
5355
|
-
}
|
|
5356
|
-
/**
|
|
5357
|
-
* Remove a user from a group
|
|
5358
|
-
*/
|
|
5359
|
-
async removeUserFromGroup(userAddress, groupId) {
|
|
5360
|
-
await this.db.deleteFrom("UserGroup").where("userAddress", "=", userAddress.toLowerCase()).where("groupId", "=", groupId).execute();
|
|
5361
|
-
}
|
|
5362
|
-
/**
|
|
5363
|
-
* Get all groups a user belongs to
|
|
5364
|
-
*/
|
|
5365
|
-
async getUserGroups(userAddress) {
|
|
5366
|
-
return this.db.selectFrom("UserGroup").innerJoin("Group", "Group.id", "UserGroup.groupId").select([
|
|
5367
|
-
"Group.id",
|
|
5368
|
-
"Group.name",
|
|
5369
|
-
"Group.description",
|
|
5370
|
-
"Group.createdAt",
|
|
5371
|
-
"Group.updatedAt"
|
|
5372
|
-
]).where("UserGroup.userAddress", "=", userAddress.toLowerCase()).execute();
|
|
5373
|
-
}
|
|
5374
|
-
/**
|
|
5375
|
-
* Get all members of a group
|
|
5376
|
-
*/
|
|
5377
|
-
async getGroupMembers(groupId) {
|
|
5378
|
-
return (await this.db.selectFrom("UserGroup").select("userAddress").where("groupId", "=", groupId).execute()).map((r) => r.userAddress);
|
|
5379
|
-
}
|
|
5380
|
-
/**
|
|
5381
|
-
* Grant a group permission on a document
|
|
5382
|
-
*/
|
|
5383
|
-
async grantGroupPermission(documentId, groupId, permission, grantedBy) {
|
|
5384
|
-
const now = /* @__PURE__ */ new Date();
|
|
5385
|
-
await this.db.insertInto("DocumentGroupPermission").values({
|
|
5386
|
-
documentId,
|
|
5387
|
-
groupId,
|
|
5388
|
-
permission,
|
|
5389
|
-
grantedBy: grantedBy.toLowerCase(),
|
|
5390
|
-
createdAt: now,
|
|
5391
|
-
updatedAt: now
|
|
5392
|
-
}).onConflict((oc) => oc.columns(["documentId", "groupId"]).doUpdateSet({
|
|
5393
|
-
permission,
|
|
5394
|
-
grantedBy: grantedBy.toLowerCase(),
|
|
5395
|
-
updatedAt: now
|
|
5396
|
-
})).execute();
|
|
5397
|
-
return await this.db.selectFrom("DocumentGroupPermission").select([
|
|
5398
|
-
"documentId",
|
|
5399
|
-
"groupId",
|
|
5400
|
-
"permission",
|
|
5401
|
-
"grantedBy",
|
|
5402
|
-
"createdAt",
|
|
5403
|
-
"updatedAt"
|
|
5404
|
-
]).where("documentId", "=", documentId).where("groupId", "=", groupId).executeTakeFirstOrThrow();
|
|
5405
|
-
}
|
|
5406
|
-
/**
|
|
5407
|
-
* Revoke a group's permission on a document
|
|
5408
|
-
*/
|
|
5409
|
-
async revokeGroupPermission(documentId, groupId) {
|
|
5410
|
-
await this.db.deleteFrom("DocumentGroupPermission").where("documentId", "=", documentId).where("groupId", "=", groupId).execute();
|
|
5411
|
-
}
|
|
5412
|
-
/**
|
|
5413
|
-
* Get all group permissions for a document
|
|
5414
|
-
*/
|
|
5415
|
-
async getDocumentGroupPermissions(documentId) {
|
|
5416
|
-
return this.db.selectFrom("DocumentGroupPermission").select([
|
|
5417
|
-
"documentId",
|
|
5418
|
-
"groupId",
|
|
5419
|
-
"permission",
|
|
5420
|
-
"grantedBy",
|
|
5421
|
-
"createdAt",
|
|
5422
|
-
"updatedAt"
|
|
5423
|
-
]).where("documentId", "=", documentId).execute();
|
|
5424
|
-
}
|
|
5425
|
-
/**
|
|
5426
|
-
* Get best permission level a user has on a document via groups
|
|
5427
|
-
*/
|
|
5428
|
-
async getUserGroupPermission(documentId, userAddress) {
|
|
5429
|
-
const result = await this.db.selectFrom("DocumentGroupPermission").innerJoin("UserGroup", "UserGroup.groupId", "DocumentGroupPermission.groupId").select("DocumentGroupPermission.permission").where("DocumentGroupPermission.documentId", "=", documentId).where("UserGroup.userAddress", "=", userAddress.toLowerCase()).execute();
|
|
5430
|
-
if (result.length === 0) return null;
|
|
5431
|
-
if (result.some((r) => r.permission === "ADMIN")) return "ADMIN";
|
|
5432
|
-
if (result.some((r) => r.permission === "WRITE")) return "WRITE";
|
|
5433
|
-
return "READ";
|
|
5434
5149
|
}
|
|
5435
5150
|
/**
|
|
5436
5151
|
* Grant a user permission to execute an operation on a document
|
|
@@ -5464,36 +5179,6 @@ var DocumentPermissionService = class {
|
|
|
5464
5179
|
await this.db.deleteFrom("OperationUserPermission").where("documentId", "=", documentId).where("operationType", "=", operationType).where("userAddress", "=", userAddress.toLowerCase()).execute();
|
|
5465
5180
|
}
|
|
5466
5181
|
/**
|
|
5467
|
-
* Grant a group permission to execute an operation on a document
|
|
5468
|
-
*/
|
|
5469
|
-
async grantGroupOperationPermission(documentId, operationType, groupId, grantedBy) {
|
|
5470
|
-
const now = /* @__PURE__ */ new Date();
|
|
5471
|
-
await this.db.insertInto("OperationGroupPermission").values({
|
|
5472
|
-
documentId,
|
|
5473
|
-
operationType,
|
|
5474
|
-
groupId,
|
|
5475
|
-
grantedBy: grantedBy.toLowerCase(),
|
|
5476
|
-
createdAt: now
|
|
5477
|
-
}).onConflict((oc) => oc.columns([
|
|
5478
|
-
"documentId",
|
|
5479
|
-
"operationType",
|
|
5480
|
-
"groupId"
|
|
5481
|
-
]).doNothing()).execute();
|
|
5482
|
-
return await this.db.selectFrom("OperationGroupPermission").select([
|
|
5483
|
-
"documentId",
|
|
5484
|
-
"operationType",
|
|
5485
|
-
"groupId",
|
|
5486
|
-
"grantedBy",
|
|
5487
|
-
"createdAt"
|
|
5488
|
-
]).where("documentId", "=", documentId).where("operationType", "=", operationType).where("groupId", "=", groupId).executeTakeFirstOrThrow();
|
|
5489
|
-
}
|
|
5490
|
-
/**
|
|
5491
|
-
* Revoke a group's permission to execute an operation
|
|
5492
|
-
*/
|
|
5493
|
-
async revokeGroupOperationPermission(documentId, operationType, groupId) {
|
|
5494
|
-
await this.db.deleteFrom("OperationGroupPermission").where("documentId", "=", documentId).where("operationType", "=", operationType).where("groupId", "=", groupId).execute();
|
|
5495
|
-
}
|
|
5496
|
-
/**
|
|
5497
5182
|
* Get all users with permission to execute an operation
|
|
5498
5183
|
*/
|
|
5499
5184
|
async getOperationUserPermissions(documentId, operationType) {
|
|
@@ -5506,35 +5191,18 @@ var DocumentPermissionService = class {
|
|
|
5506
5191
|
]).where("documentId", "=", documentId).where("operationType", "=", operationType).execute();
|
|
5507
5192
|
}
|
|
5508
5193
|
/**
|
|
5509
|
-
*
|
|
5194
|
+
* Whether an operation-permission row exists for the user on this operation.
|
|
5510
5195
|
*/
|
|
5511
|
-
async
|
|
5512
|
-
return this.db.selectFrom("OperationGroupPermission").select([
|
|
5513
|
-
"documentId",
|
|
5514
|
-
"operationType",
|
|
5515
|
-
"groupId",
|
|
5516
|
-
"grantedBy",
|
|
5517
|
-
"createdAt"
|
|
5518
|
-
]).where("documentId", "=", documentId).where("operationType", "=", operationType).execute();
|
|
5519
|
-
}
|
|
5520
|
-
/**
|
|
5521
|
-
* Check if a user can execute a specific operation on a document.
|
|
5522
|
-
* Returns true if user has direct permission or is in a group with permission.
|
|
5523
|
-
*/
|
|
5524
|
-
async canExecuteOperation(documentId, operationType, userAddress) {
|
|
5525
|
-
if (!userAddress) return false;
|
|
5196
|
+
async hasOperationGrant(documentId, operationType, userAddress) {
|
|
5526
5197
|
const normalizedAddress = userAddress.toLowerCase();
|
|
5527
|
-
|
|
5528
|
-
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();
|
|
5198
|
+
return !!await this.db.selectFrom("OperationUserPermission").select("userAddress").where("documentId", "=", documentId).where("operationType", "=", operationType).where("userAddress", "=", normalizedAddress).executeTakeFirst();
|
|
5529
5199
|
}
|
|
5530
5200
|
/**
|
|
5531
5201
|
* Check if an operation has any permissions set (is restricted)
|
|
5532
5202
|
*/
|
|
5533
5203
|
async isOperationRestricted(documentId, operationType) {
|
|
5534
5204
|
const userPermCount = await this.db.selectFrom("OperationUserPermission").select(sql`count(*)`.as("count")).where("documentId", "=", documentId).where("operationType", "=", operationType).executeTakeFirst();
|
|
5535
|
-
|
|
5536
|
-
const groupPermCount = await this.db.selectFrom("OperationGroupPermission").select(sql`count(*)`.as("count")).where("documentId", "=", documentId).where("operationType", "=", operationType).executeTakeFirst();
|
|
5537
|
-
return groupPermCount !== void 0 && Number(groupPermCount.count) > 0;
|
|
5205
|
+
return userPermCount !== void 0 && Number(userPermCount.count) > 0;
|
|
5538
5206
|
}
|
|
5539
5207
|
/**
|
|
5540
5208
|
* Check if a specific document has a protection row set to true.
|
|
@@ -5649,6 +5317,60 @@ var DocumentPermissionService = class {
|
|
|
5649
5317
|
}
|
|
5650
5318
|
};
|
|
5651
5319
|
//#endregion
|
|
5320
|
+
//#region src/services/get-parent-ids.ts
|
|
5321
|
+
/**
|
|
5322
|
+
* The canonical parent-document resolver used for permission inheritance:
|
|
5323
|
+
* a document's parents are the sources of its incoming "child"
|
|
5324
|
+
* relationships. Lookup failures resolve to "no parents" so a relationship
|
|
5325
|
+
* store outage degrades to no inherited permissions rather than an error.
|
|
5326
|
+
*/
|
|
5327
|
+
function createGetParentIdsFn(reactorClient) {
|
|
5328
|
+
return async (documentId) => {
|
|
5329
|
+
try {
|
|
5330
|
+
return (await reactorClient.getIncomingRelationships(documentId, "child")).results.map((doc) => doc.header.id);
|
|
5331
|
+
} catch {
|
|
5332
|
+
return [];
|
|
5333
|
+
}
|
|
5334
|
+
};
|
|
5335
|
+
}
|
|
5336
|
+
//#endregion
|
|
5337
|
+
//#region src/services/mcp-request-authorizer.ts
|
|
5338
|
+
/**
|
|
5339
|
+
* Authorization gate for the /mcp endpoint (AUTH_REVIEW S-C1). MCP tools have
|
|
5340
|
+
* unrestricted reactor access, so access is limited to supreme admins when
|
|
5341
|
+
* auth is enabled. OPEN policy is public; any other policy without an
|
|
5342
|
+
* AuthService fails closed.
|
|
5343
|
+
*/
|
|
5344
|
+
function createMcpRequestAuthorizer(authService, authorizationService) {
|
|
5345
|
+
return async (req) => {
|
|
5346
|
+
if (!authService) {
|
|
5347
|
+
if (authorizationService.config.policy === AuthorizationPolicy.OPEN) return { authorized: true };
|
|
5348
|
+
return {
|
|
5349
|
+
authorized: false,
|
|
5350
|
+
status: 401,
|
|
5351
|
+
message: "Authentication required"
|
|
5352
|
+
};
|
|
5353
|
+
}
|
|
5354
|
+
const context = await authService.verifyBearer(req.headers.authorization);
|
|
5355
|
+
if (context instanceof Response) return {
|
|
5356
|
+
authorized: false,
|
|
5357
|
+
status: context.status,
|
|
5358
|
+
message: "Authentication failed"
|
|
5359
|
+
};
|
|
5360
|
+
if (!context.user) return {
|
|
5361
|
+
authorized: false,
|
|
5362
|
+
status: 401,
|
|
5363
|
+
message: "Authentication required"
|
|
5364
|
+
};
|
|
5365
|
+
if (!authorizationService.isSupremeAdmin(context.user.address)) return {
|
|
5366
|
+
authorized: false,
|
|
5367
|
+
status: 403,
|
|
5368
|
+
message: "Forbidden: MCP access requires an administrator"
|
|
5369
|
+
};
|
|
5370
|
+
return { authorized: true };
|
|
5371
|
+
};
|
|
5372
|
+
}
|
|
5373
|
+
//#endregion
|
|
5652
5374
|
//#region src/utils/db.ts
|
|
5653
5375
|
function isPG(connectionString) {
|
|
5654
5376
|
if (connectionString.startsWith("postgresql://") || connectionString.startsWith("postgres://")) return true;
|
|
@@ -5774,6 +5496,33 @@ const initAnalyticsStoreSql = [
|
|
|
5774
5496
|
//#region src/server.ts
|
|
5775
5497
|
const defaultLogger = childLogger(["reactor-api", "server"]);
|
|
5776
5498
|
const DEFAULT_PORT = 4e3;
|
|
5499
|
+
/**
|
|
5500
|
+
* Doc-perms require auth: with auth off no `user` is ever resolved, so every
|
|
5501
|
+
* authorization check fails closed. Refuse to boot rather than run broken.
|
|
5502
|
+
*/
|
|
5503
|
+
function assertAuthRequiredForDocumentPermissions(authEnabled, documentPermissionsRequested) {
|
|
5504
|
+
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.");
|
|
5505
|
+
}
|
|
5506
|
+
/**
|
|
5507
|
+
* Refuses SKIP_CREDENTIAL_VERIFICATION at boot outside tests or an explicit
|
|
5508
|
+
* opt-in: it removes the only binding between a token's claimed address and its
|
|
5509
|
+
* signing key. Fail-closed — unset NODE_ENV counts as production.
|
|
5510
|
+
*/
|
|
5511
|
+
function assertSkipCredentialVerificationAllowed(authEnabled, skipCredentialVerification, env) {
|
|
5512
|
+
if (!authEnabled || !skipCredentialVerification) return;
|
|
5513
|
+
const inAutomatedTest = env.VITEST === "true" || env.NODE_ENV === "test";
|
|
5514
|
+
const acknowledged = env.ALLOW_INSECURE_SKIP_CREDENTIAL_VERIFICATION === "true";
|
|
5515
|
+
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.");
|
|
5516
|
+
}
|
|
5517
|
+
function createReadinessGate() {
|
|
5518
|
+
let ready = false;
|
|
5519
|
+
return {
|
|
5520
|
+
isReady: () => ready,
|
|
5521
|
+
markReady: () => {
|
|
5522
|
+
ready = true;
|
|
5523
|
+
}
|
|
5524
|
+
};
|
|
5525
|
+
}
|
|
5777
5526
|
function resolveAttachmentStoragePath(options) {
|
|
5778
5527
|
if (options.attachmentStoragePath) return options.attachmentStoragePath;
|
|
5779
5528
|
if (options.dbPath && !options.dbPath.startsWith("postgres")) return path.resolve(options.dbPath, "..", "attachments");
|
|
@@ -5812,11 +5561,8 @@ function makeDbClosers(knexInstance, pglite) {
|
|
|
5812
5561
|
/**
|
|
5813
5562
|
* Sets up the subgraph manager and registers subgraphs
|
|
5814
5563
|
*/
|
|
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);
|
|
5564
|
+
async function setupGraphQLManager(httpAdapter, authFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, authorizationService, authService, documentPermissionService, enableDocumentModelSubgraphs, port, reactorDriveClient) {
|
|
5565
|
+
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
5566
|
await graphqlManager.init(subgraphs.core, authFetchMiddleware);
|
|
5821
5567
|
for (const [, collection] of subgraphs.extended.entries()) for (const subgraph of collection) await graphqlManager.registerSubgraph(subgraph, "graphql");
|
|
5822
5568
|
await graphqlManager.updateRouter();
|
|
@@ -5890,6 +5636,7 @@ async function startServer(httpAdapter, port, httpsOptions, logger) {
|
|
|
5890
5636
|
async function _setupCommonInfrastructure(options) {
|
|
5891
5637
|
const port = options.port ?? DEFAULT_PORT;
|
|
5892
5638
|
const { adapter: httpAdapter } = await createHttpAdapter("express");
|
|
5639
|
+
const logger = options.logger ?? defaultLogger;
|
|
5893
5640
|
let admins = [];
|
|
5894
5641
|
let authEnabled = false;
|
|
5895
5642
|
if (options.configFile) {
|
|
@@ -5900,17 +5647,26 @@ async function _setupCommonInfrastructure(options) {
|
|
|
5900
5647
|
admins = options.auth.admins.map((a) => a.toLowerCase());
|
|
5901
5648
|
authEnabled = options.auth.enabled;
|
|
5902
5649
|
}
|
|
5903
|
-
const { AUTH_ENABLED, ADMINS, DEFAULT_PROTECTION, DOCUMENT_PERMISSIONS_ENABLED, SKIP_CREDENTIAL_VERIFICATION } = process.env;
|
|
5650
|
+
const { AUTH_ENABLED, ADMINS, DEFAULT_PROTECTION, DOCUMENT_PERMISSIONS_ENABLED, SKIP_CREDENTIAL_VERIFICATION, CREDENTIAL_VERIFICATION_CACHE_TTL_MS } = process.env;
|
|
5904
5651
|
if (AUTH_ENABLED !== void 0) authEnabled = AUTH_ENABLED === "true";
|
|
5905
5652
|
if (ADMINS !== void 0) admins = ADMINS.split(",").map((a) => a.toLowerCase());
|
|
5906
5653
|
let defaultProtection = false;
|
|
5907
5654
|
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
5655
|
let skipCredentialVerification = false;
|
|
5911
5656
|
if (SKIP_CREDENTIAL_VERIFICATION !== void 0) skipCredentialVerification = SKIP_CREDENTIAL_VERIFICATION === "true";
|
|
5912
|
-
|
|
5657
|
+
let credentialVerificationCacheTtlMs;
|
|
5658
|
+
if (CREDENTIAL_VERIFICATION_CACHE_TTL_MS !== void 0) {
|
|
5659
|
+
const parsed = Number(CREDENTIAL_VERIFICATION_CACHE_TTL_MS);
|
|
5660
|
+
if (CREDENTIAL_VERIFICATION_CACHE_TTL_MS.trim() !== "" && Number.isFinite(parsed) && parsed >= 0) credentialVerificationCacheTtlMs = parsed;
|
|
5661
|
+
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`);
|
|
5662
|
+
}
|
|
5663
|
+
const documentPermissionsRequested = options.documentPermissionService !== void 0 || DOCUMENT_PERMISSIONS_ENABLED === "true";
|
|
5664
|
+
assertAuthRequiredForDocumentPermissions(authEnabled, documentPermissionsRequested);
|
|
5665
|
+
assertSkipCredentialVerificationAllowed(authEnabled, skipCredentialVerification, process.env);
|
|
5666
|
+
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
5667
|
httpAdapter.getRoute("/health", () => new Response("OK", { status: 200 }));
|
|
5668
|
+
const readiness = createReadinessGate();
|
|
5669
|
+
httpAdapter.getRoute("/ready", () => readiness.isReady() ? new Response("OK", { status: 200 }) : new Response("starting", { status: 503 }));
|
|
5914
5670
|
const explorerPrefix = `${config.basePath}/explorer`;
|
|
5915
5671
|
httpAdapter.getRoute(`${explorerPrefix}/:endpoint?`, (request) => {
|
|
5916
5672
|
const url = new URL(request.url);
|
|
@@ -5926,7 +5682,8 @@ async function _setupCommonInfrastructure(options) {
|
|
|
5926
5682
|
authService = new AuthService({
|
|
5927
5683
|
enabled: authEnabled,
|
|
5928
5684
|
admins,
|
|
5929
|
-
skipCredentialVerification
|
|
5685
|
+
skipCredentialVerification,
|
|
5686
|
+
credentialVerificationCacheTtlMs
|
|
5930
5687
|
});
|
|
5931
5688
|
authFetchMiddleware = createAuthFetchMiddleware(authService);
|
|
5932
5689
|
}
|
|
@@ -5942,14 +5699,12 @@ async function _setupCommonInfrastructure(options) {
|
|
|
5942
5699
|
documentPermissionService = new DocumentPermissionService(db, { defaultProtection });
|
|
5943
5700
|
logger.info("Document permission service initialized");
|
|
5944
5701
|
}
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
logger.info("Authorization service initialized");
|
|
5952
|
-
}
|
|
5702
|
+
const policy = documentPermissionService ? AuthorizationPolicy.DOCUMENT_PERMISSIONS : authEnabled ? AuthorizationPolicy.ADMIN_ONLY : AuthorizationPolicy.OPEN;
|
|
5703
|
+
const authorizationConfig = {
|
|
5704
|
+
admins,
|
|
5705
|
+
defaultProtection,
|
|
5706
|
+
policy
|
|
5707
|
+
};
|
|
5953
5708
|
const attachmentStoragePath = resolveAttachmentStoragePath(options);
|
|
5954
5709
|
await mkdir(attachmentStoragePath, { recursive: true });
|
|
5955
5710
|
const { db: attachmentDb, knex: attachmentKnex, pglite: attachmentPglite } = getDbClient(options.dbPath, options.pgliteFactory);
|
|
@@ -5969,23 +5724,20 @@ async function _setupCommonInfrastructure(options) {
|
|
|
5969
5724
|
httpAdapter,
|
|
5970
5725
|
authFetchMiddleware,
|
|
5971
5726
|
authService,
|
|
5972
|
-
auth: {
|
|
5973
|
-
enabled: authEnabled,
|
|
5974
|
-
admins
|
|
5975
|
-
},
|
|
5976
5727
|
relationalDb,
|
|
5977
5728
|
analyticsStore,
|
|
5978
5729
|
documentPermissionService,
|
|
5979
|
-
|
|
5730
|
+
authorizationConfig,
|
|
5980
5731
|
attachments,
|
|
5981
5732
|
packages,
|
|
5982
|
-
dbClosers
|
|
5733
|
+
dbClosers,
|
|
5734
|
+
readiness
|
|
5983
5735
|
};
|
|
5984
5736
|
}
|
|
5985
5737
|
/**
|
|
5986
5738
|
* Private helper function containing common setup logic for API initialization
|
|
5987
5739
|
*/
|
|
5988
|
-
async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options,
|
|
5740
|
+
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
5741
|
const hostModule = {
|
|
5990
5742
|
relationalDb,
|
|
5991
5743
|
analyticsStore,
|
|
@@ -6030,6 +5782,8 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6030
5782
|
}))).flat());
|
|
6031
5783
|
}
|
|
6032
5784
|
const { httpServer, wsServer } = await startServer(httpAdapter, port, options.https, logger);
|
|
5785
|
+
const authorizationService = createAuthorizationService(authorizationConfig, documentPermissionService, createGetParentIdsFn(reactorClient));
|
|
5786
|
+
logger.info(`Authorization service initialized (policy: ${authorizationConfig.policy})`);
|
|
6033
5787
|
const coreSubgraphs = DefaultCoreSubgraphs.slice();
|
|
6034
5788
|
coreSubgraphs.push(ReactorSubgraph);
|
|
6035
5789
|
if (documentPermissionService) {
|
|
@@ -6039,12 +5793,13 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6039
5793
|
const graphqlManager = await setupGraphQLManager(httpAdapter, authFetchMiddleware, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, {
|
|
6040
5794
|
extended: subgraphs,
|
|
6041
5795
|
core: coreSubgraphs
|
|
6042
|
-
}, logger.child(["graphql-manager"]),
|
|
5796
|
+
}, logger.child(["graphql-manager"]), authorizationService, authService, documentPermissionService, options.enableDocumentModelSubgraphs, port, reactorDriveClient);
|
|
6043
5797
|
setupEventListeners(packages, graphqlManager, reactorProcessorManager, hostModule, documentModelRegistry);
|
|
6044
5798
|
if (mcpServerEnabled) {
|
|
6045
5799
|
await setupMcpServer({
|
|
6046
5800
|
client: reactorClient,
|
|
6047
|
-
syncManager
|
|
5801
|
+
syncManager,
|
|
5802
|
+
authorizeRequest: createMcpRequestAuthorizer(authService, authorizationService)
|
|
6048
5803
|
}, httpAdapter);
|
|
6049
5804
|
logger.info(`MCP server available at http://localhost:${port}/mcp`);
|
|
6050
5805
|
}
|
|
@@ -6101,7 +5856,7 @@ function buildApiDispose(args) {
|
|
|
6101
5856
|
};
|
|
6102
5857
|
}
|
|
6103
5858
|
async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
6104
|
-
const { port, httpAdapter, authFetchMiddleware, authService,
|
|
5859
|
+
const { port, httpAdapter, authFetchMiddleware, authService, relationalDb, analyticsStore, documentPermissionService, authorizationConfig, attachments, packages, dbClosers, readiness } = await _setupCommonInfrastructure(options);
|
|
6105
5860
|
const { documentModels, processors, subgraphs } = await packages.init();
|
|
6106
5861
|
const { module: reactorClientModule, reactorDriveClient } = await clientInitializer(documentModels);
|
|
6107
5862
|
const reactorClient = reactorClientModule.client;
|
|
@@ -6112,10 +5867,11 @@ async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
|
6112
5867
|
const documentModelRegistry = reactorClientModule.reactorModule?.documentModelRegistry;
|
|
6113
5868
|
if (!documentModelRegistry) throw new Error("DocumentModelRegistry not available from ReactorClientModule");
|
|
6114
5869
|
return {
|
|
6115
|
-
...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options,
|
|
5870
|
+
...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
5871
|
client: reactorClient,
|
|
6117
5872
|
syncManager,
|
|
6118
|
-
documentModelRegistry
|
|
5873
|
+
documentModelRegistry,
|
|
5874
|
+
readiness
|
|
6119
5875
|
};
|
|
6120
5876
|
}
|
|
6121
5877
|
//#endregion
|
|
@@ -6219,7 +5975,7 @@ var PackageManagementService = class {
|
|
|
6219
5975
|
}
|
|
6220
5976
|
};
|
|
6221
5977
|
//#endregion
|
|
6222
|
-
export { ADMIN_USERS, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph,
|
|
5978
|
+
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
5979
|
|
|
6224
5980
|
//# sourceMappingURL=index.mjs.map
|
|
6225
|
-
//# debugId=
|
|
5981
|
+
//# debugId=88c66104-0cca-57b8-b3ad-93532b51c2b3
|