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} 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 };