Ctrl K
ComunidadGuías…

src/features/auth-failures.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * How a refused or failed auth answers: the failure responses keyed by reason,
 * each with an html and a json variant.
 */

import { t } from "#i18n";
import { apiErrorResponse } from "#routes/api/cors.ts";
import { htmlResponse, redirectResponse } from "#routes/response.ts";

/** The HTML 403 body: a role refusal says the account cannot open the page.
 *  An owner-only refusal says only the owner account can. The audience fact
 *  comes from the same route declaration that the guards admit by. */
const forbiddenBody = (detail?: ForbiddenDetail): string =>
  detail === "owner-only"
    ? t("auth.forbidden_owner_only")
    : t("auth.forbidden_role");

/** Shared auth failure response factories */
const htmlForbidden = () => htmlResponse(forbiddenBody(), 403);
const jsonForbidden = () => apiErrorResponse("Forbidden", 403);

/** Auth failure responses keyed by reason, with html and json variants side-by-side. */
const AUTH_FAILURES = {
  forbidden: {
    html: (detail?: ForbiddenDetail) =>
      htmlResponse(forbiddenBody(detail), 403),
    json: jsonForbidden,
  },
  "invalid-api-key": {
    html: htmlForbidden,
    json: () => apiErrorResponse("Invalid API key", 401),
  },
  "invalid-csrf": {
    html: () => htmlResponse("Invalid CSRF token", 403),
    json: jsonForbidden,
  },
  "not-authenticated": {
    html: () => redirectResponse("/admin"),
    json: () => apiErrorResponse("Not authenticated", 401),
  },
} satisfies Record<
  string,
  Record<"html" | "json", (...args: never[]) => Response>
>;

type AuthFailureReason = keyof typeof AUTH_FAILURES;

/** The channel a failure answers on: the html page or the json API. */
export type AuthChannel = keyof (typeof AUTH_FAILURES)[AuthFailureReason];

/** How a forbidden response narrows its who-can-open message: the route's
 *  declared audience knows whether the page is owner-only. */
export type ForbiddenDetail = "owner-only";

/** Construct a standardized auth failure response. */
export const authFailure = (
  channel: AuthChannel,
  reason: AuthFailureReason,
  forbiddenDetail?: ForbiddenDetail,
): Response => {
  const factory = AUTH_FAILURES[reason][channel] as (
    detail?: ForbiddenDetail,
  ) => Response;
  return factory(forbiddenDetail);
};