Dev Build / cleanup (push) Canceled after 0s
Dev Build / typecheck (push) Canceled after 0s
Dev Build / build (linux-arm64, ubuntu-24.04-arm, linux-arm64) (push) Canceled after 0s
Dev Build / build (linux-x64, ubuntu-latest, linux-x64) (push) Canceled after 0s
Dev Build / build (win-x64, windows-latest, win32-x64) (push) Canceled after 0s
Dev Build / dispatch-dev-docker (push) Canceled after 0s
100 lines
3.7 KiB
Plaintext
100 lines
3.7 KiB
Plaintext
import { HTTPException } from "../../http-exception.js";
|
|
import { timingSafeEqual } from "../../utils/buffer.js";
|
|
import { auth } from "../../utils/basic-auth.js";
|
|
//#region src/middleware/basic-auth/index.ts
|
|
/**
|
|
* 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.
|
|
* @param {Function} [options.onAuthSuccess] - Callback function called on successful authentication.
|
|
* @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')
|
|
* })
|
|
* ```
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* // With onAuthSuccess callback
|
|
* app.use(
|
|
* '/auth/*',
|
|
* basicAuth({
|
|
* username: 'hono',
|
|
* password: 'ahotproject',
|
|
* onAuthSuccess: (c, username) => {
|
|
* c.set('user', { name: username, role: 'admin' })
|
|
* console.log(`User ${username} authenticated`)
|
|
* },
|
|
* })
|
|
* )
|
|
* ```
|
|
*/
|
|
const basicAuth = (options, ...users) => {
|
|
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)) {
|
|
if (options.onAuthSuccess) await options.onAuthSuccess(ctx, requestUser.username);
|
|
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) {
|
|
if (options.onAuthSuccess) await options.onAuthSuccess(ctx, requestUser.username);
|
|
await next();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
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 });
|
|
};
|
|
};
|
|
//#endregion
|
|
export { basicAuth };
|