Skip to content

Commit

Permalink
integrate mercurius auth
Browse files Browse the repository at this point in the history
  • Loading branch information
Eomm committed Jan 21, 2023
1 parent 1974edd commit 7ec5f23
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 15 deletions.
106 changes: 102 additions & 4 deletions bonus/graphql-dynamic-queries/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ async function buildApp () {
},
Grid: {
resolveType (obj) {
if (obj.adminColumn) {
if (Object.hasOwnProperty.call(obj, 'adminColumn')) {
return 'AdminGrid'
}
if (obj.moderatorColumn) {
if (Object.hasOwnProperty.call(obj, 'moderatorColumn')) {
return 'ModeratorGrid'
}
return 'UserGrid'
Expand Down Expand Up @@ -214,14 +214,112 @@ async function doQuery (app, userType, query) {
}
```

For the sake of simplicity, we will not write all the tests here,
but you can find the complete code in the [GitHub repository](https://github.com/Eomm/fastify-discord-bot-demo/tree/HEAD/bonus/graphql-dynamic-queries).
For the sake of simplicity, we will not list all the tests here,
but you can find the complete complete source code in the [GitHub repository](https://github.com/Eomm/fastify-discord-bot-demo/tree/HEAD/bonus/graphql-dynamic-queries).

Running the tests with `node test.js` will fail because we have not implemented the business logic yet.
So, let's start writing the code!

## Implementing the server-side Dynamic Queries

To implement the business logic, there are these main steps:

1. Retrieve the user's role from the request headers
2. Manage the GraphQL query to return the correct type based on the user's role

Let's solve the first point.

### How to retrieve the user's role

We can implement the user role retrieval by installing the [`mercurius-auth`](https://github.com/mercurius-js/auth) plugin.

```bash
npm i mercurius-auth@3
```

Then, we can register the plugin in our `app.js` file.
To understand what the plugin does, you can read its documentation.

In the following example, we will compare the `x-user-type` HTTP header with the `@auth` directive we are going to define in the schema.
If they match, the user will be authorized to access the field and run the query.

Let's start by defining the `@auth` directive in the schema:

```graphql
directive @auth(
role: String
) on OBJECT

# ..same as before

type AdminGrid @auth(role: "admin") {
totalRevenue: Float
}

type ModeratorGrid @auth(role: "moderator") {
banHammer: Boolean
}

type UserGrid @auth(role: "user") {
basicColumn: String
}
```

Then, we can register the plugin in our `app.js` file and implement a simple `searchData` resolver:

```js
async function buildApp () {
const app = Fastify() // the same as before

await app.register(GQL, {
schema,
resolvers: {
Query: {
searchData: async function (root, args, context, info) {
switch (context.auth.identity) {
case 'admin':
return { totalRevenue: 42 }

case 'moderator':
return { banHammer: true }

default:
return { basicColumn: 'basic' }
}
}
},
},
Grid: {} // the same as before
})

app.register(require('mercurius-auth'), {
authContext (context) {
return {
identity: context.reply.request.headers['x-user-type']
}
},
async applyPolicy (policy, parent, args, context, info) {
const role = policy.arguments[0].value.value
app.log.info('Applying policy %s on user %s', role, context.auth.identity)

// we compare the schema role directive with the user role
return context.auth.identity === role
},
authDirective: 'auth'
})

return app
}
```

Now, the user should be able to retrieve the `totalRevenue` field only if the `x-user-type` header is set to `admin`.

Nevertheless, we can't run the tests yet because we have not implemented the second point.

### Implementing the Dynamic Queries





## Summary
Expand Down
35 changes: 30 additions & 5 deletions bonus/graphql-dynamic-queries/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,31 @@ if (process.argv[2] === 'run') {
}

async function buildApp () {
const app = Fastify()
const app = Fastify({ logger: !true })

await app.register(GQL, {
schema,
resolvers: {
Query: {
searchData: async function (root, args, context, info) {
// TODO: implement the business logic
return {}
switch (context.auth.identity) {
case 'admin':
return { totalRevenue: 42 }

case 'moderator':
return { banHammer: true }

default:
return { basicColumn: 'basic' }
}
}
},
Grid: {
resolveType (obj) {
if (obj.adminColumn) {
if (Object.hasOwnProperty.call(obj, 'adminColumn')) {
return 'AdminGrid'
}
if (obj.moderatorColumn) {
if (Object.hasOwnProperty.call(obj, 'moderatorColumn')) {
return 'ModeratorGrid'
}
return 'UserGrid'
Expand All @@ -35,6 +43,23 @@ async function buildApp () {
}
})

app.register(require('mercurius-auth'), {
authContext (context) {
// you can validate the headers here
return {
identity: context.reply.request.headers['x-user-type']
}
},
async applyPolicy (policy, parent, args, context, info) {
const role = policy.arguments[0].value.value
app.log.info('Applying policy %s on user %s', role, context.auth.identity)

// we compare the schema role directive with the user role
return context.auth.identity === role
},
authDirective: 'auth'
})

return app
}

Expand Down
7 changes: 3 additions & 4 deletions bonus/graphql-dynamic-queries/gql-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@ type Query {
union Grid = AdminGrid | ModeratorGrid | UserGrid
type AdminGrid {
type AdminGrid @auth(role: "admin") {
totalRevenue: Float
}
type ModeratorGrid {
type ModeratorGrid @auth(role: "moderator") {
banHammer: Boolean
}
type UserGrid {
type UserGrid @auth(role: "user") {
basicColumn: String
}
`
17 changes: 15 additions & 2 deletions bonus/graphql-dynamic-queries/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ const { test } = require('tap')

const buildApp = require('./app')

test('Should not access', { skip: 'bug' }, async t => {
const app = await buildApp()
const res = await doQuery(app, 'none', `
query {
searchData {
... on AdminGrid {
totalRevenue
}
}
}
`)

t.equal(res.data.errors[0].message, 'Failed auth policy check on totalRevenue')
})

test('A user with the `admin` role should be able to retrieve the `totalRevenue` field without inline fragments', async t => {
const app = await buildApp()
const res = await doQuery(app, 'admin', `
Expand All @@ -14,8 +29,6 @@ test('A user with the `admin` role should be able to retrieve the `totalRevenue`
}
`)

console.log(JSON.stringify(res, null, 2))

t.same(res.data.searchData, { totalRevenue: 42 })
})

Expand Down

0 comments on commit 7ec5f23

Please sign in to comment.