Skip to content

Commit

Permalink
feat(basic-auth): added custom response message option (#3371)
Browse files Browse the repository at this point in the history
* feat(basic-auth): added custom response message option

* feat(basic-auth): using specific MessageFunction type
  • Loading branch information
marceloverdijk authored Sep 11, 2024
1 parent a86f3ce commit c50be25
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 7 deletions.
92 changes: 92 additions & 0 deletions src/middleware/basic-auth/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,42 @@ describe('Basic Auth by Middleware', () => {
return auth(c, next)
})

app.use(
'/auth-custom-invalid-user-message-string/*',
basicAuth({
username,
password,
invalidUserMessage: 'Custom unauthorized message as string',
})
)

app.use(
'/auth-custom-invalid-user-message-object/*',
basicAuth({
username,
password,
invalidUserMessage: { message: 'Custom unauthorized message as object' },
})
)

app.use(
'/auth-custom-invalid-user-message-function-string/*',
basicAuth({
username,
password,
invalidUserMessage: () => 'Custom unauthorized message as function string',
})
)

app.use(
'/auth-custom-invalid-user-message-function-object/*',
basicAuth({
username,
password,
invalidUserMessage: () => ({ message: 'Custom unauthorized message as function object' }),
})
)

app.get('/auth/*', (c) => {
handlerExecuted = true
return c.text('auth')
Expand Down Expand Up @@ -110,6 +146,24 @@ describe('Basic Auth by Middleware', () => {
return c.text('verify-user')
})

app.get('/auth-custom-invalid-user-message-string/*', (c) => {
handlerExecuted = true
return c.text('auth')
})
app.get('/auth-custom-invalid-user-message-object/*', (c) => {
handlerExecuted = true
return c.text('auth')
})
app.get('/auth-custom-invalid-user-message-function-string/*', (c) => {
handlerExecuted = true
return c.text('auth')
})

app.get('/auth-custom-invalid-user-message-function-object/*', (c) => {
handlerExecuted = true
return c.text('auth')
})

it('Should not authorize', async () => {
const req = new Request('http://localhost/auth/a')
const res = await app.request(req)
Expand Down Expand Up @@ -226,4 +280,42 @@ describe('Basic Auth by Middleware', () => {
expect(res.status).toBe(401)
expect(await res.text()).toBe('Unauthorized')
})

it('Should not authorize - custom invalid user message as string', async () => {
const req = new Request('http://localhost/auth-custom-invalid-user-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 unauthorized message as string')
})

it('Should not authorize - custom invalid user message as object', async () => {
const req = new Request('http://localhost/auth-custom-invalid-user-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 unauthorized message as object"}')
})

it('Should not authorize - custom invalid user message as function string', async () => {
const req = new Request('http://localhost/auth-custom-invalid-user-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 unauthorized message as function string')
})

it('Should not authorize - custom invalid user message as function object', async () => {
const req = new Request('http://localhost/auth-custom-invalid-user-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 unauthorized message as function object"}')
})
})
36 changes: 29 additions & 7 deletions src/middleware/basic-auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@ import type { MiddlewareHandler } from '../../types'
import { auth } from '../../utils/basic-auth'
import { timingSafeEqual } from '../../utils/buffer'

type MessageFunction = (c: Context) => string | object | Promise<string | object>

type BasicAuthOptions =
| {
username: string
password: string
realm?: string
hashFunction?: Function
invalidUserMessage?: string | object | MessageFunction
}
| {
verifyUser: (username: string, password: string, c: Context) => boolean | Promise<boolean>
realm?: string
hashFunction?: Function
invalidUserMessage?: string | object | MessageFunction
}

/**
Expand All @@ -33,6 +37,7 @@ type BasicAuthOptions =
* @param {string} [options.realm="Secure Area"] - The realm attribute for the WWW-Authenticate header.
* @param {Function} [options.hashFunction] - The hash function used for secure comparison.
* @param {Function} [options.verifyUser] - The function to verify user credentials.
* @param {string | object | MessageFunction} [options.invalidUserMessage="Unauthorized"] - The invalid user message.
* @returns {MiddlewareHandler} The middleware handler function.
* @throws {HTTPException} If neither "username and password" nor "verifyUser" options are provided.
*
Expand Down Expand Up @@ -70,6 +75,10 @@ export const basicAuth = (
options.realm = 'Secure Area'
}

if (!options.invalidUserMessage) {
options.invalidUserMessage = 'Unauthorized'
}

if (usernamePasswordInOptions) {
users.unshift({ username: options.username, password: options.password })
}
Expand All @@ -95,12 +104,25 @@ export const basicAuth = (
}
}
}
const res = new Response('Unauthorized', {
status: 401,
headers: {
'WWW-Authenticate': 'Basic realm="' + options.realm?.replace(/"/g, '\\"') + '"',
},
})
throw new HTTPException(401, { res })
// Invalid user.
const status = 401
const headers = {
'WWW-Authenticate': 'Basic realm="' + options.realm?.replace(/"/g, '\\"') + '"',
}
const responseMessage =
typeof options.invalidUserMessage === 'function'
? await options.invalidUserMessage(ctx)
: options.invalidUserMessage
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 })
}
}

0 comments on commit c50be25

Please sign in to comment.