98 lines
5.9 KiB
Plaintext
98 lines
5.9 KiB
Plaintext
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
const require_http_exception = require("../../http-exception.js");
|
|
const require_utils_buffer = require("../../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 require_http_exception.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 require_utils_buffer.timingSafeEqual(options.token, tokenValue, options.hashFunction);
|
|
else if (Array.isArray(options.token) && options.token.length > 0) {
|
|
for (const token of options.token) if (await require_utils_buffer.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
|
|
exports.bearerAuth = bearerAuth;
|