NestJSでGraphQLをGuardする

NestJSでGraphQLのQueryまたはMutationをGuardする方法を説明します。
基本的には、[NestJSの公式](https://docs.nestjs.com/guards)に掲載されていますが、GraphQLをGuardするには少し工夫が必要です。

# 環境

* NestJS 6.0

# ゴール
NestJSでGraphQLをGuardする方法はいくつかあります。
今回は、ユーザのロールによってGuardする方法を採用します。
以下のような感じでGaurdします。@nestjs/passportのjwtでGuardした上で、ユーザのロールが2となっているもののみアクセスできるmutationを実装します(queryでも同じです)

```typescript:sample.ts
    @Mutation(returns => SaveSiteConfigResult)
    @UseGuards(JwtAuthGuard)
    @Roles(2)
    savesiteconfig(@Args('savesiteconfig') _saveSiteConfig: SaveSiteConfig): Promise<SaveSiteConfigResult> {
```

# Gaurdの実装
まずは、Guardの実装です。
詳しくはコメントに記載しましたが、ざっくりとは以下の通りです。

* SetMetadataでGuard定義されているロール値を取得する
* ロール値が取得できない場合は、Guard定義されていないと言うことになるので、trueを返す
* authorizationヘッダーからJWTトークンを取得を取得し公開鍵で検証する
* JWTトークンのpayloadに設定されているロール値がGuard定義されているロール値に該当するかをチェックする

```typescript:rolesguard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { GqlExecutionContext } from '@nestjs/graphql';
import * as jwt from 'jsonwebtoken';
import { IJwtPayload } from '../auth/interfaces/ijwt-payload.interface';

@Injectable()
export class RolesGuard implements CanActivate {
    constructor(
        private readonly reflector: Reflector
    ) {}

    canActivate(context: ExecutionContext): Promise<boolean> {
        return new Promise((resolve, reject) => {
            // SetMetadataのrolesに定義されたロール値を取得する
            const roles = this.reflector.get<number[]>('roles', context.getHandler());
            // ロール値が取得できなければtrue（つまり、ガードされていない)
            if (!roles) {
                resolve(true);
            }
            else {
                // contextからGraphQLのctxを取得する
                // GraphQLのforRootで「context: ({ req }) => ({ req }),」の定義が必要
                const ctx = GqlExecutionContext.create(context);
                const req = ctx.getContext().req;
                // authorizationヘッダーからJWTトークンを取得する
                const authToken = req.headers.authorization;
                const jwtToken = authToken.substring(authToken.indexOf(' ') + 1);
                // JWTトークンを公開鍵で検証する
                jwt.verify(jwtToken, Buffer.from(process.env.RSA_PUBLIC_KEY, 'base64'), (err, payload: IJwtPayload) => {
                    // JWTトークンの検証ができなかったらエラー
                    if (err) {
                        reject(err);
                    }
                    else {
                        // ユーザのロールがGuardで設定されているロールに合致するかを検証する
                        resolve(roles.includes(payload.role));
                    }
                });
            }
        });
    }
}
```

GraphQLを使う時のポイントは、上記のコードの以下の部分です(これ以外は公式に記載のもので実現可能です)

```typescript
                // contextからGraphQLのctxを取得する
                // GraphQLのforRootで「context: ({ req }) => ({ req }),」の定義が必要
                const ctx = GqlExecutionContext.create(context);
                const req = ctx.getContext().req;
```

# Decoratorの定義
上のものでGuardするには、`@SetMetadata('roles', 1)`とする必要があります。
これでもいいのですが、もう少し分かりやすくするように、Decoratorを定義します。これで、`@Roles(1)`と書くことができます。

```typescript:roles.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: number[]) => SetMetadata('roles', roles);
```

# グローバルなGuardとして定義
app.modules.tsで作成したGuardをグローバルGuardとして定義します。
これで、全てのGraphQLで作成したGuardがコールされるようになります。

```typescript:app.modules.ts
import { APP_GUARD } from '@nestjs/core';
import { RolesGuard } from './guards/rolesguard';

@Module({
  providers: [
    { provide: APP_GUARD, useClass: RolesGuard }
  ],
})
```