Track HiveGrid Cloud planning reference

This commit is contained in:
david committed 2026-06-26 20:48:01 +02:00
1 parent b3d5ea6764
commit e341c5938a
5784 files changed
+1104929 -3

No files matched your search

@@ -0,0 +1,12 @@
import type { LoaderFunctionArgs } from "react-router";
import { serverOnly$ } from "vite-env-only/macros";
import { requireAuth } from "~/utils/loaders.middleware";
import { loadAdminDashboardRouteData, type AdminDashboardLoaderData } from "~/utils/data/.server/adminDashboardRouteData";
export type { AdminDashboardLoaderData };
export const loader = serverOnly$(async ({ request, params }: LoaderFunctionArgs) => {
await requireAuth({ request, params });
const { data, headers } = await loadAdminDashboardRouteData({ request, params });
return Response.json(data, { headers });
})!;
@@ -0,0 +1,62 @@
import { Form, Link, useActionData, useLoaderData, useNavigation } from "react-router";
import { useTranslation } from "react-i18next";
import { ADMIN_MFA_ENROLLMENT_PATH } from "~/utils/auth/mfa/paths";
import { useRootData } from "~/utils/data/useRootData";
import type { ActionData, LoaderData } from "~/routeModules/admin/mfa-challenge.impl";
export default function AdminMfaChallengeRoute() {
const { t } = useTranslation();
const data = useLoaderData<LoaderData>();
const actionData = useActionData<ActionData>();
const navigation = useNavigation();
const rootData = useRootData();
const isSubmitting = navigation.state === "submitting";
return (
<main className="mx-auto flex min-h-screen max-w-md flex-col justify-center px-6 py-12">
<div className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
<h1 className="text-xl font-semibold text-gray-900">{t("admin.mfaChallenge.title")}</h1>
<p className="mt-2 text-sm text-gray-600">{t("admin.mfaChallenge.description")}</p>
{actionData?.error ? <p className="mt-4 rounded-md bg-red-50 p-3 text-sm text-red-700">{actionData.error}</p> : null}
<Form method="post" className="mt-6 space-y-4">
{rootData.csrf && <input type="hidden" name="csrf" value={rootData.csrf} />}
<input type="hidden" name="redirectTo" value={actionData?.redirectTo ?? data.redirectTo} />
<div>
<label htmlFor="code" className="block text-sm font-medium text-gray-700">
{t("admin.mfaChallenge.fields.authenticatorCode")}
</label>
<input
id="code"
name="code"
inputMode="numeric"
autoComplete="one-time-code"
pattern="[0-9]{6}"
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-gray-900 shadow-sm focus:border-gray-900 focus:outline-none focus:ring-1 focus:ring-gray-900"
/>
</div>
<div>
<label htmlFor="recoveryCode" className="block text-sm font-medium text-gray-700">
{t("admin.mfaChallenge.fields.recoveryCode")}
</label>
<input
id="recoveryCode"
name="recoveryCode"
autoComplete="off"
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 uppercase text-gray-900 shadow-sm focus:border-gray-900 focus:outline-none focus:ring-1 focus:ring-gray-900"
/>
</div>
<button
type="submit"
disabled={isSubmitting || !data.totpEnabled}
className="w-full rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800 disabled:cursor-not-allowed disabled:opacity-60"
>
{isSubmitting ? t("admin.mfaChallenge.actions.verifying") : t("admin.mfaChallenge.actions.verifyAndContinue")}
</button>
</Form>
<Link to={data.enrollmentPath || ADMIN_MFA_ENROLLMENT_PATH} className="mt-4 inline-block text-sm text-gray-600 hover:text-gray-900">
{t("admin.mfaChallenge.actions.manageSettings")}
</Link>
</div>
</main>
);
}
@@ -0,0 +1,166 @@
import { redirect, type ActionFunctionArgs, type LoaderFunctionArgs } from "react-router";
import { serverOnly$ } from "vite-env-only/macros";
import { loadRequiredWebConfig } from "@toolctrl/config";
import { getTranslations } from "~/locale/i18next.server";
import { ADMIN_MFA_DEFAULT_REDIRECT_PATH, ADMIN_MFA_ENROLLMENT_PATH } from "~/utils/auth/mfa/paths";
import { setMfaStepUpState, type MfaStepUpMethod } from "~/utils/auth/mfa/step-up.server";
import { verifyTotpCode } from "~/services/auth/totp.server";
import { checkMfaChallengeRateLimit, mfaChallengeRateLimitKey, recordFailedMfaChallengeAttempt, resetMfaChallengeRateLimit } from "~/utils/auth/mfa/challenge-rate-limit.server";
import { absoluteAdminPath } from "~/utils/app/CrossHostUrls";
import { getHostRoutingContextFromRequest } from "~/utils/app/HostRouting.server";
import { safeRedirectPath } from "~/utils/app/RedirectSafety.server";
import { consumeUserMfaRecoveryCode, getUser, getUserTotpStatus } from "~/utils/db/users.db.server";
import { db } from "~/utils/db.server";
import { commitSession, getUserSession, validateCSRFToken } from "~/utils/session.server";
export type LoaderData = {
redirectTo: string;
totpEnabled: boolean;
enrollmentPath: string;
};
export type ActionData = {
error?: string;
redirectTo: string;
};
function isAdminHostRequest(request: Request) {
return getHostRoutingContextFromRequest(request) === "admin";
}
function getCanonicalAdminChallengeUrl(request: Request) {
const url = new URL(request.url);
return absoluteAdminPath(loadRequiredWebConfig().routing, `mfa-challenge${url.search}`);
}
function safeRedirectTo(value: FormDataEntryValue | string | null | undefined) {
return safeRedirectPath(typeof value === "string" ? value : null, { fallbackPath: ADMIN_MFA_DEFAULT_REDIRECT_PATH });
}
function isSixDigitTotpCode(code: FormDataEntryValue | null): code is string {
return typeof code === "string" && /^\d{6}$/.test(code);
}
function normalizeRecoveryCode(code: FormDataEntryValue | null) {
if (typeof code !== "string") {
return "";
}
return code.trim().toUpperCase();
}
async function getActiveTotpSecret(userId: string) {
const user = await db.user.findUnique({
where: { id: userId },
select: { totpSecretEncrypted: true, totpEnabledAt: true },
});
if (!user?.totpSecretEncrypted || !user.totpEnabledAt) {
return null;
}
return user.totpSecretEncrypted;
}
export const loader = serverOnly$(async ({ request }: LoaderFunctionArgs) => {
if (!isAdminHostRequest(request)) {
throw redirect(getCanonicalAdminChallengeUrl(request));
}
const session = await getUserSession(request);
const userId = session.get("userId");
if (typeof userId !== "string" || userId.length === 0) {
throw redirect("/login");
}
const url = new URL(request.url);
const { t } = await getTranslations(request);
const redirectTo = safeRedirectTo(url.searchParams.get("redirectTo"));
const user = await getUser(userId);
if (!user?.admin) {
throw Response.json({ error: t("admin.mfaChallenge.errors.adminAccessRequired"), redirectTo } satisfies ActionData, { status: 403 });
}
const totpStatus = await getUserTotpStatus(userId);
if (!totpStatus?.enabled) {
throw redirect(ADMIN_MFA_ENROLLMENT_PATH);
}
return Response.json({ redirectTo, totpEnabled: true, enrollmentPath: ADMIN_MFA_ENROLLMENT_PATH } satisfies LoaderData);
})!;
export const action = serverOnly$(async ({ request }: ActionFunctionArgs) => {
const { t } = await getTranslations(request);
if (!isAdminHostRequest(request)) {
return Response.json(
{ error: t("admin.mfaChallenge.errors.adminHostRequired"), redirectTo: getCanonicalAdminChallengeUrl(request) } satisfies ActionData,
{ status: 403 }
);
}
const session = await getUserSession(request);
const userId = session.get("userId");
if (typeof userId !== "string" || userId.length === 0) {
throw redirect("/login");
}
await validateCSRFToken(request);
const form = await request.formData();
const redirectTo = safeRedirectTo(form.get("redirectTo") ?? new URL(request.url).searchParams.get("redirectTo"));
const user = await getUser(userId);
if (!user?.admin) {
return Response.json({ error: t("admin.mfaChallenge.errors.adminAccessRequired"), redirectTo } satisfies ActionData, { status: 403 });
}
const totpStatus = await getUserTotpStatus(user.id);
if (!totpStatus?.enabled) {
throw redirect(ADMIN_MFA_ENROLLMENT_PATH);
}
const rateLimitKey = mfaChallengeRateLimitKey(request, user.id);
const rateLimit = await checkMfaChallengeRateLimit(rateLimitKey);
if (!rateLimit.allowed) {
return Response.json({ error: t("admin.mfaChallenge.errors.tooManyAttempts"), redirectTo } satisfies ActionData, {
status: 429,
headers: { "Retry-After": String(rateLimit.retryAfterSeconds) },
});
}
const code = form.get("code");
let method: MfaStepUpMethod | null = null;
if (isSixDigitTotpCode(code)) {
const encryptedSecret = await getActiveTotpSecret(user.id);
let totpMatches = false;
try {
totpMatches = Boolean(encryptedSecret && verifyTotpCode(encryptedSecret, code));
} catch {
totpMatches = false;
}
if (totpMatches) {
method = "totp";
}
}
if (!method) {
const recoveryCode = normalizeRecoveryCode(form.get("recoveryCode"));
if (recoveryCode.length > 0) {
const result = await consumeUserMfaRecoveryCode(user.id, recoveryCode);
if (result?.consumed) {
method = "recovery-code";
}
}
}
if (!method) {
await recordFailedMfaChallengeAttempt(rateLimitKey);
return Response.json({ error: t("admin.mfaChallenge.errors.invalidCode"), redirectTo } satisfies ActionData, { status: 400 });
}
await resetMfaChallengeRateLimit(rateLimitKey);
setMfaStepUpState(session, { userId: user.id, method, redirectTo });
throw redirect(redirectTo, {
headers: {
"Set-Cookie": await commitSession(session),
},
});
})!;