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: 3346 customized response messages #3362

Closed
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
3 changes: 2 additions & 1 deletion src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
JSONValue,
SimplifyDeepArray,
} from './utils/types'
import { APPLICATION_JSON_UTF_8 } from './utils/mime'
import type { BaseMime } from './utils/mime'

type HeaderRecord =
Expand Down Expand Up @@ -828,7 +829,7 @@ export class Context<
): JSONRespondReturn<T, U> => {
const body = JSON.stringify(object)
this.#preparedHeaders ??= {}
this.#preparedHeaders['content-type'] = 'application/json; charset=UTF-8'
this.#preparedHeaders['content-type'] = APPLICATION_JSON_UTF_8
/* eslint-disable @typescript-eslint/no-explicit-any */
return (
typeof arg === 'number' ? this.newResponse(body, arg, headers) : this.newResponse(body, arg)
Expand Down
91 changes: 91 additions & 0 deletions src/middleware/basic-auth/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,42 @@ describe('Basic Auth by Middleware', () => {
})
)

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.use('/nested/*', async (c, next) => {
const auth = basicAuth({ username: username, password: password })
return auth(c, next)
Expand Down Expand Up @@ -99,6 +135,23 @@ describe('Basic Auth by Middleware', () => {
handlerExecuted = true
return c.text('auth')
})
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')
})

app.get('/nested/*', (c) => {
handlerExecuted = true
Expand Down Expand Up @@ -226,4 +279,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"}')
})
})
35 changes: 28 additions & 7 deletions src/middleware/basic-auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { HonoRequest } from '../../request'
import type { MiddlewareHandler } from '../../types'
import { timingSafeEqual } from '../../utils/buffer'
import { decodeBase64 } from '../../utils/encode'
import { APPLICATION_JSON_UTF_8 } from '../../utils/mime'

const CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/
const USER_PASS_REGEXP = /^([^:]*):(.*)$/
Expand Down Expand Up @@ -38,11 +39,13 @@ type BasicAuthOptions =
password: string
realm?: string
hashFunction?: Function
invalidUserMessage?: string | object | Function
}
| {
verifyUser: (username: string, password: string, c: Context) => boolean | Promise<boolean>
realm?: string
hashFunction?: Function
invalidUserMessage?: string | object | Function
}

/**
Expand All @@ -56,6 +59,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 | Function} [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 @@ -93,6 +97,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 @@ -118,12 +126,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_UTF_8,
},
})
throw new HTTPException(status, { res })
}
}
Loading