From b368d28e8031bb650be25c7fbf028357c5771e46 Mon Sep 17 00:00:00 2001 From: Gabriel Massadas Date: Fri, 20 Dec 2024 18:51:08 +0000 Subject: [PATCH] Handle Access organization does not exist and Access not available cases --- .changeset/heavy-bugs-perform.md | 5 ++ packages/cloudflare-access/README.md | 5 +- packages/cloudflare-access/src/index.test.ts | 53 +++++++++++++++++--- packages/cloudflare-access/src/index.ts | 9 ++++ 4 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 .changeset/heavy-bugs-perform.md diff --git a/.changeset/heavy-bugs-perform.md b/.changeset/heavy-bugs-perform.md new file mode 100644 index 000000000..b8ec5d0a7 --- /dev/null +++ b/.changeset/heavy-bugs-perform.md @@ -0,0 +1,5 @@ +--- +'@hono/cloudflare-access': minor +--- + +Handle Access organization does not exist and Access not available cases diff --git a/packages/cloudflare-access/README.md b/packages/cloudflare-access/README.md index e1d4d3abf..533786a64 100644 --- a/packages/cloudflare-access/README.md +++ b/packages/cloudflare-access/README.md @@ -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 | @@ -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 diff --git a/packages/cloudflare-access/src/index.test.ts b/packages/cloudflare-access/src/index.test.ts index 851e6adc7..542f801de 100644 --- a/packages/cloudflare-access/src/index.test.ts +++ b/packages/cloudflare-access/src/index.test.ts @@ -116,14 +116,17 @@ 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() @@ -131,6 +134,12 @@ describe('Cloudflare Access middleware', async () => { 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() @@ -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"}) + }) }) diff --git a/packages/cloudflare-access/src/index.ts b/packages/cloudflare-access/src/index.ts index cd69ff3ce..f7718d00d 100644 --- a/packages/cloudflare-access/src/index.ts +++ b/packages/cloudflare-access/src/index.ts @@ -1,5 +1,6 @@ import { createMiddleware } from 'hono/factory' import { Context } from 'hono' +import { HTTPException } from 'hono/http-exception' export type CloudflareAccessPayload = { aud: string[], @@ -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