Skip to content

Commit

Permalink
feat: default can be value or array, enhanced examples readme
Browse files Browse the repository at this point in the history
  • Loading branch information
carlocorradini committed Oct 12, 2022
1 parent a1663c9 commit 9a6614d
Show file tree
Hide file tree
Showing 15 changed files with 149 additions and 44 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog and release notes

## v0.3.0

- **Breaking**: Renamed `typeName` to `enumName` in `roles` and `permissions` configurations

- **Breaking**: Rename `defaultValue` to `default` in `roles` and `permissions` configurations

- `roles` and `permissions` configuration `default` value `TRole` value or `TRole` array

## v0.2.0

### Features
Expand Down
25 changes: 11 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,25 +66,22 @@ yarn add graphql-auth-directive
1. Define a custom `auth` function:

> Class based `auth` is also possible leveraging Dependency Injection (DI) mechanism. \
> Define a class that implements `AuthFnClass<Context>` interface.
> Define a class that implements `AuthFnClass<TContext, TRole, TPermission>` interface.

```ts
import type { AuthFn } from 'graphql-auth-directive';
import type { Context } from './Context'; // Your context type
import type { Roles, Permissions } from './User'; // Your roles and permissions enum

export const authFn: AuthFn<Context> = (
export const authFn: AuthFn<Context, Roles, Permissions> = (
{ context: { user } }, // Context
{ roles, permissions } // @auth(roles: [...], permissions: [...])
) => {
if (!user) {
// No user
return false;
}
// No user
if (!user) { return false; }

if (roles.length === 0 && permissions.length === 0) {
// Only authentication required
return true;
}
// Only authentication required
if (roles.length === 0 && permissions.length === 0) { return true; }

// Roles
const rolesMatch: boolean =
Expand Down Expand Up @@ -124,8 +121,8 @@ yarn add graphql-auth-directive

// Build schema
let schema = makeExecutableSchema({
typeDefs: [authDirective.typeDefs, typeDefs], // Add typeDefs
resolvers
typeDefs: [authDirective.typeDefs, typeDefs], // TypeDefs
resolvers // Resolvers
});
schema = authDirective.transformer(schema); // Transform schema

Expand All @@ -148,8 +145,8 @@ yarn add graphql-auth-directive
| `name` | `string` | `auth` | Directive name. |
| `auth` | `Auth<TContext, TRole, TPermission>` | | Auth function (`AuthFn<TContext, TRole, TPermission>`) or class (`AuthFnClass<TContext, TRole, TPermission>`). |
| `authMode` | `'ERROR' \| 'NULL'` | `ERROR` | Auth mode if access is not granted. `ERROR` throws an error. `NULL` returns `null`. |
| `roles` | `{ typeName?: string, defaultValue?: string }` | `{ typeName: 'String', defaultValue: '' }` | Roles configuration. `typeName` is the type name of `roles` array. `defaultValue` is the default value, an empty value is equivalent to `[]`. |
| `permissions` | `{ typeName?: string, defaultValue?: string }` | `{ typeName: 'String', defaultValue: '' }` | Permissions configuration. `typeName` is the type name of `permissions` array. `defaultValue` is the default value, an empty value is equivalent to `[]`. |
| `roles` | `{ enumName?: string, default?: TRole \| TRole[] }` | `{ enumName: undefined, default: undefined }` | Roles configuration. `enumName` is the enum name for `roles` array type, default is `String`. `default` is the default value, default to `[]`. |
| `permissions` | `{ enumName?: string, default?: TPermission \| TPermission[] }` | `{ enumName: undefined, default: undefined }` | Permissions configuration. `enumName` is the enum name for `permissions` array type, default is `String`. `default` is the default value, default to `[]`. |
| `authenticationError` | `ClassTypeEmptyConstructor<Error>` | `AuthenticationError` | Authentication error class. An error class must extends `Error`. |
| `authorizationError` | `ClassTypeEmptyConstructor<Error>` | `AuthorizationError` | Authorization error class. An error class must extends `Error`. |
| `container` | `ContainerType` | `IOCContainer` | Dependency injection container. |
Expand Down
4 changes: 2 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ Build examples:
npx tsc --build tsconfig.json
```

## Simple
## [Simple](./simple)

```console
node ../build/examples/simple/index.js
```

## [TypeGraphQL](https://github.com/MichalLytek/type-graphql)
## [TypeGraphQL](./typegraphql)

```console
node ../build/examples/typegraphql/index.js
Expand Down
1 change: 1 addition & 0 deletions examples/simple/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Simple
4 changes: 2 additions & 2 deletions examples/simple/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import { resolvers } from './resolvers';
// Build auth directive
const authDirective = buildAuthDirective<Context, UserRoles, UserPermissions>({
auth: authFn,
roles: { typeName: 'UserRoles' },
permissions: { typeName: 'UserPermissions' }
roles: { enumName: 'UserRoles' },
permissions: { enumName: 'UserPermissions' }
});
// const authDirective = buildAuthDirective({ auth: authFnClass, ... });

Expand Down
8 changes: 5 additions & 3 deletions examples/typegraphql/Auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/

import { Directive } from 'type-graphql';
import type { AuthData } from '../../src';
import { AuthData, toArrayString } from '../../src';
import type { UserRoles, UserPermissions } from '../__commons';

type AuthArgs = {
Expand Down Expand Up @@ -59,14 +59,16 @@ export function Auth(
sdl += `(`;

if (authData.roles.length > 0) {
sdl += `roles: [${authData.roles.join(',')}]`;
sdl += `roles: ${toArrayString({ value: authData.roles })}`;
}

if (authData.permissions.length > 0) {
if (authData.roles.length > 0) {
sdl += ', ';
}
sdl += `permissions: [${authData.permissions.join(',')}]`;
sdl += `permissions: ${toArrayString({
value: authData.permissions
})}`;
}

sdl += `)`;
Expand Down
5 changes: 5 additions & 0 deletions examples/typegraphql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# [TypeGraphQL](https://github.com/MichalLytek/type-graphql)

- Building the *schema* requires a little bit more work than simply calling `buildSchema(...)`. See [index.ts](./index.ts) for more information.

- To avoid errors and repetitions, create the `@Auth(...)` decorator that wraps `@Directive('@auth(...)')`. See [Auth.ts](./Auth.ts) for more information.
4 changes: 2 additions & 2 deletions examples/typegraphql/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ import { PostResolver } from './PostResolver';
// Build auth directive
const authDirective = buildAuthDirective<Context, UserRoles, UserPermissions>({
auth: authFn,
roles: { typeName: 'UserRoles' },
permissions: { typeName: 'UserPermissions' }
roles: { enumName: 'UserRoles' },
permissions: { enumName: 'UserPermissions' }
});
// const authDirective = buildAuthDirective({ auth: authFnClass, ... });

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "graphql-auth-directive",
"version": "0.2.0",
"version": "0.3.0",
"private": false,
"description": "GraphQL @auth directive that protects resources from unauthenticated and unauthorized access",
"keywords": [
Expand Down
45 changes: 31 additions & 14 deletions src/buildAuthDirective.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,28 +33,47 @@ import type {
AuthDirective
} from '~/types';
import { AuthenticationError, AuthorizationError } from '~/errors';
import { IOCContainer } from '~/utils';
import { IOCContainer, toArrayString } from '~/utils';

type Opts<TContext, TRole, TPermission> = Required<
Omit<
AuthDirectiveArgs<TContext, TRole, TPermission>,
'container' | 'roles' | 'permissions'
>
> & {
container: IOCContainer;
roles: {
type: string;
default: string;
};
permissions: {
type: string;
default: string;
};
};

export function buildAuthDirective<
TContext = Context,
TRole = string,
TPermission = string
>(inArgs: AuthDirectiveArgs<TContext, TRole, TPermission>) {
const opts: Required<
Omit<AuthDirectiveArgs<TContext, TRole, TPermission>, 'container'> & {
container: IOCContainer;
}
> = {
const opts: Opts<TContext, TRole, TPermission> = {
name: inArgs.name ?? 'auth',
auth: inArgs.auth,
authMode: inArgs.authMode ?? 'ERROR',
roles: {
typeName: inArgs.roles?.typeName ?? 'String',
defaultValue: inArgs?.roles?.defaultValue ?? ''
type: inArgs.roles?.enumName ?? 'String',
default: toArrayString({
value: inArgs.roles?.default,
isEnum: !!inArgs.roles?.enumName
})
},
permissions: {
typeName: inArgs.permissions?.typeName ?? 'String',
defaultValue: inArgs?.permissions?.defaultValue ?? ''
type: inArgs.permissions?.enumName ?? 'String',
default: toArrayString({
value: inArgs.permissions?.default,
isEnum: !!inArgs.permissions?.enumName
})
},
authenticationError: inArgs.authenticationError ?? AuthenticationError,
authorizationError: inArgs.authorizationError ?? AuthorizationError,
Expand All @@ -67,11 +86,9 @@ export function buildAuthDirective<
"""Protect the resource from unauthenticated and unauthorized access."""
directive @${opts.name}(
"""Allowed roles to access the resource."""
roles: [${opts.roles.typeName}!]! = [${opts.roles.defaultValue}],
roles: [${opts.roles.type}!]! = ${opts.roles.default},
"""Allowed permissions to access the resource."""
permissions: [${opts.permissions.typeName}!]! = [${
opts.permissions.defaultValue ?? ''
}],
permissions: [${opts.permissions.type}!]! = ${opts.permissions.default},
) on OBJECT | FIELD | FIELD_DEFINITION`,
transformer: (schema: GraphQLSchema) =>
mapSchema(schema, {
Expand Down
12 changes: 6 additions & 6 deletions src/types/AuthDirectiveArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,26 +53,26 @@ export type AuthDirectiveArgs<
*/
roles?: {
/**
* Type name.
* Enum name.
*/
typeName?: string;
enumName?: string;
/**
* Default value.
*/
defaultValue?: string;
default?: TRole | TRole[];
};
/**
* Permissions configuration.
*/
permissions?: {
/**
* Type name.
* Enum name.
*/
typeName?: string;
enumName?: string;
/**
* Default value.
*/
defaultValue?: string;
default?: TRole | TRole[];
};
/**
* Authentication error class.
Expand Down
34 changes: 34 additions & 0 deletions src/types/ToArrayStringArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* MIT License
*
* Copyright (c) 2022-2022 Carlo Corradini
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

export type ToArrayStringArgs<T> = {
/**
* Value to flatten.
*/
value: T | T[];
/**
* Flag if value is enum.
*/
isEnum?: boolean;
};
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ export * from './ClassTypeEmptyConstructor';
export * from './ContainerGetter';
export * from './ContainerType';
export * from './Context';
export * from './ToArrayStringArgs';
export * from './ResolverData';
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@
*/

export * from './container';
export * from './toArrayString';
39 changes: 39 additions & 0 deletions src/utils/toArrayString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* MIT License
*
* Copyright (c) 2022-2022 Carlo Corradini
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

import type { ToArrayStringArgs } from '~/types';

export function toArrayString<T>(args: ToArrayStringArgs<T>): string {
// eslint-disable-next-line no-nested-ternary
const array = !args.value
? [] // 'undefined' or 'null'
: Array.isArray(args.value)
? args.value // Array
: [args.value]; // Value
const isEnum = args.isEnum ?? true;

return `[${array
.map((value) => `${isEnum ? '' : '"'}${value}${isEnum ? '' : '"'}`)
.join(',')}]`;
}

0 comments on commit 9a6614d

Please sign in to comment.