Skip to content

Commit

Permalink
test: add integration tests for auth0 (#284)
Browse files Browse the repository at this point in the history
* test: add integration tests for auth0

* test: split unit and integration test commands

* docs: document dev environment env vars

* test: use native fetch

* chore: setup CI env vars

* chore: run integration tests in a separate CI job

* chore: rename CI job build-and-test --> build

* chore: run all ci tests into same build job

* test: initialize fastify-auth0-verify inline

* refactor: configure eslint test globals

* test: extract invalid auth token as named var

* test: user inject shortcuts

* test: unwrap protect route registration

* test: cleanup server build
  • Loading branch information
toomuchdesign authored Apr 20, 2023
1 parent 65514d0 commit 52e4c6a
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 64 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
AUTH0_DOMAIN=
AUTH0_CLIENT_ID=
AUTH0_CLIENT_SECRET=
AUTH0_API_AUDIENCE=
12 changes: 11 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
module.exports = {
extends: ['standard', 'plugin:prettier/recommended']
extends: ['standard', 'plugin:prettier/recommended'],
overrides: [
{
extends: ['standard', 'plugin:prettier/recommended'],
files: ['test/**'],
plugins: ['jest'],
env: {
'jest/globals': true
}
}
]
}
14 changes: 8 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
name: ci
on:
push:
branches:
branches:
- master
pull_request:
jobs:
build:
runs-on: ubuntu-latest
env:
AUTH0_DOMAIN: ${{ secrets.AUTH0_DOMAIN }}
AUTH0_CLIENT_ID: ${{ secrets.AUTH0_CLIENT_ID }}
AUTH0_CLIENT_SECRET: ${{ secrets.AUTH0_CLIENT_SECRET }}
AUTH0_API_AUDIENCE: ${{ secrets.AUTH0_API_AUDIENCE }}
strategy:
matrix:
node:
- 14
- 16
- 18
node: [14, 16, 18]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: npm install
- run: npm run test:ci
- run: npm run test:integration
automerge:
needs: build
runs-on: ubuntu-latest
Expand All @@ -28,4 +31,3 @@ jobs:
contents: write
steps:
- uses: fastify/github-action-merge-dependabot@v3

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ coverage
node_modules
.vscode
package-lock.json
.env
51 changes: 20 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,16 @@ This decorator can be used as `preValidation` hook to add authenticate to your r
Example:

```js
const server = require('fastify')()
const fastify = require('fastify')
const server = fastify()

server.register(require('fastify-auth0-verify'), {
await server.register(require('fastify-auth0-verify'), {
domain: '<auth0 auth domain>',
audience: '<auth0 app audience>'
})

server.register(function (instance, _options, done) {
instance.get('/verify', {
handler: function (request, reply) {
reply.send(request.user)
},
preValidation: instance.authenticate
})

done()
server.get('/verify', { preValidation: server.authenticate }, (request, reply) => {
reply.send(request.user)
})

server.listen(0, err => {
Expand All @@ -66,34 +60,16 @@ server.listen(0, err => {
You can configure there to be more than one Auth0 API audiences:

```js
const server = require('fastify')()

server.register(require('fastify-auth0-verify'), {
await server.register(require('fastify-auth0-verify'), {
domain: '<auth0 auth domain>',
audience: ['<auth0 app audience>', '<auth0 admin audience>']
})

server.register(function (instance, _options, done) {
instance.get('/verify', {
handler: function (request, reply) {
reply.send(request.user)
},
preValidation: instance.authenticate
})
done()
})

server.listen(APP_PORT, err => {
if (err) {
throw err
}
})
```

You can include [@fastify/jwt verify](https://github.com/fastify/fastify-jwt#verify) options:

```js
server.register(require('fastify-auth0-verify'), {
await server.register(require('fastify-auth0-verify'), {
domain: '<auth0 auth domain>',
audience: ['<auth0 app audience>', '<auth0 admin audience>'],
cache: true, // @fastify/jwt cache
Expand All @@ -106,6 +82,19 @@ server.register(require('fastify-auth0-verify'), {

See [CONTRIBUTING.md](./CONTRIBUTING.md)

## Developer notes

### Tests

Tests are currently split into **unit** and **integration**. Integration tests needs the following environment variables:

| Env var | |
| --------------------- | ----------------------------------------------------------- |
| `AUTH0_DOMAIN` | Auth0 dashboard -> application -> Settings -> Domain |
| `AUTH0_CLIENT_ID` | Auth0 dashboard -> application -> Settings -> Client ID |
| `AUTH0_CLIENT_SECRET` | Auth0 dashboard -> application -> Settings -> Client Secret |
| `AUTH0_API_AUDIENCE` | Auth0 application identifier |

## License

Copyright NearForm Ltd. Licensed under the [Apache-2.0 license](http://www.apache.org/licenses/LICENSE-2.0).
14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,13 @@
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"test": "jest test --coverage --coverageReporters=html --coverageReporters=text && tsd",
"test:ci": "npm run lint && jest --coverage --ci --coverageReporters=json && tsd",
"lint": "eslint index.js test.js",
"test": "jest ./test/test.js --coverage",
"test:ci": "npm run lint && npm run test -- --ci --coverageReporters=json && npm run test:types",
"test:integration": "jest ./test/integration.test.js",
"test:types": "tsd",
"lint": "eslint index.js test",
"prepublishOnly": "npm run test:ci",
"postpublish": "git push origin && git push origin -f --tags",
"tsd": "tsd"
"postpublish": "git push origin && git push origin -f --tags"
},
"dependencies": {
"@fastify/cookie": "^8.0.0",
Expand All @@ -53,10 +54,13 @@
"node-fetch": "^2.6.1"
},
"devDependencies": {
"cross-fetch": "^3.1.5",
"dotenv": "^16.0.3",
"eslint": "^8.23.0",
"eslint-config-prettier": "^8.5.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-n": "^15.2.5",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-promise": "^6.0.1",
Expand Down
101 changes: 101 additions & 0 deletions test/integration.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
require('dotenv').config()
const Fastify = require('fastify')
const fetch = require('cross-fetch')

if (
!process.env.AUTH0_DOMAIN ||
!process.env.AUTH0_CLIENT_ID ||
!process.env.AUTH0_CLIENT_SECRET ||
!process.env.AUTH0_API_AUDIENCE
) {
throw new Error('Integration tests needs a set of environment variables to be set')
}

async function buildServer() {
const server = Fastify()

// Setup fastify-auth0-verify
await server.register(require('../'), {
domain: process.env.AUTH0_DOMAIN,
secret: process.env.AUTH0_CLIENT_SECRET
})

// Setup auth0 protected route
server.get('/protected', { preValidation: server.authenticate }, (req, reply) => {
reply.send({ route: 'Protected route' })
})

// Setup auth0 public route
server.get('/public', (req, reply) => {
reply.send({ route: 'Public route' })
})

await server.listen({ port: 0 })
return server
}

describe('Authentication against Auth0', () => {
let server

beforeAll(async function () {
server = await buildServer()
})

afterAll(() => server.close())

it('Protects protected routes', async () => {
const publicResponse = await server.inject('/public')
expect(publicResponse.statusCode).toEqual(200)
expect(publicResponse.json()).toEqual({ route: 'Public route' })

const protectedResponseWithoutAuthHeader = await server.inject('/protected')
expect(protectedResponseWithoutAuthHeader.statusCode).toEqual(401)
expect(protectedResponseWithoutAuthHeader.json()).toEqual({
error: 'Unauthorized',
message: 'Missing Authorization HTTP header.',
statusCode: 401
})

const invalidAuthToken =
'Bearer eyuhbGcpOpuSUzI1NpIsInR5cCI6IkpOVCIsImtpZCI6IkNPTFuKTFumQ2tZeURuSE1aamNUap7.eyupc3MpOpuodHRwczovL2Rldp0zZTh1d2poYjF4MnZqY2U4LnVzLmF1dGgwLmNvbS8pLCuzdWIpOpu6RUIzaEM0VUhrV3hjQ3uOQ2d2RzZlNkdmQOuZRkRrYUBjbGllbnRzIpwpYOVkIjopSldULOZlcmlmeS10ZON0IpwpaWF0IjoxNjgxODM0NjYxLCuleHApOjE2ODE5MjEwNjEsImF6cCI6InpFQjNoQzRVSGtOeGNDcldDZ3ZHNmU2R2ZBcllGRGthIpwpZ3R5IjopY2xpZW50LWNyZWRlbnRpYWxzIn0.MdxfrZF5EB9ByFABzEdBGENjc0d9eoML_TDKftxrg2352VqvoD3dnxxn1rpIAqjcpWSI4BKvf3hNlcDwoOyhT2kmHxDgcNv22dG9ZAY5vEkm6csDtUeBbVuqdjx30zwbcYDf_pZ4euuCLE-ysOI8WpvYvsOGTjGBpjdFZAyGqPIL0RTUrtwh6lrVzGGl9oKPQgq-ZuFOtUQOO7w4jItHZ40SpvzPYfrLY4P6DfYbxcwSTc9OjE86vvUON0EunTdjhkyml-c28svnxu5WFvfsuUT56Cbw1AYKogg12-OHLYuyS2VQblxCQfIogaDZPTY114M8PCb0ZBL19jNO6oxzA'
const protectedResponseWithInvalidAuthHeader = await server.inject({
method: 'GET',
url: '/protected',
headers: {
Authorization: invalidAuthToken
}
})
expect(protectedResponseWithInvalidAuthHeader.statusCode).toEqual(401)
expect(protectedResponseWithInvalidAuthHeader.json()).toEqual({
code: 'FST_JWT_AUTHORIZATION_TOKEN_INVALID',
error: 'Unauthorized',
message: 'Authorization token is invalid: The token header is not a valid base64url serialized JSON.',
statusCode: 401
})
})

it('Returns protected route when expected auth header is provided', async () => {
const authResponse = await fetch(`https://${process.env.AUTH0_DOMAIN}/oauth/token`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
client_id: process.env.AUTH0_CLIENT_ID,
client_secret: process.env.AUTH0_CLIENT_SECRET,
audience: process.env.AUTH0_API_AUDIENCE,
grant_type: 'client_credentials'
})
})

const { token_type: tokenType, access_token: accessToken } = await authResponse.json()

const protectedResponse = await server.inject({
method: 'GET',
url: '/protected',
headers: {
Authorization: `${tokenType} ${accessToken}`
}
})
expect(protectedResponse.statusCode).toEqual(200)
expect(protectedResponse.json()).toEqual({ route: 'Protected route' })
})
})
File renamed without changes.
File renamed without changes.
File renamed without changes.
31 changes: 10 additions & 21 deletions test.js → test/test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* global describe, beforeEach, afterEach, beforeAll, afterAll, it, expect, jest */

'use strict'

const { readFileSync } = require('fs')
Expand Down Expand Up @@ -187,27 +185,18 @@ const tokens = {

async function buildServer(options) {
const server = fastify()
await server.register(require('../'), options)
await server.register(require('@fastify/cookie'))

server.register(require('.'), options)
server.register(require('@fastify/cookie'))
server.register(function (instance, options, done) {
instance.get('/verify', {
async handler(request) {
return request.user
},
preValidation: instance.authenticate
})

instance.get('/decode', {
async handler(request) {
return {
regular: await request.jwtDecode(),
full: await request.jwtDecode({ decode: { complete: true } })
}
}
})
server.get('/verify', { preValidation: server.authenticate }, req => {
return req.user
})

done()
server.get('/decode', async req => {
return {
regular: await req.jwtDecode(),
full: await req.jwtDecode({ decode: { complete: true } })
}
})

await server.listen({ port: 0 })
Expand Down

0 comments on commit 52e4c6a

Please sign in to comment.