@axinom/mosaic-graphql-common 0.1.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/common/checks.d.ts +9 -0
- package/dist/common/checks.d.ts.map +1 -0
- package/dist/common/checks.js +21 -0
- package/dist/common/checks.js.map +1 -0
- package/dist/common/index.d.ts +3 -0
- package/dist/common/index.d.ts.map +1 -0
- package/dist/common/index.js +19 -0
- package/dist/common/index.js.map +1 -0
- package/dist/common/types.d.ts +13 -0
- package/dist/common/types.d.ts.map +1 -0
- package/dist/common/types.js +15 -0
- package/dist/common/types.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/add-error-codes-enum-plugin.d.ts +27 -0
- package/dist/plugins/add-error-codes-enum-plugin.d.ts.map +1 -0
- package/dist/plugins/add-error-codes-enum-plugin.js +78 -0
- package/dist/plugins/add-error-codes-enum-plugin.js.map +1 -0
- package/dist/plugins/annotate-types-with-permissions-plugin.d.ts +22 -0
- package/dist/plugins/annotate-types-with-permissions-plugin.d.ts.map +1 -0
- package/dist/plugins/annotate-types-with-permissions-plugin.js +145 -0
- package/dist/plugins/annotate-types-with-permissions-plugin.js.map +1 -0
- package/dist/plugins/deprecate-stray-node-id-fields-plugin.d.ts +14 -0
- package/dist/plugins/deprecate-stray-node-id-fields-plugin.d.ts.map +1 -0
- package/dist/plugins/deprecate-stray-node-id-fields-plugin.js +37 -0
- package/dist/plugins/deprecate-stray-node-id-fields-plugin.js.map +1 -0
- package/dist/plugins/generic-bulk-plugin-factory.d.ts +49 -0
- package/dist/plugins/generic-bulk-plugin-factory.d.ts.map +1 -0
- package/dist/plugins/generic-bulk-plugin-factory.js +181 -0
- package/dist/plugins/generic-bulk-plugin-factory.js.map +1 -0
- package/dist/plugins/graphiql-management-mode-plugin-hook.d.ts +13 -0
- package/dist/plugins/graphiql-management-mode-plugin-hook.d.ts.map +1 -0
- package/dist/plugins/graphiql-management-mode-plugin-hook.js +44 -0
- package/dist/plugins/graphiql-management-mode-plugin-hook.js.map +1 -0
- package/dist/plugins/index.d.ts +10 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +26 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/omit-from-query-root-plugin.d.ts +17 -0
- package/dist/plugins/omit-from-query-root-plugin.d.ts.map +1 -0
- package/dist/plugins/omit-from-query-root-plugin.js +43 -0
- package/dist/plugins/omit-from-query-root-plugin.js.map +1 -0
- package/dist/plugins/operations-enum-generator-plugin-factory.d.ts +15 -0
- package/dist/plugins/operations-enum-generator-plugin-factory.d.ts.map +1 -0
- package/dist/plugins/operations-enum-generator-plugin-factory.js +108 -0
- package/dist/plugins/operations-enum-generator-plugin-factory.js.map +1 -0
- package/dist/plugins/subscriptions-plugin-factory.d.ts +9 -0
- package/dist/plugins/subscriptions-plugin-factory.d.ts.map +1 -0
- package/dist/plugins/subscriptions-plugin-factory.js +67 -0
- package/dist/plugins/subscriptions-plugin-factory.js.map +1 -0
- package/dist/plugins/validation-directives-plugin.d.ts +6 -0
- package/dist/plugins/validation-directives-plugin.d.ts.map +1 -0
- package/dist/plugins/validation-directives-plugin.js +117 -0
- package/dist/plugins/validation-directives-plugin.js.map +1 -0
- package/dist/postgraphile/enhance-graphql-errors.d.ts +48 -0
- package/dist/postgraphile/enhance-graphql-errors.d.ts.map +1 -0
- package/dist/postgraphile/enhance-graphql-errors.js +67 -0
- package/dist/postgraphile/enhance-graphql-errors.js.map +1 -0
- package/dist/postgraphile/index.d.ts +4 -0
- package/dist/postgraphile/index.d.ts.map +1 -0
- package/dist/postgraphile/index.js +20 -0
- package/dist/postgraphile/index.js.map +1 -0
- package/dist/postgraphile/postgraphile-options-builder.d.ts +273 -0
- package/dist/postgraphile/postgraphile-options-builder.d.ts.map +1 -0
- package/dist/postgraphile/postgraphile-options-builder.js +419 -0
- package/dist/postgraphile/postgraphile-options-builder.js.map +1 -0
- package/dist/postgraphile/websocket-utils.d.ts +11 -0
- package/dist/postgraphile/websocket-utils.d.ts.map +1 -0
- package/dist/postgraphile/websocket-utils.js +17 -0
- package/dist/postgraphile/websocket-utils.js.map +1 -0
- package/package.json +61 -0
- package/src/common/checks.ts +23 -0
- package/src/common/index.ts +2 -0
- package/src/common/types.ts +15 -0
- package/src/index.ts +3 -0
- package/src/plugins/add-error-codes-enum-plugin.ts +102 -0
- package/src/plugins/annotate-types-with-permissions-plugin.spec.ts +158 -0
- package/src/plugins/annotate-types-with-permissions-plugin.ts +205 -0
- package/src/plugins/deprecate-stray-node-id-fields-plugin.ts +41 -0
- package/src/plugins/generic-bulk-plugin-factory.ts +313 -0
- package/src/plugins/graphiql-management-mode-plugin-hook.ts +46 -0
- package/src/plugins/index.ts +9 -0
- package/src/plugins/omit-from-query-root-plugin.ts +69 -0
- package/src/plugins/operations-enum-generator-plugin-factory.ts +130 -0
- package/src/plugins/subscriptions-plugin-factory.ts +114 -0
- package/src/plugins/validation-directives-plugin.ts +141 -0
- package/src/postgraphile/enhance-graphql-errors.spec.ts +241 -0
- package/src/postgraphile/enhance-graphql-errors.ts +138 -0
- package/src/postgraphile/index.ts +3 -0
- package/src/postgraphile/postgraphile-options-builder.spec.ts +744 -0
- package/src/postgraphile/postgraphile-options-builder.ts +510 -0
- package/src/postgraphile/websocket-utils.ts +19 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import 'jest-extended';
|
|
2
|
+
import { exportedForTestingAnnotatePlugin } from './annotate-types-with-permissions-plugin';
|
|
3
|
+
const getPolicy = (
|
|
4
|
+
overrides: {
|
|
5
|
+
permissive?: string;
|
|
6
|
+
command?: string;
|
|
7
|
+
using?: string;
|
|
8
|
+
withcheck?: string;
|
|
9
|
+
} = {},
|
|
10
|
+
): any => {
|
|
11
|
+
return {
|
|
12
|
+
schemaname: 'app_public',
|
|
13
|
+
tablename: 'entities',
|
|
14
|
+
permissive: 'PERMISSIVE',
|
|
15
|
+
command: 'SELECT',
|
|
16
|
+
using: `((SELECT user_has_permission('READER,EDITOR,ADMIN'::text)) AND ((entity_type)::text = 'MOVIE_GENRE'::text))`,
|
|
17
|
+
withcheck: null,
|
|
18
|
+
...overrides,
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe('AnnotateTypesWithPermissionsPlugin', () => {
|
|
23
|
+
describe('setPermissionsCommentText', () => {
|
|
24
|
+
it.each([undefined, null, '', ' '])(
|
|
25
|
+
'empty original description without policies -> empty result',
|
|
26
|
+
(description) => {
|
|
27
|
+
// Act
|
|
28
|
+
const result =
|
|
29
|
+
exportedForTestingAnnotatePlugin.setPermissionsCommentText(
|
|
30
|
+
description,
|
|
31
|
+
[],
|
|
32
|
+
'INSERT',
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// Assert
|
|
36
|
+
expect(result).toBe('');
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
it.each([undefined, null, '', ' '])(
|
|
41
|
+
'empty original description with matching policy -> permissions without description',
|
|
42
|
+
(description) => {
|
|
43
|
+
// Act
|
|
44
|
+
const result =
|
|
45
|
+
exportedForTestingAnnotatePlugin.setPermissionsCommentText(
|
|
46
|
+
description,
|
|
47
|
+
[getPolicy()],
|
|
48
|
+
'SELECT',
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Assert
|
|
52
|
+
expect(result).toBe('@permissions: READER,EDITOR,ADMIN');
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
it('non-empty original description without matching policy -> description without permissions', () => {
|
|
57
|
+
// Act
|
|
58
|
+
const result = exportedForTestingAnnotatePlugin.setPermissionsCommentText(
|
|
59
|
+
'Test description of some GraphQL type.',
|
|
60
|
+
[getPolicy({ command: 'UPDATE' })],
|
|
61
|
+
'SELECT',
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Assert
|
|
65
|
+
expect(result).toBe('Test description of some GraphQL type.');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it.each(['SELECT', 'ALL'])(
|
|
69
|
+
'non-empty original description with matching policy -> permissions with description',
|
|
70
|
+
(command) => {
|
|
71
|
+
// Act
|
|
72
|
+
const result =
|
|
73
|
+
exportedForTestingAnnotatePlugin.setPermissionsCommentText(
|
|
74
|
+
'Test description of some GraphQL type.',
|
|
75
|
+
[getPolicy({ command })],
|
|
76
|
+
'SELECT',
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Assert
|
|
80
|
+
expect(result).toBe(
|
|
81
|
+
'Test description of some GraphQL type.\n@permissions: READER,EDITOR,ADMIN',
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
it('non-empty original description with restrictive and permissive policies -> description with restrictive permissions', () => {
|
|
87
|
+
// Act
|
|
88
|
+
const result = exportedForTestingAnnotatePlugin.setPermissionsCommentText(
|
|
89
|
+
'Test description of some GraphQL type.',
|
|
90
|
+
[
|
|
91
|
+
getPolicy({
|
|
92
|
+
command: 'DELETE',
|
|
93
|
+
permissive: 'RESTRICTIVE',
|
|
94
|
+
using: `user_has_permission('ADMIN'::text)`,
|
|
95
|
+
}),
|
|
96
|
+
getPolicy({
|
|
97
|
+
command: 'ALL',
|
|
98
|
+
permissive: 'PERMISSIVE',
|
|
99
|
+
using: `(user_has_permission('READER,EDITOR,ADMIN'::text) AND (1 = 1))`,
|
|
100
|
+
withcheck: `(user_has_permission('EDITOR,ADMIN'::text) AND (1 = 1))`,
|
|
101
|
+
}),
|
|
102
|
+
],
|
|
103
|
+
'DELETE',
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Assert
|
|
107
|
+
expect(result).toBe(
|
|
108
|
+
'Test description of some GraphQL type.\n@permissions: ADMIN',
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('non-empty original description with multiple matching policies -> description with multiple permission sets', () => {
|
|
113
|
+
// Act
|
|
114
|
+
const result = exportedForTestingAnnotatePlugin.setPermissionsCommentText(
|
|
115
|
+
'Test description of some GraphQL type.',
|
|
116
|
+
[
|
|
117
|
+
// Backwards-compatibility control case
|
|
118
|
+
getPolicy({
|
|
119
|
+
command: 'UPDATE',
|
|
120
|
+
permissive: 'PERMISSIVE',
|
|
121
|
+
using: `(user_has_permission('TVSHOWS_EDIT,ADMIN'::text) AND ((entity_type)::text = 'TVSHOW'::text))`,
|
|
122
|
+
withcheck: `(user_has_permission('TVSHOWS_EDIT,ADMIN'::text) AND ((entity_type)::text = 'TVSHOW'::text))`,
|
|
123
|
+
}),
|
|
124
|
+
getPolicy({
|
|
125
|
+
command: 'UPDATE',
|
|
126
|
+
permissive: 'PERMISSIVE',
|
|
127
|
+
using: `((SELECT user_has_permission('SETTINGS_EDIT,ADMIN'::text)) AND ((entity_type)::text = 'MOVIE_GENRE'::text))`,
|
|
128
|
+
withcheck: `((SELECT user_has_permission('SETTINGS_EDIT,ADMIN'::text)) AND ((entity_type)::text = 'MOVIE_GENRE'::text))`,
|
|
129
|
+
}),
|
|
130
|
+
getPolicy({
|
|
131
|
+
command: 'UPDATE',
|
|
132
|
+
permissive: 'PERMISSIVE',
|
|
133
|
+
using: `((SELECT user_has_permission('MOVIES_EDIT,ADMIN'::text)) AND ((entity_type)::text = 'MOVIE'::text))`,
|
|
134
|
+
withcheck: `((SELECT user_has_permission('MOVIES_EDIT,ADMIN'::text)) AND ((entity_type)::text = 'MOVIE'::text))`,
|
|
135
|
+
}),
|
|
136
|
+
getPolicy({
|
|
137
|
+
command: 'UPDATE',
|
|
138
|
+
permissive: 'PERMISSIVE',
|
|
139
|
+
using: `((SELECT user_has_permission('TVSHOWS_EDIT,ADMIN'::text)) AND ((entity_type)::text = 'EPISODE'::text))`,
|
|
140
|
+
withcheck: `((SELECT user_has_permission('TVSHOWS_EDIT,ADMIN'::text)) AND ((entity_type)::text = 'EPISODE'::text))`,
|
|
141
|
+
}),
|
|
142
|
+
getPolicy({
|
|
143
|
+
command: 'UPDATE',
|
|
144
|
+
permissive: 'PERMISSIVE',
|
|
145
|
+
using: `((SELECT user_has_permission('SETTINGS_EDIT,ADMIN'::text)) AND ((entity_type)::text = 'TVSHOW_GENRE'::text))`,
|
|
146
|
+
withcheck: `((SELECT user_has_permission('SETTINGS_EDIT,ADMIN'::text)) AND ((entity_type)::text = 'TVSHOW_GENRE'::text))`,
|
|
147
|
+
}),
|
|
148
|
+
],
|
|
149
|
+
'UPDATE',
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Assert
|
|
153
|
+
expect(result).toBe(
|
|
154
|
+
'Test description of some GraphQL type.\n@permissions: MOVIES_EDIT,ADMIN\n@permissions: SETTINGS_EDIT,ADMIN\n@permissions: TVSHOWS_EDIT,ADMIN',
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
import { Plugin } from 'postgraphile';
|
|
3
|
+
import { isNullOrWhitespace } from '../common';
|
|
4
|
+
|
|
5
|
+
interface PolicyRow {
|
|
6
|
+
schemaname: string;
|
|
7
|
+
tablename: string;
|
|
8
|
+
permissive: 'PERMISSIVE' | 'RESTRICTIVE';
|
|
9
|
+
command: string;
|
|
10
|
+
using: string | null;
|
|
11
|
+
withcheck: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type SqlOperation = 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE';
|
|
15
|
+
type AllOperations = 'ALL' | SqlOperation;
|
|
16
|
+
|
|
17
|
+
async function loadPolicies(ownerPool: Pool): Promise<PolicyRow[]> {
|
|
18
|
+
const client = await ownerPool.connect();
|
|
19
|
+
try {
|
|
20
|
+
// get all policies, sort so "ALL" is last and custom ones come earlier
|
|
21
|
+
const queryResult = await client.query(`
|
|
22
|
+
select
|
|
23
|
+
schemaname,
|
|
24
|
+
tablename,
|
|
25
|
+
permissive,
|
|
26
|
+
cmd as command,
|
|
27
|
+
qual as using,
|
|
28
|
+
with_check as withcheck
|
|
29
|
+
from pg_policies
|
|
30
|
+
order by command desc`);
|
|
31
|
+
return queryResult.rows;
|
|
32
|
+
} finally {
|
|
33
|
+
client.release();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parsePermissions(sqlPolicy: string): string {
|
|
38
|
+
const matches = /(ax_utils\.)?user_has_permission\('([^']+)/.exec(sqlPolicy);
|
|
39
|
+
return matches && matches.length > 2 ? matches[2] : '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getPermissions(
|
|
43
|
+
policies: PolicyRow[],
|
|
44
|
+
commands: AllOperations[],
|
|
45
|
+
field: 'using' | 'withcheck',
|
|
46
|
+
): string[] {
|
|
47
|
+
let matchingPolicies = policies.filter(
|
|
48
|
+
(p) => commands.find((cmd) => p.command === cmd) && p[field],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// If RESTRICTIVE policies found, list only them and remove PERMISSIVE ones
|
|
52
|
+
if (matchingPolicies.some((policy) => policy.permissive === 'RESTRICTIVE')) {
|
|
53
|
+
matchingPolicies = matchingPolicies.filter(
|
|
54
|
+
(policy) => policy.permissive === 'RESTRICTIVE',
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return [
|
|
59
|
+
...new Set<string>( // removes duplicates
|
|
60
|
+
matchingPolicies
|
|
61
|
+
.map((p) => parsePermissions(p[field] as string))
|
|
62
|
+
.filter((p) => !isNullOrWhitespace(p))
|
|
63
|
+
.sort(),
|
|
64
|
+
),
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function setPermissionsCommentText(
|
|
69
|
+
currentDescription: string | null | undefined,
|
|
70
|
+
policies: PolicyRow[],
|
|
71
|
+
type: SqlOperation,
|
|
72
|
+
): string {
|
|
73
|
+
const field = type === 'SELECT' || type === 'DELETE' ? 'using' : 'withcheck';
|
|
74
|
+
|
|
75
|
+
const permissions = getPermissions(policies, ['ALL', type], field);
|
|
76
|
+
const description = isNullOrWhitespace(currentDescription)
|
|
77
|
+
? ''
|
|
78
|
+
: currentDescription;
|
|
79
|
+
return permissions.length > 0
|
|
80
|
+
? [
|
|
81
|
+
isNullOrWhitespace(description) ? description : `${description}\n`,
|
|
82
|
+
...permissions
|
|
83
|
+
.map((permission) => `@permissions: ${permission}`)
|
|
84
|
+
.join('\n'),
|
|
85
|
+
].join('')
|
|
86
|
+
: description;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// all available hooks are defined here:
|
|
90
|
+
// https://www.graphile.org/graphile-build/all-hooks/
|
|
91
|
+
/**
|
|
92
|
+
* Plugin that adds permissions annotation to graphql schema descriptions.
|
|
93
|
+
*
|
|
94
|
+
* @param graphileBuildOptions should be of type `Partial<Options> & { ownerPool: Pool }`
|
|
95
|
+
*/
|
|
96
|
+
export const AnnotateTypesWithPermissionsPlugin: Plugin = async (
|
|
97
|
+
builder,
|
|
98
|
+
{ ownerPool },
|
|
99
|
+
) => {
|
|
100
|
+
const allPolicies = await loadPolicies(ownerPool);
|
|
101
|
+
builder.hook('GraphQLInputObjectType', (obj, _build, context) => {
|
|
102
|
+
const scope = context.scope;
|
|
103
|
+
|
|
104
|
+
// PostGraphile input Types that do not correspond to a database table
|
|
105
|
+
if (
|
|
106
|
+
scope.isPointInputType ||
|
|
107
|
+
scope.isIntervalInputType ||
|
|
108
|
+
scope.isPgConnectionFilterOperators ||
|
|
109
|
+
scope.isPgConnectionFilterMany ||
|
|
110
|
+
scope.isPgCondition ||
|
|
111
|
+
scope.isPgConnectionFilter
|
|
112
|
+
) {
|
|
113
|
+
return obj;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// PostGraphile generated input types for DB mutations
|
|
117
|
+
if (scope.pgIntrospection) {
|
|
118
|
+
const policies = allPolicies.filter(
|
|
119
|
+
(value) =>
|
|
120
|
+
value.schemaname === scope.pgIntrospection.namespaceName &&
|
|
121
|
+
value.tablename === scope.pgIntrospection.name,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
let operation: SqlOperation;
|
|
125
|
+
|
|
126
|
+
switch (true) {
|
|
127
|
+
case scope.isPgCreateInputType:
|
|
128
|
+
operation = 'INSERT';
|
|
129
|
+
break;
|
|
130
|
+
case scope.isPgUpdateInputType:
|
|
131
|
+
operation = 'UPDATE';
|
|
132
|
+
break;
|
|
133
|
+
case scope.isPgDeleteInputType:
|
|
134
|
+
operation = 'DELETE';
|
|
135
|
+
break;
|
|
136
|
+
default:
|
|
137
|
+
return obj;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
obj.description = setPermissionsCommentText(
|
|
141
|
+
obj.description,
|
|
142
|
+
policies,
|
|
143
|
+
operation,
|
|
144
|
+
);
|
|
145
|
+
return obj;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// The input objects that come to this code-path are custom input types
|
|
149
|
+
// and require custom logic to protect them!
|
|
150
|
+
// Only works if they have input >types< - if they have no input or scalar
|
|
151
|
+
// input values they cannot be found by this method.
|
|
152
|
+
// Example: PopulateInput from the populate endpoint
|
|
153
|
+
return obj;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
builder.hook('GraphQLObjectType', (obj, _build, context) => {
|
|
157
|
+
const scope = context.scope;
|
|
158
|
+
|
|
159
|
+
if (
|
|
160
|
+
// PostGraphile Types that do not correspond to a database table
|
|
161
|
+
scope.isPointType ||
|
|
162
|
+
scope.isIntervalType ||
|
|
163
|
+
scope.isPgConnectionFilterOperators ||
|
|
164
|
+
scope.isPgConnectionFilterMany ||
|
|
165
|
+
scope.isPgCondition ||
|
|
166
|
+
scope.isPgConnectionFilter ||
|
|
167
|
+
scope.isPageInfo ||
|
|
168
|
+
scope.isEdgeType ||
|
|
169
|
+
scope.isRootQuery ||
|
|
170
|
+
scope.isRootMutation ||
|
|
171
|
+
scope.isRootSubscription ||
|
|
172
|
+
scope.isMutationPayload ||
|
|
173
|
+
scope.isPgDeletePayloadType ||
|
|
174
|
+
scope.isPgUpdatePayloadType ||
|
|
175
|
+
// TODO: Procedures have no row level security: secure them in another way
|
|
176
|
+
context?.scope?.pgIntrospection?.kind === 'procedure'
|
|
177
|
+
) {
|
|
178
|
+
return obj;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// PostGraphile generated types for DB queries
|
|
182
|
+
if (scope.pgIntrospection) {
|
|
183
|
+
const policies = allPolicies.filter(
|
|
184
|
+
(value) =>
|
|
185
|
+
value.schemaname === scope.pgIntrospection.namespaceName &&
|
|
186
|
+
value.tablename === scope.pgIntrospection.name,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
obj.description = setPermissionsCommentText(
|
|
190
|
+
obj.description,
|
|
191
|
+
policies,
|
|
192
|
+
'SELECT',
|
|
193
|
+
);
|
|
194
|
+
return obj;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// The objects that come to this code-path are custom types
|
|
198
|
+
// and require custom logic to protect them!
|
|
199
|
+
// Only works if they have >types< - if they have no input or scalar
|
|
200
|
+
// input values they cannot be found by this method.
|
|
201
|
+
return obj;
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export const exportedForTestingAnnotatePlugin = { setPermissionsCommentText };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Plugin } from 'postgraphile';
|
|
2
|
+
import { UnreachableCaseError } from '../common';
|
|
3
|
+
|
|
4
|
+
type DeprecateMode = 'DEPRECATE' | 'DROP';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* NodePlugin is excluded by default to remove all references of NodeId fields
|
|
8
|
+
* and byNodeId operations. A few such instances are not removed and handled
|
|
9
|
+
* explicitly by this plugin, e.g. deletedEntityNodeId in the
|
|
10
|
+
* DeleteEntityPayload types.
|
|
11
|
+
*
|
|
12
|
+
* By default, the plugin marks each field as deprecated. Pass an explicit
|
|
13
|
+
* `DROP` value to drop the fields instead.
|
|
14
|
+
*/
|
|
15
|
+
export const DeprecateStrayNodeIdFieldsPlugin =
|
|
16
|
+
(mode: DeprecateMode = 'DEPRECATE'): Plugin =>
|
|
17
|
+
(builder) => {
|
|
18
|
+
builder.hook('GraphQLObjectType:fields', (fields, _build, context) => {
|
|
19
|
+
const typeName = context.Self.name as string;
|
|
20
|
+
if (typeName.startsWith('Delete') && typeName.endsWith('Payload')) {
|
|
21
|
+
Object.keys(fields)
|
|
22
|
+
.filter(
|
|
23
|
+
(name) => name.startsWith('deleted') && name.endsWith('NodeId'),
|
|
24
|
+
)
|
|
25
|
+
.map((name) => {
|
|
26
|
+
switch (mode) {
|
|
27
|
+
case 'DEPRECATE':
|
|
28
|
+
fields[name].deprecationReason = 'The field is obsolete.';
|
|
29
|
+
break;
|
|
30
|
+
case 'DROP':
|
|
31
|
+
delete fields[name];
|
|
32
|
+
break;
|
|
33
|
+
|
|
34
|
+
default:
|
|
35
|
+
throw new UnreachableCaseError(mode);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return fields;
|
|
40
|
+
});
|
|
41
|
+
};
|