Skip to content

Latest commit

 

History

History
180 lines (158 loc) · 3.76 KB

federation.md

File metadata and controls

180 lines (158 loc) · 3.76 KB

Federation

The auth plugin also works in the Mercurius gateway. Federated services must implement the auth directive within their schemas but do not need the auth plugin registered. When registering the auth plugin on the gateway server, it will need to load the same auth directive that the federated services both use.

'use strict'

const Fastify = require('fastify')
const mercurius = require('mercurius')
const mercuriusAuth = require('mercurius-auth')

async function createService (schema, resolvers = {}) {
  const service = Fastify()
  service.register(mercurius, {
    schema,
    resolvers,
    federationMetadata: true
  })
  await service.listen({ port: 0 })
  return [service, service.server.address().port]
}

const users = {
  u1: {
    id: 'u1',
    name: 'John'
  },
  u2: {
    id: 'u2',
    name: 'Jane'
  }
}

const posts = {
  p1: {
    pid: 'p1',
    title: 'Post 1',
    content: 'Content 1',
    authorId: 'u1'
  },
  p2: {
    pid: 'p2',
    title: 'Post 2',
    content: 'Content 2',
    authorId: 'u2'
  },
  p3: {
    pid: 'p3',
    title: 'Post 3',
    content: 'Content 3',
    authorId: 'u1'
  },
  p4: {
    pid: 'p4',
    title: 'Post 4',
    content: 'Content 4',
    authorId: 'u1'
  }
}

async function start (authOpts) {
  // User service
  const userServiceSchema = `
  directive @auth(
    requires: Role = ADMIN,
  ) on OBJECT | FIELD_DEFINITION

  enum Role {
    ADMIN
    REVIEWER
    USER
    UNKNOWN
  }

  directive @notUsed on OBJECT | FIELD_DEFINITION

  type Query @extends {
    me: User
  }

  type User @key(fields: "id") {
    id: ID! @notUsed
    name: String @auth(requires: ADMIN) @notUsed
  }`
  const userServiceResolvers = {
    Query: {
      me: (root, args, context, info) => {
        return users.u1
      }
    },
    User: {
      __resolveReference: (user, args, context, info) => {
        return users[user.id]
      }
    }
  }
  const [, userServicePort] = await createService(userServiceSchema, userServiceResolvers)

  // Post service
  const postServiceSchema = `
  directive @auth(
    requires: Role = ADMIN,
  ) on OBJECT | FIELD_DEFINITION

  enum Role {
    ADMIN
    REVIEWER
    USER
    UNKNOWN
  }

  directive @notUsed on OBJECT | FIELD_DEFINITION

  type Post @key(fields: "pid") {
    pid: ID!
    author: User @auth(requires: ADMIN)
  }

  extend type Query {
    topPosts(count: Int): [Post] @auth(requires: ADMIN)
  }

  type User @key(fields: "id") @extends {
    id: ID! @external
    topPosts(count: Int!): [Post] @notUsed
  }`
  const postServiceResolvers = {
    Post: {
      __resolveReference: (post, args, context, info) => {
        return posts[post.pid]
      },
      author: (post, args, context, info) => {
        return {
          __typename: 'User',
          id: post.authorId
        }
      }
    },
    User: {
      topPosts: (user, { count }, context, info) => {
        return Object.values(posts).filter(p => p.authorId === user.id).slice(0, count)
      }
    },
    Query: {
      topPosts: (root, { count = 2 }) => Object.values(posts).slice(0, count)
    }
  }
  const [, postServicePort] = await createService(postServiceSchema, postServiceResolvers)

  const gateway = Fastify()

  gateway.register(mercurius, {
    gateway: {
      services: [{
        name: 'user',
        url: `http://127.0.0.1:${userServicePort}/graphql`
      }, {
        name: 'post',
        url: `http://127.0.0.1:${postServicePort}/graphql`
      }]
    }
  })

  gateway.register(mercuriusAuth, authOpts || {
    authContext (context) {
      return {
        identity: context.reply.request.headers['x-user']
      }
    },
    async applyPolicy (authDirectiveAST, parent, args, context, info) {
      return context.auth.identity === 'admin'
    },
    authDirective: 'auth'
  })

  await gateway.listen({ port: 3000 })
}

start()