@envelop/operation-field-permissions 9.0.1-alpha-20260116145543-f197583d9cd21e690cc9cff89254fcc8e1baa649 → 9.1.0-alpha-20260120163957-b4744604646cfe51bb9485f5a65ddd6bc24c2966
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/LICENSE +21 -0
- package/cjs/index.js +112 -0
- package/cjs/package.json +1 -0
- package/esm/index.js +108 -0
- package/package.json +32 -52
- package/typings/index.d.cts +18 -0
- package/typings/index.d.ts +18 -0
- package/dist/index.cjs +0 -68
- package/dist/index.d.cts +0 -21
- package/dist/index.d.mts +0 -21
- package/dist/index.mjs +0 -68
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Dotan Simha
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/cjs/index.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useOperationFieldPermissions = void 0;
|
|
4
|
+
const graphql_1 = require("graphql");
|
|
5
|
+
const core_1 = require("@envelop/core");
|
|
6
|
+
const extended_validation_1 = require("@envelop/extended-validation");
|
|
7
|
+
const promise_helpers_1 = require("@whatwg-node/promise-helpers");
|
|
8
|
+
const OPERATION_PERMISSIONS_SYMBOL = Symbol('OPERATION_PERMISSIONS_SYMBOL');
|
|
9
|
+
/**
|
|
10
|
+
* Returns a set of type names that allow access to all fields in the type.
|
|
11
|
+
*/
|
|
12
|
+
const getWildcardTypes = (scope) => {
|
|
13
|
+
const wildcardTypes = new Set();
|
|
14
|
+
for (const item of scope) {
|
|
15
|
+
if (item.endsWith('*')) {
|
|
16
|
+
const [typeName] = item.split('.');
|
|
17
|
+
wildcardTypes.add(typeName);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return wildcardTypes;
|
|
21
|
+
};
|
|
22
|
+
const toSet = (input) => typeof input === 'string' ? new Set([input]) : input;
|
|
23
|
+
const getContext = (input) => {
|
|
24
|
+
if (typeof input !== 'object' || !input || !(OPERATION_PERMISSIONS_SYMBOL in input)) {
|
|
25
|
+
throw new Error('OperationScopeRule was used without context.');
|
|
26
|
+
}
|
|
27
|
+
return input[OPERATION_PERMISSIONS_SYMBOL];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Validate whether a user is allowed to execute a certain GraphQL operation.
|
|
31
|
+
*/
|
|
32
|
+
const OperationScopeRule = (options) => (context, executionArgs) => {
|
|
33
|
+
const permissionContext = getContext(executionArgs.contextValue);
|
|
34
|
+
const handleField = (node, objectType) => {
|
|
35
|
+
const schemaCoordinate = `${objectType.name}.${node.name.value}`;
|
|
36
|
+
if (!permissionContext.allowAll &&
|
|
37
|
+
!permissionContext.wildcardTypes.has(objectType.name) &&
|
|
38
|
+
!permissionContext.schemaCoordinates.has(schemaCoordinate)) {
|
|
39
|
+
// We should use GraphQLError once the object constructor lands in stable GraphQL.js
|
|
40
|
+
// and useMaskedErrors supports it.
|
|
41
|
+
const error = new graphql_1.GraphQLError(options.formatError(schemaCoordinate));
|
|
42
|
+
error.nodes = [node];
|
|
43
|
+
context.reportError(error);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
Field(node) {
|
|
48
|
+
const type = context.getType();
|
|
49
|
+
if (type) {
|
|
50
|
+
const namedType = (0, graphql_1.getNamedType)(type);
|
|
51
|
+
if ((0, graphql_1.isIntrospectionType)(namedType)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const parentType = context.getParentType();
|
|
56
|
+
if (parentType) {
|
|
57
|
+
if ((0, graphql_1.isIntrospectionType)(parentType)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
// We handle objects, interface and union permissions differently.
|
|
61
|
+
// When accessing an an object field, we check simply run the check.
|
|
62
|
+
if ((0, graphql_1.isObjectType)(parentType)) {
|
|
63
|
+
handleField(node, parentType);
|
|
64
|
+
}
|
|
65
|
+
// To allow a union case, every type in the union has to be allowed/
|
|
66
|
+
// If one of the types doesn't permit access we should throw a validation error.
|
|
67
|
+
if ((0, graphql_1.isUnionType)(parentType)) {
|
|
68
|
+
for (const objectType of parentType.getTypes()) {
|
|
69
|
+
handleField(node, objectType);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Same goes for interfaces. Every implementation should allow the access of the given
|
|
73
|
+
// field to pass the validation rule.
|
|
74
|
+
if ((0, graphql_1.isInterfaceType)(parentType)) {
|
|
75
|
+
for (const objectType of executionArgs.schema.getImplementations(parentType).objects) {
|
|
76
|
+
handleField(node, objectType);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
const defaultFormatError = (schemaCoordinate) => `Insufficient permissions for selecting '${schemaCoordinate}'.`;
|
|
85
|
+
const useOperationFieldPermissions = (opts) => {
|
|
86
|
+
return {
|
|
87
|
+
onPluginInit({ addPlugin }) {
|
|
88
|
+
addPlugin((0, extended_validation_1.useExtendedValidation)({
|
|
89
|
+
rules: [
|
|
90
|
+
OperationScopeRule({
|
|
91
|
+
formatError: opts.formatError ?? defaultFormatError,
|
|
92
|
+
}),
|
|
93
|
+
],
|
|
94
|
+
}));
|
|
95
|
+
addPlugin((0, core_1.useExtendContext)(context => (0, promise_helpers_1.handleMaybePromise)(() => opts.getPermissions(context), permissions => {
|
|
96
|
+
// Schema coordinates is a set of type-name field-name strings that
|
|
97
|
+
// describe the position of a field in the schema.
|
|
98
|
+
const schemaCoordinates = toSet(permissions);
|
|
99
|
+
const wildcardTypes = getWildcardTypes(schemaCoordinates);
|
|
100
|
+
const scopeContext = {
|
|
101
|
+
schemaCoordinates,
|
|
102
|
+
wildcardTypes,
|
|
103
|
+
allowAll: schemaCoordinates.has('*'),
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
[OPERATION_PERMISSIONS_SYMBOL]: scopeContext,
|
|
107
|
+
};
|
|
108
|
+
})));
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
exports.useOperationFieldPermissions = useOperationFieldPermissions;
|
package/cjs/package.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { getNamedType, GraphQLError, isInterfaceType, isIntrospectionType, isObjectType, isUnionType, } from 'graphql';
|
|
2
|
+
import { useExtendContext } from '@envelop/core';
|
|
3
|
+
import { useExtendedValidation } from '@envelop/extended-validation';
|
|
4
|
+
import { handleMaybePromise } from '@whatwg-node/promise-helpers';
|
|
5
|
+
const OPERATION_PERMISSIONS_SYMBOL = Symbol('OPERATION_PERMISSIONS_SYMBOL');
|
|
6
|
+
/**
|
|
7
|
+
* Returns a set of type names that allow access to all fields in the type.
|
|
8
|
+
*/
|
|
9
|
+
const getWildcardTypes = (scope) => {
|
|
10
|
+
const wildcardTypes = new Set();
|
|
11
|
+
for (const item of scope) {
|
|
12
|
+
if (item.endsWith('*')) {
|
|
13
|
+
const [typeName] = item.split('.');
|
|
14
|
+
wildcardTypes.add(typeName);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return wildcardTypes;
|
|
18
|
+
};
|
|
19
|
+
const toSet = (input) => typeof input === 'string' ? new Set([input]) : input;
|
|
20
|
+
const getContext = (input) => {
|
|
21
|
+
if (typeof input !== 'object' || !input || !(OPERATION_PERMISSIONS_SYMBOL in input)) {
|
|
22
|
+
throw new Error('OperationScopeRule was used without context.');
|
|
23
|
+
}
|
|
24
|
+
return input[OPERATION_PERMISSIONS_SYMBOL];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Validate whether a user is allowed to execute a certain GraphQL operation.
|
|
28
|
+
*/
|
|
29
|
+
const OperationScopeRule = (options) => (context, executionArgs) => {
|
|
30
|
+
const permissionContext = getContext(executionArgs.contextValue);
|
|
31
|
+
const handleField = (node, objectType) => {
|
|
32
|
+
const schemaCoordinate = `${objectType.name}.${node.name.value}`;
|
|
33
|
+
if (!permissionContext.allowAll &&
|
|
34
|
+
!permissionContext.wildcardTypes.has(objectType.name) &&
|
|
35
|
+
!permissionContext.schemaCoordinates.has(schemaCoordinate)) {
|
|
36
|
+
// We should use GraphQLError once the object constructor lands in stable GraphQL.js
|
|
37
|
+
// and useMaskedErrors supports it.
|
|
38
|
+
const error = new GraphQLError(options.formatError(schemaCoordinate));
|
|
39
|
+
error.nodes = [node];
|
|
40
|
+
context.reportError(error);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
Field(node) {
|
|
45
|
+
const type = context.getType();
|
|
46
|
+
if (type) {
|
|
47
|
+
const namedType = getNamedType(type);
|
|
48
|
+
if (isIntrospectionType(namedType)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const parentType = context.getParentType();
|
|
53
|
+
if (parentType) {
|
|
54
|
+
if (isIntrospectionType(parentType)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
// We handle objects, interface and union permissions differently.
|
|
58
|
+
// When accessing an an object field, we check simply run the check.
|
|
59
|
+
if (isObjectType(parentType)) {
|
|
60
|
+
handleField(node, parentType);
|
|
61
|
+
}
|
|
62
|
+
// To allow a union case, every type in the union has to be allowed/
|
|
63
|
+
// If one of the types doesn't permit access we should throw a validation error.
|
|
64
|
+
if (isUnionType(parentType)) {
|
|
65
|
+
for (const objectType of parentType.getTypes()) {
|
|
66
|
+
handleField(node, objectType);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Same goes for interfaces. Every implementation should allow the access of the given
|
|
70
|
+
// field to pass the validation rule.
|
|
71
|
+
if (isInterfaceType(parentType)) {
|
|
72
|
+
for (const objectType of executionArgs.schema.getImplementations(parentType).objects) {
|
|
73
|
+
handleField(node, objectType);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
const defaultFormatError = (schemaCoordinate) => `Insufficient permissions for selecting '${schemaCoordinate}'.`;
|
|
82
|
+
export const useOperationFieldPermissions = (opts) => {
|
|
83
|
+
return {
|
|
84
|
+
onPluginInit({ addPlugin }) {
|
|
85
|
+
addPlugin(useExtendedValidation({
|
|
86
|
+
rules: [
|
|
87
|
+
OperationScopeRule({
|
|
88
|
+
formatError: opts.formatError ?? defaultFormatError,
|
|
89
|
+
}),
|
|
90
|
+
],
|
|
91
|
+
}));
|
|
92
|
+
addPlugin(useExtendContext(context => handleMaybePromise(() => opts.getPermissions(context), permissions => {
|
|
93
|
+
// Schema coordinates is a set of type-name field-name strings that
|
|
94
|
+
// describe the position of a field in the schema.
|
|
95
|
+
const schemaCoordinates = toSet(permissions);
|
|
96
|
+
const wildcardTypes = getWildcardTypes(schemaCoordinates);
|
|
97
|
+
const scopeContext = {
|
|
98
|
+
schemaCoordinates,
|
|
99
|
+
wildcardTypes,
|
|
100
|
+
allowAll: schemaCoordinates.has('*'),
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
[OPERATION_PERMISSIONS_SYMBOL]: scopeContext,
|
|
104
|
+
};
|
|
105
|
+
})));
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@envelop/operation-field-permissions",
|
|
3
|
-
"version": "9.0
|
|
4
|
-
"
|
|
3
|
+
"version": "9.1.0-alpha-20260120163957-b4744604646cfe51bb9485f5a65ddd6bc24c2966",
|
|
4
|
+
"sideEffects": false,
|
|
5
|
+
"peerDependencies": {
|
|
6
|
+
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
7
|
+
"@envelop/core": "^5.5.0-alpha-20260120163957-b4744604646cfe51bb9485f5a65ddd6bc24c2966"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@whatwg-node/promise-helpers": "^1.2.4",
|
|
11
|
+
"tslib": "^2.5.0",
|
|
12
|
+
"@envelop/extended-validation": "^7.1.0-alpha-20260120163957-b4744604646cfe51bb9485f5a65ddd6bc24c2966"
|
|
13
|
+
},
|
|
5
14
|
"repository": {
|
|
6
15
|
"type": "git",
|
|
7
|
-
"url": "https://github.com/graphql-hive/
|
|
16
|
+
"url": "https://github.com/graphql-hive/envelop.git",
|
|
8
17
|
"directory": "packages/plugins/operation-field-permissions"
|
|
9
18
|
},
|
|
10
19
|
"author": "Laurin Quast <laurinquast@googlemail.com.com>",
|
|
@@ -12,71 +21,42 @@
|
|
|
12
21
|
"engines": {
|
|
13
22
|
"node": ">=18.0.0"
|
|
14
23
|
},
|
|
15
|
-
"main": "
|
|
16
|
-
"
|
|
17
|
-
"
|
|
24
|
+
"main": "cjs/index.js",
|
|
25
|
+
"module": "esm/index.js",
|
|
26
|
+
"typings": "typings/index.d.ts",
|
|
27
|
+
"typescript": {
|
|
28
|
+
"definition": "typings/index.d.ts"
|
|
29
|
+
},
|
|
30
|
+
"type": "module",
|
|
18
31
|
"exports": {
|
|
19
32
|
".": {
|
|
20
33
|
"require": {
|
|
21
|
-
"types": "./
|
|
22
|
-
"default": "./
|
|
34
|
+
"types": "./typings/index.d.cts",
|
|
35
|
+
"default": "./cjs/index.js"
|
|
23
36
|
},
|
|
24
37
|
"import": {
|
|
25
|
-
"types": "./
|
|
26
|
-
"default": "./
|
|
38
|
+
"types": "./typings/index.d.ts",
|
|
39
|
+
"default": "./esm/index.js"
|
|
27
40
|
},
|
|
28
41
|
"default": {
|
|
29
|
-
"types": "./
|
|
30
|
-
"default": "./
|
|
42
|
+
"types": "./typings/index.d.ts",
|
|
43
|
+
"default": "./esm/index.js"
|
|
31
44
|
}
|
|
32
45
|
},
|
|
33
46
|
"./*": {
|
|
34
47
|
"require": {
|
|
35
|
-
"types": "./
|
|
36
|
-
"default": "./
|
|
48
|
+
"types": "./typings/*.d.cts",
|
|
49
|
+
"default": "./cjs/*.js"
|
|
37
50
|
},
|
|
38
51
|
"import": {
|
|
39
|
-
"types": "./
|
|
40
|
-
"default": "./
|
|
52
|
+
"types": "./typings/*.d.ts",
|
|
53
|
+
"default": "./esm/*.js"
|
|
41
54
|
},
|
|
42
55
|
"default": {
|
|
43
|
-
"types": "./
|
|
44
|
-
"default": "./
|
|
56
|
+
"types": "./typings/*.d.ts",
|
|
57
|
+
"default": "./esm/*.js"
|
|
45
58
|
}
|
|
46
59
|
},
|
|
47
60
|
"./package.json": "./package.json"
|
|
48
|
-
},
|
|
49
|
-
"files": [
|
|
50
|
-
"dist"
|
|
51
|
-
],
|
|
52
|
-
"scripts": {
|
|
53
|
-
"build": "tsdown",
|
|
54
|
-
"check": "tsc --pretty --noEmit"
|
|
55
|
-
},
|
|
56
|
-
"peerDependencies": {
|
|
57
|
-
"@envelop/core": "workspace:^",
|
|
58
|
-
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
|
|
59
|
-
},
|
|
60
|
-
"dependencies": {
|
|
61
|
-
"@envelop/extended-validation": "workspace:^",
|
|
62
|
-
"@whatwg-node/promise-helpers": "^1.2.4",
|
|
63
|
-
"tslib": "^2.5.0"
|
|
64
|
-
},
|
|
65
|
-
"devDependencies": {
|
|
66
|
-
"@envelop/core": "workspace:^",
|
|
67
|
-
"@graphql-tools/schema": "10.0.30",
|
|
68
|
-
"graphql": "16.12.0",
|
|
69
|
-
"tsdown": "^0.20.0-beta.1",
|
|
70
|
-
"typescript": "5.9.3"
|
|
71
|
-
},
|
|
72
|
-
"publishConfig": {
|
|
73
|
-
"access": "public"
|
|
74
|
-
},
|
|
75
|
-
"sideEffects": false,
|
|
76
|
-
"buildOptions": {
|
|
77
|
-
"input": "./src/index.ts"
|
|
78
|
-
},
|
|
79
|
-
"typescript": {
|
|
80
|
-
"definition": "dist/index.d.mts"
|
|
81
61
|
}
|
|
82
|
-
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Plugin, PromiseOrValue } from '@envelop/core';
|
|
2
|
+
declare const OPERATION_PERMISSIONS_SYMBOL: unique symbol;
|
|
3
|
+
type ScopeContext = {
|
|
4
|
+
allowAll: boolean;
|
|
5
|
+
wildcardTypes: Set<string>;
|
|
6
|
+
schemaCoordinates: Set<string>;
|
|
7
|
+
};
|
|
8
|
+
type OperationScopeRuleOptions = {
|
|
9
|
+
formatError: (schemaCoordinate: string) => string;
|
|
10
|
+
};
|
|
11
|
+
type OperationScopeOptions<TContext> = {
|
|
12
|
+
getPermissions: (context: TContext) => PromiseOrValue<Set<string> | string>;
|
|
13
|
+
formatError?: OperationScopeRuleOptions['formatError'];
|
|
14
|
+
};
|
|
15
|
+
export declare const useOperationFieldPermissions: <TContext>(opts: OperationScopeOptions<TContext>) => Plugin<{
|
|
16
|
+
[OPERATION_PERMISSIONS_SYMBOL]: ScopeContext;
|
|
17
|
+
}>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Plugin, PromiseOrValue } from '@envelop/core';
|
|
2
|
+
declare const OPERATION_PERMISSIONS_SYMBOL: unique symbol;
|
|
3
|
+
type ScopeContext = {
|
|
4
|
+
allowAll: boolean;
|
|
5
|
+
wildcardTypes: Set<string>;
|
|
6
|
+
schemaCoordinates: Set<string>;
|
|
7
|
+
};
|
|
8
|
+
type OperationScopeRuleOptions = {
|
|
9
|
+
formatError: (schemaCoordinate: string) => string;
|
|
10
|
+
};
|
|
11
|
+
type OperationScopeOptions<TContext> = {
|
|
12
|
+
getPermissions: (context: TContext) => PromiseOrValue<Set<string> | string>;
|
|
13
|
+
formatError?: OperationScopeRuleOptions['formatError'];
|
|
14
|
+
};
|
|
15
|
+
export declare const useOperationFieldPermissions: <TContext>(opts: OperationScopeOptions<TContext>) => Plugin<{
|
|
16
|
+
[OPERATION_PERMISSIONS_SYMBOL]: ScopeContext;
|
|
17
|
+
}>;
|
|
18
|
+
export {};
|
package/dist/index.cjs
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
let graphql = require("graphql");
|
|
2
|
-
let _envelop_core = require("@envelop/core");
|
|
3
|
-
let _envelop_extended_validation = require("@envelop/extended-validation");
|
|
4
|
-
let _whatwg_node_promise_helpers = require("@whatwg-node/promise-helpers");
|
|
5
|
-
|
|
6
|
-
//#region src/index.ts
|
|
7
|
-
const OPERATION_PERMISSIONS_SYMBOL = Symbol("OPERATION_PERMISSIONS_SYMBOL");
|
|
8
|
-
/**
|
|
9
|
-
* Returns a set of type names that allow access to all fields in the type.
|
|
10
|
-
*/
|
|
11
|
-
const getWildcardTypes = (scope) => {
|
|
12
|
-
const wildcardTypes = /* @__PURE__ */ new Set();
|
|
13
|
-
for (const item of scope) if (item.endsWith("*")) {
|
|
14
|
-
const typeName = item.split(".")[0];
|
|
15
|
-
wildcardTypes.add(typeName);
|
|
16
|
-
}
|
|
17
|
-
return wildcardTypes;
|
|
18
|
-
};
|
|
19
|
-
const toSet = (input) => typeof input === "string" ? new Set([input]) : input;
|
|
20
|
-
const getContext = (input) => {
|
|
21
|
-
if (typeof input !== "object" || !input || !(OPERATION_PERMISSIONS_SYMBOL in input)) throw new Error("OperationScopeRule was used without context.");
|
|
22
|
-
return input[OPERATION_PERMISSIONS_SYMBOL];
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* Validate whether a user is allowed to execute a certain GraphQL operation.
|
|
26
|
-
*/
|
|
27
|
-
const OperationScopeRule = (options) => (context, executionArgs) => {
|
|
28
|
-
const permissionContext = getContext(executionArgs.contextValue);
|
|
29
|
-
const handleField = (node, objectType) => {
|
|
30
|
-
const schemaCoordinate = `${objectType.name}.${node.name.value}`;
|
|
31
|
-
if (!permissionContext.allowAll && !permissionContext.wildcardTypes.has(objectType.name) && !permissionContext.schemaCoordinates.has(schemaCoordinate)) {
|
|
32
|
-
const error = new graphql.GraphQLError(options.formatError(schemaCoordinate));
|
|
33
|
-
error.nodes = [node];
|
|
34
|
-
context.reportError(error);
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
return { Field(node) {
|
|
38
|
-
const type = context.getType();
|
|
39
|
-
if (type) {
|
|
40
|
-
if ((0, graphql.isIntrospectionType)((0, graphql.getNamedType)(type))) return false;
|
|
41
|
-
}
|
|
42
|
-
const parentType = context.getParentType();
|
|
43
|
-
if (parentType) {
|
|
44
|
-
if ((0, graphql.isIntrospectionType)(parentType)) return false;
|
|
45
|
-
if ((0, graphql.isObjectType)(parentType)) handleField(node, parentType);
|
|
46
|
-
if ((0, graphql.isUnionType)(parentType)) for (const objectType of parentType.getTypes()) handleField(node, objectType);
|
|
47
|
-
if ((0, graphql.isInterfaceType)(parentType)) for (const objectType of executionArgs.schema.getImplementations(parentType).objects) handleField(node, objectType);
|
|
48
|
-
}
|
|
49
|
-
} };
|
|
50
|
-
};
|
|
51
|
-
const defaultFormatError = (schemaCoordinate) => `Insufficient permissions for selecting '${schemaCoordinate}'.`;
|
|
52
|
-
const useOperationFieldPermissions = (opts) => {
|
|
53
|
-
return { onPluginInit({ addPlugin }) {
|
|
54
|
-
addPlugin((0, _envelop_extended_validation.useExtendedValidation)({ rules: [OperationScopeRule({ formatError: opts.formatError ?? defaultFormatError })] }));
|
|
55
|
-
addPlugin((0, _envelop_core.useExtendContext)((context) => (0, _whatwg_node_promise_helpers.handleMaybePromise)(() => opts.getPermissions(context), (permissions) => {
|
|
56
|
-
const schemaCoordinates = toSet(permissions);
|
|
57
|
-
const scopeContext = {
|
|
58
|
-
schemaCoordinates,
|
|
59
|
-
wildcardTypes: getWildcardTypes(schemaCoordinates),
|
|
60
|
-
allowAll: schemaCoordinates.has("*")
|
|
61
|
-
};
|
|
62
|
-
return { [OPERATION_PERMISSIONS_SYMBOL]: scopeContext };
|
|
63
|
-
})));
|
|
64
|
-
} };
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
//#endregion
|
|
68
|
-
exports.useOperationFieldPermissions = useOperationFieldPermissions;
|
package/dist/index.d.cts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Plugin, PromiseOrValue } from "@envelop/core";
|
|
2
|
-
|
|
3
|
-
//#region src/index.d.ts
|
|
4
|
-
declare const OPERATION_PERMISSIONS_SYMBOL: unique symbol;
|
|
5
|
-
type ScopeContext = {
|
|
6
|
-
allowAll: boolean;
|
|
7
|
-
wildcardTypes: Set<string>;
|
|
8
|
-
schemaCoordinates: Set<string>;
|
|
9
|
-
};
|
|
10
|
-
type OperationScopeRuleOptions = {
|
|
11
|
-
formatError: (schemaCoordinate: string) => string;
|
|
12
|
-
};
|
|
13
|
-
type OperationScopeOptions<TContext> = {
|
|
14
|
-
getPermissions: (context: TContext) => PromiseOrValue<Set<string> | string>;
|
|
15
|
-
formatError?: OperationScopeRuleOptions['formatError'];
|
|
16
|
-
};
|
|
17
|
-
declare const useOperationFieldPermissions: <TContext>(opts: OperationScopeOptions<TContext>) => Plugin<{
|
|
18
|
-
[OPERATION_PERMISSIONS_SYMBOL]: ScopeContext;
|
|
19
|
-
}>;
|
|
20
|
-
//#endregion
|
|
21
|
-
export { useOperationFieldPermissions };
|
package/dist/index.d.mts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Plugin, PromiseOrValue } from "@envelop/core";
|
|
2
|
-
|
|
3
|
-
//#region src/index.d.ts
|
|
4
|
-
declare const OPERATION_PERMISSIONS_SYMBOL: unique symbol;
|
|
5
|
-
type ScopeContext = {
|
|
6
|
-
allowAll: boolean;
|
|
7
|
-
wildcardTypes: Set<string>;
|
|
8
|
-
schemaCoordinates: Set<string>;
|
|
9
|
-
};
|
|
10
|
-
type OperationScopeRuleOptions = {
|
|
11
|
-
formatError: (schemaCoordinate: string) => string;
|
|
12
|
-
};
|
|
13
|
-
type OperationScopeOptions<TContext> = {
|
|
14
|
-
getPermissions: (context: TContext) => PromiseOrValue<Set<string> | string>;
|
|
15
|
-
formatError?: OperationScopeRuleOptions['formatError'];
|
|
16
|
-
};
|
|
17
|
-
declare const useOperationFieldPermissions: <TContext>(opts: OperationScopeOptions<TContext>) => Plugin<{
|
|
18
|
-
[OPERATION_PERMISSIONS_SYMBOL]: ScopeContext;
|
|
19
|
-
}>;
|
|
20
|
-
//#endregion
|
|
21
|
-
export { useOperationFieldPermissions };
|
package/dist/index.mjs
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { GraphQLError, getNamedType, isInterfaceType, isIntrospectionType, isObjectType, isUnionType } from "graphql";
|
|
2
|
-
import { useExtendContext } from "@envelop/core";
|
|
3
|
-
import { useExtendedValidation } from "@envelop/extended-validation";
|
|
4
|
-
import { handleMaybePromise } from "@whatwg-node/promise-helpers";
|
|
5
|
-
|
|
6
|
-
//#region src/index.ts
|
|
7
|
-
const OPERATION_PERMISSIONS_SYMBOL = Symbol("OPERATION_PERMISSIONS_SYMBOL");
|
|
8
|
-
/**
|
|
9
|
-
* Returns a set of type names that allow access to all fields in the type.
|
|
10
|
-
*/
|
|
11
|
-
const getWildcardTypes = (scope) => {
|
|
12
|
-
const wildcardTypes = /* @__PURE__ */ new Set();
|
|
13
|
-
for (const item of scope) if (item.endsWith("*")) {
|
|
14
|
-
const typeName = item.split(".")[0];
|
|
15
|
-
wildcardTypes.add(typeName);
|
|
16
|
-
}
|
|
17
|
-
return wildcardTypes;
|
|
18
|
-
};
|
|
19
|
-
const toSet = (input) => typeof input === "string" ? new Set([input]) : input;
|
|
20
|
-
const getContext = (input) => {
|
|
21
|
-
if (typeof input !== "object" || !input || !(OPERATION_PERMISSIONS_SYMBOL in input)) throw new Error("OperationScopeRule was used without context.");
|
|
22
|
-
return input[OPERATION_PERMISSIONS_SYMBOL];
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* Validate whether a user is allowed to execute a certain GraphQL operation.
|
|
26
|
-
*/
|
|
27
|
-
const OperationScopeRule = (options) => (context, executionArgs) => {
|
|
28
|
-
const permissionContext = getContext(executionArgs.contextValue);
|
|
29
|
-
const handleField = (node, objectType) => {
|
|
30
|
-
const schemaCoordinate = `${objectType.name}.${node.name.value}`;
|
|
31
|
-
if (!permissionContext.allowAll && !permissionContext.wildcardTypes.has(objectType.name) && !permissionContext.schemaCoordinates.has(schemaCoordinate)) {
|
|
32
|
-
const error = new GraphQLError(options.formatError(schemaCoordinate));
|
|
33
|
-
error.nodes = [node];
|
|
34
|
-
context.reportError(error);
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
return { Field(node) {
|
|
38
|
-
const type = context.getType();
|
|
39
|
-
if (type) {
|
|
40
|
-
if (isIntrospectionType(getNamedType(type))) return false;
|
|
41
|
-
}
|
|
42
|
-
const parentType = context.getParentType();
|
|
43
|
-
if (parentType) {
|
|
44
|
-
if (isIntrospectionType(parentType)) return false;
|
|
45
|
-
if (isObjectType(parentType)) handleField(node, parentType);
|
|
46
|
-
if (isUnionType(parentType)) for (const objectType of parentType.getTypes()) handleField(node, objectType);
|
|
47
|
-
if (isInterfaceType(parentType)) for (const objectType of executionArgs.schema.getImplementations(parentType).objects) handleField(node, objectType);
|
|
48
|
-
}
|
|
49
|
-
} };
|
|
50
|
-
};
|
|
51
|
-
const defaultFormatError = (schemaCoordinate) => `Insufficient permissions for selecting '${schemaCoordinate}'.`;
|
|
52
|
-
const useOperationFieldPermissions = (opts) => {
|
|
53
|
-
return { onPluginInit({ addPlugin }) {
|
|
54
|
-
addPlugin(useExtendedValidation({ rules: [OperationScopeRule({ formatError: opts.formatError ?? defaultFormatError })] }));
|
|
55
|
-
addPlugin(useExtendContext((context) => handleMaybePromise(() => opts.getPermissions(context), (permissions) => {
|
|
56
|
-
const schemaCoordinates = toSet(permissions);
|
|
57
|
-
const scopeContext = {
|
|
58
|
-
schemaCoordinates,
|
|
59
|
-
wildcardTypes: getWildcardTypes(schemaCoordinates),
|
|
60
|
-
allowAll: schemaCoordinates.has("*")
|
|
61
|
-
};
|
|
62
|
-
return { [OPERATION_PERMISSIONS_SYMBOL]: scopeContext };
|
|
63
|
-
})));
|
|
64
|
-
} };
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
//#endregion
|
|
68
|
-
export { useOperationFieldPermissions };
|