@envelop/generic-auth
Advanced tools
Comparing version 2.0.0-alpha-c8eef8e.0 to 2.0.0-alpha-f336686.0
import { DefaultContext, Plugin } from '@envelop/core'; | ||
import { DirectiveNode, FieldNode, GraphQLError, GraphQLObjectType } from 'graphql'; | ||
import { DirectiveNode, GraphQLError, GraphQLResolveInfo } from 'graphql'; | ||
export * from './utils'; | ||
export declare class UnauthenticatedError extends GraphQLError { | ||
} | ||
export declare type ResolveUserFn<UserType, ContextType = DefaultContext> = (context: ContextType) => null | UserType | Promise<UserType | null>; | ||
export declare type ValidateUserFnParams<UserType> = { | ||
/** The user object. */ | ||
user: UserType; | ||
/** The field node from the operation that is being validated. */ | ||
fieldNode: FieldNode; | ||
/** The object type which has the field that is being validated. */ | ||
objectType: GraphQLObjectType; | ||
/** The directive node used for the authentication (If using an SDL flow). */ | ||
fieldAuthDirectiveNode: DirectiveNode | undefined; | ||
/** The extensions used for authentication (If using an extension based flow). */ | ||
fieldAuthExtension: unknown | undefined; | ||
}; | ||
export declare type ValidateUserFn<UserType> = (params: ValidateUserFnParams<UserType>) => void | UnauthenticatedError; | ||
export declare type ValidateUserFn<UserType, ContextType = DefaultContext> = (user: UserType, context: ContextType, resolverInfo?: { | ||
root: unknown; | ||
args: Record<string, unknown>; | ||
context: ContextType; | ||
info: GraphQLResolveInfo; | ||
}, directiveNode?: DirectiveNode) => void | Promise<void>; | ||
export declare const DIRECTIVE_SDL = "\n directive @auth on FIELD_DEFINITION\n"; | ||
@@ -30,2 +24,7 @@ export declare const SKIP_AUTH_DIRECTIVE_SDL = "\n directive @skipAuth on FIELD_DEFINITION\n"; | ||
/** | ||
* Here you can implement any custom to check if the user is valid and have access to the server. | ||
* This method is being triggered in different flows, besed on the mode you chose to implement. | ||
*/ | ||
validateUser?: ValidateUserFn<UserType, ContextType>; | ||
/** | ||
* Overrides the default field name for injecting the user into the execution `context`. | ||
@@ -47,8 +46,2 @@ * @default currentUser | ||
authDirectiveName?: 'skipAuth' | string; | ||
/** | ||
* Customize how the user is validated. E.g. apply authorization role based validation. | ||
* The validation is applied during the extended validation phase. | ||
* @default `defaultProtectAllValidateFn` | ||
*/ | ||
validateUser?: ValidateUserFn<UserType>; | ||
} | { | ||
@@ -65,3 +58,3 @@ /** | ||
*/ | ||
mode: 'protect-single'; | ||
mode: 'protect-auth-directive'; | ||
/** | ||
@@ -72,13 +65,6 @@ * Overrides the default directive name | ||
authDirectiveName?: 'auth' | string; | ||
/** | ||
* Customize how the user is validated. E.g. apply authorization role based validation. | ||
* The validation is applied during the extended validation phase. | ||
* @default `defaultProtectSingleValidateFn` | ||
*/ | ||
validateUser?: ValidateUserFn<UserType>; | ||
}); | ||
export declare function defaultProtectAllValidateFn<UserType>(params: ValidateUserFnParams<UserType>): void | UnauthenticatedError; | ||
export declare function defaultProtectSingleValidateFn<UserType>(params: ValidateUserFnParams<UserType>): void | UnauthenticatedError; | ||
export declare function defaultValidateFn<UserType, ContextType>(user: UserType, contextType: ContextType): void; | ||
export declare const useGenericAuth: <UserType extends {} = {}, ContextType extends DefaultContext = DefaultContext>(options: GenericAuthPluginOptions<UserType, ContextType>) => Plugin<{ | ||
validateUser: ValidateUserFn<UserType>; | ||
validateUser: ValidateUserFn<UserType, ContextType>; | ||
}>; |
134
index.js
@@ -6,4 +6,13 @@ 'use strict'; | ||
const graphql = require('graphql'); | ||
const extendedValidation = require('@envelop/extended-validation'); | ||
function getDirective(info, name) { | ||
var _a; | ||
const { parentType, fieldName, schema } = info; | ||
const schemaType = schema.getType(parentType.name); | ||
const field = schemaType.getFields()[fieldName]; | ||
const astNode = field.astNode; | ||
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); | ||
return authDirective || null; | ||
} | ||
class UnauthenticatedError extends graphql.GraphQLError { | ||
@@ -17,83 +26,29 @@ } | ||
`; | ||
function defaultProtectAllValidateFn(params) { | ||
if (params.user == null && !params.fieldAuthDirectiveNode && !params.fieldAuthExtension) { | ||
const schemaCoordinate = `${params.objectType.name}.${params.fieldNode.name.value}`; | ||
return new UnauthenticatedError(`Accessing '${schemaCoordinate}' requires authentication.`, [params.fieldNode]); | ||
function defaultValidateFn(user, contextType) { | ||
if (!user) { | ||
throw new UnauthenticatedError('Unauthenticated!'); | ||
} | ||
} | ||
function defaultProtectSingleValidateFn(params) { | ||
if (params.user == null && (params.fieldAuthDirectiveNode || params.fieldAuthExtension)) { | ||
const schemaCoordinate = `${params.objectType.name}.${params.fieldNode.name.value}`; | ||
return new UnauthenticatedError(`Accessing '${schemaCoordinate}' requires authentication.`, [params.fieldNode]); | ||
} | ||
} | ||
const useGenericAuth = (options) => { | ||
var _a, _b; | ||
const contextFieldName = options.contextFieldName || 'currentUser'; | ||
if (options.mode === 'protect-all' || options.mode === 'protect-single') { | ||
const directiveName = (_a = options.authDirectiveName) !== null && _a !== void 0 ? _a : (options.mode === 'protect-all' ? 'skipAuth' : 'auth'); | ||
const validateUser = (_b = options.validateUser) !== null && _b !== void 0 ? _b : (options.mode === 'protect-all' ? defaultProtectAllValidateFn : defaultProtectSingleValidateFn); | ||
const extractAuthMeta = (input) => { | ||
var _a, _b, _c; | ||
return { | ||
fieldAuthExtension: (_a = input.extensions) === null || _a === void 0 ? void 0 : _a[directiveName], | ||
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), | ||
}; | ||
}; | ||
const fieldName = options.contextFieldName || 'currentUser'; | ||
const validateUser = options.validateUser || defaultValidateFn; | ||
if (options.mode === 'protect-all') { | ||
return { | ||
onPluginInit({ addPlugin }) { | ||
addPlugin(extendedValidation.useExtendedValidation({ | ||
rules: [ | ||
function AuthorizationExtendedValidationRule(context, args) { | ||
const user = args.contextValue[contextFieldName]; | ||
const handleField = (fieldNode, objectType) => { | ||
const field = objectType.getFields()[fieldNode.name.value]; | ||
if (field == null) { | ||
// field is null/undefined if this is an introspection field | ||
return; | ||
} | ||
const { fieldAuthExtension, fieldAuthDirectiveNode } = extractAuthMeta(field); | ||
const error = validateUser({ | ||
user, | ||
fieldNode, | ||
objectType, | ||
fieldAuthDirectiveNode, | ||
fieldAuthExtension, | ||
}); | ||
if (error) { | ||
context.reportError(error); | ||
} | ||
}; | ||
return { | ||
Field(node) { | ||
const fieldType = graphql.getNamedType(context.getParentType()); | ||
if (graphql.isIntrospectionType(fieldType)) { | ||
return false; | ||
} | ||
if (graphql.isObjectType(fieldType)) { | ||
handleField(node, fieldType); | ||
} | ||
else if (graphql.isUnionType(fieldType)) { | ||
for (const objectType of fieldType.getTypes()) { | ||
handleField(node, objectType); | ||
} | ||
} | ||
else if (graphql.isInterfaceType(fieldType)) { | ||
for (const objectType of args.schema.getImplementations(fieldType).objects) { | ||
handleField(node, objectType); | ||
} | ||
} | ||
return undefined; | ||
}, | ||
}; | ||
}, | ||
], | ||
})); | ||
}, | ||
async onContextBuilding({ context, extendContext }) { | ||
const user = await options.resolveUserFn(context); | ||
extendContext({ | ||
[contextFieldName]: user, | ||
[fieldName]: user, | ||
validateUser, | ||
}); | ||
}, | ||
onExecute() { | ||
return { | ||
async onResolverCalled({ args, root, context, info }) { | ||
const authDirectiveNode = getDirective(info, options.authDirectiveName || 'skipAuth'); | ||
if (authDirectiveNode) | ||
return; | ||
await context.validateUser(context[fieldName], context); | ||
}, | ||
}; | ||
}, | ||
}; | ||
@@ -106,3 +61,4 @@ } | ||
extendContext({ | ||
[contextFieldName]: user, | ||
[fieldName]: user, | ||
validateUser: () => validateUser(user, context), | ||
}); | ||
@@ -112,2 +68,28 @@ }, | ||
} | ||
else if (options.mode === 'protect-auth-directive') { | ||
return { | ||
async onContextBuilding({ context, extendContext }) { | ||
const user = await options.resolveUserFn(context); | ||
extendContext({ | ||
[fieldName]: user, | ||
validateUser, | ||
}); | ||
}, | ||
onExecute() { | ||
return { | ||
async onResolverCalled({ args, root, context, info }) { | ||
const authDirectiveNode = getDirective(info, options.authDirectiveName || 'auth'); | ||
if (authDirectiveNode) { | ||
await context.validateUser(context[fieldName], context, { | ||
info, | ||
context: context, | ||
args, | ||
root, | ||
}, authDirectiveNode); | ||
} | ||
}, | ||
}; | ||
}, | ||
}; | ||
} | ||
return {}; | ||
@@ -119,4 +101,4 @@ }; | ||
exports.UnauthenticatedError = UnauthenticatedError; | ||
exports.defaultProtectAllValidateFn = defaultProtectAllValidateFn; | ||
exports.defaultProtectSingleValidateFn = defaultProtectSingleValidateFn; | ||
exports.defaultValidateFn = defaultValidateFn; | ||
exports.getDirective = getDirective; | ||
exports.useGenericAuth = useGenericAuth; |
{ | ||
"name": "@envelop/generic-auth", | ||
"version": "2.0.0-alpha-c8eef8e.0", | ||
"version": "2.0.0-alpha-f336686.0", | ||
"sideEffects": false, | ||
"peerDependencies": { | ||
"@envelop/core": "^1.6.1", | ||
"@envelop/core": "1.7.0-alpha-f336686.0", | ||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" | ||
}, | ||
"dependencies": { | ||
"@envelop/extended-validation": "^1.3.1" | ||
}, | ||
"repository": { | ||
@@ -33,4 +30,5 @@ "type": "git", | ||
"import": "./*.mjs" | ||
} | ||
}, | ||
"./package.json": "./package.json" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
2
6
19301
249
+ Added@envelop/core@1.7.0-alpha-f336686.0(transitive)
- Removed@envelop/extended-validation@^1.3.1
- Removed@envelop/core@1.7.12.6.0(transitive)
- Removed@envelop/extended-validation@1.9.0(transitive)
- Removed@envelop/types@2.4.0(transitive)
- Removed@graphql-tools/utils@8.13.1(transitive)
- Removedtslib@2.4.02.8.1(transitive)