@powerhousedao/vetra 6.0.0-dev.23 → 6.0.0-dev.24
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/dist/subgraphs/__tests__/app-module-permissions.test.d.ts +2 -0
- package/dist/subgraphs/__tests__/app-module-permissions.test.d.ts.map +1 -0
- package/dist/subgraphs/__tests__/app-module-permissions.test.js +436 -0
- package/dist/subgraphs/__tests__/permission-utils.test.d.ts +2 -0
- package/dist/subgraphs/__tests__/permission-utils.test.d.ts.map +1 -0
- package/dist/subgraphs/__tests__/permission-utils.test.js +505 -0
- package/dist/subgraphs/__tests__/vetra-read-model-permissions.test.d.ts +2 -0
- package/dist/subgraphs/__tests__/vetra-read-model-permissions.test.d.ts.map +1 -0
- package/dist/subgraphs/__tests__/vetra-read-model-permissions.test.js +319 -0
- package/dist/subgraphs/app-module/resolvers.d.ts.map +1 -1
- package/dist/subgraphs/app-module/resolvers.js +53 -9
- package/dist/subgraphs/document-editor/resolvers.d.ts.map +1 -1
- package/dist/subgraphs/document-editor/resolvers.js +45 -7
- package/dist/subgraphs/permission-utils.d.ts +31 -0
- package/dist/subgraphs/permission-utils.d.ts.map +1 -0
- package/dist/subgraphs/permission-utils.js +101 -0
- package/dist/subgraphs/processor-module/resolvers.d.ts.map +1 -1
- package/dist/subgraphs/processor-module/resolvers.js +49 -8
- package/dist/subgraphs/subgraph-module/resolvers.d.ts.map +1 -1
- package/dist/subgraphs/subgraph-module/resolvers.js +37 -5
- package/dist/subgraphs/vetra-package/resolvers.d.ts.map +1 -1
- package/dist/subgraphs/vetra-package/resolvers.js +69 -13
- package/dist/subgraphs/vetra-read-model/resolvers.d.ts +2 -2
- package/dist/subgraphs/vetra-read-model/resolvers.d.ts.map +1 -1
- package/dist/subgraphs/vetra-read-model/resolvers.js +16 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/vitest.config.d.ts.map +1 -1
- package/dist/vitest.config.js +1 -0
- package/package.json +15 -15
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { GraphQLError } from "graphql";
|
|
2
|
+
/**
|
|
3
|
+
* Check if user has global read access (admin, user, or guest)
|
|
4
|
+
*/
|
|
5
|
+
export function hasGlobalReadAccess(ctx) {
|
|
6
|
+
const isGlobalAdmin = ctx.isAdmin?.(ctx.user?.address ?? "");
|
|
7
|
+
const isGlobalUser = ctx.isUser?.(ctx.user?.address ?? "");
|
|
8
|
+
const isGlobalGuest = ctx.isGuest?.(ctx.user?.address ?? "") || process.env.FREE_ENTRY === "true";
|
|
9
|
+
return !!(isGlobalAdmin || isGlobalUser || isGlobalGuest);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Check if user has global write access (admin or user, not guest)
|
|
13
|
+
*/
|
|
14
|
+
export function hasGlobalWriteAccess(ctx) {
|
|
15
|
+
const isGlobalAdmin = ctx.isAdmin?.(ctx.user?.address ?? "");
|
|
16
|
+
const isGlobalUser = ctx.isUser?.(ctx.user?.address ?? "");
|
|
17
|
+
return !!(isGlobalAdmin || isGlobalUser);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get the parent IDs function for hierarchical permission checks
|
|
21
|
+
*/
|
|
22
|
+
function getParentIdsFn(subgraph) {
|
|
23
|
+
return async (documentId) => {
|
|
24
|
+
try {
|
|
25
|
+
const result = await subgraph.reactorClient.getParents(documentId);
|
|
26
|
+
return result.results.map((doc) => doc.header.id);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if user can read a document (with hierarchy)
|
|
35
|
+
*/
|
|
36
|
+
export async function canReadDocument(subgraph, documentId, ctx) {
|
|
37
|
+
// Global access allows reading
|
|
38
|
+
if (hasGlobalReadAccess(ctx)) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
// Check document-level permissions with hierarchy
|
|
42
|
+
if (subgraph.documentPermissionService) {
|
|
43
|
+
return subgraph.documentPermissionService.canRead(documentId, ctx.user?.address, getParentIdsFn(subgraph));
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if user can write to a document (with hierarchy)
|
|
49
|
+
*/
|
|
50
|
+
export async function canWriteDocument(subgraph, documentId, ctx) {
|
|
51
|
+
// Global write access allows writing
|
|
52
|
+
if (hasGlobalWriteAccess(ctx)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
// Check document-level permissions with hierarchy
|
|
56
|
+
if (subgraph.documentPermissionService) {
|
|
57
|
+
return subgraph.documentPermissionService.canWrite(documentId, ctx.user?.address, getParentIdsFn(subgraph));
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Throw an error if user cannot read the document
|
|
63
|
+
*/
|
|
64
|
+
export async function assertCanRead(subgraph, documentId, ctx) {
|
|
65
|
+
const canRead = await canReadDocument(subgraph, documentId, ctx);
|
|
66
|
+
if (!canRead) {
|
|
67
|
+
throw new GraphQLError("Forbidden: insufficient permissions to read this document");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Throw an error if user cannot write to the document
|
|
72
|
+
*/
|
|
73
|
+
export async function assertCanWrite(subgraph, documentId, ctx) {
|
|
74
|
+
const canWrite = await canWriteDocument(subgraph, documentId, ctx);
|
|
75
|
+
if (!canWrite) {
|
|
76
|
+
throw new GraphQLError("Forbidden: insufficient permissions to write to this document");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if user can execute a specific operation on a document.
|
|
81
|
+
* Throws an error if the operation is restricted and user lacks permission.
|
|
82
|
+
*/
|
|
83
|
+
export async function assertCanExecuteOperation(subgraph, documentId, operationType, ctx) {
|
|
84
|
+
// Skip if no permission service
|
|
85
|
+
if (!subgraph.documentPermissionService) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Global admins bypass operation-level restrictions
|
|
89
|
+
if (ctx.isAdmin?.(ctx.user?.address ?? "")) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Check if this operation has any restrictions set
|
|
93
|
+
const isRestricted = await subgraph.documentPermissionService.isOperationRestricted(documentId, operationType);
|
|
94
|
+
if (isRestricted) {
|
|
95
|
+
// Operation is restricted, check if user has permission
|
|
96
|
+
const canExecute = await subgraph.documentPermissionService.canExecuteOperation(documentId, operationType, ctx.user?.address);
|
|
97
|
+
if (!canExecute) {
|
|
98
|
+
throw new GraphQLError(`Forbidden: insufficient permissions to execute operation "${operationType}" on this document`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../../../subgraphs/processor-module/resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../../../subgraphs/processor-module/resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAW,MAAM,4BAA4B,CAAC;AA2BxE,eAAO,MAAM,YAAY,GACvB,UAAU,YAAY,KACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CA0SxB,CAAC"}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { addFile } from "document-drive";
|
|
2
2
|
import { setName } from "document-model";
|
|
3
|
+
import { GraphQLError } from "graphql";
|
|
3
4
|
import { actions, processorModuleDocumentType, } from "@powerhousedao/vetra/document-models/processor-module";
|
|
5
|
+
import { assertCanRead, assertCanWrite, assertCanExecuteOperation, canReadDocument, hasGlobalReadAccess, hasGlobalWriteAccess, } from "../permission-utils.js";
|
|
4
6
|
export const getResolvers = (subgraph) => {
|
|
5
7
|
const reactor = subgraph.reactor;
|
|
6
8
|
return {
|
|
7
9
|
Query: {
|
|
8
|
-
ProcessorModule:
|
|
10
|
+
ProcessorModule: (_, __, ctx) => {
|
|
9
11
|
return {
|
|
10
12
|
getDocument: async (args) => {
|
|
11
13
|
const { docId, driveId } = args;
|
|
12
14
|
if (!docId) {
|
|
13
15
|
throw new Error("Document id is required");
|
|
14
16
|
}
|
|
17
|
+
// Check read permission before accessing document
|
|
18
|
+
await assertCanRead(subgraph, docId, ctx);
|
|
15
19
|
if (driveId) {
|
|
16
20
|
const docIds = await reactor.getDocuments(driveId);
|
|
17
21
|
if (!docIds.includes(docId)) {
|
|
@@ -32,6 +36,8 @@ export const getResolvers = (subgraph) => {
|
|
|
32
36
|
},
|
|
33
37
|
getDocuments: async (args) => {
|
|
34
38
|
const { driveId } = args;
|
|
39
|
+
// Check read permission on drive before listing documents
|
|
40
|
+
await assertCanRead(subgraph, driveId, ctx);
|
|
35
41
|
const docsIds = await reactor.getDocuments(driveId);
|
|
36
42
|
const docs = await Promise.all(docsIds.map(async (docId) => {
|
|
37
43
|
const doc = await reactor.getDocument(docId);
|
|
@@ -46,14 +52,34 @@ export const getResolvers = (subgraph) => {
|
|
|
46
52
|
revision: doc.header?.revision?.global ?? 0,
|
|
47
53
|
};
|
|
48
54
|
}));
|
|
49
|
-
|
|
55
|
+
const filteredByType = docs.filter((doc) => doc.header.documentType === processorModuleDocumentType);
|
|
56
|
+
// If user doesn't have global read access, filter by document-level permissions
|
|
57
|
+
if (!hasGlobalReadAccess(ctx) &&
|
|
58
|
+
subgraph.documentPermissionService) {
|
|
59
|
+
const filteredDocs = [];
|
|
60
|
+
for (const doc of filteredByType) {
|
|
61
|
+
const canRead = await canReadDocument(subgraph, doc.id, ctx);
|
|
62
|
+
if (canRead) {
|
|
63
|
+
filteredDocs.push(doc);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return filteredDocs;
|
|
67
|
+
}
|
|
68
|
+
return filteredByType;
|
|
50
69
|
},
|
|
51
70
|
};
|
|
52
71
|
},
|
|
53
72
|
},
|
|
54
73
|
Mutation: {
|
|
55
|
-
ProcessorModule_createDocument: async (_, args) => {
|
|
74
|
+
ProcessorModule_createDocument: async (_, args, ctx) => {
|
|
56
75
|
const { driveId, name } = args;
|
|
76
|
+
// If creating under a drive, check write permission on drive
|
|
77
|
+
if (driveId) {
|
|
78
|
+
await assertCanWrite(subgraph, driveId, ctx);
|
|
79
|
+
}
|
|
80
|
+
else if (!hasGlobalWriteAccess(ctx)) {
|
|
81
|
+
throw new GraphQLError("Forbidden: insufficient permissions to create documents");
|
|
82
|
+
}
|
|
57
83
|
const document = await reactor.addDocument(processorModuleDocumentType);
|
|
58
84
|
if (driveId) {
|
|
59
85
|
await reactor.addAction(driveId, addFile({
|
|
@@ -67,8 +93,11 @@ export const getResolvers = (subgraph) => {
|
|
|
67
93
|
}
|
|
68
94
|
return document.header.id;
|
|
69
95
|
},
|
|
70
|
-
ProcessorModule_setProcessorName: async (_, args) => {
|
|
96
|
+
ProcessorModule_setProcessorName: async (_, args, ctx) => {
|
|
71
97
|
const { docId, input } = args;
|
|
98
|
+
// Check write permission before mutating document
|
|
99
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
100
|
+
await assertCanExecuteOperation(subgraph, docId, "SET_PROCESSOR_NAME", ctx);
|
|
72
101
|
const doc = await reactor.getDocument(docId);
|
|
73
102
|
if (!doc) {
|
|
74
103
|
throw new Error("Document not found");
|
|
@@ -79,8 +108,11 @@ export const getResolvers = (subgraph) => {
|
|
|
79
108
|
}
|
|
80
109
|
return true;
|
|
81
110
|
},
|
|
82
|
-
ProcessorModule_setProcessorType: async (_, args) => {
|
|
111
|
+
ProcessorModule_setProcessorType: async (_, args, ctx) => {
|
|
83
112
|
const { docId, input } = args;
|
|
113
|
+
// Check write permission before mutating document
|
|
114
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
115
|
+
await assertCanExecuteOperation(subgraph, docId, "SET_PROCESSOR_TYPE", ctx);
|
|
84
116
|
const doc = await reactor.getDocument(docId);
|
|
85
117
|
if (!doc) {
|
|
86
118
|
throw new Error("Document not found");
|
|
@@ -91,8 +123,11 @@ export const getResolvers = (subgraph) => {
|
|
|
91
123
|
}
|
|
92
124
|
return true;
|
|
93
125
|
},
|
|
94
|
-
ProcessorModule_addDocumentType: async (_, args) => {
|
|
126
|
+
ProcessorModule_addDocumentType: async (_, args, ctx) => {
|
|
95
127
|
const { docId, input } = args;
|
|
128
|
+
// Check write permission before mutating document
|
|
129
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
130
|
+
await assertCanExecuteOperation(subgraph, docId, "ADD_DOCUMENT_TYPE", ctx);
|
|
96
131
|
const doc = await reactor.getDocument(docId);
|
|
97
132
|
if (!doc) {
|
|
98
133
|
throw new Error("Document not found");
|
|
@@ -103,8 +138,11 @@ export const getResolvers = (subgraph) => {
|
|
|
103
138
|
}
|
|
104
139
|
return true;
|
|
105
140
|
},
|
|
106
|
-
ProcessorModule_removeDocumentType: async (_, args) => {
|
|
141
|
+
ProcessorModule_removeDocumentType: async (_, args, ctx) => {
|
|
107
142
|
const { docId, input } = args;
|
|
143
|
+
// Check write permission before mutating document
|
|
144
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
145
|
+
await assertCanExecuteOperation(subgraph, docId, "REMOVE_DOCUMENT_TYPE", ctx);
|
|
108
146
|
const doc = await reactor.getDocument(docId);
|
|
109
147
|
if (!doc) {
|
|
110
148
|
throw new Error("Document not found");
|
|
@@ -115,8 +153,11 @@ export const getResolvers = (subgraph) => {
|
|
|
115
153
|
}
|
|
116
154
|
return true;
|
|
117
155
|
},
|
|
118
|
-
ProcessorModule_setProcessorStatus: async (_, args) => {
|
|
156
|
+
ProcessorModule_setProcessorStatus: async (_, args, ctx) => {
|
|
119
157
|
const { docId, input } = args;
|
|
158
|
+
// Check write permission before mutating document
|
|
159
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
160
|
+
await assertCanExecuteOperation(subgraph, docId, "SET_PROCESSOR_STATUS", ctx);
|
|
120
161
|
const doc = await reactor.getDocument(docId);
|
|
121
162
|
if (!doc) {
|
|
122
163
|
throw new Error("Document not found");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../../../subgraphs/subgraph-module/resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../../../subgraphs/subgraph-module/resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAW,MAAM,4BAA4B,CAAC;AAwBxE,eAAO,MAAM,YAAY,GACvB,UAAU,YAAY,KACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAiMxB,CAAC"}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { addFile } from "document-drive";
|
|
2
2
|
import { setName } from "document-model";
|
|
3
|
+
import { GraphQLError } from "graphql";
|
|
3
4
|
import { actions, subgraphModuleDocumentType, } from "@powerhousedao/vetra/document-models/subgraph-module";
|
|
5
|
+
import { assertCanRead, assertCanWrite, assertCanExecuteOperation, canReadDocument, hasGlobalReadAccess, hasGlobalWriteAccess, } from "../permission-utils.js";
|
|
4
6
|
export const getResolvers = (subgraph) => {
|
|
5
7
|
const reactor = subgraph.reactor;
|
|
6
8
|
return {
|
|
7
9
|
Query: {
|
|
8
|
-
SubgraphModule:
|
|
10
|
+
SubgraphModule: (_, __, ctx) => {
|
|
9
11
|
return {
|
|
10
12
|
getDocument: async (args) => {
|
|
11
13
|
const { docId, driveId } = args;
|
|
12
14
|
if (!docId) {
|
|
13
15
|
throw new Error("Document id is required");
|
|
14
16
|
}
|
|
17
|
+
// Check read permission before accessing document
|
|
18
|
+
await assertCanRead(subgraph, docId, ctx);
|
|
15
19
|
if (driveId) {
|
|
16
20
|
const docIds = await reactor.getDocuments(driveId);
|
|
17
21
|
if (!docIds.includes(docId)) {
|
|
@@ -32,6 +36,8 @@ export const getResolvers = (subgraph) => {
|
|
|
32
36
|
},
|
|
33
37
|
getDocuments: async (args) => {
|
|
34
38
|
const { driveId } = args;
|
|
39
|
+
// Check read permission on drive before listing documents
|
|
40
|
+
await assertCanRead(subgraph, driveId, ctx);
|
|
35
41
|
const docsIds = await reactor.getDocuments(driveId);
|
|
36
42
|
const docs = await Promise.all(docsIds.map(async (docId) => {
|
|
37
43
|
const doc = await reactor.getDocument(docId);
|
|
@@ -46,14 +52,34 @@ export const getResolvers = (subgraph) => {
|
|
|
46
52
|
revision: doc.header?.revision?.global ?? 0,
|
|
47
53
|
};
|
|
48
54
|
}));
|
|
49
|
-
|
|
55
|
+
const filteredByType = docs.filter((doc) => doc.header.documentType === subgraphModuleDocumentType);
|
|
56
|
+
// If user doesn't have global read access, filter by document-level permissions
|
|
57
|
+
if (!hasGlobalReadAccess(ctx) &&
|
|
58
|
+
subgraph.documentPermissionService) {
|
|
59
|
+
const filteredDocs = [];
|
|
60
|
+
for (const doc of filteredByType) {
|
|
61
|
+
const canRead = await canReadDocument(subgraph, doc.id, ctx);
|
|
62
|
+
if (canRead) {
|
|
63
|
+
filteredDocs.push(doc);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return filteredDocs;
|
|
67
|
+
}
|
|
68
|
+
return filteredByType;
|
|
50
69
|
},
|
|
51
70
|
};
|
|
52
71
|
},
|
|
53
72
|
},
|
|
54
73
|
Mutation: {
|
|
55
|
-
SubgraphModule_createDocument: async (_, args) => {
|
|
74
|
+
SubgraphModule_createDocument: async (_, args, ctx) => {
|
|
56
75
|
const { driveId, name } = args;
|
|
76
|
+
// If creating under a drive, check write permission on drive
|
|
77
|
+
if (driveId) {
|
|
78
|
+
await assertCanWrite(subgraph, driveId, ctx);
|
|
79
|
+
}
|
|
80
|
+
else if (!hasGlobalWriteAccess(ctx)) {
|
|
81
|
+
throw new GraphQLError("Forbidden: insufficient permissions to create documents");
|
|
82
|
+
}
|
|
57
83
|
const document = await reactor.addDocument(subgraphModuleDocumentType);
|
|
58
84
|
if (driveId) {
|
|
59
85
|
await reactor.addAction(driveId, addFile({
|
|
@@ -67,8 +93,11 @@ export const getResolvers = (subgraph) => {
|
|
|
67
93
|
}
|
|
68
94
|
return document.header.id;
|
|
69
95
|
},
|
|
70
|
-
SubgraphModule_setSubgraphName: async (_, args) => {
|
|
96
|
+
SubgraphModule_setSubgraphName: async (_, args, ctx) => {
|
|
71
97
|
const { docId, input } = args;
|
|
98
|
+
// Check write permission before mutating document
|
|
99
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
100
|
+
await assertCanExecuteOperation(subgraph, docId, "SET_SUBGRAPH_NAME", ctx);
|
|
72
101
|
const doc = await reactor.getDocument(docId);
|
|
73
102
|
if (!doc) {
|
|
74
103
|
throw new Error("Document not found");
|
|
@@ -79,8 +108,11 @@ export const getResolvers = (subgraph) => {
|
|
|
79
108
|
}
|
|
80
109
|
return true;
|
|
81
110
|
},
|
|
82
|
-
SubgraphModule_setSubgraphStatus: async (_, args) => {
|
|
111
|
+
SubgraphModule_setSubgraphStatus: async (_, args, ctx) => {
|
|
83
112
|
const { docId, input } = args;
|
|
113
|
+
// Check write permission before mutating document
|
|
114
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
115
|
+
await assertCanExecuteOperation(subgraph, docId, "SET_SUBGRAPH_STATUS", ctx);
|
|
84
116
|
const doc = await reactor.getDocument(docId);
|
|
85
117
|
if (!doc) {
|
|
86
118
|
throw new Error("Document not found");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../../../subgraphs/vetra-package/resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../../../subgraphs/vetra-package/resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAW,MAAM,4BAA4B,CAAC;AAgCxE,eAAO,MAAM,YAAY,GACvB,UAAU,YAAY,KACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAwdxB,CAAC"}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { addFile } from "document-drive";
|
|
2
2
|
import { setName } from "document-model";
|
|
3
|
+
import { GraphQLError } from "graphql";
|
|
3
4
|
import { actions, vetraPackageDocumentType, } from "@powerhousedao/vetra/document-models/vetra-package";
|
|
5
|
+
import { assertCanRead, assertCanWrite, assertCanExecuteOperation, canReadDocument, hasGlobalReadAccess, hasGlobalWriteAccess, } from "../permission-utils.js";
|
|
4
6
|
export const getResolvers = (subgraph) => {
|
|
5
7
|
const reactor = subgraph.reactor;
|
|
6
8
|
return {
|
|
7
9
|
Query: {
|
|
8
|
-
VetraPackage:
|
|
10
|
+
VetraPackage: (_, __, ctx) => {
|
|
9
11
|
return {
|
|
10
12
|
getDocument: async (args) => {
|
|
11
13
|
const { docId, driveId } = args;
|
|
12
14
|
if (!docId) {
|
|
13
15
|
throw new Error("Document id is required");
|
|
14
16
|
}
|
|
17
|
+
// Check read permission before accessing document
|
|
18
|
+
await assertCanRead(subgraph, docId, ctx);
|
|
15
19
|
if (driveId) {
|
|
16
20
|
const docIds = await reactor.getDocuments(driveId);
|
|
17
21
|
if (!docIds.includes(docId)) {
|
|
@@ -32,6 +36,8 @@ export const getResolvers = (subgraph) => {
|
|
|
32
36
|
},
|
|
33
37
|
getDocuments: async (args) => {
|
|
34
38
|
const { driveId } = args;
|
|
39
|
+
// Check read permission on drive before listing documents
|
|
40
|
+
await assertCanRead(subgraph, driveId, ctx);
|
|
35
41
|
const docsIds = await reactor.getDocuments(driveId);
|
|
36
42
|
const docs = await Promise.all(docsIds.map(async (docId) => {
|
|
37
43
|
const doc = await reactor.getDocument(docId);
|
|
@@ -46,14 +52,34 @@ export const getResolvers = (subgraph) => {
|
|
|
46
52
|
revision: doc.header?.revision?.global ?? 0,
|
|
47
53
|
};
|
|
48
54
|
}));
|
|
49
|
-
|
|
55
|
+
const filteredByType = docs.filter((doc) => doc.header.documentType === vetraPackageDocumentType);
|
|
56
|
+
// If user doesn't have global read access, filter by document-level permissions
|
|
57
|
+
if (!hasGlobalReadAccess(ctx) &&
|
|
58
|
+
subgraph.documentPermissionService) {
|
|
59
|
+
const filteredDocs = [];
|
|
60
|
+
for (const doc of filteredByType) {
|
|
61
|
+
const canRead = await canReadDocument(subgraph, doc.id, ctx);
|
|
62
|
+
if (canRead) {
|
|
63
|
+
filteredDocs.push(doc);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return filteredDocs;
|
|
67
|
+
}
|
|
68
|
+
return filteredByType;
|
|
50
69
|
},
|
|
51
70
|
};
|
|
52
71
|
},
|
|
53
72
|
},
|
|
54
73
|
Mutation: {
|
|
55
|
-
VetraPackage_createDocument: async (_, args) => {
|
|
74
|
+
VetraPackage_createDocument: async (_, args, ctx) => {
|
|
56
75
|
const { driveId, name } = args;
|
|
76
|
+
// If creating under a drive, check write permission on drive
|
|
77
|
+
if (driveId) {
|
|
78
|
+
await assertCanWrite(subgraph, driveId, ctx);
|
|
79
|
+
}
|
|
80
|
+
else if (!hasGlobalWriteAccess(ctx)) {
|
|
81
|
+
throw new GraphQLError("Forbidden: insufficient permissions to create documents");
|
|
82
|
+
}
|
|
57
83
|
const document = await reactor.addDocument(vetraPackageDocumentType);
|
|
58
84
|
if (driveId) {
|
|
59
85
|
await reactor.addAction(driveId, addFile({
|
|
@@ -67,8 +93,11 @@ export const getResolvers = (subgraph) => {
|
|
|
67
93
|
}
|
|
68
94
|
return document.header.id;
|
|
69
95
|
},
|
|
70
|
-
VetraPackage_setPackageName: async (_, args) => {
|
|
96
|
+
VetraPackage_setPackageName: async (_, args, ctx) => {
|
|
71
97
|
const { docId, input } = args;
|
|
98
|
+
// Check write permission before mutating document
|
|
99
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
100
|
+
await assertCanExecuteOperation(subgraph, docId, "SET_PACKAGE_NAME", ctx);
|
|
72
101
|
const doc = await reactor.getDocument(docId);
|
|
73
102
|
if (!doc) {
|
|
74
103
|
throw new Error("Document not found");
|
|
@@ -79,8 +108,11 @@ export const getResolvers = (subgraph) => {
|
|
|
79
108
|
}
|
|
80
109
|
return true;
|
|
81
110
|
},
|
|
82
|
-
VetraPackage_setPackageDescription: async (_, args) => {
|
|
111
|
+
VetraPackage_setPackageDescription: async (_, args, ctx) => {
|
|
83
112
|
const { docId, input } = args;
|
|
113
|
+
// Check write permission before mutating document
|
|
114
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
115
|
+
await assertCanExecuteOperation(subgraph, docId, "SET_PACKAGE_DESCRIPTION", ctx);
|
|
84
116
|
const doc = await reactor.getDocument(docId);
|
|
85
117
|
if (!doc) {
|
|
86
118
|
throw new Error("Document not found");
|
|
@@ -91,8 +123,11 @@ export const getResolvers = (subgraph) => {
|
|
|
91
123
|
}
|
|
92
124
|
return true;
|
|
93
125
|
},
|
|
94
|
-
VetraPackage_setPackageCategory: async (_, args) => {
|
|
126
|
+
VetraPackage_setPackageCategory: async (_, args, ctx) => {
|
|
95
127
|
const { docId, input } = args;
|
|
128
|
+
// Check write permission before mutating document
|
|
129
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
130
|
+
await assertCanExecuteOperation(subgraph, docId, "SET_PACKAGE_CATEGORY", ctx);
|
|
96
131
|
const doc = await reactor.getDocument(docId);
|
|
97
132
|
if (!doc) {
|
|
98
133
|
throw new Error("Document not found");
|
|
@@ -103,8 +138,11 @@ export const getResolvers = (subgraph) => {
|
|
|
103
138
|
}
|
|
104
139
|
return true;
|
|
105
140
|
},
|
|
106
|
-
VetraPackage_setPackageAuthor: async (_, args) => {
|
|
141
|
+
VetraPackage_setPackageAuthor: async (_, args, ctx) => {
|
|
107
142
|
const { docId, input } = args;
|
|
143
|
+
// Check write permission before mutating document
|
|
144
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
145
|
+
await assertCanExecuteOperation(subgraph, docId, "SET_PACKAGE_AUTHOR", ctx);
|
|
108
146
|
const doc = await reactor.getDocument(docId);
|
|
109
147
|
if (!doc) {
|
|
110
148
|
throw new Error("Document not found");
|
|
@@ -115,8 +153,11 @@ export const getResolvers = (subgraph) => {
|
|
|
115
153
|
}
|
|
116
154
|
return true;
|
|
117
155
|
},
|
|
118
|
-
VetraPackage_setPackageAuthorName: async (_, args) => {
|
|
156
|
+
VetraPackage_setPackageAuthorName: async (_, args, ctx) => {
|
|
119
157
|
const { docId, input } = args;
|
|
158
|
+
// Check write permission before mutating document
|
|
159
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
160
|
+
await assertCanExecuteOperation(subgraph, docId, "SET_PACKAGE_AUTHOR_NAME", ctx);
|
|
120
161
|
const doc = await reactor.getDocument(docId);
|
|
121
162
|
if (!doc) {
|
|
122
163
|
throw new Error("Document not found");
|
|
@@ -127,8 +168,11 @@ export const getResolvers = (subgraph) => {
|
|
|
127
168
|
}
|
|
128
169
|
return true;
|
|
129
170
|
},
|
|
130
|
-
VetraPackage_setPackageAuthorWebsite: async (_, args) => {
|
|
171
|
+
VetraPackage_setPackageAuthorWebsite: async (_, args, ctx) => {
|
|
131
172
|
const { docId, input } = args;
|
|
173
|
+
// Check write permission before mutating document
|
|
174
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
175
|
+
await assertCanExecuteOperation(subgraph, docId, "SET_PACKAGE_AUTHOR_WEBSITE", ctx);
|
|
132
176
|
const doc = await reactor.getDocument(docId);
|
|
133
177
|
if (!doc) {
|
|
134
178
|
throw new Error("Document not found");
|
|
@@ -139,8 +183,11 @@ export const getResolvers = (subgraph) => {
|
|
|
139
183
|
}
|
|
140
184
|
return true;
|
|
141
185
|
},
|
|
142
|
-
VetraPackage_addPackageKeyword: async (_, args) => {
|
|
186
|
+
VetraPackage_addPackageKeyword: async (_, args, ctx) => {
|
|
143
187
|
const { docId, input } = args;
|
|
188
|
+
// Check write permission before mutating document
|
|
189
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
190
|
+
await assertCanExecuteOperation(subgraph, docId, "ADD_PACKAGE_KEYWORD", ctx);
|
|
144
191
|
const doc = await reactor.getDocument(docId);
|
|
145
192
|
if (!doc) {
|
|
146
193
|
throw new Error("Document not found");
|
|
@@ -151,8 +198,11 @@ export const getResolvers = (subgraph) => {
|
|
|
151
198
|
}
|
|
152
199
|
return true;
|
|
153
200
|
},
|
|
154
|
-
VetraPackage_removePackageKeyword: async (_, args) => {
|
|
201
|
+
VetraPackage_removePackageKeyword: async (_, args, ctx) => {
|
|
155
202
|
const { docId, input } = args;
|
|
203
|
+
// Check write permission before mutating document
|
|
204
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
205
|
+
await assertCanExecuteOperation(subgraph, docId, "REMOVE_PACKAGE_KEYWORD", ctx);
|
|
156
206
|
const doc = await reactor.getDocument(docId);
|
|
157
207
|
if (!doc) {
|
|
158
208
|
throw new Error("Document not found");
|
|
@@ -163,8 +213,11 @@ export const getResolvers = (subgraph) => {
|
|
|
163
213
|
}
|
|
164
214
|
return true;
|
|
165
215
|
},
|
|
166
|
-
VetraPackage_setPackageGithubUrl: async (_, args) => {
|
|
216
|
+
VetraPackage_setPackageGithubUrl: async (_, args, ctx) => {
|
|
167
217
|
const { docId, input } = args;
|
|
218
|
+
// Check write permission before mutating document
|
|
219
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
220
|
+
await assertCanExecuteOperation(subgraph, docId, "SET_PACKAGE_GITHUB_URL", ctx);
|
|
168
221
|
const doc = await reactor.getDocument(docId);
|
|
169
222
|
if (!doc) {
|
|
170
223
|
throw new Error("Document not found");
|
|
@@ -175,8 +228,11 @@ export const getResolvers = (subgraph) => {
|
|
|
175
228
|
}
|
|
176
229
|
return true;
|
|
177
230
|
},
|
|
178
|
-
VetraPackage_setPackageNpmUrl: async (_, args) => {
|
|
231
|
+
VetraPackage_setPackageNpmUrl: async (_, args, ctx) => {
|
|
179
232
|
const { docId, input } = args;
|
|
233
|
+
// Check write permission before mutating document
|
|
234
|
+
await assertCanWrite(subgraph, docId, ctx);
|
|
235
|
+
await assertCanExecuteOperation(subgraph, docId, "SET_PACKAGE_NPM_URL", ctx);
|
|
180
236
|
const doc = await reactor.getDocument(docId);
|
|
181
237
|
if (!doc) {
|
|
182
238
|
throw new Error("Document not found");
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare const getResolvers: (subgraph:
|
|
1
|
+
import type { BaseSubgraph } from "@powerhousedao/reactor-api";
|
|
2
|
+
export declare const getResolvers: (subgraph: BaseSubgraph) => Record<string, unknown>;
|
|
3
3
|
//# sourceMappingURL=resolvers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../../../subgraphs/vetra-read-model/resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../../../subgraphs/vetra-read-model/resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAW,MAAM,4BAA4B,CAAC;AAKxE,eAAO,MAAM,YAAY,GACvB,UAAU,YAAY,KACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAsExB,CAAC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { VetraReadModelProcessorLegacy } from "../../processors/vetra-read-model/index.legacy.js";
|
|
2
|
+
import { canReadDocument, hasGlobalReadAccess } from "../permission-utils.js";
|
|
2
3
|
export const getResolvers = (subgraph) => {
|
|
3
4
|
const db = subgraph.relationalDb;
|
|
4
5
|
return {
|
|
5
6
|
Query: {
|
|
6
|
-
vetraPackages: async (
|
|
7
|
+
vetraPackages: async (_parent, args, ctx) => {
|
|
7
8
|
const { search, documentId_in } = args;
|
|
8
9
|
const sortOrder = args.sortOrder || "asc";
|
|
9
10
|
let query = VetraReadModelProcessorLegacy.query("vetra-packages", db)
|
|
@@ -16,7 +17,8 @@ export const getResolvers = (subgraph) => {
|
|
|
16
17
|
query = query.where("document_id", "in", documentId_in);
|
|
17
18
|
}
|
|
18
19
|
query = query.orderBy("name", sortOrder);
|
|
19
|
-
|
|
20
|
+
const results = await query.execute();
|
|
21
|
+
const mappedResults = results.map((pkg) => ({
|
|
20
22
|
...pkg,
|
|
21
23
|
documentId: pkg.document_id,
|
|
22
24
|
name: pkg.name,
|
|
@@ -29,6 +31,18 @@ export const getResolvers = (subgraph) => {
|
|
|
29
31
|
keywords: pkg.keywords,
|
|
30
32
|
driveId: pkg.drive_id,
|
|
31
33
|
}));
|
|
34
|
+
// If user doesn't have global read access, filter by document-level permissions
|
|
35
|
+
if (!hasGlobalReadAccess(ctx) && subgraph.documentPermissionService) {
|
|
36
|
+
const filteredResults = [];
|
|
37
|
+
for (const pkg of mappedResults) {
|
|
38
|
+
const canRead = await canReadDocument(subgraph, pkg.documentId, ctx);
|
|
39
|
+
if (canRead) {
|
|
40
|
+
filteredResults.push(pkg);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return filteredResults;
|
|
44
|
+
}
|
|
45
|
+
return mappedResults;
|
|
32
46
|
},
|
|
33
47
|
},
|
|
34
48
|
};
|