@envelop/generic-auth 3.0.0 → 4.0.0-alpha-bed7c7d.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/index.d.ts CHANGED
@@ -1,15 +1,21 @@
1
1
  import { DefaultContext, Plugin } from '@envelop/core';
2
- import { DirectiveNode, GraphQLError, GraphQLResolveInfo } from 'graphql';
3
- export * from './utils';
2
+ import { DirectiveNode, FieldNode, GraphQLError, GraphQLObjectType } from 'graphql';
4
3
  export declare class UnauthenticatedError extends GraphQLError {
5
4
  }
6
5
  export declare type ResolveUserFn<UserType, ContextType = DefaultContext> = (context: ContextType) => null | UserType | Promise<UserType | null>;
7
- export declare type ValidateUserFn<UserType, ContextType = DefaultContext> = (user: UserType, context: ContextType, resolverInfo?: {
8
- root: unknown;
9
- args: Record<string, unknown>;
10
- context: ContextType;
11
- info: GraphQLResolveInfo;
12
- }, directiveNode?: DirectiveNode) => void | Promise<void>;
6
+ export declare type ValidateUserFnParams<UserType> = {
7
+ /** The user object. */
8
+ user: UserType;
9
+ /** The field node from the operation that is being validated. */
10
+ fieldNode: FieldNode;
11
+ /** The object type which has the field that is being validated. */
12
+ objectType: GraphQLObjectType;
13
+ /** The directive node used for the authentication (If using an SDL flow). */
14
+ fieldAuthDirectiveNode: DirectiveNode | undefined;
15
+ /** The extensions used for authentication (If using an extension based flow). */
16
+ fieldAuthExtension: unknown | undefined;
17
+ };
18
+ export declare type ValidateUserFn<UserType> = (params: ValidateUserFnParams<UserType>) => void | UnauthenticatedError;
13
19
  export declare const DIRECTIVE_SDL = "\n directive @auth on FIELD_DEFINITION\n";
14
20
  export declare const SKIP_AUTH_DIRECTIVE_SDL = "\n directive @skipAuth on FIELD_DEFINITION\n";
15
21
  export declare type GenericAuthPluginOptions<UserType extends {} = {}, ContextType extends DefaultContext = DefaultContext> = {
@@ -20,11 +26,6 @@ export declare type GenericAuthPluginOptions<UserType extends {} = {}, ContextTy
20
26
  * Make sure to either return `null` or the user object.
21
27
  */
22
28
  resolveUserFn: ResolveUserFn<UserType, ContextType>;
23
- /**
24
- * Here you can implement any custom to check if the user is valid and have access to the server.
25
- * This method is being triggered in different flows, besed on the mode you chose to implement.
26
- */
27
- validateUser?: ValidateUserFn<UserType, ContextType>;
28
29
  /**
29
30
  * Overrides the default field name for injecting the user into the execution `context`.
30
31
  * @default currentUser
@@ -42,6 +43,12 @@ export declare type GenericAuthPluginOptions<UserType extends {} = {}, ContextTy
42
43
  * @default skipAuth
43
44
  */
44
45
  authDirectiveName?: 'skipAuth' | string;
46
+ /**
47
+ * Customize how the user is validated. E.g. apply authorization role based validation.
48
+ * The validation is applied during the extended validation phase.
49
+ * @default `defaultProtectAllValidateFn`
50
+ */
51
+ validateUser?: ValidateUserFn<UserType>;
45
52
  } | {
46
53
  /**
47
54
  * Just resolves the user and inject to authenticated user into the `context`.
@@ -53,14 +60,21 @@ export declare type GenericAuthPluginOptions<UserType extends {} = {}, ContextTy
53
60
  * resolves the user and inject to authenticated user into the `context`.
54
61
  * And checks for `@auth` directives usages to run validation automatically.
55
62
  */
56
- mode: 'protect-auth-directive';
63
+ mode: 'protect-single';
57
64
  /**
58
65
  * Overrides the default directive name
59
66
  * @default auth
60
67
  */
61
68
  authDirectiveName?: 'auth' | string;
69
+ /**
70
+ * Customize how the user is validated. E.g. apply authorization role based validation.
71
+ * The validation is applied during the extended validation phase.
72
+ * @default `defaultProtectSingleValidateFn`
73
+ */
74
+ validateUser?: ValidateUserFn<UserType>;
62
75
  });
63
- export declare function defaultValidateFn<UserType, ContextType>(user: UserType, contextType: ContextType): void;
76
+ export declare function defaultProtectAllValidateFn<UserType>(params: ValidateUserFnParams<UserType>): void | UnauthenticatedError;
77
+ export declare function defaultProtectSingleValidateFn<UserType>(params: ValidateUserFnParams<UserType>): void | UnauthenticatedError;
64
78
  export declare const useGenericAuth: <UserType extends {} = {}, ContextType extends DefaultContext = DefaultContext>(options: GenericAuthPluginOptions<UserType, ContextType>) => Plugin<{
65
- validateUser: ValidateUserFn<UserType, ContextType>;
79
+ validateUser: ValidateUserFn<UserType>;
66
80
  }>;
package/index.js CHANGED
@@ -3,16 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  const graphql = require('graphql');
6
-
7
- function getDirective(info, name) {
8
- var _a;
9
- const { parentType, fieldName, schema } = info;
10
- const schemaType = schema.getType(parentType.name);
11
- const field = schemaType.getFields()[fieldName];
12
- const astNode = field.astNode;
13
- const authDirective = (_a = astNode === null || astNode === void 0 ? void 0 : astNode.directives) === null || _a === void 0 ? void 0 : _a.find(d => d.name.value === name);
14
- return authDirective || null;
15
- }
6
+ const extendedValidation = require('@envelop/extended-validation');
16
7
 
17
8
  class UnauthenticatedError extends graphql.GraphQLError {
18
9
  }
@@ -22,62 +13,97 @@ const DIRECTIVE_SDL = /* GraphQL */ `
22
13
  const SKIP_AUTH_DIRECTIVE_SDL = /* GraphQL */ `
23
14
  directive @skipAuth on FIELD_DEFINITION
24
15
  `;
25
- function defaultValidateFn(user, contextType) {
26
- if (!user) {
27
- throw new UnauthenticatedError('Unauthenticated!');
16
+ function defaultProtectAllValidateFn(params) {
17
+ if (params.user == null && !params.fieldAuthDirectiveNode && !params.fieldAuthExtension) {
18
+ const schemaCoordinate = `${params.objectType.name}.${params.fieldNode.name.value}`;
19
+ return new UnauthenticatedError(`Accessing '${schemaCoordinate}' requires authentication.`, [params.fieldNode]);
20
+ }
21
+ }
22
+ function defaultProtectSingleValidateFn(params) {
23
+ if (params.user == null && (params.fieldAuthDirectiveNode || params.fieldAuthExtension)) {
24
+ const schemaCoordinate = `${params.objectType.name}.${params.fieldNode.name.value}`;
25
+ return new UnauthenticatedError(`Accessing '${schemaCoordinate}' requires authentication.`, [params.fieldNode]);
28
26
  }
29
27
  }
30
28
  const useGenericAuth = (options) => {
31
- const fieldName = options.contextFieldName || 'currentUser';
32
- const validateUser = options.validateUser || defaultValidateFn;
33
- if (options.mode === 'protect-all') {
34
- return {
35
- async onContextBuilding({ context, extendContext }) {
36
- const user = await options.resolveUserFn(context);
37
- extendContext({
38
- [fieldName]: user,
39
- validateUser,
40
- });
41
- },
42
- async onResolverCalled({ args, root, context, info }) {
43
- const authDirectiveNode = getDirective(info, options.authDirectiveName || 'skipAuth');
44
- if (authDirectiveNode)
45
- return;
46
- await context.validateUser(context[fieldName], context);
47
- },
29
+ var _a, _b;
30
+ const contextFieldName = options.contextFieldName || 'currentUser';
31
+ if (options.mode === 'protect-all' || options.mode === 'protect-single') {
32
+ const directiveName = (_a = options.authDirectiveName) !== null && _a !== void 0 ? _a : (options.mode === 'protect-all' ? 'skipAuth' : 'auth');
33
+ const validateUser = (_b = options.validateUser) !== null && _b !== void 0 ? _b : (options.mode === 'protect-all' ? defaultProtectAllValidateFn : defaultProtectSingleValidateFn);
34
+ const extractAuthMeta = (input) => {
35
+ var _a, _b, _c;
36
+ return {
37
+ fieldAuthExtension: (_a = input.extensions) === null || _a === void 0 ? void 0 : _a[directiveName],
38
+ fieldAuthDirectiveNode: (_c = (_b = input.astNode) === null || _b === void 0 ? void 0 : _b.directives) === null || _c === void 0 ? void 0 : _c.find(directive => directive.name.value === directiveName),
39
+ };
48
40
  };
49
- }
50
- else if (options.mode === 'resolve-only') {
51
41
  return {
42
+ onPluginInit({ addPlugin }) {
43
+ addPlugin(extendedValidation.useExtendedValidation({
44
+ rules: [
45
+ function AuthorizationExtendedValidationRule(context, args) {
46
+ const user = args.contextValue[contextFieldName];
47
+ const handleField = (fieldNode, objectType) => {
48
+ const field = objectType.getFields()[fieldNode.name.value];
49
+ if (field == null) {
50
+ // field is null/undefined if this is an introspection field
51
+ return;
52
+ }
53
+ const { fieldAuthExtension, fieldAuthDirectiveNode } = extractAuthMeta(field);
54
+ const error = validateUser({
55
+ user,
56
+ fieldNode,
57
+ objectType,
58
+ fieldAuthDirectiveNode,
59
+ fieldAuthExtension,
60
+ });
61
+ if (error) {
62
+ context.reportError(error);
63
+ }
64
+ };
65
+ return {
66
+ Field(node) {
67
+ const fieldType = graphql.getNamedType(context.getParentType());
68
+ if (graphql.isIntrospectionType(fieldType)) {
69
+ return false;
70
+ }
71
+ if (graphql.isObjectType(fieldType)) {
72
+ handleField(node, fieldType);
73
+ }
74
+ else if (graphql.isUnionType(fieldType)) {
75
+ for (const objectType of fieldType.getTypes()) {
76
+ handleField(node, objectType);
77
+ }
78
+ }
79
+ else if (graphql.isInterfaceType(fieldType)) {
80
+ for (const objectType of args.schema.getImplementations(fieldType).objects) {
81
+ handleField(node, objectType);
82
+ }
83
+ }
84
+ return undefined;
85
+ },
86
+ };
87
+ },
88
+ ],
89
+ }));
90
+ },
52
91
  async onContextBuilding({ context, extendContext }) {
53
92
  const user = await options.resolveUserFn(context);
54
93
  extendContext({
55
- [fieldName]: user,
56
- validateUser: () => validateUser(user, context),
94
+ [contextFieldName]: user,
57
95
  });
58
96
  },
59
97
  };
60
98
  }
61
- else if (options.mode === 'protect-auth-directive') {
99
+ else if (options.mode === 'resolve-only') {
62
100
  return {
63
101
  async onContextBuilding({ context, extendContext }) {
64
102
  const user = await options.resolveUserFn(context);
65
103
  extendContext({
66
- [fieldName]: user,
67
- validateUser,
104
+ [contextFieldName]: user,
68
105
  });
69
106
  },
70
- async onResolverCalled({ args, root, context, info }) {
71
- const authDirectiveNode = getDirective(info, options.authDirectiveName || 'auth');
72
- if (authDirectiveNode) {
73
- await context.validateUser(context[fieldName], context, {
74
- info,
75
- context: context,
76
- args,
77
- root,
78
- }, authDirectiveNode);
79
- }
80
- },
81
107
  };
82
108
  }
83
109
  return {};
@@ -86,6 +112,6 @@ const useGenericAuth = (options) => {
86
112
  exports.DIRECTIVE_SDL = DIRECTIVE_SDL;
87
113
  exports.SKIP_AUTH_DIRECTIVE_SDL = SKIP_AUTH_DIRECTIVE_SDL;
88
114
  exports.UnauthenticatedError = UnauthenticatedError;
89
- exports.defaultValidateFn = defaultValidateFn;
90
- exports.getDirective = getDirective;
115
+ exports.defaultProtectAllValidateFn = defaultProtectAllValidateFn;
116
+ exports.defaultProtectSingleValidateFn = defaultProtectSingleValidateFn;
91
117
  exports.useGenericAuth = useGenericAuth;
package/index.mjs CHANGED
@@ -1,14 +1,5 @@
1
- import { GraphQLError } from 'graphql';
2
-
3
- function getDirective(info, name) {
4
- var _a;
5
- const { parentType, fieldName, schema } = info;
6
- const schemaType = schema.getType(parentType.name);
7
- const field = schemaType.getFields()[fieldName];
8
- const astNode = field.astNode;
9
- const authDirective = (_a = astNode === null || astNode === void 0 ? void 0 : astNode.directives) === null || _a === void 0 ? void 0 : _a.find(d => d.name.value === name);
10
- return authDirective || null;
11
- }
1
+ import { GraphQLError, getNamedType, isIntrospectionType, isObjectType, isUnionType, isInterfaceType } from 'graphql';
2
+ import { useExtendedValidation } from '@envelop/extended-validation';
12
3
 
13
4
  class UnauthenticatedError extends GraphQLError {
14
5
  }
@@ -18,65 +9,100 @@ const DIRECTIVE_SDL = /* GraphQL */ `
18
9
  const SKIP_AUTH_DIRECTIVE_SDL = /* GraphQL */ `
19
10
  directive @skipAuth on FIELD_DEFINITION
20
11
  `;
21
- function defaultValidateFn(user, contextType) {
22
- if (!user) {
23
- throw new UnauthenticatedError('Unauthenticated!');
12
+ function defaultProtectAllValidateFn(params) {
13
+ if (params.user == null && !params.fieldAuthDirectiveNode && !params.fieldAuthExtension) {
14
+ const schemaCoordinate = `${params.objectType.name}.${params.fieldNode.name.value}`;
15
+ return new UnauthenticatedError(`Accessing '${schemaCoordinate}' requires authentication.`, [params.fieldNode]);
16
+ }
17
+ }
18
+ function defaultProtectSingleValidateFn(params) {
19
+ if (params.user == null && (params.fieldAuthDirectiveNode || params.fieldAuthExtension)) {
20
+ const schemaCoordinate = `${params.objectType.name}.${params.fieldNode.name.value}`;
21
+ return new UnauthenticatedError(`Accessing '${schemaCoordinate}' requires authentication.`, [params.fieldNode]);
24
22
  }
25
23
  }
26
24
  const useGenericAuth = (options) => {
27
- const fieldName = options.contextFieldName || 'currentUser';
28
- const validateUser = options.validateUser || defaultValidateFn;
29
- if (options.mode === 'protect-all') {
30
- return {
31
- async onContextBuilding({ context, extendContext }) {
32
- const user = await options.resolveUserFn(context);
33
- extendContext({
34
- [fieldName]: user,
35
- validateUser,
36
- });
37
- },
38
- async onResolverCalled({ args, root, context, info }) {
39
- const authDirectiveNode = getDirective(info, options.authDirectiveName || 'skipAuth');
40
- if (authDirectiveNode)
41
- return;
42
- await context.validateUser(context[fieldName], context);
43
- },
25
+ var _a, _b;
26
+ const contextFieldName = options.contextFieldName || 'currentUser';
27
+ if (options.mode === 'protect-all' || options.mode === 'protect-single') {
28
+ const directiveName = (_a = options.authDirectiveName) !== null && _a !== void 0 ? _a : (options.mode === 'protect-all' ? 'skipAuth' : 'auth');
29
+ const validateUser = (_b = options.validateUser) !== null && _b !== void 0 ? _b : (options.mode === 'protect-all' ? defaultProtectAllValidateFn : defaultProtectSingleValidateFn);
30
+ const extractAuthMeta = (input) => {
31
+ var _a, _b, _c;
32
+ return {
33
+ fieldAuthExtension: (_a = input.extensions) === null || _a === void 0 ? void 0 : _a[directiveName],
34
+ fieldAuthDirectiveNode: (_c = (_b = input.astNode) === null || _b === void 0 ? void 0 : _b.directives) === null || _c === void 0 ? void 0 : _c.find(directive => directive.name.value === directiveName),
35
+ };
44
36
  };
45
- }
46
- else if (options.mode === 'resolve-only') {
47
37
  return {
38
+ onPluginInit({ addPlugin }) {
39
+ addPlugin(useExtendedValidation({
40
+ rules: [
41
+ function AuthorizationExtendedValidationRule(context, args) {
42
+ const user = args.contextValue[contextFieldName];
43
+ const handleField = (fieldNode, objectType) => {
44
+ const field = objectType.getFields()[fieldNode.name.value];
45
+ if (field == null) {
46
+ // field is null/undefined if this is an introspection field
47
+ return;
48
+ }
49
+ const { fieldAuthExtension, fieldAuthDirectiveNode } = extractAuthMeta(field);
50
+ const error = validateUser({
51
+ user,
52
+ fieldNode,
53
+ objectType,
54
+ fieldAuthDirectiveNode,
55
+ fieldAuthExtension,
56
+ });
57
+ if (error) {
58
+ context.reportError(error);
59
+ }
60
+ };
61
+ return {
62
+ Field(node) {
63
+ const fieldType = getNamedType(context.getParentType());
64
+ if (isIntrospectionType(fieldType)) {
65
+ return false;
66
+ }
67
+ if (isObjectType(fieldType)) {
68
+ handleField(node, fieldType);
69
+ }
70
+ else if (isUnionType(fieldType)) {
71
+ for (const objectType of fieldType.getTypes()) {
72
+ handleField(node, objectType);
73
+ }
74
+ }
75
+ else if (isInterfaceType(fieldType)) {
76
+ for (const objectType of args.schema.getImplementations(fieldType).objects) {
77
+ handleField(node, objectType);
78
+ }
79
+ }
80
+ return undefined;
81
+ },
82
+ };
83
+ },
84
+ ],
85
+ }));
86
+ },
48
87
  async onContextBuilding({ context, extendContext }) {
49
88
  const user = await options.resolveUserFn(context);
50
89
  extendContext({
51
- [fieldName]: user,
52
- validateUser: () => validateUser(user, context),
90
+ [contextFieldName]: user,
53
91
  });
54
92
  },
55
93
  };
56
94
  }
57
- else if (options.mode === 'protect-auth-directive') {
95
+ else if (options.mode === 'resolve-only') {
58
96
  return {
59
97
  async onContextBuilding({ context, extendContext }) {
60
98
  const user = await options.resolveUserFn(context);
61
99
  extendContext({
62
- [fieldName]: user,
63
- validateUser,
100
+ [contextFieldName]: user,
64
101
  });
65
102
  },
66
- async onResolverCalled({ args, root, context, info }) {
67
- const authDirectiveNode = getDirective(info, options.authDirectiveName || 'auth');
68
- if (authDirectiveNode) {
69
- await context.validateUser(context[fieldName], context, {
70
- info,
71
- context: context,
72
- args,
73
- root,
74
- }, authDirectiveNode);
75
- }
76
- },
77
103
  };
78
104
  }
79
105
  return {};
80
106
  };
81
107
 
82
- export { DIRECTIVE_SDL, SKIP_AUTH_DIRECTIVE_SDL, UnauthenticatedError, defaultValidateFn, getDirective, useGenericAuth };
108
+ export { DIRECTIVE_SDL, SKIP_AUTH_DIRECTIVE_SDL, UnauthenticatedError, defaultProtectAllValidateFn, defaultProtectSingleValidateFn, useGenericAuth };
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@envelop/generic-auth",
3
- "version": "3.0.0",
3
+ "version": "4.0.0-alpha-bed7c7d.0",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
6
  "@envelop/core": "^2.0.0",
7
7
  "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
8
8
  },
9
+ "dependencies": {
10
+ "@envelop/extended-validation": "^1.3.1"
11
+ },
9
12
  "repository": {
10
13
  "type": "git",
11
14
  "url": "https://github.com/dotansimha/envelop.git",
package/utils.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import { DirectiveNode, GraphQLResolveInfo } from 'graphql';
2
- export declare function getDirective(info: GraphQLResolveInfo, name: string): null | DirectiveNode;