forked from honojs/hono
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
128 lines (119 loc) · 4.03 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
* @module
* Basic Auth Middleware for Hono.
*/
import type { Context } from '../../context'
import { HTTPException } from '../../http-exception'
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
}
/**
* Basic Auth Middleware for Hono.
*
* @see {@link https://hono.dev/docs/middleware/builtin/basic-auth}
*
* @param {BasicAuthOptions} options - The options for the basic authentication middleware.
* @param {string} options.username - The username for authentication.
* @param {string} options.password - The password for authentication.
* @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.
*
* @example
* ```ts
* const app = new Hono()
*
* app.use(
* '/auth/*',
* basicAuth({
* username: 'hono',
* password: 'ahotproject',
* })
* )
*
* app.get('/auth/page', (c) => {
* return c.text('You are authorized')
* })
* ```
*/
export const basicAuth = (
options: BasicAuthOptions,
...users: { username: string; password: string }[]
): MiddlewareHandler => {
const usernamePasswordInOptions = 'username' in options && 'password' in options
const verifyUserInOptions = 'verifyUser' in options
if (!(usernamePasswordInOptions || verifyUserInOptions)) {
throw new Error(
'basic auth middleware requires options for "username and password" or "verifyUser"'
)
}
if (!options.realm) {
options.realm = 'Secure Area'
}
if (!options.invalidUserMessage) {
options.invalidUserMessage = 'Unauthorized'
}
if (usernamePasswordInOptions) {
users.unshift({ username: options.username, password: options.password })
}
return async function basicAuth(ctx, next) {
const requestUser = auth(ctx.req.raw)
if (requestUser) {
if (verifyUserInOptions) {
if (await options.verifyUser(requestUser.username, requestUser.password, ctx)) {
await next()
return
}
} else {
for (const user of users) {
const [usernameEqual, passwordEqual] = await Promise.all([
timingSafeEqual(user.username, requestUser.username, options.hashFunction),
timingSafeEqual(user.password, requestUser.password, options.hashFunction),
])
if (usernameEqual && passwordEqual) {
await next()
return
}
}
}
}
// 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',
},
})
throw new HTTPException(status, { res })
}
}