@envelop/generic-auth
Advanced tools
Comparing version 0.0.1-alpha-09d8ef4.0 to 0.0.1-alpha-546ecf8.0
@@ -5,3 +5,3 @@ 'use strict'; | ||
function hasDirective(info, name) { | ||
function getDirective(info, name) { | ||
const { parentType, fieldName, schema } = info; | ||
@@ -13,3 +13,3 @@ const schemaType = schema.getType(parentType.name); | ||
const authDirective = directives.find(d => d.name.value === name); | ||
return !!authDirective; | ||
return authDirective || null; | ||
} | ||
@@ -30,6 +30,6 @@ | ||
const validateUser = options.validateUser || defaultValidateFn; | ||
if (options.mode === 'authenticate-all') { | ||
if (options.mode === 'protect-all') { | ||
return { | ||
async onContextBuilding({ context, extendContext }) { | ||
const user = await options.extractUserFn(context); | ||
const user = await options.resolveUserFn(context); | ||
await validateUser(user, context); | ||
@@ -42,6 +42,6 @@ extendContext({ | ||
} | ||
else if (options.mode === 'just-extract') { | ||
else if (options.mode === 'resolve-only') { | ||
return { | ||
async onContextBuilding({ context, extendContext }) { | ||
const user = await options.extractUserFn(context); | ||
const user = await options.resolveUserFn(context); | ||
extendContext({ | ||
@@ -54,6 +54,6 @@ [fieldName]: user, | ||
} | ||
else if (options.mode === 'auth-directive') { | ||
else if (options.mode === 'protect-auth-directive') { | ||
return { | ||
async onContextBuilding({ context, extendContext }) { | ||
const user = await options.extractUserFn(context); | ||
const user = await options.resolveUserFn(context); | ||
extendContext({ | ||
@@ -66,6 +66,11 @@ [fieldName]: user, | ||
return { | ||
async onResolverCalled({ context, info }) { | ||
const shouldAuth = hasDirective(info, options.authDirectiveName || 'auth'); | ||
if (shouldAuth) { | ||
await context.validateUser(context[fieldName], context, info); | ||
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); | ||
} | ||
@@ -83,3 +88,4 @@ }, | ||
exports.defaultValidateFn = defaultValidateFn; | ||
exports.getDirective = getDirective; | ||
exports.useGenericAuth = useGenericAuth; | ||
//# sourceMappingURL=index.cjs.js.map |
import { DefaultContext, Plugin } from '@envelop/types'; | ||
import { GraphQLResolveInfo } from 'graphql'; | ||
import { DirectiveNode, GraphQLResolveInfo } from 'graphql'; | ||
export * from './utils'; | ||
export declare class UnauthenticatedError extends Error { | ||
} | ||
export declare type ExtractUserFn<UserType, ContextType = unknown> = (context: ContextType) => null | UserType | Promise<UserType>; | ||
export declare type ValidateUserFn<UserType, ContextType = unknown> = (user: UserType, context: ContextType, info?: GraphQLResolveInfo) => void | Promise<void>; | ||
export declare type ResolveUserFn<UserType, ContextType = unknown> = (context: ContextType) => null | UserType | Promise<UserType>; | ||
export declare type ValidateUserFn<UserType, ContextType = unknown> = (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"; | ||
@@ -15,3 +21,3 @@ export declare type GenericAuthPluginOptions<UserType, ContextType> = { | ||
*/ | ||
extractUserFn: ExtractUserFn<UserType, ContextType>; | ||
resolveUserFn: ResolveUserFn<UserType, ContextType>; | ||
/** | ||
@@ -32,15 +38,15 @@ * Here you can implement any custom to check if the user is valid and have access to the server. | ||
*/ | ||
mode: 'authenticate-all'; | ||
mode: 'protect-all'; | ||
} | { | ||
/** | ||
* Just extracts the user and inject to authenticated user into the `context`. | ||
* Just resolves the user and inject to authenticated user into the `context`. | ||
* User validation needs to be implemented by you, in your resolvers. | ||
*/ | ||
mode: 'just-extract'; | ||
mode: 'resolve-only'; | ||
} | { | ||
/** | ||
* Extracts the user and inject to authenticated user into the `context`. | ||
* resolves the user and inject to authenticated user into the `context`. | ||
* And checks for `@auth` directives usages to run validation automatically. | ||
*/ | ||
mode: 'auth-directive'; | ||
mode: 'protect-auth-directive'; | ||
/** | ||
@@ -47,0 +53,0 @@ * Overrides the default directive name |
@@ -1,2 +0,2 @@ | ||
function hasDirective(info, name) { | ||
function getDirective(info, name) { | ||
const { parentType, fieldName, schema } = info; | ||
@@ -8,3 +8,3 @@ const schemaType = schema.getType(parentType.name); | ||
const authDirective = directives.find(d => d.name.value === name); | ||
return !!authDirective; | ||
return authDirective || null; | ||
} | ||
@@ -25,6 +25,6 @@ | ||
const validateUser = options.validateUser || defaultValidateFn; | ||
if (options.mode === 'authenticate-all') { | ||
if (options.mode === 'protect-all') { | ||
return { | ||
async onContextBuilding({ context, extendContext }) { | ||
const user = await options.extractUserFn(context); | ||
const user = await options.resolveUserFn(context); | ||
await validateUser(user, context); | ||
@@ -37,6 +37,6 @@ extendContext({ | ||
} | ||
else if (options.mode === 'just-extract') { | ||
else if (options.mode === 'resolve-only') { | ||
return { | ||
async onContextBuilding({ context, extendContext }) { | ||
const user = await options.extractUserFn(context); | ||
const user = await options.resolveUserFn(context); | ||
extendContext({ | ||
@@ -49,6 +49,6 @@ [fieldName]: user, | ||
} | ||
else if (options.mode === 'auth-directive') { | ||
else if (options.mode === 'protect-auth-directive') { | ||
return { | ||
async onContextBuilding({ context, extendContext }) { | ||
const user = await options.extractUserFn(context); | ||
const user = await options.resolveUserFn(context); | ||
extendContext({ | ||
@@ -61,6 +61,11 @@ [fieldName]: user, | ||
return { | ||
async onResolverCalled({ context, info }) { | ||
const shouldAuth = hasDirective(info, options.authDirectiveName || 'auth'); | ||
if (shouldAuth) { | ||
await context.validateUser(context[fieldName], context, info); | ||
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); | ||
} | ||
@@ -75,3 +80,3 @@ }, | ||
export { DIRECTIVE_SDL, UnauthenticatedError, defaultValidateFn, useGenericAuth }; | ||
export { DIRECTIVE_SDL, UnauthenticatedError, defaultValidateFn, getDirective, useGenericAuth }; | ||
//# sourceMappingURL=index.esm.js.map |
{ | ||
"name": "@envelop/generic-auth", | ||
"version": "0.0.1-alpha-09d8ef4.0", | ||
"version": "0.0.1-alpha-546ecf8.0", | ||
"sideEffects": false, | ||
@@ -5,0 +5,0 @@ "peerDependencies": { |
109
README.md
## `@envelop/generic-auth` | ||
This plugin allows you to implement custom authentication flow, by providing a custom user extraction based on the original HTTP request. The extracted user is being injected into the GraphQL execution `context` and you can use it in your resolvers to fetch the current user. | ||
This plugin allows you to implement custom authentication flow by providing a custom user resolver based on the original HTTP request. The resolved user is injected into the GraphQL execution `context`, and you can use it in your resolvers to fetch the current user. | ||
@@ -9,5 +9,5 @@ > The plugin also comes with an optional `@auth` directive that can be added to your GraphQL schema and helps you to protect your GraphQL schema in a declarative way. | ||
- **Option #1 - Complete Protection**: to protect your entire GraphQL schema, by validating the user before executing the request. | ||
- **Option #2 - Fine-grain Protection**: Use the plugin to inject to authenticated user into the `context`, and later you can verify it in your resolvers. | ||
- **Option #3 - Fine-grain Protection with Directives**: Uses `@auth` SDL directive to automatically protect specific GraphQL fields. | ||
- **Option #1 - Complete Protection**: protected the entire GraphQL schema from unauthenticated access. | ||
- **Option #2 - Manual Validation**: the plugin will just resolve the user and injects it into the `context` without validating the user. | ||
- **Option #3 - Automatic validation using GraphQL directives**: Look for `@auth` directive and automatically protect specific GraphQL fields. | ||
@@ -24,3 +24,3 @@ ## Getting Started | ||
1. Extract your user from the request by implementing `extractUser`: | ||
1. Resolve your user from the request by implementing `resolveUserFn`: | ||
@@ -30,3 +30,3 @@ Use this method to only extract the user from the context, with any custom code, for example: | ||
```ts | ||
import { ExtractUserFn } from '@envelop/generic-auth'; | ||
import { ResolveUserFn } from '@envelop/generic-auth'; | ||
@@ -37,3 +37,3 @@ type UserType = { | ||
const extractUserFn: ExtractUserFn<UserType> = async context => { | ||
const resolveUserFn: ResolveUserFn<UserType> = async context => { | ||
// Here you can implement any custom sync/async code, and use the context built so far in Envelop and the HTTP request | ||
@@ -58,3 +58,3 @@ // to find the current user. | ||
This method is optional, by default, it will just check the value returned by `extractUserFn` and throw an error in case of a falsey value. | ||
This method is optional; the default method will just verify the value returned by `resolveUser` and throw an error in case of a false value (`false | null | undefined`). | ||
@@ -68,4 +68,3 @@ ```ts | ||
// If you are using `auth-directive` mode, you'll also get a third parameter for the GraphQLResolveInfo | ||
// of the resolvers being resolved at the moment. | ||
// 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. | ||
@@ -78,7 +77,7 @@ if (!user) { | ||
Now, configure your plugin based on the mode you with to use: | ||
Now, configure your plugin based on the mode you wish to use: | ||
#### Option #1 - `authenticate-all` | ||
#### Option #1 - `protect-all` | ||
This mode offers complete protection for the entire API. It protects your entire GraphQL schema, by validating the user before executing the request. | ||
This mode offers complete protection for the entire API. It protects your entire GraphQL schema by validating the user before executing the request. | ||
@@ -89,3 +88,3 @@ To setup this mode, use the following config: | ||
import { envelop } from '@envelop/core'; | ||
import { useGenericAuth, ExtractUserFn, ValidateUserFn } from '@envelop/generic-auth'; | ||
import { useGenericAuth, resolveUser, ValidateUserFn } from '@envelop/generic-auth'; | ||
@@ -95,3 +94,3 @@ type UserType = { | ||
}; | ||
const extractUserFn: ExtractUserFn<UserType> = async context => { | ||
const resolveUserFn: ResolveUserFn<UserType> = async context => { | ||
/* ... */ | ||
@@ -107,5 +106,5 @@ }; | ||
useGenericAuth({ | ||
extractUserFn, | ||
resolveUser, | ||
validateUser, | ||
mode: 'authenticate-all', | ||
mode: 'protect-all', | ||
}), | ||
@@ -116,9 +115,9 @@ ], | ||
#### Option #2 - `just-extract` | ||
#### Option #2 - `resolve-only` | ||
This mode uses the plugin to inject to authenticated user into the `context`, and later you can verify it in your resolvers. | ||
This mode uses the plugin to inject the authenticated user into the `context`, and later you can verify it in your resolvers. | ||
```ts | ||
import { envelop } from '@envelop/core'; | ||
import { useGenericAuth, ExtractUserFn, ValidateUserFn } from '@envelop/generic-auth'; | ||
import { useGenericAuth, resolveUser, ValidateUserFn } from '@envelop/generic-auth'; | ||
@@ -128,3 +127,3 @@ type UserType = { | ||
}; | ||
const extractUserFn: ExtractUserFn<UserType> = async context => { | ||
const resolveUserFn: ResolveUserFn<UserType> = async context => { | ||
/* ... */ | ||
@@ -140,5 +139,5 @@ }; | ||
useGenericAuth({ | ||
extractUserFn, | ||
resolveUser, | ||
validateUser, | ||
mode: 'just-extract', | ||
mode: 'resolve-only', | ||
}), | ||
@@ -164,3 +163,3 @@ ], | ||
#### Option #3 - `auth-directive` | ||
#### Option #3 - `protect-auth-directive` | ||
@@ -171,3 +170,3 @@ This mode is similar to option #2, but it uses `@auth` SDL directive to automatically protect specific GraphQL fields. | ||
import { envelop } from '@envelop/core'; | ||
import { useGenericAuth, ExtractUserFn, ValidateUserFn } from '@envelop/generic-auth'; | ||
import { useGenericAuth, resolveUser, ValidateUserFn } from '@envelop/generic-auth'; | ||
@@ -177,3 +176,3 @@ type UserType = { | ||
}; | ||
const extractUserFn: ExtractUserFn<UserType> = async context => { | ||
const resolveUserFn: ResolveUserFn<UserType> = async context => { | ||
/* ... */ | ||
@@ -189,5 +188,5 @@ }; | ||
useGenericAuth({ | ||
extractUserFn, | ||
resolveUser, | ||
validateUser, | ||
mode: 'auth-directive', | ||
mode: 'protect-auth-directive', | ||
}), | ||
@@ -198,3 +197,3 @@ ], | ||
> By defualt, we assume that you have the GraphQL directive defition as part of your GraphQL schema (`directive @auth on FIELD_DEFINITION`). | ||
> By default, we assume that you have the GraphQL directive definition as part of your GraphQL schema (`directive @auth on FIELD_DEFINITION`). | ||
@@ -207,3 +206,3 @@ Then, in your GraphQL schema SDL, you can add `@auth` directive to your fields, and the `validateUser` will get called only while resolving that specific field: | ||
protectedField: String @auth | ||
publicField: String | ||
# publicField: String | ||
} | ||
@@ -215,1 +214,51 @@ ``` | ||
> If you are using a different directive for authentication, you can pass `authDirectiveName` configuration to customize it. | ||
##### Extend authentication with custom directive logic | ||
You can also specify a custom `validateUser` function and get access to the `GraphQLResolveInfo` object while using the `protect-auth-directive` mode: | ||
```ts | ||
import { ValidateUserFn } from '@envelop/generic-auth'; | ||
const validateUser: ValidateUserFn<UserType> = async (user, context, { root, args, context, info }) => { | ||
// Now you can use the 3rd parameter to implement custom logic for user validation, with access | ||
// to the resolver data and information. | ||
if (!user) { | ||
throw new Error(`Unauthenticated!`); | ||
} | ||
}; | ||
``` | ||
And it's also possible to add custom parameters to your `@auth` directive. Here's an example for adding role-aware authentication: | ||
```graphql | ||
enum Role { | ||
ADMIN | ||
MEMBER | ||
} | ||
directive @auth(role: Role!) on FIELD_DEFINITION | ||
``` | ||
Then, you use the `directiveNode` parameter to check the arguments: | ||
```ts | ||
import { ValidateUserFn } from '@envelop/generic-auth'; | ||
const validateUser: ValidateUserFn<UserType> = async (user, context, { root, args, context, info }, directiveNode) => { | ||
// Now you can use the 3rd parameter to implement custom logic for user validation, with access | ||
// to the resolver data and information. | ||
if (!user) { | ||
throw new Error(`Unauthenticated!`); | ||
} | ||
const valueNode = authDirectiveNode.arguments.find(arg => arg.name.value === 'role').value as EnumValueNode; | ||
const role = valueNode.value; | ||
if (role !== user.role) { | ||
throw new Error(`No permissions!`); | ||
} | ||
}; | ||
``` |
@@ -1,2 +0,2 @@ | ||
import { GraphQLResolveInfo } from 'graphql'; | ||
export declare function hasDirective(info: GraphQLResolveInfo, name: string): boolean; | ||
import { DirectiveNode, GraphQLResolveInfo } from 'graphql'; | ||
export declare function getDirective(info: GraphQLResolveInfo, name: string): null | DirectiveNode; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
27658
213
245