Skip to content

Commit

Permalink
style: better readme and removed examples spaghetti code
Browse files Browse the repository at this point in the history
  • Loading branch information
carlocorradini committed Oct 12, 2022
1 parent 96c97d5 commit a1663c9
Show file tree
Hide file tree
Showing 28 changed files with 138 additions and 468 deletions.
91 changes: 28 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,49 +33,40 @@ yarn add graphql-auth-directive

> See [examples](./examples) directory
1. Add `@auth` directive to schema:
1. Add `@auth` directive to *GraphQL* schema:

> See [Positioning](#positioning) for allowed positions
> `on OBJECT | FIELD | FIELD_DEFINITION`
```graphql
# Authentication required
@auth

# ADMIN Enum role required
@auth(roles: [ADMIN])

# ADMIN or MODERATOR Enum role required
@auth(roles: [ADMIN, MODERATOR])

# "ADMIN" String role required
@auth(roles: ["ADMIN"])

# "ADMIN" or "MODERATOR" String role required
@auth(roles: ["ADMIN", "MODERATOR"])

# READ_POST Enum permission required
@auth(permissions: [READ_POST])

# READ_POST or EDIT_POST Enum permission required
@auth(roles: [READ_POST, EDIT_POST])

# READ_POST String permission required
@auth(permissions: ["READ_POST"])

# READ_POST or EDIT_POST String permission required
@auth(permissions: ["READ_POST", "EDIT_POST"])

# ADMIN Enum role and EDIT_POST Enum permission required
@auth(roles: [ADMIN], permissions: [EDIT_POST])

# ADMIN String role and EDIT_POST String permission required
@auth(roles: ["ADMIN"], permissions: ["EDIT_POST"])
type Example @auth {
unprotected: String!
adminProtected: String! @auth(roles: [ADMIN])
}

type Query {
unprotected: String!
protected: String! @auth
adminRoleProtected: String! @auth(roles: [ADMIN])
adminOrModeratorRolesProtected: String! @auth(roles: [ADMIN, MODERATOR])
viewPermissionProtected: String! @auth(permissions: [VIEW])
viewOrEditPermissionsProtected: String! @auth(permissions: [VIEW, EDIT])
roleAndPermissionProtected: String! @auth(roles: [ADMIN], permissions: [VIEW])
# ...
}

type Mutation {
# Same as Query
}

type Subscription {
# Same as Query
}
```

1. Create a custom `auth` function:
1. Define a custom `auth` function:

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

```ts
import type { AuthFn } from 'graphql-auth-directive';
Expand Down Expand Up @@ -155,40 +146,14 @@ yarn add graphql-auth-directive
| Name | Type | Default Value | Description |
| --------------------- | ---------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | `string` | `auth` | Directive name. |
| `auth` | `Auth<TContext>` | | Auth function (`AuthFn<TContext>`) or class (`AuthFnClass<TContext>`). |
| `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 `[]`. |
| `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. |

### Positioning

> `on OBJECT | FIELD | FIELD_DEFINITION`
```graphql
type Protected @auth {
unprotected: String!
}

type ProtectedField {
protected: String! @auth
}

type Query {
protected: String! @auth
}

type Mutation {
protected: String! @auth
}

type Subscription {
protected: String! @auth
}
```

## Integrations

### [TypeGraphQL](https://github.com/MichalLytek/type-graphql)
Expand Down
4 changes: 2 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Examples

Every example outputs an authorization token that can be used for `@auth` resources. Remember to add the token in the header: `Authorization: Bearer <TOKEN>`

Build examples:

```console
npx tsc --build tsconfig.json
```

Every example outputs an authorization token that can be used for `@auth` resources. Remember to add the token in the header: `Authorization: Bearer <TOKEN>`

## Simple

```console
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import { authFn } from './authFn';
export class AuthFnClass
implements IAuthFnClass<Context, UserRoles, UserPermissions>
{
// eslint-disable-next-line class-methods-use-this
public auth(
resolverData: ResolverData<Context>,
authData: AuthData<UserRoles, UserPermissions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
*/

import type { ExpressContext } from 'apollo-server-express';
import type { Context } from './Context';
import { TokenPayload, verify } from './token';
import { AuthenticationError } from '../../src';
import type { Context } from './Context';
import { TokenPayload, verifyToken } from './token';

export async function contextHelper({ req }: ExpressContext): Promise<Context> {
let user: TokenPayload | undefined;
Expand All @@ -45,7 +45,7 @@ export async function contextHelper({ req }: ExpressContext): Promise<Context> {
const token = credentials;

try {
const decodedToken = verify(token);
const decodedToken = verifyToken(token);
user = decodedToken;
} catch (error) {
throw new AuthenticationError();
Expand Down
37 changes: 29 additions & 8 deletions examples/typegraphql/data.ts → examples/__commons/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@ import type { User } from './User';
import type { Post } from './Post';
import { UserPermissions } from './UserPermissions';
import { UserRoles } from './UserRoles';
import { sign } from './token';

export const SUPER_USER: User = {
id: 0,
secret: true,
roles: [UserRoles.ADMIN],
permissions: [UserPermissions.DELETE_POST]
};

export const users: User[] = [
{
id: 0,
secret: true,
roles: [UserRoles.ADMIN],
permissions: [UserPermissions.DELETE_POST]
},
SUPER_USER,
{
id: 1,
secret: false,
Expand All @@ -45,4 +46,24 @@ export const users: User[] = [

export const posts: Post[] = [{ id: 0, content: 'Hello World!', creatorId: 1 }];

export const TOKEN = sign(users[0]);
export function addPost(content: string, creatorId: number): Post {
const newPost: Post = {
id: posts.length > 0 ? posts[posts.length - 1].id + 1 : 0,
content,
creatorId
};
posts.push(newPost);
return newPost;
}

export function removePost(id: number): Post | null {
const postIndex = posts.findIndex((p) => p.id === id);
let postDeleted: Post | null = null;

if (postIndex !== -1) {
postDeleted = posts[postIndex];
posts.splice(postIndex, 1);
}

return postDeleted;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@
* SOFTWARE.
*/

import { registerEnumType } from 'type-graphql';

export enum UserPermissions {
DELETE_POST = 'DELETE_POST'
}

registerEnumType(UserPermissions, {
name: 'UserPermissions'
});
export * from './authFn';
export * from './authFnClass';
export * from './Context';
export * from './contextHelper';
export * from './db';
export * from './main';
export * from './Post';
export * from './token';
export * from './User';
export * from './UserPermissions';
export * from './UserRoles';
24 changes: 14 additions & 10 deletions examples/simple/token.ts → examples/__commons/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,21 @@
* SOFTWARE.
*/

import jwt from 'jsonwebtoken';
import type { User } from './User';
import type { GraphQLSchema } from 'graphql';
import { ApolloServer } from 'apollo-server';
import { contextHelper } from './contextHelper';
import { TOKEN } from './token';

export type TokenPayload = Omit<User, 'secret'>;
export async function main(schema: GraphQLSchema) {
const server = new ApolloServer({ schema, context: contextHelper });

const SECRET = 'secret';
const serverInfo = await server.listen({
port: 8080,
host: '0.0.0.0'
});

export function sign(payload: TokenPayload) {
return jwt.sign(payload, SECRET);
}

export function verify(token: string) {
return jwt.verify(token, SECRET) as TokenPayload;
// eslint-disable-next-line no-console
console.info(`TOKEN: ${TOKEN}`);
// eslint-disable-next-line no-console
console.info(`Server started at ${serverInfo.url}`);
}
7 changes: 5 additions & 2 deletions examples/typegraphql/token.ts → examples/__commons/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,18 @@

import jwt from 'jsonwebtoken';
import type { User } from './User';
import { SUPER_USER } from './db';

export type TokenPayload = Omit<User, 'secret'>;

const SECRET = 'secret';

export function sign(payload: TokenPayload) {
export function signToken(payload: TokenPayload) {
return jwt.sign(payload, SECRET);
}

export function verify(token: string) {
export function verifyToken(token: string) {
return jwt.verify(token, SECRET) as TokenPayload;
}

export const TOKEN = signToken(SUPER_USER);
48 changes: 0 additions & 48 deletions examples/simple/data.ts

This file was deleted.

32 changes: 9 additions & 23 deletions examples/simple/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@
*/

import { makeExecutableSchema } from '@graphql-tools/schema';
import { ApolloServer } from 'apollo-server';
import { buildAuthDirective } from '../../src';
import {
authFn,
Context,
UserRoles,
UserPermissions,
main
} from '../__commons';
import { typeDefs } from './typeDefs';
import { resolvers } from './resolvers';
import { authFn } from './authFn';
import { contextHelper } from './contextHelper';
import { TOKEN } from './data';
import type { Context } from './Context';
import type { UserRoles } from './UserRoles';
import type { UserPermissions } from './UserPermissions';

// Build auth directive
const authDirective = buildAuthDirective<Context, UserRoles, UserPermissions>({
Expand All @@ -49,19 +49,5 @@ let schema = makeExecutableSchema({
});
schema = authDirective.transformer(schema);

// Build server
const server = new ApolloServer({ schema, context: contextHelper });

// Start server
async function main() {
const serverInfo = await server.listen({
port: 8080,
host: '0.0.0.0'
});

// eslint-disable-next-line no-console
console.info(`TOKEN: ${TOKEN}`);
// eslint-disable-next-line no-console
console.info(`Server started at ${serverInfo.url}`);
}
main();
// Main
main(schema);
Loading

0 comments on commit a1663c9

Please sign in to comment.