@envelop/generic-auth 7.0.0 → 8.0.0-alpha-20240810114324-912296f8
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 +27 -27
- package/cjs/index.js +76 -31
- package/esm/index.js +76 -31
- package/package.json +2 -2
- package/typings/index.d.cts +22 -9
- package/typings/index.d.ts +22 -9
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@ This plugin allows you to implement custom authentication flow by providing a cu
|
|
|
4
4
|
based on the original HTTP request. The resolved user is injected into the GraphQL execution
|
|
5
5
|
`context`, and you can use it in your resolvers to fetch the current user.
|
|
6
6
|
|
|
7
|
-
> The plugin also comes with an optional `@
|
|
8
|
-
> and helps you to protect your GraphQL schema in a declarative way.
|
|
7
|
+
> The plugin also comes with an optional `@authenticated` directive that can be added to your
|
|
8
|
+
> GraphQL schema and helps you to protect your GraphQL schema in a declarative way.
|
|
9
9
|
|
|
10
10
|
There are several possible flows for using this plugin (see below for setup examples):
|
|
11
11
|
|
|
@@ -15,8 +15,8 @@ There are several possible flows for using this plugin (see below for setup exam
|
|
|
15
15
|
- **Option #2 - Manual Validation**: the plugin will just resolve the user and injects it into the
|
|
16
16
|
`context` without validating access to schema field.
|
|
17
17
|
- **Option #3 - Granular field access by using schema field directives or field extensions**: Look
|
|
18
|
-
for an `@
|
|
19
|
-
GraphQL fields.
|
|
18
|
+
for an `@authenticated` directive or `authenticated` extension field and automatically protect
|
|
19
|
+
those specific GraphQL fields.
|
|
20
20
|
|
|
21
21
|
## Getting Started
|
|
22
22
|
|
|
@@ -70,7 +70,7 @@ const validateUser: ValidateUserFn<UserType> = params => {
|
|
|
70
70
|
// This method is being triggered in different flows, based on the mode you chose to implement.
|
|
71
71
|
|
|
72
72
|
// If you are using the `protect-auth-directive` mode, you'll also get 2 additional parameters: the resolver parameters as object and the DirectiveNode of the auth directive.
|
|
73
|
-
// In `protect-auth-directive` mode, this function will always get called and you can use these parameters to check if the field has the `@
|
|
73
|
+
// In `protect-auth-directive` mode, this function will always get called and you can use these parameters to check if the field has the `@authenticated` or `@skipAuth` directive
|
|
74
74
|
|
|
75
75
|
if (!user) {
|
|
76
76
|
return new Error(`Unauthenticated!`)
|
|
@@ -175,7 +175,7 @@ type UserType = {
|
|
|
175
175
|
const resolveUserFn: ResolveUserFn<UserType> = async context => {
|
|
176
176
|
/* ... */
|
|
177
177
|
}
|
|
178
|
-
const validateUser: ValidateUserFn<UserType> =
|
|
178
|
+
const validateUser: ValidateUserFn<UserType> = params => {
|
|
179
179
|
/* ... */
|
|
180
180
|
}
|
|
181
181
|
|
|
@@ -198,7 +198,11 @@ Then, in your resolvers, you can execute the check method based on your needs:
|
|
|
198
198
|
const resolvers = {
|
|
199
199
|
Query: {
|
|
200
200
|
me: async (root, args, context) => {
|
|
201
|
-
|
|
201
|
+
const validationError = context.validateUser()
|
|
202
|
+
if (validationError) {
|
|
203
|
+
throw validationError
|
|
204
|
+
}
|
|
205
|
+
|
|
202
206
|
const currentUser = context.currentUser
|
|
203
207
|
|
|
204
208
|
return currentUser
|
|
@@ -209,8 +213,8 @@ const resolvers = {
|
|
|
209
213
|
|
|
210
214
|
#### Option #3 - `protect-granular`
|
|
211
215
|
|
|
212
|
-
This mode is similar to option #2, but it uses the `@
|
|
213
|
-
for protecting specific GraphQL fields.
|
|
216
|
+
This mode is similar to option #2, but it uses the `@authenticated` SDL directive or `auth` field
|
|
217
|
+
extension for protecting specific GraphQL fields.
|
|
214
218
|
|
|
215
219
|
```ts
|
|
216
220
|
import { execute, parse, specifiedRules, subscribe, validate } from 'graphql'
|
|
@@ -243,15 +247,15 @@ const getEnveloped = envelop({
|
|
|
243
247
|
##### Protect a field using a field `directive`
|
|
244
248
|
|
|
245
249
|
> By default, we assume that you have the GraphQL directive definition as part of your GraphQL
|
|
246
|
-
> schema (`directive @
|
|
250
|
+
> schema (`directive @authenticated on FIELD_DEFINITION`).
|
|
247
251
|
|
|
248
|
-
Then, in your GraphQL schema SDL, you can add `@
|
|
252
|
+
Then, in your GraphQL schema SDL, you can add `@authenticated` directive to your fields, and the
|
|
249
253
|
`validateUser` will get called only while resolving that specific field:
|
|
250
254
|
|
|
251
255
|
```graphql
|
|
252
256
|
type Query {
|
|
253
|
-
me: User! @
|
|
254
|
-
protectedField: String @
|
|
257
|
+
me: User! @authenticated
|
|
258
|
+
protectedField: String @authenticated
|
|
255
259
|
# publicField: String
|
|
256
260
|
}
|
|
257
261
|
```
|
|
@@ -273,7 +277,7 @@ const GraphQLQueryType = new GraphQLObjectType({
|
|
|
273
277
|
type: GraphQLInt,
|
|
274
278
|
resolve: () => 1,
|
|
275
279
|
extensions: {
|
|
276
|
-
|
|
280
|
+
authenticated: true
|
|
277
281
|
}
|
|
278
282
|
}
|
|
279
283
|
}
|
|
@@ -292,7 +296,7 @@ the `protect-all` and `protect-granular` mode:
|
|
|
292
296
|
import { GraphQLError } from 'graphql'
|
|
293
297
|
import { ValidateUserFn } from '@envelop/generic-auth'
|
|
294
298
|
|
|
295
|
-
const validateUser: ValidateUserFn<UserType> =
|
|
299
|
+
const validateUser: ValidateUserFn<UserType> = ({ user }) => {
|
|
296
300
|
// Now you can use the 3rd parameter to implement custom logic for user validation, with access
|
|
297
301
|
// to the resolver data and information.
|
|
298
302
|
|
|
@@ -304,8 +308,8 @@ const validateUser: ValidateUserFn<UserType> = async ({ user }) => {
|
|
|
304
308
|
|
|
305
309
|
##### With a custom directive with arguments
|
|
306
310
|
|
|
307
|
-
It is possible to add custom parameters to your `@
|
|
308
|
-
role-aware authentication:
|
|
311
|
+
It is possible to add custom parameters to your `@authenticated` directive. Here's an example for
|
|
312
|
+
adding role-aware authentication:
|
|
309
313
|
|
|
310
314
|
```graphql
|
|
311
315
|
enum Role {
|
|
@@ -313,7 +317,7 @@ enum Role {
|
|
|
313
317
|
MEMBER
|
|
314
318
|
}
|
|
315
319
|
|
|
316
|
-
directive @
|
|
320
|
+
directive @authenticated(role: Role!) on FIELD_DEFINITION
|
|
317
321
|
```
|
|
318
322
|
|
|
319
323
|
Then, you use the `directiveNode` parameter to check the arguments:
|
|
@@ -321,7 +325,7 @@ Then, you use the `directiveNode` parameter to check the arguments:
|
|
|
321
325
|
```ts
|
|
322
326
|
import { ValidateUserFn } from '@envelop/generic-auth'
|
|
323
327
|
|
|
324
|
-
const validateUser: ValidateUserFn<UserType> =
|
|
328
|
+
const validateUser: ValidateUserFn<UserType> = ({ user, fieldAuthDirectiveNode }) => {
|
|
325
329
|
// Now you can use the fieldAuthDirectiveNode parameter to implement custom logic for user validation, with access
|
|
326
330
|
// to the resolver auth directive arguments.
|
|
327
331
|
|
|
@@ -367,7 +371,7 @@ const resolvers = {
|
|
|
367
371
|
user: {
|
|
368
372
|
me: (_, __, { currentUser }) => currentUser,
|
|
369
373
|
extensions: {
|
|
370
|
-
|
|
374
|
+
authenticated: {
|
|
371
375
|
role: 'USER'
|
|
372
376
|
}
|
|
373
377
|
}
|
|
@@ -385,11 +389,7 @@ validation.
|
|
|
385
389
|
```ts
|
|
386
390
|
import { ValidateUserFn } from '@envelop/generic-auth'
|
|
387
391
|
|
|
388
|
-
const validateUser: ValidateUserFn<UserType> =
|
|
389
|
-
user,
|
|
390
|
-
executionArgs,
|
|
391
|
-
fieldAuthExtension
|
|
392
|
-
}) => {
|
|
392
|
+
const validateUser: ValidateUserFn<UserType> = ({ user, executionArgs, fieldAuthExtension }) => {
|
|
393
393
|
if (!user) {
|
|
394
394
|
return new Error(`Unauthenticated!`)
|
|
395
395
|
}
|
|
@@ -397,7 +397,7 @@ const validateUser: ValidateUserFn<UserType> = async ({
|
|
|
397
397
|
// You have access to the object define in the resolver tree, allowing to define any custom logic you want.
|
|
398
398
|
const validate = fieldAuthExtension?.validate
|
|
399
399
|
if (validate) {
|
|
400
|
-
|
|
400
|
+
return validate({
|
|
401
401
|
user,
|
|
402
402
|
variables: executionArgs.variableValues,
|
|
403
403
|
context: executionArgs.contextValue
|
|
@@ -410,7 +410,7 @@ const resolvers = {
|
|
|
410
410
|
user: {
|
|
411
411
|
resolve: (_, { userId }) => getUser(userId),
|
|
412
412
|
extensions: {
|
|
413
|
-
|
|
413
|
+
authenticated: {
|
|
414
414
|
validate: ({ user, variables, context }) => {
|
|
415
415
|
// We can now have access to the operation and variables to decide if the user can execute the query
|
|
416
416
|
if (user.id !== variables.userId) {
|
package/cjs/index.js
CHANGED
|
@@ -1,33 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useGenericAuth = exports.defaultProtectSingleValidateFn = exports.defaultProtectAllValidateFn = exports.
|
|
3
|
+
exports.useGenericAuth = exports.defaultProtectSingleValidateFn = exports.defaultProtectAllValidateFn = exports.createUnauthenticatedError = exports.SKIP_AUTH_DIRECTIVE_SDL = exports.DIRECTIVE_SDL = void 0;
|
|
4
4
|
const graphql_1 = require("graphql");
|
|
5
5
|
const extended_validation_1 = require("@envelop/extended-validation");
|
|
6
6
|
const utils_1 = require("@graphql-tools/utils");
|
|
7
|
-
class UnauthenticatedError extends graphql_1.GraphQLError {
|
|
8
|
-
}
|
|
9
|
-
exports.UnauthenticatedError = UnauthenticatedError;
|
|
10
7
|
exports.DIRECTIVE_SDL = `
|
|
11
|
-
directive @
|
|
8
|
+
directive @authenticated on FIELD_DEFINITION
|
|
12
9
|
`;
|
|
13
10
|
exports.SKIP_AUTH_DIRECTIVE_SDL = `
|
|
14
11
|
directive @skipAuth on FIELD_DEFINITION
|
|
15
12
|
`;
|
|
13
|
+
function createUnauthenticatedError(params) {
|
|
14
|
+
return (0, utils_1.createGraphQLError)('Unauthorized field or type', {
|
|
15
|
+
nodes: params?.fieldNode ? [params.fieldNode] : undefined,
|
|
16
|
+
path: params?.path,
|
|
17
|
+
extensions: {
|
|
18
|
+
code: 'UNAUTHORIZED_FIELD_OR_TYPE',
|
|
19
|
+
http: {
|
|
20
|
+
status: params?.statusCode ?? 401,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
exports.createUnauthenticatedError = createUnauthenticatedError;
|
|
16
26
|
function defaultProtectAllValidateFn(params) {
|
|
17
27
|
if (params.user == null && !params.fieldAuthDirectiveNode && !params.fieldAuthExtension) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
params.
|
|
21
|
-
|
|
28
|
+
return createUnauthenticatedError({
|
|
29
|
+
fieldNode: params.fieldNode,
|
|
30
|
+
path: params.path,
|
|
31
|
+
});
|
|
22
32
|
}
|
|
23
33
|
}
|
|
24
34
|
exports.defaultProtectAllValidateFn = defaultProtectAllValidateFn;
|
|
25
35
|
function defaultProtectSingleValidateFn(params) {
|
|
26
36
|
if (params.user == null && (params.fieldAuthDirectiveNode || params.fieldAuthExtension)) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
params.
|
|
30
|
-
|
|
37
|
+
return createUnauthenticatedError({
|
|
38
|
+
fieldNode: params.fieldNode,
|
|
39
|
+
path: params.path,
|
|
40
|
+
});
|
|
31
41
|
}
|
|
32
42
|
}
|
|
33
43
|
exports.defaultProtectSingleValidateFn = defaultProtectSingleValidateFn;
|
|
@@ -35,7 +45,7 @@ const useGenericAuth = (options) => {
|
|
|
35
45
|
const contextFieldName = options.contextFieldName || 'currentUser';
|
|
36
46
|
if (options.mode === 'protect-all' || options.mode === 'protect-granular') {
|
|
37
47
|
const directiveOrExtensionFieldName = options.directiveOrExtensionFieldName ??
|
|
38
|
-
(options.mode === 'protect-all' ? 'skipAuth' : '
|
|
48
|
+
(options.mode === 'protect-all' ? 'skipAuth' : 'authenticated');
|
|
39
49
|
const validateUser = options.validateUser ??
|
|
40
50
|
(options.mode === 'protect-all'
|
|
41
51
|
? defaultProtectAllValidateFn
|
|
@@ -46,51 +56,86 @@ const useGenericAuth = (options) => {
|
|
|
46
56
|
fieldAuthDirectiveNode: input.astNode?.directives?.find(directive => directive.name.value === directiveOrExtensionFieldName),
|
|
47
57
|
};
|
|
48
58
|
};
|
|
59
|
+
const rejectUnauthenticated = 'rejectUnauthenticated' in options ? options.rejectUnauthenticated !== false : true;
|
|
49
60
|
return {
|
|
50
61
|
onPluginInit({ addPlugin }) {
|
|
51
62
|
addPlugin((0, extended_validation_1.useExtendedValidation)({
|
|
63
|
+
rejectOnErrors: rejectUnauthenticated,
|
|
52
64
|
rules: [
|
|
53
65
|
function AuthorizationExtendedValidationRule(context, args) {
|
|
66
|
+
const operations = {};
|
|
67
|
+
const fragments = {};
|
|
68
|
+
for (const definition of args.document.definitions) {
|
|
69
|
+
if (definition.kind === 'OperationDefinition') {
|
|
70
|
+
operations[definition.name?.value ?? args.operationName ?? 'Anonymous'] =
|
|
71
|
+
definition;
|
|
72
|
+
}
|
|
73
|
+
else if (definition.kind === 'FragmentDefinition') {
|
|
74
|
+
fragments[definition.name.value] = definition;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
54
77
|
const user = args.contextValue[contextFieldName];
|
|
55
|
-
const handleField = (fieldNode,
|
|
56
|
-
const field =
|
|
78
|
+
const handleField = ({ node: fieldNode, path, }, parentType) => {
|
|
79
|
+
const field = parentType.getFields()[fieldNode.name.value];
|
|
57
80
|
if (field == null) {
|
|
58
81
|
// field is null/undefined if this is an introspection field
|
|
59
82
|
return;
|
|
60
83
|
}
|
|
61
84
|
const { fieldAuthExtension, fieldAuthDirectiveNode } = extractAuthMeta(field);
|
|
62
|
-
const
|
|
85
|
+
const resolvePath = [];
|
|
86
|
+
let curr = args.document;
|
|
87
|
+
for (const pathItem of path) {
|
|
88
|
+
curr = curr[pathItem];
|
|
89
|
+
if (curr?.kind === 'Field') {
|
|
90
|
+
resolvePath.push(curr.name.value);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return validateUser({
|
|
63
94
|
user,
|
|
64
95
|
fieldNode,
|
|
65
|
-
|
|
96
|
+
parentType,
|
|
66
97
|
fieldAuthDirectiveNode,
|
|
67
98
|
fieldAuthExtension,
|
|
68
99
|
executionArgs: args,
|
|
100
|
+
field,
|
|
101
|
+
path: resolvePath,
|
|
69
102
|
});
|
|
70
|
-
if (error) {
|
|
71
|
-
context.reportError(error);
|
|
72
|
-
}
|
|
73
103
|
};
|
|
74
104
|
return {
|
|
75
|
-
Field(node) {
|
|
105
|
+
Field(node, key, parent, path, ancestors) {
|
|
76
106
|
if (!(0, utils_1.shouldIncludeNode)(args.variableValues, node)) {
|
|
77
107
|
return;
|
|
78
108
|
}
|
|
79
109
|
const fieldType = (0, graphql_1.getNamedType)(context.getParentType());
|
|
80
110
|
if ((0, graphql_1.isIntrospectionType)(fieldType)) {
|
|
81
|
-
return
|
|
82
|
-
}
|
|
83
|
-
if ((0, graphql_1.isObjectType)(fieldType)) {
|
|
84
|
-
handleField(node, fieldType);
|
|
111
|
+
return node;
|
|
85
112
|
}
|
|
86
|
-
|
|
113
|
+
if ((0, graphql_1.isUnionType)(fieldType)) {
|
|
87
114
|
for (const objectType of fieldType.getTypes()) {
|
|
88
|
-
handleField(
|
|
115
|
+
const error = handleField({
|
|
116
|
+
node,
|
|
117
|
+
key,
|
|
118
|
+
parent,
|
|
119
|
+
path,
|
|
120
|
+
ancestors,
|
|
121
|
+
}, objectType);
|
|
122
|
+
if (error) {
|
|
123
|
+
context.reportError(error);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
89
126
|
}
|
|
90
127
|
}
|
|
91
|
-
else if ((0, graphql_1.isInterfaceType)(fieldType)) {
|
|
92
|
-
|
|
93
|
-
|
|
128
|
+
else if ((0, graphql_1.isObjectType)(fieldType) || (0, graphql_1.isInterfaceType)(fieldType)) {
|
|
129
|
+
const error = handleField({
|
|
130
|
+
node,
|
|
131
|
+
key,
|
|
132
|
+
parent,
|
|
133
|
+
path,
|
|
134
|
+
ancestors,
|
|
135
|
+
}, fieldType);
|
|
136
|
+
if (error) {
|
|
137
|
+
context.reportError(error);
|
|
138
|
+
return null;
|
|
94
139
|
}
|
|
95
140
|
}
|
|
96
141
|
return undefined;
|
package/esm/index.js
CHANGED
|
@@ -1,35 +1,45 @@
|
|
|
1
|
-
import { getNamedType,
|
|
1
|
+
import { getNamedType, isInterfaceType, isIntrospectionType, isObjectType, isUnionType, } from 'graphql';
|
|
2
2
|
import { useExtendedValidation } from '@envelop/extended-validation';
|
|
3
|
-
import { shouldIncludeNode } from '@graphql-tools/utils';
|
|
4
|
-
export class UnauthenticatedError extends GraphQLError {
|
|
5
|
-
}
|
|
3
|
+
import { createGraphQLError, shouldIncludeNode } from '@graphql-tools/utils';
|
|
6
4
|
export const DIRECTIVE_SDL = /* GraphQL */ `
|
|
7
|
-
directive @
|
|
5
|
+
directive @authenticated on FIELD_DEFINITION
|
|
8
6
|
`;
|
|
9
7
|
export const SKIP_AUTH_DIRECTIVE_SDL = /* GraphQL */ `
|
|
10
8
|
directive @skipAuth on FIELD_DEFINITION
|
|
11
9
|
`;
|
|
10
|
+
export function createUnauthenticatedError(params) {
|
|
11
|
+
return createGraphQLError('Unauthorized field or type', {
|
|
12
|
+
nodes: params?.fieldNode ? [params.fieldNode] : undefined,
|
|
13
|
+
path: params?.path,
|
|
14
|
+
extensions: {
|
|
15
|
+
code: 'UNAUTHORIZED_FIELD_OR_TYPE',
|
|
16
|
+
http: {
|
|
17
|
+
status: params?.statusCode ?? 401,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
12
22
|
export function defaultProtectAllValidateFn(params) {
|
|
13
23
|
if (params.user == null && !params.fieldAuthDirectiveNode && !params.fieldAuthExtension) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
params.
|
|
17
|
-
|
|
24
|
+
return createUnauthenticatedError({
|
|
25
|
+
fieldNode: params.fieldNode,
|
|
26
|
+
path: params.path,
|
|
27
|
+
});
|
|
18
28
|
}
|
|
19
29
|
}
|
|
20
30
|
export function defaultProtectSingleValidateFn(params) {
|
|
21
31
|
if (params.user == null && (params.fieldAuthDirectiveNode || params.fieldAuthExtension)) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
params.
|
|
25
|
-
|
|
32
|
+
return createUnauthenticatedError({
|
|
33
|
+
fieldNode: params.fieldNode,
|
|
34
|
+
path: params.path,
|
|
35
|
+
});
|
|
26
36
|
}
|
|
27
37
|
}
|
|
28
38
|
export const useGenericAuth = (options) => {
|
|
29
39
|
const contextFieldName = options.contextFieldName || 'currentUser';
|
|
30
40
|
if (options.mode === 'protect-all' || options.mode === 'protect-granular') {
|
|
31
41
|
const directiveOrExtensionFieldName = options.directiveOrExtensionFieldName ??
|
|
32
|
-
(options.mode === 'protect-all' ? 'skipAuth' : '
|
|
42
|
+
(options.mode === 'protect-all' ? 'skipAuth' : 'authenticated');
|
|
33
43
|
const validateUser = options.validateUser ??
|
|
34
44
|
(options.mode === 'protect-all'
|
|
35
45
|
? defaultProtectAllValidateFn
|
|
@@ -40,51 +50,86 @@ export const useGenericAuth = (options) => {
|
|
|
40
50
|
fieldAuthDirectiveNode: input.astNode?.directives?.find(directive => directive.name.value === directiveOrExtensionFieldName),
|
|
41
51
|
};
|
|
42
52
|
};
|
|
53
|
+
const rejectUnauthenticated = 'rejectUnauthenticated' in options ? options.rejectUnauthenticated !== false : true;
|
|
43
54
|
return {
|
|
44
55
|
onPluginInit({ addPlugin }) {
|
|
45
56
|
addPlugin(useExtendedValidation({
|
|
57
|
+
rejectOnErrors: rejectUnauthenticated,
|
|
46
58
|
rules: [
|
|
47
59
|
function AuthorizationExtendedValidationRule(context, args) {
|
|
60
|
+
const operations = {};
|
|
61
|
+
const fragments = {};
|
|
62
|
+
for (const definition of args.document.definitions) {
|
|
63
|
+
if (definition.kind === 'OperationDefinition') {
|
|
64
|
+
operations[definition.name?.value ?? args.operationName ?? 'Anonymous'] =
|
|
65
|
+
definition;
|
|
66
|
+
}
|
|
67
|
+
else if (definition.kind === 'FragmentDefinition') {
|
|
68
|
+
fragments[definition.name.value] = definition;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
48
71
|
const user = args.contextValue[contextFieldName];
|
|
49
|
-
const handleField = (fieldNode,
|
|
50
|
-
const field =
|
|
72
|
+
const handleField = ({ node: fieldNode, path, }, parentType) => {
|
|
73
|
+
const field = parentType.getFields()[fieldNode.name.value];
|
|
51
74
|
if (field == null) {
|
|
52
75
|
// field is null/undefined if this is an introspection field
|
|
53
76
|
return;
|
|
54
77
|
}
|
|
55
78
|
const { fieldAuthExtension, fieldAuthDirectiveNode } = extractAuthMeta(field);
|
|
56
|
-
const
|
|
79
|
+
const resolvePath = [];
|
|
80
|
+
let curr = args.document;
|
|
81
|
+
for (const pathItem of path) {
|
|
82
|
+
curr = curr[pathItem];
|
|
83
|
+
if (curr?.kind === 'Field') {
|
|
84
|
+
resolvePath.push(curr.name.value);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return validateUser({
|
|
57
88
|
user,
|
|
58
89
|
fieldNode,
|
|
59
|
-
|
|
90
|
+
parentType,
|
|
60
91
|
fieldAuthDirectiveNode,
|
|
61
92
|
fieldAuthExtension,
|
|
62
93
|
executionArgs: args,
|
|
94
|
+
field,
|
|
95
|
+
path: resolvePath,
|
|
63
96
|
});
|
|
64
|
-
if (error) {
|
|
65
|
-
context.reportError(error);
|
|
66
|
-
}
|
|
67
97
|
};
|
|
68
98
|
return {
|
|
69
|
-
Field(node) {
|
|
99
|
+
Field(node, key, parent, path, ancestors) {
|
|
70
100
|
if (!shouldIncludeNode(args.variableValues, node)) {
|
|
71
101
|
return;
|
|
72
102
|
}
|
|
73
103
|
const fieldType = getNamedType(context.getParentType());
|
|
74
104
|
if (isIntrospectionType(fieldType)) {
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
if (isObjectType(fieldType)) {
|
|
78
|
-
handleField(node, fieldType);
|
|
105
|
+
return node;
|
|
79
106
|
}
|
|
80
|
-
|
|
107
|
+
if (isUnionType(fieldType)) {
|
|
81
108
|
for (const objectType of fieldType.getTypes()) {
|
|
82
|
-
handleField(
|
|
109
|
+
const error = handleField({
|
|
110
|
+
node,
|
|
111
|
+
key,
|
|
112
|
+
parent,
|
|
113
|
+
path,
|
|
114
|
+
ancestors,
|
|
115
|
+
}, objectType);
|
|
116
|
+
if (error) {
|
|
117
|
+
context.reportError(error);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
83
120
|
}
|
|
84
121
|
}
|
|
85
|
-
else if (isInterfaceType(fieldType)) {
|
|
86
|
-
|
|
87
|
-
|
|
122
|
+
else if (isObjectType(fieldType) || isInterfaceType(fieldType)) {
|
|
123
|
+
const error = handleField({
|
|
124
|
+
node,
|
|
125
|
+
key,
|
|
126
|
+
parent,
|
|
127
|
+
path,
|
|
128
|
+
ancestors,
|
|
129
|
+
}, fieldType);
|
|
130
|
+
if (error) {
|
|
131
|
+
context.reportError(error);
|
|
132
|
+
return null;
|
|
88
133
|
}
|
|
89
134
|
}
|
|
90
135
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@envelop/generic-auth",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0-alpha-20240810114324-912296f8",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@envelop/core": "^5.0.0",
|
|
7
7
|
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@envelop/extended-validation": "
|
|
10
|
+
"@envelop/extended-validation": "4.1.0-alpha-20240810114324-912296f8",
|
|
11
11
|
"@graphql-tools/utils": "^10.0.6",
|
|
12
12
|
"tslib": "^2.5.0"
|
|
13
13
|
},
|
package/typings/index.d.cts
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
import { DirectiveNode, ExecutionArgs, FieldNode, GraphQLError, GraphQLObjectType } from 'graphql';
|
|
1
|
+
import { DirectiveNode, ExecutionArgs, FieldNode, GraphQLError, GraphQLField, GraphQLInterfaceType, GraphQLObjectType } from 'graphql';
|
|
2
2
|
import { DefaultContext, Maybe, Plugin, PromiseOrValue } from '@envelop/core';
|
|
3
|
-
export declare class UnauthenticatedError extends GraphQLError {
|
|
4
|
-
}
|
|
5
3
|
export type ResolveUserFn<UserType, ContextType = DefaultContext> = (context: ContextType) => PromiseOrValue<Maybe<UserType>>;
|
|
6
4
|
export type ValidateUserFnParams<UserType> = {
|
|
7
5
|
/** The user object. */
|
|
8
6
|
user: UserType;
|
|
9
7
|
/** The field node from the operation that is being validated. */
|
|
10
8
|
fieldNode: FieldNode;
|
|
11
|
-
/** The
|
|
12
|
-
|
|
9
|
+
/** The parent type which has the field that is being validated. */
|
|
10
|
+
parentType: GraphQLObjectType | GraphQLInterfaceType;
|
|
11
|
+
/** The object field */
|
|
12
|
+
field: GraphQLField<any, any>;
|
|
13
13
|
/** The directive node used for the authentication (If using an SDL flow). */
|
|
14
14
|
fieldAuthDirectiveNode: DirectiveNode | undefined;
|
|
15
15
|
/** The extensions used for authentication (If using an extension based flow). */
|
|
16
16
|
fieldAuthExtension: unknown | undefined;
|
|
17
17
|
/** The args passed to the execution function (including operation context and variables) **/
|
|
18
18
|
executionArgs: ExecutionArgs;
|
|
19
|
+
/** Resolve path */
|
|
20
|
+
path: ReadonlyArray<string | number>;
|
|
19
21
|
};
|
|
20
|
-
export type ValidateUserFn<UserType> = (params: ValidateUserFnParams<UserType>) => void |
|
|
21
|
-
export declare const DIRECTIVE_SDL = "\n directive @
|
|
22
|
+
export type ValidateUserFn<UserType> = (params: ValidateUserFnParams<UserType>) => void | GraphQLError;
|
|
23
|
+
export declare const DIRECTIVE_SDL = "\n directive @authenticated on FIELD_DEFINITION\n";
|
|
22
24
|
export declare const SKIP_AUTH_DIRECTIVE_SDL = "\n directive @skipAuth on FIELD_DEFINITION\n";
|
|
23
25
|
export type GenericAuthPluginOptions<UserType extends {} = {}, ContextType = DefaultContext, CurrentUserKey extends string = 'currentUser'> = {
|
|
24
26
|
/**
|
|
@@ -74,9 +76,20 @@ export type GenericAuthPluginOptions<UserType extends {} = {}, ContextType = Def
|
|
|
74
76
|
* @default `defaultProtectSingleValidateFn`
|
|
75
77
|
*/
|
|
76
78
|
validateUser?: ValidateUserFn<UserType>;
|
|
79
|
+
/**
|
|
80
|
+
* Reject on unauthenticated requests.
|
|
81
|
+
* @default true
|
|
82
|
+
*/
|
|
83
|
+
rejectUnauthenticated?: boolean;
|
|
77
84
|
});
|
|
78
|
-
export declare function
|
|
79
|
-
|
|
85
|
+
export declare function createUnauthenticatedError(params?: {
|
|
86
|
+
fieldNode?: FieldNode;
|
|
87
|
+
path?: ReadonlyArray<string | number>;
|
|
88
|
+
message?: string;
|
|
89
|
+
statusCode?: number;
|
|
90
|
+
}): GraphQLError;
|
|
91
|
+
export declare function defaultProtectAllValidateFn<UserType>(params: ValidateUserFnParams<UserType>): void | GraphQLError;
|
|
92
|
+
export declare function defaultProtectSingleValidateFn<UserType>(params: ValidateUserFnParams<UserType>): void | GraphQLError;
|
|
80
93
|
export declare const useGenericAuth: <UserType extends {} = {}, ContextType extends Record<any, any> = DefaultContext, CurrentUserKey extends string = "currentUser">(options: GenericAuthPluginOptions<UserType, ContextType, CurrentUserKey>) => Plugin<{
|
|
81
94
|
validateUser: ValidateUserFn<UserType>;
|
|
82
95
|
} & Record<CurrentUserKey, UserType>>;
|
package/typings/index.d.ts
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
import { DirectiveNode, ExecutionArgs, FieldNode, GraphQLError, GraphQLObjectType } from 'graphql';
|
|
1
|
+
import { DirectiveNode, ExecutionArgs, FieldNode, GraphQLError, GraphQLField, GraphQLInterfaceType, GraphQLObjectType } from 'graphql';
|
|
2
2
|
import { DefaultContext, Maybe, Plugin, PromiseOrValue } from '@envelop/core';
|
|
3
|
-
export declare class UnauthenticatedError extends GraphQLError {
|
|
4
|
-
}
|
|
5
3
|
export type ResolveUserFn<UserType, ContextType = DefaultContext> = (context: ContextType) => PromiseOrValue<Maybe<UserType>>;
|
|
6
4
|
export type ValidateUserFnParams<UserType> = {
|
|
7
5
|
/** The user object. */
|
|
8
6
|
user: UserType;
|
|
9
7
|
/** The field node from the operation that is being validated. */
|
|
10
8
|
fieldNode: FieldNode;
|
|
11
|
-
/** The
|
|
12
|
-
|
|
9
|
+
/** The parent type which has the field that is being validated. */
|
|
10
|
+
parentType: GraphQLObjectType | GraphQLInterfaceType;
|
|
11
|
+
/** The object field */
|
|
12
|
+
field: GraphQLField<any, any>;
|
|
13
13
|
/** The directive node used for the authentication (If using an SDL flow). */
|
|
14
14
|
fieldAuthDirectiveNode: DirectiveNode | undefined;
|
|
15
15
|
/** The extensions used for authentication (If using an extension based flow). */
|
|
16
16
|
fieldAuthExtension: unknown | undefined;
|
|
17
17
|
/** The args passed to the execution function (including operation context and variables) **/
|
|
18
18
|
executionArgs: ExecutionArgs;
|
|
19
|
+
/** Resolve path */
|
|
20
|
+
path: ReadonlyArray<string | number>;
|
|
19
21
|
};
|
|
20
|
-
export type ValidateUserFn<UserType> = (params: ValidateUserFnParams<UserType>) => void |
|
|
21
|
-
export declare const DIRECTIVE_SDL = "\n directive @
|
|
22
|
+
export type ValidateUserFn<UserType> = (params: ValidateUserFnParams<UserType>) => void | GraphQLError;
|
|
23
|
+
export declare const DIRECTIVE_SDL = "\n directive @authenticated on FIELD_DEFINITION\n";
|
|
22
24
|
export declare const SKIP_AUTH_DIRECTIVE_SDL = "\n directive @skipAuth on FIELD_DEFINITION\n";
|
|
23
25
|
export type GenericAuthPluginOptions<UserType extends {} = {}, ContextType = DefaultContext, CurrentUserKey extends string = 'currentUser'> = {
|
|
24
26
|
/**
|
|
@@ -74,9 +76,20 @@ export type GenericAuthPluginOptions<UserType extends {} = {}, ContextType = Def
|
|
|
74
76
|
* @default `defaultProtectSingleValidateFn`
|
|
75
77
|
*/
|
|
76
78
|
validateUser?: ValidateUserFn<UserType>;
|
|
79
|
+
/**
|
|
80
|
+
* Reject on unauthenticated requests.
|
|
81
|
+
* @default true
|
|
82
|
+
*/
|
|
83
|
+
rejectUnauthenticated?: boolean;
|
|
77
84
|
});
|
|
78
|
-
export declare function
|
|
79
|
-
|
|
85
|
+
export declare function createUnauthenticatedError(params?: {
|
|
86
|
+
fieldNode?: FieldNode;
|
|
87
|
+
path?: ReadonlyArray<string | number>;
|
|
88
|
+
message?: string;
|
|
89
|
+
statusCode?: number;
|
|
90
|
+
}): GraphQLError;
|
|
91
|
+
export declare function defaultProtectAllValidateFn<UserType>(params: ValidateUserFnParams<UserType>): void | GraphQLError;
|
|
92
|
+
export declare function defaultProtectSingleValidateFn<UserType>(params: ValidateUserFnParams<UserType>): void | GraphQLError;
|
|
80
93
|
export declare const useGenericAuth: <UserType extends {} = {}, ContextType extends Record<any, any> = DefaultContext, CurrentUserKey extends string = "currentUser">(options: GenericAuthPluginOptions<UserType, ContextType, CurrentUserKey>) => Plugin<{
|
|
81
94
|
validateUser: ValidateUserFn<UserType>;
|
|
82
95
|
} & Record<CurrentUserKey, UserType>>;
|