From ed347a035f357b574aeb37e4eab1ad73d568e8c4 Mon Sep 17 00:00:00 2001 From: Marcel Overdijk Date: Tue, 3 Sep 2024 18:18:27 +0200 Subject: [PATCH 1/2] feat(bearer-auth): added custom response message options --- src/middleware/bearer-auth/index.test.ts | 297 +++++++++++++++++++++++ src/middleware/bearer-auth/index.ts | 96 ++++++-- 2 files changed, 372 insertions(+), 21 deletions(-) diff --git a/src/middleware/bearer-auth/index.test.ts b/src/middleware/bearer-auth/index.test.ts index 632d9c9af..8c017f48e 100644 --- a/src/middleware/bearer-auth/index.test.ts +++ b/src/middleware/bearer-auth/index.test.ts @@ -68,6 +68,163 @@ describe('Bearer Auth by Middleware', () => { handlerExecuted = true return c.text('auth-custom-header') }) + + app.use( + '/auth-custom-no-authentication-header-message-string/*', + bearerAuth({ + token, + noAuthenticationHeaderMessage: 'Custom no authentication header message as string', + }) + ) + app.get('/auth-custom-no-authentication-header-message-string/*', (c) => { + handlerExecuted = true + return c.text('auth') + }) + + app.use( + '/auth-custom-no-authentication-header-message-object/*', + bearerAuth({ + token, + noAuthenticationHeaderMessage: { + message: 'Custom no authentication header message as object', + }, + }) + ) + app.get('/auth-custom-no-authentication-header-message-object/*', (c) => { + handlerExecuted = true + return c.text('auth') + }) + + app.use( + '/auth-custom-no-authentication-header-message-function-string/*', + bearerAuth({ + token, + noAuthenticationHeaderMessage: () => + 'Custom no authentication header message as function string', + }) + ) + app.get('/auth-custom-no-authentication-header-message-function-string/*', (c) => { + handlerExecuted = true + return c.text('auth') + }) + + app.use( + '/auth-custom-no-authentication-header-message-function-object/*', + bearerAuth({ + token, + noAuthenticationHeaderMessage: () => ({ + message: 'Custom no authentication header message as function object', + }), + }) + ) + app.get('/auth-custom-no-authentication-header-message-function-object/*', (c) => { + handlerExecuted = true + return c.text('auth') + }) + + app.use( + '/auth-custom-invalid-authentication-header-message-string/*', + bearerAuth({ + token, + invalidAuthenticationHeaderMeasage: + 'Custom invalid authentication header message as string', + }) + ) + app.get('/auth-custom-invalid-authentication-header-message-string/*', (c) => { + handlerExecuted = true + return c.text('auth') + }) + + app.use( + '/auth-custom-invalid-authentication-header-message-object/*', + bearerAuth({ + token, + invalidAuthenticationHeaderMeasage: { + message: 'Custom invalid authentication header message as object', + }, + }) + ) + app.get('/auth-custom-invalid-authentication-header-message-object/*', (c) => { + handlerExecuted = true + return c.text('auth') + }) + + app.use( + '/auth-custom-invalid-authentication-header-message-function-string/*', + bearerAuth({ + token, + invalidAuthenticationHeaderMeasage: () => + 'Custom invalid authentication header message as function string', + }) + ) + app.get('/auth-custom-invalid-authentication-header-message-function-string/*', (c) => { + handlerExecuted = true + return c.text('auth') + }) + + app.use( + '/auth-custom-invalid-authentication-header-message-function-object/*', + bearerAuth({ + token, + invalidAuthenticationHeaderMeasage: () => ({ + message: 'Custom invalid authentication header message as function object', + }), + }) + ) + app.get('/auth-custom-invalid-authentication-header-message-function-object/*', (c) => { + handlerExecuted = true + return c.text('auth') + }) + + app.use( + '/auth-custom-invalid-token-message-string/*', + bearerAuth({ + token, + invalidTokenMessage: 'Custom invalid token message as string', + }) + ) + app.get('/auth-custom-invalid-token-message-string/*', (c) => { + handlerExecuted = true + return c.text('auth') + }) + + app.use( + '/auth-custom-invalid-token-message-object/*', + bearerAuth({ + token, + invalidTokenMessage: { message: 'Custom invalid token message as object' }, + }) + ) + app.get('/auth-custom-invalid-token-message-object/*', (c) => { + handlerExecuted = true + return c.text('auth') + }) + + app.use( + '/auth-custom-invalid-token-message-function-string/*', + bearerAuth({ + token, + invalidTokenMessage: () => 'Custom invalid token message as function string', + }) + ) + app.get('/auth-custom-invalid-token-message-function-string/*', (c) => { + handlerExecuted = true + return c.text('auth') + }) + + app.use( + '/auth-custom-invalid-token-message-function-object/*', + bearerAuth({ + token, + invalidTokenMessage: () => ({ + message: 'Custom invalid token message as function object', + }), + }) + ) + app.get('/auth-custom-invalid-token-message-function-object/*', (c) => { + handlerExecuted = true + return c.text('auth') + }) }) it('Should authorize', async () => { @@ -228,4 +385,144 @@ describe('Bearer Auth by Middleware', () => { expect(res.status).toBe(401) expect(await res.text()).toBe('Unauthorized') }) + + it('Should not authorize - custom no authorization header message as string', async () => { + const req = new Request('http://localhost/auth-custom-no-authentication-header-message-string') + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(401) + expect(handlerExecuted).toBeFalsy() + expect(await res.text()).toBe('Custom no authentication header message as string') + }) + + it('Should not authorize - custom no authorization header message as object', async () => { + const req = new Request('http://localhost/auth-custom-no-authentication-header-message-object') + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(401) + expect(res.headers.get('Content-Type')).toMatch('application/json; charset=UTF-8') + expect(handlerExecuted).toBeFalsy() + expect(await res.text()).toBe('{"message":"Custom no authentication header message as object"}') + }) + + it('Should not authorize - custom no authorization header message as function string', async () => { + const req = new Request( + 'http://localhost/auth-custom-no-authentication-header-message-function-string' + ) + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(401) + expect(handlerExecuted).toBeFalsy() + expect(await res.text()).toBe('Custom no authentication header message as function string') + }) + + it('Should not authorize - custom no authorization header message as function object', async () => { + const req = new Request( + 'http://localhost/auth-custom-no-authentication-header-message-function-object' + ) + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(401) + expect(res.headers.get('Content-Type')).toMatch('application/json; charset=UTF-8') + expect(handlerExecuted).toBeFalsy() + expect(await res.text()).toBe( + '{"message":"Custom no authentication header message as function object"}' + ) + }) + + it('Should not authorize - custom invalid authentication header message as string', async () => { + const req = new Request( + 'http://localhost/auth-custom-invalid-authentication-header-message-string' + ) + req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=') + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(400) + expect(handlerExecuted).toBeFalsy() + expect(await res.text()).toBe('Custom invalid authentication header message as string') + }) + + it('Should not authorize - custom invalid authentication header message as object', async () => { + const req = new Request( + 'http://localhost/auth-custom-invalid-authentication-header-message-object' + ) + req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=') + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(400) + expect(res.headers.get('Content-Type')).toMatch('application/json; charset=UTF-8') + expect(handlerExecuted).toBeFalsy() + expect(await res.text()).toBe( + '{"message":"Custom invalid authentication header message as object"}' + ) + }) + + it('Should not authorize - custom invalid authentication header message as function string', async () => { + const req = new Request( + 'http://localhost/auth-custom-invalid-authentication-header-message-function-string' + ) + req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=') + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(400) + expect(handlerExecuted).toBeFalsy() + expect(await res.text()).toBe('Custom invalid authentication header message as function string') + }) + + it('Should not authorize - custom invalid authentication header message as function object', async () => { + const req = new Request( + 'http://localhost/auth-custom-invalid-authentication-header-message-function-object' + ) + req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=') + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(400) + expect(res.headers.get('Content-Type')).toMatch('application/json; charset=UTF-8') + expect(handlerExecuted).toBeFalsy() + expect(await res.text()).toBe( + '{"message":"Custom invalid authentication header message as function object"}' + ) + }) + + it('Should not authorize - custom invalid token message as string', async () => { + const req = new Request('http://localhost/auth-custom-invalid-token-message-string') + req.headers.set('Authorization', 'Bearer invalid-token') + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(401) + expect(handlerExecuted).toBeFalsy() + expect(await res.text()).toBe('Custom invalid token message as string') + }) + + it('Should not authorize - custom invalid token message as object', async () => { + const req = new Request('http://localhost/auth-custom-invalid-token-message-object') + req.headers.set('Authorization', 'Bearer invalid-token') + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(401) + expect(res.headers.get('Content-Type')).toMatch('application/json; charset=UTF-8') + expect(handlerExecuted).toBeFalsy() + expect(await res.text()).toBe('{"message":"Custom invalid token message as object"}') + }) + + it('Should not authorize - custom invalid token message as function string', async () => { + const req = new Request('http://localhost/auth-custom-invalid-token-message-function-string') + req.headers.set('Authorization', 'Bearer invalid-token') + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(401) + expect(handlerExecuted).toBeFalsy() + expect(await res.text()).toBe('Custom invalid token message as function string') + }) + + it('Should not authorize - custom invalid token message as function object', async () => { + const req = new Request('http://localhost/auth-custom-invalid-token-message-function-object') + req.headers.set('Authorization', 'Bearer invalid-token') + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(401) + expect(res.headers.get('Content-Type')).toMatch('application/json; charset=UTF-8') + expect(handlerExecuted).toBeFalsy() + expect(await res.text()).toBe('{"message":"Custom invalid token message as function object"}') + }) }) diff --git a/src/middleware/bearer-auth/index.ts b/src/middleware/bearer-auth/index.ts index 6f6208ba4..15074aa3b 100644 --- a/src/middleware/bearer-auth/index.ts +++ b/src/middleware/bearer-auth/index.ts @@ -19,6 +19,9 @@ type BearerAuthOptions = prefix?: string headerName?: string hashFunction?: Function + noAuthenticationHeaderMessage?: string | object | Function + invalidAuthenticationHeaderMeasage?: string | object | Function + invalidTokenMessage?: string | object | Function } | { realm?: string @@ -26,6 +29,9 @@ type BearerAuthOptions = headerName?: string verifyToken: (token: string, c: Context) => boolean | Promise hashFunction?: Function + noAuthenticationHeaderMessage?: string | object | Function + invalidAuthenticationHeaderMeasage?: string | object | Function + invalidTokenMessage?: string | object | Function } /** @@ -40,6 +46,9 @@ type BearerAuthOptions = * @param {string} [options.prefix="Bearer"] - The prefix (or known as `schema`) for the Authorization header value. If set to the empty string, no prefix is expected. * @param {string} [options.headerName=Authorization] - The header name. * @param {Function} [options.hashFunction] - A function to handle hashing for safe comparison of authentication tokens. + * @param {string | object | Function} [options.noAuthenticationHeaderMessage="Unauthorized"] - The no authentication header message. + * @param {string | object | Function} [options.invalidAuthenticationHeaderMeasage="Bad Request"] - The invalid authentication header message. + * @param {string | object | Function} [options.invalidTokenMessage="Unauthorized"] - The invalid token message. * @returns {MiddlewareHandler} The middleware handler function. * @throws {Error} If neither "token" nor "verifyToken" options are provided. * @throws {HTTPException} If authentication fails, with 401 status code for missing or invalid token, or 400 status code for invalid request. @@ -67,6 +76,15 @@ export const bearerAuth = (options: BearerAuthOptions): MiddlewareHandler => { if (options.prefix === undefined) { options.prefix = PREFIX } + if (!options.noAuthenticationHeaderMessage) { + options.noAuthenticationHeaderMessage = 'Unauthorized' + } + if (!options.invalidAuthenticationHeaderMeasage) { + options.invalidAuthenticationHeaderMeasage = 'Bad Request' + } + if (!options.invalidTokenMessage) { + options.invalidTokenMessage = 'Unauthorized' + } const realm = options.realm?.replace(/"/g, '\\"') const prefixRegexStr = options.prefix === '' ? '' : `${options.prefix} +` @@ -77,24 +95,48 @@ export const bearerAuth = (options: BearerAuthOptions): MiddlewareHandler => { const headerToken = c.req.header(options.headerName || HEADER) if (!headerToken) { // No Authorization header - const res = new Response('Unauthorized', { - status: 401, - headers: { - 'WWW-Authenticate': `${wwwAuthenticatePrefix}realm="` + realm + '"', - }, - }) - throw new HTTPException(401, { res }) + const status = 401 + const headers = { + 'WWW-Authenticate': `${wwwAuthenticatePrefix}realm="` + realm + '"', + } + const responseMessage = + typeof options.noAuthenticationHeaderMessage === 'function' + ? await options.noAuthenticationHeaderMessage(c) + : options.noAuthenticationHeaderMessage + const res = + typeof responseMessage === 'string' + ? new Response(responseMessage, { status, headers }) + : new Response(JSON.stringify(responseMessage), { + status, + headers: { + ...headers, + 'content-type': 'application/json; charset=UTF-8', + }, + }) + throw new HTTPException(status, { res }) } else { const match = regexp.exec(headerToken) if (!match) { // Invalid Request - const res = new Response('Bad Request', { - status: 400, - headers: { - 'WWW-Authenticate': `${wwwAuthenticatePrefix}error="invalid_request"`, - }, - }) - throw new HTTPException(400, { res }) + const status = 400 + const headers = { + 'WWW-Authenticate': `${wwwAuthenticatePrefix}error="invalid_request"`, + } + const responseMessage = + typeof options.invalidAuthenticationHeaderMeasage === 'function' + ? await options.invalidAuthenticationHeaderMeasage(c) + : options.invalidAuthenticationHeaderMeasage + const res = + typeof responseMessage === 'string' + ? new Response(responseMessage, { status, headers }) + : new Response(JSON.stringify(responseMessage), { + status, + headers: { + ...headers, + 'content-type': 'application/json; charset=UTF-8', + }, + }) + throw new HTTPException(status, { res }) } else { let equal = false if ('verifyToken' in options) { @@ -111,13 +153,25 @@ export const bearerAuth = (options: BearerAuthOptions): MiddlewareHandler => { } if (!equal) { // Invalid Token - const res = new Response('Unauthorized', { - status: 401, - headers: { - 'WWW-Authenticate': `${wwwAuthenticatePrefix}error="invalid_token"`, - }, - }) - throw new HTTPException(401, { res }) + const status = 401 + const headers = { + 'WWW-Authenticate': `${wwwAuthenticatePrefix}error="invalid_token"`, + } + const responseMessage = + typeof options.invalidTokenMessage === 'function' + ? await options.invalidTokenMessage(c) + : options.invalidTokenMessage + const res = + typeof responseMessage === 'string' + ? new Response(responseMessage, { status, headers }) + : new Response(JSON.stringify(responseMessage), { + status, + headers: { + ...headers, + 'content-type': 'application/json; charset=UTF-8', + }, + }) + throw new HTTPException(status, { res }) } } } From 92654788c60c2a4c51f57756dac1426fe48933df Mon Sep 17 00:00:00 2001 From: Marcel Overdijk Date: Wed, 4 Sep 2024 17:58:40 +0200 Subject: [PATCH 2/2] feat(bearer-auth): added custom response message options --- src/middleware/bearer-auth/index.test.ts | 265 ++--------------------- src/middleware/bearer-auth/index.ts | 97 +++------ 2 files changed, 52 insertions(+), 310 deletions(-) diff --git a/src/middleware/bearer-auth/index.test.ts b/src/middleware/bearer-auth/index.test.ts index 8c017f48e..a93dcb03a 100644 --- a/src/middleware/bearer-auth/index.test.ts +++ b/src/middleware/bearer-auth/index.test.ts @@ -70,158 +70,43 @@ describe('Bearer Auth by Middleware', () => { }) app.use( - '/auth-custom-no-authentication-header-message-string/*', + '/auth-custom-no-authentication-header-message/*', bearerAuth({ token, - noAuthenticationHeaderMessage: 'Custom no authentication header message as string', - }) - ) - app.get('/auth-custom-no-authentication-header-message-string/*', (c) => { - handlerExecuted = true - return c.text('auth') - }) - - app.use( - '/auth-custom-no-authentication-header-message-object/*', - bearerAuth({ - token, - noAuthenticationHeaderMessage: { - message: 'Custom no authentication header message as object', + noAuthenticationHeaderMessage: (c) => { + return c.json({ message: 'Custom no authentication header message' }) }, }) ) - app.get('/auth-custom-no-authentication-header-message-object/*', (c) => { - handlerExecuted = true - return c.text('auth') - }) - - app.use( - '/auth-custom-no-authentication-header-message-function-string/*', - bearerAuth({ - token, - noAuthenticationHeaderMessage: () => - 'Custom no authentication header message as function string', - }) - ) - app.get('/auth-custom-no-authentication-header-message-function-string/*', (c) => { + app.get('/auth-custom-no-authentication-header-message/*', (c) => { handlerExecuted = true return c.text('auth') }) app.use( - '/auth-custom-no-authentication-header-message-function-object/*', + '/auth-custom-invalid-authentication-header-message/*', bearerAuth({ token, - noAuthenticationHeaderMessage: () => ({ - message: 'Custom no authentication header message as function object', - }), - }) - ) - app.get('/auth-custom-no-authentication-header-message-function-object/*', (c) => { - handlerExecuted = true - return c.text('auth') - }) - - app.use( - '/auth-custom-invalid-authentication-header-message-string/*', - bearerAuth({ - token, - invalidAuthenticationHeaderMeasage: - 'Custom invalid authentication header message as string', - }) - ) - app.get('/auth-custom-invalid-authentication-header-message-string/*', (c) => { - handlerExecuted = true - return c.text('auth') - }) - - app.use( - '/auth-custom-invalid-authentication-header-message-object/*', - bearerAuth({ - token, - invalidAuthenticationHeaderMeasage: { - message: 'Custom invalid authentication header message as object', + invalidAuthenticationHeaderMeasage: (c) => { + return c.json({ message: 'Custom invalid authentication header message' }) }, }) ) - app.get('/auth-custom-invalid-authentication-header-message-object/*', (c) => { - handlerExecuted = true - return c.text('auth') - }) - - app.use( - '/auth-custom-invalid-authentication-header-message-function-string/*', - bearerAuth({ - token, - invalidAuthenticationHeaderMeasage: () => - 'Custom invalid authentication header message as function string', - }) - ) - app.get('/auth-custom-invalid-authentication-header-message-function-string/*', (c) => { - handlerExecuted = true - return c.text('auth') - }) - - app.use( - '/auth-custom-invalid-authentication-header-message-function-object/*', - bearerAuth({ - token, - invalidAuthenticationHeaderMeasage: () => ({ - message: 'Custom invalid authentication header message as function object', - }), - }) - ) - app.get('/auth-custom-invalid-authentication-header-message-function-object/*', (c) => { - handlerExecuted = true - return c.text('auth') - }) - - app.use( - '/auth-custom-invalid-token-message-string/*', - bearerAuth({ - token, - invalidTokenMessage: 'Custom invalid token message as string', - }) - ) - app.get('/auth-custom-invalid-token-message-string/*', (c) => { - handlerExecuted = true - return c.text('auth') - }) - - app.use( - '/auth-custom-invalid-token-message-object/*', - bearerAuth({ - token, - invalidTokenMessage: { message: 'Custom invalid token message as object' }, - }) - ) - app.get('/auth-custom-invalid-token-message-object/*', (c) => { - handlerExecuted = true - return c.text('auth') - }) - - app.use( - '/auth-custom-invalid-token-message-function-string/*', - bearerAuth({ - token, - invalidTokenMessage: () => 'Custom invalid token message as function string', - }) - ) - app.get('/auth-custom-invalid-token-message-function-string/*', (c) => { + app.get('/auth-custom-invalid-authentication-header-message/*', (c) => { handlerExecuted = true return c.text('auth') }) app.use( - '/auth-custom-invalid-token-message-function-object/*', + '/auth-custom-invalid-token-message/*', bearerAuth({ token, - invalidTokenMessage: () => ({ - message: 'Custom invalid token message as function object', - }), + invalidTokenMessage: (c) => { + return c.json({ message: 'Custom invalid token message' }) + }, }) ) - app.get('/auth-custom-invalid-token-message-function-object/*', (c) => { + app.get('/auth-custom-invalid-token-message/*', (c) => { handlerExecuted = true return c.text('auth') }) @@ -386,143 +271,35 @@ describe('Bearer Auth by Middleware', () => { expect(await res.text()).toBe('Unauthorized') }) - it('Should not authorize - custom no authorization header message as string', async () => { - const req = new Request('http://localhost/auth-custom-no-authentication-header-message-string') - const res = await app.request(req) - expect(res).not.toBeNull() - expect(res.status).toBe(401) - expect(handlerExecuted).toBeFalsy() - expect(await res.text()).toBe('Custom no authentication header message as string') - }) - - it('Should not authorize - custom no authorization header message as object', async () => { - const req = new Request('http://localhost/auth-custom-no-authentication-header-message-object') + it('Should not authorize - custom no authorization header message', async () => { + const req = new Request('http://localhost/auth-custom-no-authentication-header-message') const res = await app.request(req) expect(res).not.toBeNull() expect(res.status).toBe(401) expect(res.headers.get('Content-Type')).toMatch('application/json; charset=UTF-8') expect(handlerExecuted).toBeFalsy() - expect(await res.text()).toBe('{"message":"Custom no authentication header message as object"}') - }) - - it('Should not authorize - custom no authorization header message as function string', async () => { - const req = new Request( - 'http://localhost/auth-custom-no-authentication-header-message-function-string' - ) - const res = await app.request(req) - expect(res).not.toBeNull() - expect(res.status).toBe(401) - expect(handlerExecuted).toBeFalsy() - expect(await res.text()).toBe('Custom no authentication header message as function string') + expect(await res.text()).toBe('{"message":"Custom no authentication header message"}') }) - it('Should not authorize - custom no authorization header message as function object', async () => { - const req = new Request( - 'http://localhost/auth-custom-no-authentication-header-message-function-object' - ) - const res = await app.request(req) - expect(res).not.toBeNull() - expect(res.status).toBe(401) - expect(res.headers.get('Content-Type')).toMatch('application/json; charset=UTF-8') - expect(handlerExecuted).toBeFalsy() - expect(await res.text()).toBe( - '{"message":"Custom no authentication header message as function object"}' - ) - }) - - it('Should not authorize - custom invalid authentication header message as string', async () => { - const req = new Request( - 'http://localhost/auth-custom-invalid-authentication-header-message-string' - ) - req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=') - const res = await app.request(req) - expect(res).not.toBeNull() - expect(res.status).toBe(400) - expect(handlerExecuted).toBeFalsy() - expect(await res.text()).toBe('Custom invalid authentication header message as string') - }) - - it('Should not authorize - custom invalid authentication header message as object', async () => { - const req = new Request( - 'http://localhost/auth-custom-invalid-authentication-header-message-object' - ) + it('Should not authorize - custom invalid authentication header message', async () => { + const req = new Request('http://localhost/auth-custom-invalid-authentication-header-message') req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=') const res = await app.request(req) expect(res).not.toBeNull() expect(res.status).toBe(400) expect(res.headers.get('Content-Type')).toMatch('application/json; charset=UTF-8') expect(handlerExecuted).toBeFalsy() - expect(await res.text()).toBe( - '{"message":"Custom invalid authentication header message as object"}' - ) - }) - - it('Should not authorize - custom invalid authentication header message as function string', async () => { - const req = new Request( - 'http://localhost/auth-custom-invalid-authentication-header-message-function-string' - ) - req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=') - const res = await app.request(req) - expect(res).not.toBeNull() - expect(res.status).toBe(400) - expect(handlerExecuted).toBeFalsy() - expect(await res.text()).toBe('Custom invalid authentication header message as function string') - }) - - it('Should not authorize - custom invalid authentication header message as function object', async () => { - const req = new Request( - 'http://localhost/auth-custom-invalid-authentication-header-message-function-object' - ) - req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=') - const res = await app.request(req) - expect(res).not.toBeNull() - expect(res.status).toBe(400) - expect(res.headers.get('Content-Type')).toMatch('application/json; charset=UTF-8') - expect(handlerExecuted).toBeFalsy() - expect(await res.text()).toBe( - '{"message":"Custom invalid authentication header message as function object"}' - ) + expect(await res.text()).toBe('{"message":"Custom invalid authentication header message"}') }) it('Should not authorize - custom invalid token message as string', async () => { - const req = new Request('http://localhost/auth-custom-invalid-token-message-string') - req.headers.set('Authorization', 'Bearer invalid-token') - const res = await app.request(req) - expect(res).not.toBeNull() - expect(res.status).toBe(401) - expect(handlerExecuted).toBeFalsy() - expect(await res.text()).toBe('Custom invalid token message as string') - }) - - it('Should not authorize - custom invalid token message as object', async () => { - const req = new Request('http://localhost/auth-custom-invalid-token-message-object') - req.headers.set('Authorization', 'Bearer invalid-token') - const res = await app.request(req) - expect(res).not.toBeNull() - expect(res.status).toBe(401) - expect(res.headers.get('Content-Type')).toMatch('application/json; charset=UTF-8') - expect(handlerExecuted).toBeFalsy() - expect(await res.text()).toBe('{"message":"Custom invalid token message as object"}') - }) - - it('Should not authorize - custom invalid token message as function string', async () => { - const req = new Request('http://localhost/auth-custom-invalid-token-message-function-string') - req.headers.set('Authorization', 'Bearer invalid-token') - const res = await app.request(req) - expect(res).not.toBeNull() - expect(res.status).toBe(401) - expect(handlerExecuted).toBeFalsy() - expect(await res.text()).toBe('Custom invalid token message as function string') - }) - - it('Should not authorize - custom invalid token message as function object', async () => { - const req = new Request('http://localhost/auth-custom-invalid-token-message-function-object') + const req = new Request('http://localhost/auth-custom-invalid-token-message') req.headers.set('Authorization', 'Bearer invalid-token') const res = await app.request(req) expect(res).not.toBeNull() expect(res.status).toBe(401) expect(res.headers.get('Content-Type')).toMatch('application/json; charset=UTF-8') expect(handlerExecuted).toBeFalsy() - expect(await res.text()).toBe('{"message":"Custom invalid token message as function object"}') + expect(await res.text()).toBe('{"message":"Custom invalid token message"}') }) }) diff --git a/src/middleware/bearer-auth/index.ts b/src/middleware/bearer-auth/index.ts index 15074aa3b..a394815a6 100644 --- a/src/middleware/bearer-auth/index.ts +++ b/src/middleware/bearer-auth/index.ts @@ -12,6 +12,8 @@ const TOKEN_STRINGS = '[A-Za-z0-9._~+/-]+=*' const PREFIX = 'Bearer' const HEADER = 'Authorization' +type CustomMessageFunction = (c: Context) => Response | Promise + type BearerAuthOptions = | { token: string | string[] @@ -19,9 +21,9 @@ type BearerAuthOptions = prefix?: string headerName?: string hashFunction?: Function - noAuthenticationHeaderMessage?: string | object | Function - invalidAuthenticationHeaderMeasage?: string | object | Function - invalidTokenMessage?: string | object | Function + noAuthenticationHeaderMessage?: CustomMessageFunction + invalidAuthenticationHeaderMeasage?: CustomMessageFunction + invalidTokenMessage?: CustomMessageFunction } | { realm?: string @@ -29,9 +31,9 @@ type BearerAuthOptions = headerName?: string verifyToken: (token: string, c: Context) => boolean | Promise hashFunction?: Function - noAuthenticationHeaderMessage?: string | object | Function - invalidAuthenticationHeaderMeasage?: string | object | Function - invalidTokenMessage?: string | object | Function + noAuthenticationHeaderMessage?: CustomMessageFunction + invalidAuthenticationHeaderMeasage?: CustomMessageFunction + invalidTokenMessage?: CustomMessageFunction } /** @@ -46,9 +48,9 @@ type BearerAuthOptions = * @param {string} [options.prefix="Bearer"] - The prefix (or known as `schema`) for the Authorization header value. If set to the empty string, no prefix is expected. * @param {string} [options.headerName=Authorization] - The header name. * @param {Function} [options.hashFunction] - A function to handle hashing for safe comparison of authentication tokens. - * @param {string | object | Function} [options.noAuthenticationHeaderMessage="Unauthorized"] - The no authentication header message. - * @param {string | object | Function} [options.invalidAuthenticationHeaderMeasage="Bad Request"] - The invalid authentication header message. - * @param {string | object | Function} [options.invalidTokenMessage="Unauthorized"] - The invalid token message. + * @param {CustomMessageFunction} [options.noAuthenticationHeaderMessage="Unauthorized"] - The no authentication header message. + * @param {CustomMessageFunction} [options.invalidAuthenticationHeaderMeasage="Bad Request"] - The invalid authentication header message. + * @param {CustomMessageFunction} [options.invalidTokenMessage="Unauthorized"] - The invalid token message. * @returns {MiddlewareHandler} The middleware handler function. * @throws {Error} If neither "token" nor "verifyToken" options are provided. * @throws {HTTPException} If authentication fails, with 401 status code for missing or invalid token, or 400 status code for invalid request. @@ -76,15 +78,6 @@ export const bearerAuth = (options: BearerAuthOptions): MiddlewareHandler => { if (options.prefix === undefined) { options.prefix = PREFIX } - if (!options.noAuthenticationHeaderMessage) { - options.noAuthenticationHeaderMessage = 'Unauthorized' - } - if (!options.invalidAuthenticationHeaderMeasage) { - options.invalidAuthenticationHeaderMeasage = 'Bad Request' - } - if (!options.invalidTokenMessage) { - options.invalidTokenMessage = 'Unauthorized' - } const realm = options.realm?.replace(/"/g, '\\"') const prefixRegexStr = options.prefix === '' ? '' : `${options.prefix} +` @@ -96,46 +89,28 @@ export const bearerAuth = (options: BearerAuthOptions): MiddlewareHandler => { if (!headerToken) { // No Authorization header const status = 401 - const headers = { - 'WWW-Authenticate': `${wwwAuthenticatePrefix}realm="` + realm + '"', + c.status(status) + c.header('WWW-Authenticate', `${wwwAuthenticatePrefix}realm="` + realm + '"') + const defaultNoAuthenticationHeaderMessage = (c: Context) => { + return c.text('Unauthorized') } - const responseMessage = - typeof options.noAuthenticationHeaderMessage === 'function' - ? await options.noAuthenticationHeaderMessage(c) - : options.noAuthenticationHeaderMessage - const res = - typeof responseMessage === 'string' - ? new Response(responseMessage, { status, headers }) - : new Response(JSON.stringify(responseMessage), { - status, - headers: { - ...headers, - 'content-type': 'application/json; charset=UTF-8', - }, - }) + const noAuthenticationHeaderMessage = + options.noAuthenticationHeaderMessage ?? defaultNoAuthenticationHeaderMessage + const res = await noAuthenticationHeaderMessage(c) throw new HTTPException(status, { res }) } else { const match = regexp.exec(headerToken) if (!match) { // Invalid Request const status = 400 - const headers = { - 'WWW-Authenticate': `${wwwAuthenticatePrefix}error="invalid_request"`, + c.status(status) + c.header('WWW-Authenticate', `${wwwAuthenticatePrefix}error="invalid_request"`) + const defaultInvalidAuthenticationHeaderMeasage = (c: Context) => { + return c.text('Bad Request') } - const responseMessage = - typeof options.invalidAuthenticationHeaderMeasage === 'function' - ? await options.invalidAuthenticationHeaderMeasage(c) - : options.invalidAuthenticationHeaderMeasage - const res = - typeof responseMessage === 'string' - ? new Response(responseMessage, { status, headers }) - : new Response(JSON.stringify(responseMessage), { - status, - headers: { - ...headers, - 'content-type': 'application/json; charset=UTF-8', - }, - }) + const invalidAuthenticationHeaderMeasage = + options.invalidAuthenticationHeaderMeasage ?? defaultInvalidAuthenticationHeaderMeasage + const res = await invalidAuthenticationHeaderMeasage(c) throw new HTTPException(status, { res }) } else { let equal = false @@ -154,23 +129,13 @@ export const bearerAuth = (options: BearerAuthOptions): MiddlewareHandler => { if (!equal) { // Invalid Token const status = 401 - const headers = { - 'WWW-Authenticate': `${wwwAuthenticatePrefix}error="invalid_token"`, + c.status(status) + c.header('WWW-Authenticate', `${wwwAuthenticatePrefix}error="invalid_token"`) + const defaultInvalidTokenMessage = (c: Context) => { + return c.text('Unauthorized') } - const responseMessage = - typeof options.invalidTokenMessage === 'function' - ? await options.invalidTokenMessage(c) - : options.invalidTokenMessage - const res = - typeof responseMessage === 'string' - ? new Response(responseMessage, { status, headers }) - : new Response(JSON.stringify(responseMessage), { - status, - headers: { - ...headers, - 'content-type': 'application/json; charset=UTF-8', - }, - }) + const invalidTokenMessage = options.invalidTokenMessage ?? defaultInvalidTokenMessage + const res = await invalidTokenMessage(c) throw new HTTPException(status, { res }) } }