Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cloudflare-access): Handle Access organization does not exist and Access not available cases #898

Merged
merged 1 commit into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/heavy-bugs-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hono/cloudflare-access': minor
---

Handle Access organization does not exist and Access not available cases
5 changes: 3 additions & 2 deletions packages/cloudflare-access/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,13 @@ const app = new Hono<{ Variables: myVariables & CloudflareAccessVariables }>()
app.use('*', cloudflareAccess('my-access-team-name'))
app.get('/', (c) => {
const payload = c.get('accessPayload')

return c.text(`You just authenticated with the email ${payload.email}`)
})

export default app
```


## Errors throw by the middleware

| Error | HTTP Code |
Expand All @@ -55,6 +54,8 @@ export default app
| Authentication error: Token is expired | 401 |
| Authentication error: Expected team name {your-team-name}, but received ${different-team-signed-token} | 401 |
| Authentication error: Invalid Token | 401 |
| Authentication error: The Access Organization 'my-team-name' does not exist | 500 |
| Authentication error: Received unexpected HTTP code 500 from Cloudflare Access | 500 |

## Author

Expand Down
53 changes: 46 additions & 7 deletions packages/cloudflare-access/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,30 @@ describe('Cloudflare Access middleware', async () => {
const keyPair2 = await generateJWTKeyPair();
const keyPair3 = await generateJWTKeyPair();

vi.stubGlobal('fetch', async () => {
return Response.json({
keys: [
publicKeyToJWK(keyPair1.publicKey),
publicKeyToJWK(keyPair2.publicKey),
],
beforeEach(() => {
vi.clearAllMocks();
vi.stubGlobal('fetch', async () => {
return Response.json({
keys: [
publicKeyToJWK(keyPair1.publicKey),
publicKeyToJWK(keyPair2.publicKey),
],
})
})
})
});

const app = new Hono()

app.use('/*', cloudflareAccess('my-cool-team-name'))
app.get('/hello-behind-access', (c) => c.text('foo'))
app.get('/access-payload', (c) => c.json(c.get('accessPayload')))

app.onError((err, c) => {
return c.json({
err: err.toString(),
}, 500)
})

it('Should be throw Missing bearer token when nothing is sent', async () => {
const res = await app.request('http://localhost/hello-behind-access')
expect(res).not.toBeNull()
Expand Down Expand Up @@ -248,4 +257,34 @@ describe('Cloudflare Access middleware', async () => {
"exp":expect.any(Number)
})
})

it('Should throw an error, if the access organization does not exist', async () => {
vi.stubGlobal('fetch', async () => {
return Response.json({success: false}, {status: 404})
})

const res = await app.request('http://localhost/hello-behind-access', {
headers: {
'cf-access-jwt-assertion': 'asdads'
}
})
expect(res).not.toBeNull()
expect(res.status).toBe(500)
expect(await res.json()).toEqual({"err":"Error: Authentication error: The Access Organization 'my-cool-team-name' does not exist"})
})

it('Should throw an error, if the access certs url is unavailable', async () => {
vi.stubGlobal('fetch', async () => {
return Response.json({success: false}, {status: 500})
})

const res = await app.request('http://localhost/hello-behind-access', {
headers: {
'cf-access-jwt-assertion': 'asdads'
}
})
expect(res).not.toBeNull()
expect(res.status).toBe(500)
expect(await res.json()).toEqual({"err":"Error: Authentication error: Received unexpected HTTP code 500 from Cloudflare Access"})
})
})
9 changes: 9 additions & 0 deletions packages/cloudflare-access/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createMiddleware } from 'hono/factory'
import { Context } from 'hono'
import { HTTPException } from 'hono/http-exception'

export type CloudflareAccessPayload = {
aud: string[],
Expand Down Expand Up @@ -89,6 +90,14 @@ async function getPublicKeys(accessTeamName: string) {
},
})

if (!result.ok) {
if (result.status === 404) {
throw new HTTPException(500, { message: `Authentication error: The Access Organization '${accessTeamName}' does not exist` })
}

throw new HTTPException(500, { message: `Authentication error: Received unexpected HTTP code ${result.status} from Cloudflare Access` })
}

const data: any = await result.json()

// Because we keep CryptoKey's in memory between requests, we need to make sure they are refreshed once in a while
Expand Down
Loading