add site targets, client resources, and auto login

This commit is contained in:
miloschwartz
2025-08-14 18:24:21 -07:00
parent 67ba225003
commit 5c04b1e14a
80 changed files with 5651 additions and 2385 deletions

View File

@@ -10,6 +10,7 @@ import { Input } from "@/components/ui/input";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
@@ -112,12 +113,11 @@ export default function InitialSetupPage() {
<FormItem>
<FormLabel>{t("setupToken")}</FormLabel>
<FormControl>
<Input
{...field}
placeholder={t("setupTokenPlaceholder")}
autoComplete="off"
/>
<Input {...field} autoComplete="off" />
</FormControl>
<FormDescription>
{t("setupTokenDescription")}
</FormDescription>
<FormMessage />
</FormItem>
)}

View File

@@ -0,0 +1,100 @@
"use client";
import { useEffect, useState } from "react";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { GenerateOidcUrlResponse } from "@server/routers/idp";
import { AxiosResponse } from "axios";
import { useRouter } from "next/navigation";
import {
Card,
CardHeader,
CardTitle,
CardContent,
CardDescription
} from "@app/components/ui/card";
import { Alert, AlertDescription } from "@app/components/ui/alert";
import { Loader2, CheckCircle2, AlertCircle } from "lucide-react";
import { useTranslations } from "next-intl";
type AutoLoginHandlerProps = {
resourceId: number;
skipToIdpId: number;
redirectUrl: string;
};
export default function AutoLoginHandler({
resourceId,
skipToIdpId,
redirectUrl
}: AutoLoginHandlerProps) {
const { env } = useEnvContext();
const api = createApiClient({ env });
const router = useRouter();
const t = useTranslations();
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function initiateAutoLogin() {
setLoading(true);
try {
const res = await api.post<
AxiosResponse<GenerateOidcUrlResponse>
>(`/auth/idp/${skipToIdpId}/oidc/generate-url`, {
redirectUrl
});
if (res.data.data.redirectUrl) {
// Redirect to the IDP for authentication
window.location.href = res.data.data.redirectUrl;
} else {
setError(t("autoLoginErrorNoRedirectUrl"));
}
} catch (e) {
console.error("Failed to generate OIDC URL:", e);
setError(formatAxiosError(e, t("autoLoginErrorGeneratingUrl")));
} finally {
setLoading(false);
}
}
initiateAutoLogin();
}, []);
return (
<div className="flex items-center justify-center min-h-screen">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>{t("autoLoginTitle")}</CardTitle>
<CardDescription>{t("autoLoginDescription")}</CardDescription>
</CardHeader>
<CardContent className="flex flex-col items-center space-y-4">
{loading && (
<div className="flex items-center space-x-2">
<Loader2 className="h-5 w-5 animate-spin" />
<span>{t("autoLoginProcessing")}</span>
</div>
)}
{!loading && !error && (
<div className="flex items-center space-x-2 text-green-600">
<CheckCircle2 className="h-5 w-5" />
<span>{t("autoLoginRedirecting")}</span>
</div>
)}
{error && (
<Alert variant="destructive" className="w-full">
<AlertCircle className="h-5 w-5" />
<AlertDescription className="flex flex-col space-y-2">
<span>{t("autoLoginError")}</span>
<span className="text-xs">{error}</span>
</AlertDescription>
</Alert>
)}
</CardContent>
</Card>
</div>
);
}

View File

@@ -15,6 +15,7 @@ import AccessToken from "./AccessToken";
import { pullEnv } from "@app/lib/pullEnv";
import { LoginFormIDP } from "@app/components/LoginForm";
import { ListIdpsResponse } from "@server/routers/idp";
import AutoLoginHandler from "./AutoLoginHandler";
export const dynamic = "force-dynamic";
@@ -30,11 +31,13 @@ export default async function ResourceAuthPage(props: {
const env = pullEnv();
const authHeader = await authCookieHeader();
let authInfo: GetResourceAuthInfoResponse | undefined;
try {
const res = await internal.get<
AxiosResponse<GetResourceAuthInfoResponse>
>(`/resource/${params.resourceId}/auth`, await authCookieHeader());
>(`/resource/${params.resourceId}/auth`, authHeader);
if (res && res.status === 200) {
authInfo = res.data.data;
@@ -62,10 +65,9 @@ export default async function ResourceAuthPage(props: {
const redirectPort = new URL(searchParams.redirect).port;
const serverResourceHostWithPort = `${serverResourceHost}:${redirectPort}`;
if (serverResourceHost === redirectHost) {
redirectUrl = searchParams.redirect;
} else if ( serverResourceHostWithPort === redirectHost ) {
} else if (serverResourceHostWithPort === redirectHost) {
redirectUrl = searchParams.redirect;
}
} catch (e) {}
@@ -144,6 +146,19 @@ export default async function ResourceAuthPage(props: {
name: idp.name
})) as LoginFormIDP[];
if (authInfo.skipToIdpId && authInfo.skipToIdpId !== null) {
const idp = loginIdps.find((idp) => idp.idpId === authInfo.skipToIdpId);
if (idp) {
return (
<AutoLoginHandler
resourceId={authInfo.resourceId}
skipToIdpId={authInfo.skipToIdpId}
redirectUrl={redirectUrl}
/>
);
}
}
return (
<>
{userIsUnauthorized && isSSOOnly ? (