Files
SnowLuma/.pnpm-store/v11/files/db/5ab88bad10a6f8d21e8068e8075ddb26feb652816c4797bf43810fb3d2d45d6a7dd6d596efe3ecc25219bc787ef50def6dd2ee7588ba89f914729b41e90861
T
TanTan 2a69b23b47
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
docs: 仓库链接指向本镜像仓库
2026-11-13 09:27:07 +08:00

97 lines
5.8 KiB
Plaintext

import { HTTPException } from "../../http-exception.js";
import { timingSafeEqual } from "../../utils/buffer.js";
//#region src/middleware/bearer-auth/index.ts
const TOKEN_STRINGS = "[A-Za-z0-9._~+/-]+=*";
const PREFIX = "Bearer";
const HEADER = "Authorization";
/**
* Bearer Auth Middleware for Hono.
*
* @see {@link https://hono.dev/docs/middleware/builtin/bearer-auth}
*
* @template E - The environment type.
* @param {BearerAuthOptions} options - The options for the bearer authentication middleware.
* @param {string | string[]} [options.token] - The string or array of strings to validate the incoming bearer token against.
* @param {Function} [options.verifyToken] - The function to verify the token.
* @param {string} [options.realm=""] - The domain name of the realm, as part of the returned WWW-Authenticate challenge header.
* @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 | MessageFunction} [options.noAuthenticationHeader.message="Unauthorized"] - The no authentication header message.
* @param {string | object | MessageFunction} [options.noAuthenticationHeader.wwwAuthenticateHeader="Bearer realm=\"\""] - The response header value for the WWW-Authenticate header when no authentication header is provided.
* @param {string | object | MessageFunction} [options.invalidAuthenticationHeader.message="Bad Request"] - The invalid authentication header message.
* @param {string | object | MessageFunction} [options.invalidAuthenticationHeader.wwwAuthenticateHeader="Bearer error=\"invalid_request\""] - The response header value for the WWW-Authenticate header when authentication header is invalid.
* @param {string | object | MessageFunction} [options.invalidToken.message="Unauthorized"] - The invalid token message.
* @param {string | object | MessageFunction} [options.invalidToken.wwwAuthenticateHeader="Bearer error=\"invalid_token\""] - The response header value for the WWW-Authenticate header when token is invalid.
* @returns {MiddlewareHandler<E>} 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.
*
* @example
* ```ts
* const app = new Hono()
*
* const token = 'honoishot'
*
* app.use('/api/*', bearerAuth({ token }))
*
* app.get('/api/page', (c) => {
* return c.json({ message: 'You are authorized' })
* })
* ```
*/
const bearerAuth = (options) => {
if (!("token" in options || "verifyToken" in options)) throw new Error("bearer auth middleware requires options for \"token\" or \"verifyToken\"");
if (!options.realm) options.realm = "";
if (options.prefix === void 0) options.prefix = PREFIX;
const realm = options.realm?.replace(/"/g, "\\\"");
const prefix = options.prefix;
const tokenRegexp = new RegExp(`^${TOKEN_STRINGS}$`);
const wwwAuthenticatePrefix = prefix === "" ? "" : `${prefix} `;
const throwHTTPException = async (c, status, wwwAuthenticateHeader, messageOption) => {
const wwwAuthenticateHeaderValue = typeof wwwAuthenticateHeader === "function" ? await wwwAuthenticateHeader(c) : wwwAuthenticateHeader;
const headers = { "WWW-Authenticate": typeof wwwAuthenticateHeaderValue === "string" ? wwwAuthenticateHeaderValue : `${wwwAuthenticatePrefix}${Object.entries(wwwAuthenticateHeaderValue).map(([key, value]) => `${key}="${value}"`).join(",")}` };
const responseMessage = typeof messageOption === "function" ? await messageOption(c) : messageOption;
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 });
};
return async function bearerAuth(c, next) {
const headerToken = c.req.header(options.headerName || HEADER);
if (!headerToken) await throwHTTPException(c, 401, options.noAuthenticationHeader?.wwwAuthenticateHeader || `${wwwAuthenticatePrefix}realm="${realm}"`, options.noAuthenticationHeader?.message || options.noAuthenticationHeaderMessage || "Unauthorized");
else {
let tokenValue;
if (prefix === "") tokenValue = headerToken;
else {
const headerLower = headerToken.toLowerCase();
const prefixLower = prefix.toLowerCase();
if (headerLower.startsWith(prefixLower) && headerToken[prefix.length] === " ") tokenValue = headerToken.slice(prefix.length).trimStart();
}
if (!tokenValue || !tokenRegexp.test(tokenValue)) await throwHTTPException(c, 400, options.invalidAuthenticationHeader?.wwwAuthenticateHeader || `${wwwAuthenticatePrefix}error="invalid_request"`, options.invalidAuthenticationHeader?.message || options.invalidAuthenticationHeaderMessage || "Bad Request");
else {
let equal = false;
if ("verifyToken" in options) equal = await options.verifyToken(tokenValue, c);
else if (typeof options.token === "string") equal = await timingSafeEqual(options.token, tokenValue, options.hashFunction);
else if (Array.isArray(options.token) && options.token.length > 0) {
for (const token of options.token) if (await timingSafeEqual(token, tokenValue, options.hashFunction)) {
equal = true;
break;
}
}
if (!equal) await throwHTTPException(c, 401, options.invalidToken?.wwwAuthenticateHeader || `${wwwAuthenticatePrefix}error="invalid_token"`, options.invalidToken?.message || options.invalidTokenMessage || "Unauthorized");
}
}
await next();
};
};
//#endregion
export { bearerAuth };