きみはねこみたいなにゃんにゃんなまほう

ねこもスクリプトをかくなり

graphql-tools の makeExecutableSchema で Directive を定義して簡易認可を実装してみる

Schema directives | GraphQL Tools によると graphql-tools の makeExecutableSchema で Directive を実装できるようなので 簡易的な認可ロジックを実装してみます。

あくまで Directive のハンズオンなので認可ロジックはとても簡単なものです。

認証情報は context 経由で処理の部分に渡すことにします。

「タイトルは自由に取得できるけど本文は許された人にしか閲覧できないドキュメント」を取得するためのクエリを定義していきます。

import { graphql } from 'graphql'
import { makeExecutableSchema } from 'graphql-tools'

const typeDefs = `
  directive @auth(permitted: [String!]) on FIELD

  type Document {
    title: String!
    content: String! @auth(permitted: ["asa-taka"])
  }

  type Query {
    document: Document!
  }
`

// Directive の処理を定義する
const directiveResolvers = {

  // @auth Directive の処理を定義
  auth(next, src, args, ctx, info) {
    return next().then(res => {

      // コンテクスの認証ユーザが `permitted` に含まれていたら値を素通し
      if (args.permitted.includes(ctx.auth.name)) return res
      
      // 認証エラーを返してあげる (*˘꒳˘*) やさしい
      // info の中身は複雑なので適当に調べながら...
      const path = info.path
      throw new Error(`User not permitted: ${path.prev.key}.${path.key}`)
    })
  },
}

const schema = makeExecutableSchema({ typeDefs, directiveResolvers })

const query = `{
  document { title content }
}`

// データソースの準備
const rootValue = {
  document: { title: 'My Document', content: 'Awesome contents...' },
}

// コンテクスト経由で認証情報を渡してやる
const context = { auth: { name: 'asa-taka' } }

graphql(schema, query, rootValue, context).then(console.log, console.error)

これを実行すると

{ data: { document: { title: 'My Document', content: 'Awesome contents...' } } }

が得られます。ここから認証情報を

const context = { auth: { name: 'some-other-user' } }

と変更すると

{ errors: [ { Error: User not permitted: document.content

とエラーが返されます。ここから更に認証フィールドである content をクエリから除外して

const query = `{
  document { title }
}`

とすると

{ data: { document: { title: 'My Document' } } }

エラーなくデータが得られます。

ディレクティブの定義

基本的な使い方は Schema directives | GraphQL Tools に書いてある通りです。

まず GraphQLSchema 内で利用する Directive を定義します。 GraphQL Schema Language では以下のように Custom Directive を定義できます

directive @auth(permitted: [String!]) on FIELD

次に定義した Directive についての処理の実装を定義します。 graphql-tools の makeExecutableSchema では directiveResolver プロパティでディレクティブの処理を定義できます。

const directiveResolvers = {
  auth(next, src, args, ctx, info) {
    return next().then(res => {
      if (args.permitted.includes(ctx.auth.name)) return res
      const path = info.path
      throw new Error(`User not permitted: ${path.prev.key}.${path.key}`)
    })
  },
}

const schema = makeExecutableSchema({ typeDefs, directiveResolvers })

GraphQL の Directive も Apollo のおかげで意外と簡単に実装できますね。

これまで

...と、Directive 実装に苦労してきた経緯があるのですが、makeExecutableSchema はどう実装されているのか気になりますね。