This commit is contained in:
Owen
2025-10-04 18:36:44 -07:00
parent 3123f858bb
commit c2c907852d
320 changed files with 35785 additions and 2984 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
@@ -22,10 +22,7 @@ import {
CardTitle
} from "@app/components/ui/card";
import { Alert, AlertDescription } from "@app/components/ui/alert";
import { LoginResponse } from "@server/routers/auth";
import { useRouter } from "next/navigation";
import { AxiosResponse } from "axios";
import { formatAxiosError } from "@app/lib/api";
import { LockIcon, FingerprintIcon } from "lucide-react";
import { createApiClient } from "@app/lib/api";
import {
@@ -49,6 +46,9 @@ import {
} from "@app/actions/server";
import { redirect as redirectTo } from "next/navigation";
import { useEnvContext } from "@app/hooks/useEnvContext";
// @ts-ignore
import { loadReoScript } from "reodotdev";
import { build } from "@server/build";
export type LoginFormIDP = {
idpId: number;
@@ -60,13 +60,18 @@ type LoginFormProps = {
redirect?: string;
onLogin?: () => void | Promise<void>;
idps?: LoginFormIDP[];
orgId?: string;
};
export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
export default function LoginForm({
redirect,
onLogin,
idps,
orgId
}: LoginFormProps) {
const router = useRouter();
const { env } = useEnvContext();
const api = createApiClient({ env });
const [error, setError] = useState<string | null>(null);
@@ -77,10 +82,32 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
const [showSecurityKeyPrompt, setShowSecurityKeyPrompt] = useState(false);
const t = useTranslations();
const currentHost = typeof window !== "undefined" ? window.location.hostname : "";
const currentHost =
typeof window !== "undefined" ? window.location.hostname : "";
const expectedHost = new URL(env.app.dashboardUrl).host;
const isExpectedHost = currentHost === expectedHost;
const [reo, setReo] = useState<any | undefined>(undefined);
useEffect(() => {
async function init() {
if (env.app.environment !== "prod") {
return;
}
try {
const clientID = env.server.reoClientId;
const reoClient = await loadReoScript({ clientID });
await reoClient.init({ clientID });
setReo(reoClient);
} catch (e) {
console.error("Failed to load Reo script", e);
}
}
if (build == "saas") {
init();
}
}, []);
const formSchema = z.object({
email: z.string().email({ message: t("emailInvalid") }),
password: z.string().min(8, { message: t("passwordRequirementsChars") })
@@ -183,26 +210,13 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
}
}
} catch (e: any) {
if (e.isAxiosError) {
setError(
formatAxiosError(
e,
t("securityKeyAuthError", {
defaultValue:
"Failed to authenticate with security key"
})
)
);
} else {
console.error(e);
setError(
e.message ||
t("securityKeyAuthError", {
defaultValue:
"Failed to authenticate with security key"
})
);
}
console.error(e);
setError(
t("securityKeyAuthError", {
defaultValue:
"An unexpected error occurred. Please try again."
})
);
} finally {
setLoading(false);
setShowSecurityKeyPrompt(false);
@@ -224,6 +238,18 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
code
});
try {
const identity = {
username: email,
type: "email" // can be one of email, github, linkedin, gmail, userID,
};
if (reo) {
reo.identify(identity);
}
} catch (e) {
console.error("Reo identify error:", e);
}
if (response.error) {
setError(response.message);
return;
@@ -253,7 +279,11 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
if (data.emailVerificationRequired) {
if (!isExpectedHost) {
setError(t("emailVerificationRequired", { dashboardUrl: env.app.dashboardUrl }));
setError(
t("emailVerificationRequired", {
dashboardUrl: env.app.dashboardUrl
})
);
return;
}
if (redirect) {
@@ -266,7 +296,11 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
if (data.twoFactorSetupRequired) {
if (!isExpectedHost) {
setError(t("twoFactorSetupRequired", { dashboardUrl: env.app.dashboardUrl }));
setError(
t("twoFactorSetupRequired", {
dashboardUrl: env.app.dashboardUrl
})
);
return;
}
const setupUrl = `/auth/2fa/setup?email=${encodeURIComponent(email)}${redirect ? `&redirect=${encodeURIComponent(redirect)}` : ""}`;
@@ -278,25 +312,13 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
await onLogin();
}
} catch (e: any) {
if (e.isAxiosError) {
const errorMessage = formatAxiosError(
e,
t("loginError", {
defaultValue: "Failed to log in"
})
);
setError(errorMessage);
return;
} else {
console.error(e);
setError(
e.message ||
t("loginError", {
defaultValue: "Failed to log in"
})
);
return;
}
console.error(e);
setError(
t("loginError", {
defaultValue:
"An unexpected error occurred. Please try again."
})
);
} finally {
setLoading(false);
}
@@ -307,7 +329,8 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
try {
const data = await generateOidcUrlProxy(
idpId,
redirect || "/"
redirect || "/",
orgId
);
const url = data.data?.redirectUrl;
if (data.error) {
@@ -527,7 +550,8 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
</div>
{idps.map((idp) => {
const effectiveType = idp.variant || idp.name.toLowerCase();
const effectiveType =
idp.variant || idp.name.toLowerCase();
return (
<Button