@envelop/generic-auth
Advanced tools
Comparing version 8.0.0-alpha-20240812121953-81638fef to 8.0.0-alpha-20240812201747-f93b5b19
@@ -8,12 +8,12 @@ "use strict"; | ||
exports.DIRECTIVE_SDL = ` | ||
directive @authenticated on FIELD_DEFINITION | ||
directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | ||
`; | ||
exports.SKIP_AUTH_DIRECTIVE_SDL = ` | ||
directive @skipAuth on FIELD_DEFINITION | ||
directive @skipAuth on FIELD_DEFINITION | OBJECT | INTERFACE | ||
`; | ||
exports.REQUIRES_SCOPES_DIRECTIVE_SDL = ` | ||
directive @requiresScopes(scopes: [[String!]!]!) on FIELD_DEFINITION | ||
directive @requiresScopes(scopes: [[String!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | ||
`; | ||
exports.POLICY_DIRECTIVE_SDL = ` | ||
directive @policy(policies: [String!]!) on FIELD_DEFINITION | ||
directive @policy(policies: [String!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | ||
`; | ||
@@ -20,0 +20,0 @@ function createUnauthenticatedError(params) { |
@@ -5,12 +5,12 @@ import { getNamedType, isInterfaceType, isIntrospectionType, isObjectType, isUnionType, } from 'graphql'; | ||
export const DIRECTIVE_SDL = /* GraphQL */ ` | ||
directive @authenticated on FIELD_DEFINITION | ||
directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | ||
`; | ||
export const SKIP_AUTH_DIRECTIVE_SDL = /* GraphQL */ ` | ||
directive @skipAuth on FIELD_DEFINITION | ||
directive @skipAuth on FIELD_DEFINITION | OBJECT | INTERFACE | ||
`; | ||
export const REQUIRES_SCOPES_DIRECTIVE_SDL = /* GraphQL */ ` | ||
directive @requiresScopes(scopes: [[String!]!]!) on FIELD_DEFINITION | ||
directive @requiresScopes(scopes: [[String!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | ||
`; | ||
export const POLICY_DIRECTIVE_SDL = /* GraphQL */ ` | ||
directive @policy(policies: [String!]!) on FIELD_DEFINITION | ||
directive @policy(policies: [String!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | ||
`; | ||
@@ -17,0 +17,0 @@ export function createUnauthenticatedError(params) { |
{ | ||
"name": "@envelop/generic-auth", | ||
"version": "8.0.0-alpha-20240812121953-81638fef", | ||
"version": "8.0.0-alpha-20240812201747-f93b5b19", | ||
"sideEffects": false, | ||
@@ -10,3 +10,3 @@ "peerDependencies": { | ||
"dependencies": { | ||
"@envelop/extended-validation": "4.1.0-alpha-20240812121953-81638fef", | ||
"@envelop/extended-validation": "4.1.0-alpha-20240812201747-f93b5b19", | ||
"@graphql-tools/utils": "^10.5.1", | ||
@@ -13,0 +13,0 @@ "tslib": "^2.5.0" |
124
README.md
@@ -249,6 +249,6 @@ ## `@envelop/generic-auth` | ||
##### Protect a field using a field `directive` | ||
##### Protect a field using a field or type `directive` | ||
> By default, we assume that you have the GraphQL directive definition as part of your GraphQL | ||
> schema (`directive @authenticated on FIELD_DEFINITION`). | ||
> schema (`directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE`). | ||
@@ -271,3 +271,3 @@ Then, in your GraphQL schema SDL, you can add `@authenticated` directive to your fields, and the | ||
##### Protect a field using a field extension | ||
##### Protect a field or type using extensions | ||
@@ -315,62 +315,104 @@ ```typescript | ||
##### With a custom directive with arguments | ||
##### Role/scope based authentication with `@requiresScope` directive | ||
It is possible to add custom parameters to your `@authenticated` directive. Here's an example for | ||
adding role-aware authentication: | ||
You can use `@requiresScope` directive to protect your schema based on the user's role or scope. | ||
Here's an example of how you can use it: | ||
```graphql | ||
enum Role { | ||
ADMIN | ||
MEMBER | ||
directive @requiresScopes(scopes: [[String!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | ||
type Query { | ||
me: User! @requiresScopes(scopes: [["read:user"]]) | ||
protectedField: String @requiresScopes(scopes: [["read:admin"]]) | ||
publicField: String | ||
} | ||
directive @authenticated(role: Role!) on FIELD_DEFINITION | ||
``` | ||
Then, you use the `directiveNode` parameter to check the arguments: | ||
By default, the plugin will try to extract available scopes for the current user from `scope` | ||
property which is expected to be a string like `read:user read:admin`. However you can customize | ||
this behavior by providing a custom `extractScopes` function. | ||
```ts | ||
import { ValidateUserFn } from '@envelop/generic-auth' | ||
useGenericAuth({ | ||
resolveUserFn, | ||
validateUser, | ||
mode: 'protect-granular', | ||
extractScopes: user => user.scopes // Expected to return an array of strings | ||
}) | ||
``` | ||
const validateUser: ValidateUserFn<UserType> = ({ user, fieldAuthDirectiveNode }) => { | ||
// Now you can use the fieldAuthDirectiveNode parameter to implement custom logic for user validation, with access | ||
// to the resolver auth directive arguments. | ||
You can also apply `AND` or `OR` logic to the scopes: | ||
if (!user) { | ||
return new Error(`Unauthenticated!`) | ||
} | ||
```graphql | ||
type Query { | ||
# This field requires the user to have `read:user` OR `read:admin` scopes | ||
me: User! @requiresScopes(scopes: [["read:user"], ["read:admin"]]) | ||
# This field requires the user to have `read:user` AND `read:admin` scopes | ||
protectedField: String @requiresScopes(scopes: [["read:admin", "read:user"]]) | ||
publicField: String | ||
} | ||
``` | ||
const valueNode = fieldAuthDirectiveNode.arguments.find(arg => arg.name.value === 'role') | ||
.value as EnumValueNode | ||
const role = valueNode.value | ||
##### `@policy` directive to fetch the roles from a policy service | ||
if (role !== user.role) { | ||
return new Error(`No permissions!`) | ||
} | ||
You can use the `@policy` directive to fetch the roles from a policy service. Here's an example of | ||
how you can use it: | ||
```graphql | ||
directive @policy(name: String!) on FIELD_DEFINITION | OBJECT | INTERFACE | ||
type Query { | ||
me: User! @policy(policies: [["read:user"]]) | ||
protectedField: String @policy(policies: [["read:admin"]]) | ||
publicField: String | ||
} | ||
``` | ||
##### With a custom field extensions | ||
It has the same logic with `@requiresScopes` but it can asynchronously fetch the roles from a | ||
source; | ||
You can use custom field extension to pass data to your `validateUser` function instead of using a | ||
directive. Here's an example for adding role-aware authentication: | ||
```ts | ||
import { ValidateUserFn } from '@envelop/generic-auth' | ||
useGenericAuth({ | ||
resolveUserFn, | ||
validateUser, | ||
mode: 'protect-granular', | ||
fetchPolicies: async user => { | ||
const res = await fetch('https://policy-service.com', { | ||
headers: { | ||
Authorization: `Bearer ${user.token}` | ||
} | ||
}) | ||
// Expected to return an array of strings | ||
return res.json() | ||
} | ||
}) | ||
``` | ||
const validateUser: ValidateUserFn<UserType> = ({ user, fieldAuthExtension }) => { | ||
// Now you can use the fieldAuthDirectiveNode parameter to implement custom logic for user validation, with access | ||
// to the resolver auth directive arguments. | ||
##### Reject the whole operation if the user is not authenticated for the entire selection set | ||
if (!user) { | ||
return new Error(`Unauthenticated!`) | ||
} | ||
By default, the plugin will reject the whole operation if the user is not authenticated for the | ||
selection set fully. But if you want to allow partial execution, you can set `rejectUnauthorized` to | ||
`false`. | ||
const role = fieldAuthExtension.role | ||
When `rejectUnauthorized` is set to `false`, the plugin will behave like below; | ||
if (role !== user.role) { | ||
return new Error(`No permissions!`) | ||
```graphql | ||
query { | ||
me { | ||
# This field will not be executed if the user is not authenticated | ||
id | ||
name | ||
} | ||
protectedField # This field will not be executed if the user is not authenticated | ||
publicField # This field will be executed even if the user is not authenticated | ||
} | ||
``` | ||
##### With a custom field extensions | ||
You can use custom field extension to pass data to your `validateUser` function instead of using a | ||
directive. Here's an example for adding role-aware authentication: | ||
```ts | ||
const resolvers = { | ||
@@ -382,4 +424,4 @@ Query: { | ||
directives: { | ||
authenticated: { | ||
role: 'USER' | ||
requiresScopes: { | ||
scopes: [['read:user']] | ||
} | ||
@@ -386,0 +428,0 @@ } |
@@ -40,6 +40,6 @@ import { ExecutionArgs, FieldNode, GraphQLError, GraphQLField, GraphQLInterfaceType, GraphQLObjectType } from 'graphql'; | ||
export type ValidateUserFn<UserType> = (params: ValidateUserFnParams<UserType>) => void | GraphQLError; | ||
export declare const DIRECTIVE_SDL = "\n directive @authenticated on FIELD_DEFINITION\n"; | ||
export declare const SKIP_AUTH_DIRECTIVE_SDL = "\n directive @skipAuth on FIELD_DEFINITION\n"; | ||
export declare const REQUIRES_SCOPES_DIRECTIVE_SDL = "\n directive @requiresScopes(scopes: [[String!]!]!) on FIELD_DEFINITION\n"; | ||
export declare const POLICY_DIRECTIVE_SDL = "\n directive @policy(policies: [String!]!) on FIELD_DEFINITION\n"; | ||
export declare const DIRECTIVE_SDL = "\n directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE\n"; | ||
export declare const SKIP_AUTH_DIRECTIVE_SDL = "\n directive @skipAuth on FIELD_DEFINITION | OBJECT | INTERFACE\n"; | ||
export declare const REQUIRES_SCOPES_DIRECTIVE_SDL = "\n directive @requiresScopes(scopes: [[String!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE\n"; | ||
export declare const POLICY_DIRECTIVE_SDL = "\n directive @policy(policies: [String!]!) on FIELD_DEFINITION | OBJECT | INTERFACE\n"; | ||
export type GenericAuthPluginOptions<UserType extends {} = {}, ContextType = DefaultContext, CurrentUserKey extends string = 'currentUser'> = { | ||
@@ -46,0 +46,0 @@ /** |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
53489
476