diff --git a/messages/en-US.json b/messages/en-US.json index 2e97bcd1..94e0266c 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1519,5 +1519,7 @@ "domainPickerSubdomainSanitized": "Subdomain sanitized", "domainPickerSubdomainCorrected": "\"{sub}\" was corrected to \"{sanitized}\"", "resourceAddEntrypointsEditFile": "Edit file: config/traefik/traefik_config.yml", - "resourceExposePortsEditFile": "Edit file: docker-compose.yml" + "resourceExposePortsEditFile": "Edit file: docker-compose.yml", + "emailVerificationRequired": "Email verification is required. Please log in again via {dashboardUrl}/auth/login complete this step. Then, come back here.", + "twoFactorSetupRequired": "Two-factor authentication setup is required. Please log in again via {dashboardUrl}/auth/login complete this step. Then, come back here." } diff --git a/src/actions/server.ts b/src/actions/server.ts new file mode 100644 index 00000000..0aea77c7 --- /dev/null +++ b/src/actions/server.ts @@ -0,0 +1,394 @@ +"use server"; + +import { cookies } from "next/headers"; +import { ResponseT } from "@server/types/Response"; + +type CookieOptions = { + path?: string; + httpOnly?: boolean; + secure?: boolean; + sameSite?: "lax" | "strict" | "none"; + expires?: Date; + maxAge?: number; +}; + +function parseSetCookieString(setCookie: string): { + name: string; + value: string; + options: CookieOptions; +} { + const parts = setCookie.split(";").map((p) => p.trim()); + const [nameValue, ...attrParts] = parts; + const [name, ...valParts] = nameValue.split("="); + const value = valParts.join("="); // handles '=' inside JWT + + const options: CookieOptions = {}; + + for (const attr of attrParts) { + const [k, v] = attr.split("=").map((s) => s.trim()); + switch (k.toLowerCase()) { + case "path": + options.path = v; + break; + case "httponly": + options.httpOnly = true; + break; + case "secure": + options.secure = true; + break; + case "samesite": + options.sameSite = + v?.toLowerCase() as CookieOptions["sameSite"]; + break; + case "expires": + options.expires = new Date(v); + break; + case "max-age": + options.maxAge = parseInt(v, 10); + break; + } + } + + return { name, value, options }; +} + +async function makeApiRequest( + url: string, + method: "GET" | "POST", + body?: any, + additionalHeaders: Record = {} +): Promise> { + // Get existing cookies to forward + const allCookies = await cookies(); + const cookieHeader = allCookies.toString(); + + const headers: Record = { + "Content-Type": "application/json", + "X-CSRF-Token": "x-csrf-protection", + ...(cookieHeader && { Cookie: cookieHeader }), + ...additionalHeaders + }; + + let res: Response; + try { + res = await fetch(url, { + method, + headers, + body: body ? JSON.stringify(body) : undefined + }); + } catch (fetchError) { + console.error("API request failed:", fetchError); + return { + data: null, + success: false, + error: true, + message: "Failed to connect to server. Please try again.", + status: 0 + }; + } + + // Handle Set-Cookie header + const rawSetCookie = res.headers.get("set-cookie"); + if (rawSetCookie) { + try { + const { name, value, options } = parseSetCookieString(rawSetCookie); + const allCookies = await cookies(); + allCookies.set(name, value, options); + } catch (cookieError) { + console.error("Failed to parse Set-Cookie header:", cookieError); + // Continue without setting cookies rather than failing + } + } + + let responseData; + try { + responseData = await res.json(); + } catch (jsonError) { + console.error("Failed to parse response JSON:", jsonError); + return { + data: null, + success: false, + error: true, + message: "Invalid response format from server. Please try again.", + status: res.status + }; + } + + if (!responseData) { + console.error("Invalid response structure:", responseData); + return { + data: null, + success: false, + error: true, + message: + "Invalid response structure from server. Please try again.", + status: res.status + }; + } + + // If the API returned an error, return the error message + if (!res.ok || responseData.error) { + return { + data: null, + success: false, + error: true, + message: + responseData.message || + `Server responded with ${res.status}: ${res.statusText}`, + status: res.status + }; + } + + // Handle successful responses where data can be null + if (responseData.success && responseData.data === null) { + return { + data: null, + success: true, + error: false, + message: responseData.message || "Success", + status: res.status + }; + } + + if (!responseData.data) { + console.error("Invalid response structure:", responseData); + return { + data: null, + success: false, + error: true, + message: + "Invalid response structure from server. Please try again.", + status: res.status + }; + } + + return { + data: responseData.data, + success: true, + error: false, + message: responseData.message || "Success", + status: res.status + }; +} + +// ============================================================================ +// AUTH TYPES AND FUNCTIONS +// ============================================================================ + +export type LoginRequest = { + email: string; + password: string; + code?: string; +}; + +export type LoginResponse = { + useSecurityKey?: boolean; + codeRequested?: boolean; + emailVerificationRequired?: boolean; + twoFactorSetupRequired?: boolean; +}; + +export type SecurityKeyStartRequest = { + email?: string; +}; + +export type SecurityKeyStartResponse = { + tempSessionId: string; + challenge: string; + allowCredentials: any[]; + timeout: number; + rpId: string; + userVerification: "required" | "preferred" | "discouraged"; +}; + +export type SecurityKeyVerifyRequest = { + credential: any; +}; + +export type SecurityKeyVerifyResponse = { + success: boolean; + message?: string; +}; + +export async function loginProxy( + request: LoginRequest +): Promise> { + const serverPort = process.env.SERVER_EXTERNAL_PORT; + const url = `http://localhost:${serverPort}/api/v1/auth/login`; + + console.log("Making login request to:", url); + + return await makeApiRequest(url, "POST", request); +} + +export async function securityKeyStartProxy( + request: SecurityKeyStartRequest +): Promise> { + const serverPort = process.env.SERVER_EXTERNAL_PORT; + const url = `http://localhost:${serverPort}/api/v1/auth/security-key/authenticate/start`; + + console.log("Making security key start request to:", url); + + return await makeApiRequest(url, "POST", request); +} + +export async function securityKeyVerifyProxy( + request: SecurityKeyVerifyRequest, + tempSessionId: string +): Promise> { + const serverPort = process.env.SERVER_EXTERNAL_PORT; + const url = `http://localhost:${serverPort}/api/v1/auth/security-key/authenticate/verify`; + + console.log("Making security key verify request to:", url); + + return await makeApiRequest( + url, + "POST", + request, + { + "X-Temp-Session-Id": tempSessionId + } + ); +} + +// ============================================================================ +// RESOURCE TYPES AND FUNCTIONS +// ============================================================================ + +export type ResourcePasswordRequest = { + password: string; +}; + +export type ResourcePasswordResponse = { + session?: string; +}; + +export type ResourcePincodeRequest = { + pincode: string; +}; + +export type ResourcePincodeResponse = { + session?: string; +}; + +export type ResourceWhitelistRequest = { + email: string; + otp?: string; +}; + +export type ResourceWhitelistResponse = { + otpSent?: boolean; + session?: string; +}; + +export type ResourceAccessResponse = { + success: boolean; + message?: string; +}; + +export async function resourcePasswordProxy( + resourceId: number, + request: ResourcePasswordRequest +): Promise> { + const serverPort = process.env.SERVER_EXTERNAL_PORT; + const url = `http://localhost:${serverPort}/api/v1/auth/resource/${resourceId}/password`; + + console.log("Making resource password request to:", url); + + return await makeApiRequest(url, "POST", request); +} + +export async function resourcePincodeProxy( + resourceId: number, + request: ResourcePincodeRequest +): Promise> { + const serverPort = process.env.SERVER_EXTERNAL_PORT; + const url = `http://localhost:${serverPort}/api/v1/auth/resource/${resourceId}/pincode`; + + console.log("Making resource pincode request to:", url); + + return await makeApiRequest(url, "POST", request); +} + +export async function resourceWhitelistProxy( + resourceId: number, + request: ResourceWhitelistRequest +): Promise> { + const serverPort = process.env.SERVER_EXTERNAL_PORT; + const url = `http://localhost:${serverPort}/api/v1/auth/resource/${resourceId}/whitelist`; + + console.log("Making resource whitelist request to:", url); + + return await makeApiRequest( + url, + "POST", + request + ); +} + +export async function resourceAccessProxy( + resourceId: number +): Promise> { + const serverPort = process.env.SERVER_EXTERNAL_PORT; + const url = `http://localhost:${serverPort}/api/v1/resource/${resourceId}`; + + console.log("Making resource access request to:", url); + + return await makeApiRequest(url, "GET"); +} + +// ============================================================================ +// IDP TYPES AND FUNCTIONS +// ============================================================================ + +export type GenerateOidcUrlRequest = { + redirectUrl: string; +}; + +export type GenerateOidcUrlResponse = { + redirectUrl: string; +}; + +export type ValidateOidcUrlCallbackRequest = { + code: string; + state: string; + storedState: string; +}; + +export type ValidateOidcUrlCallbackResponse = { + redirectUrl: string; +}; + +export async function validateOidcUrlCallbackProxy( + idpId: string, + code: string, + expectedState: string, + stateCookie: string, + loginPageId?: number +): Promise> { + const serverPort = process.env.SERVER_EXTERNAL_PORT; + const url = `http://localhost:${serverPort}/api/v1/auth/idp/${idpId}/oidc/validate-callback${loginPageId ? "?loginPageId=" + loginPageId : ""}`; + + console.log("Making OIDC callback validation request to:", url); + + return await makeApiRequest(url, "POST", { + code: code, + state: expectedState, + storedState: stateCookie + }); +} + +export async function generateOidcUrlProxy( + idpId: number, + redirect: string, + orgId?: string +): Promise> { + const serverPort = process.env.SERVER_EXTERNAL_PORT; + const url = `http://localhost:${serverPort}/api/v1/auth/idp/${idpId}/oidc/generate-url${orgId ? `?orgId=${orgId}` : ""}`; + + console.log("Making OIDC URL generation request to:", url); + + return await makeApiRequest(url, "POST", { + redirectUrl: redirect || "/" + }); +} diff --git a/src/app/auth/reset-password/page.tsx b/src/app/auth/reset-password/page.tsx index 490f89f7..1245ca09 100644 --- a/src/app/auth/reset-password/page.tsx +++ b/src/app/auth/reset-password/page.tsx @@ -5,6 +5,8 @@ import ResetPasswordForm from "@app/components/ResetPasswordForm"; import Link from "next/link"; import { cleanRedirect } from "@app/lib/cleanRedirect"; import { getTranslations } from "next-intl/server"; +import { internal } from "@app/lib/api"; +import { authCookieHeader } from "@app/lib/api/cookies"; export const dynamic = "force-dynamic"; @@ -22,7 +24,19 @@ export default async function Page(props: { const t = await getTranslations(); if (user) { - redirect("/"); + let loggedOut = false; + try { + // log out the user if they are logged in + await internal.post( + "/auth/logout", + undefined, + await authCookieHeader() + ); + loggedOut = true; + } catch (e) {} + if (!loggedOut) { + redirect("/"); + } } let redirectUrl: string | undefined = undefined; @@ -45,8 +59,8 @@ export default async function Page(props: { diff --git a/src/app/favicon.ico b/src/app/favicon.ico deleted file mode 100644 index bcaab339..00000000 Binary files a/src/app/favicon.ico and /dev/null differ diff --git a/src/app/page.tsx b/src/app/page.tsx index 5c150c58..06b6b61c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -29,10 +29,13 @@ export default async function Page(props: { const getUser = cache(verifySession); const user = await getUser({ skipCheckVerifyEmail: true }); - const setupRes = await internal.get< + let complete = false; + try { + const setupRes = await internal.get< AxiosResponse - >(`/auth/initial-setup-complete`, await authCookieHeader()); - const complete = setupRes.data.data.complete; + >(`/auth/initial-setup-complete`, await authCookieHeader()); + complete = setupRes.data.data.complete; + } catch (e) {} if (!complete) { redirect("/auth/initial-setup"); } diff --git a/src/components/AutoLoginHandler.tsx b/src/components/AutoLoginHandler.tsx index c489a759..f7183076 100644 --- a/src/components/AutoLoginHandler.tsx +++ b/src/components/AutoLoginHandler.tsx @@ -3,9 +3,8 @@ 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 { redirect, useRouter } from "next/navigation"; import { Card, CardHeader, @@ -16,6 +15,7 @@ import { import { Alert, AlertDescription } from "@app/components/ui/alert"; import { Loader2, CheckCircle2, AlertCircle } from "lucide-react"; import { useTranslations } from "next-intl"; +import { generateOidcUrlProxy } from "@app/actions/server"; type AutoLoginHandlerProps = { resourceId: number; @@ -40,24 +40,38 @@ export default function AutoLoginHandler({ async function initiateAutoLogin() { setLoading(true); + let doRedirect: string | undefined; try { - const res = await api.post< - AxiosResponse - >(`/auth/idp/${skipToIdpId}/oidc/generate-url`, { + const response = await generateOidcUrlProxy( + skipToIdpId, redirectUrl - }); + ); - if (res.data.data.redirectUrl) { - // Redirect to the IDP for authentication - window.location.href = res.data.data.redirectUrl; + if (response.error) { + setError(response.message); + setLoading(false); + return; + } + + const data = response.data; + const url = data?.redirectUrl; + if (url) { + doRedirect = url; } else { setError(t("autoLoginErrorNoRedirectUrl")); } - } catch (e) { + } catch (e: any) { console.error("Failed to generate OIDC URL:", e); - setError(formatAxiosError(e, t("autoLoginErrorGeneratingUrl"))); + setError( + t("autoLoginErrorGeneratingUrl", { + defaultValue: "An unexpected error occurred. Please try again." + }) + ); } finally { setLoading(false); + if (doRedirect) { + redirect(doRedirect); + } } } diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index 65e57156..2bef4ac0 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -28,7 +28,6 @@ import { AxiosResponse } from "axios"; import { formatAxiosError } from "@app/lib/api"; import { LockIcon, FingerprintIcon } from "lucide-react"; import { createApiClient } from "@app/lib/api"; -import { useEnvContext } from "@app/hooks/useEnvContext"; import { InputOTP, InputOTPGroup, @@ -42,6 +41,14 @@ import { GenerateOidcUrlResponse } from "@server/routers/idp"; import { Separator } from "./ui/separator"; import { useTranslations } from "next-intl"; import { startAuthentication } from "@simplewebauthn/browser"; +import { + generateOidcUrlProxy, + loginProxy, + securityKeyStartProxy, + securityKeyVerifyProxy +} from "@app/actions/server"; +import { redirect as redirectTo } from "next/navigation"; +import { useEnvContext } from "@app/hooks/useEnvContext"; export type LoginFormIDP = { idpId: number; @@ -70,6 +77,9 @@ 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 expectedHost = new URL(env.app.dashboardUrl).host; + const isExpectedHost = currentHost === expectedHost; const formSchema = z.object({ email: z.string().email({ message: t("emailInvalid") }), @@ -102,39 +112,39 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) { try { // Start WebAuthn authentication without email - const startRes = await api.post( - "/auth/security-key/authenticate/start", - {} - ); + const startResponse = await securityKeyStartProxy({}); - if (!startRes) { - setError( - t("securityKeyAuthError", { - defaultValue: - "Failed to start security key authentication" - }) - ); + if (startResponse.error) { + setError(startResponse.message); return; } - const { tempSessionId, ...options } = startRes.data.data; + const { tempSessionId, ...options } = startResponse.data!; // Perform WebAuthn authentication try { - const credential = await startAuthentication(options); + const credential = await startAuthentication({ + optionsJSON: { + ...options, + userVerification: options.userVerification as + | "required" + | "preferred" + | "discouraged" + } + }); // Verify authentication - const verifyRes = await api.post( - "/auth/security-key/authenticate/verify", + const verifyResponse = await securityKeyVerifyProxy( { credential }, - { - headers: { - "X-Temp-Session-Id": tempSessionId - } - } + tempSessionId ); - if (verifyRes) { + if (verifyResponse.error) { + setError(verifyResponse.message); + return; + } + + if (verifyResponse.success) { if (onLogin) { await onLogin(); } @@ -208,30 +218,44 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) { setShowSecurityKeyPrompt(false); try { - const res = await api.post>( - "/auth/login", - { - email, - password, - code + const response = await loginProxy({ + email, + password, + code + }); + + if (response.error) { + setError(response.message); + return; + } + + const data = response.data; + + // Handle case where data is null (e.g., already logged in) + if (!data) { + if (onLogin) { + await onLogin(); } - ); + return; + } - const data = res.data.data; - - if (data?.useSecurityKey) { + if (data.useSecurityKey) { await initiateSecurityKeyAuth(); return; } - if (data?.codeRequested) { + if (data.codeRequested) { setMfaRequested(true); setLoading(false); mfaForm.reset(); return; } - if (data?.emailVerificationRequired) { + if (data.emailVerificationRequired) { + if (!isExpectedHost) { + setError(t("emailVerificationRequired", { dashboardUrl: env.app.dashboardUrl })); + return; + } if (redirect) { router.push(`/auth/verify-email?redirect=${redirect}`); } else { @@ -240,7 +264,11 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) { return; } - if (data?.twoFactorSetupRequired) { + if (data.twoFactorSetupRequired) { + if (!isExpectedHost) { + setError(t("twoFactorSetupRequired", { dashboardUrl: env.app.dashboardUrl })); + return; + } const setupUrl = `/auth/2fa/setup?email=${encodeURIComponent(email)}${redirect ? `&redirect=${encodeURIComponent(redirect)}` : ""}`; router.push(setupUrl); return; @@ -275,25 +303,26 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) { } async function loginWithIdp(idpId: number) { + let redirectUrl: string | undefined; try { - const res = await api.post>( - `/auth/idp/${idpId}/oidc/generate-url`, - { - redirectUrl: redirect || "/" - } + const data = await generateOidcUrlProxy( + idpId, + redirect || "/" ); - - console.log(res); - - if (!res) { - setError(t("loginError")); + const url = data.data?.redirectUrl; + if (data.error) { + setError(data.message); return; } - - const data = res.data.data; - window.location.href = data.redirectUrl; - } catch (e) { - console.error(formatAxiosError(e)); + if (url) { + redirectUrl = url; + } + } catch (e: any) { + setError(e.message || t("loginError")); + console.error(e); + } + if (redirectUrl) { + redirectTo(redirectUrl); } } @@ -355,7 +384,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
{t("passwordForgot")} diff --git a/src/components/ResourceAuthPortal.tsx b/src/components/ResourceAuthPortal.tsx index c9877857..3018d7f2 100644 --- a/src/components/ResourceAuthPortal.tsx +++ b/src/components/ResourceAuthPortal.tsx @@ -39,6 +39,12 @@ import { AuthWithWhitelistResponse } from "@server/routers/resource"; import ResourceAccessDenied from "@app/components/ResourceAccessDenied"; +import { + resourcePasswordProxy, + resourcePincodeProxy, + resourceWhitelistProxy, + resourceAccessProxy, +} from "@app/actions/server"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; @@ -173,100 +179,126 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { return fullUrl.toString(); } - const onWhitelistSubmit = (values: any) => { + const onWhitelistSubmit = async (values: any) => { setLoadingLogin(true); - api.post>( - `/auth/resource/${props.resource.id}/whitelist`, - { email: values.email, otp: values.otp } - ) - .then((res) => { - setWhitelistError(null); + setWhitelistError(null); - if (res.data.data.otpSent) { - setOtpState("otp_sent"); - submitOtpForm.setValue("email", values.email); - toast({ - title: t("otpEmailSent"), - description: t("otpEmailSentDescription") - }); - return; - } + try { + const response = await resourceWhitelistProxy(props.resource.id, { + email: values.email, + otp: values.otp + }); - const session = res.data.data.session; - if (session) { - window.location.href = appendRequestToken( - props.redirect, - session - ); - } - }) - .catch((e) => { - console.error(e); - setWhitelistError( - formatAxiosError(e, t("otpEmailErrorAuthenticate")) - ); - }) - .then(() => setLoadingLogin(false)); - }; - - const onPinSubmit = (values: z.infer) => { - setLoadingLogin(true); - api.post>( - `/auth/resource/${props.resource.id}/pincode`, - { pincode: values.pin } - ) - .then((res) => { - setPincodeError(null); - const session = res.data.data.session; - if (session) { - window.location.href = appendRequestToken( - props.redirect, - session - ); - } - }) - .catch((e) => { - console.error(e); - setPincodeError( - formatAxiosError(e, t("pincodeErrorAuthenticate")) - ); - }) - .then(() => setLoadingLogin(false)); - }; - - const onPasswordSubmit = (values: z.infer) => { - setLoadingLogin(true); - - api.post>( - `/auth/resource/${props.resource.id}/password`, - { - password: values.password + if (response.error) { + setWhitelistError(response.message); + return; } - ) - .then((res) => { - setPasswordError(null); - const session = res.data.data.session; - if (session) { - window.location.href = appendRequestToken( - props.redirect, - session - ); - } - }) - .catch((e) => { - console.error(e); - setPasswordError( - formatAxiosError(e, t("passwordErrorAuthenticate")) + + const data = response.data!; + if (data.otpSent) { + setOtpState("otp_sent"); + submitOtpForm.setValue("email", values.email); + toast({ + title: t("otpEmailSent"), + description: t("otpEmailSentDescription") + }); + return; + } + + const session = data.session; + if (session) { + window.location.href = appendRequestToken( + props.redirect, + session ); - }) - .finally(() => setLoadingLogin(false)); + } + } catch (e: any) { + console.error(e); + setWhitelistError( + t("otpEmailErrorAuthenticate", { + defaultValue: "An unexpected error occurred. Please try again." + }) + ); + } finally { + setLoadingLogin(false); + } + }; + + const onPinSubmit = async (values: z.infer) => { + setLoadingLogin(true); + setPincodeError(null); + + try { + const response = await resourcePincodeProxy(props.resource.id, { + pincode: values.pin + }); + + if (response.error) { + setPincodeError(response.message); + return; + } + + const session = response.data!.session; + if (session) { + window.location.href = appendRequestToken( + props.redirect, + session + ); + } + } catch (e: any) { + console.error(e); + setPincodeError( + t("pincodeErrorAuthenticate", { + defaultValue: "An unexpected error occurred. Please try again." + }) + ); + } finally { + setLoadingLogin(false); + } + }; + + const onPasswordSubmit = async (values: z.infer) => { + setLoadingLogin(true); + setPasswordError(null); + + try { + const response = await resourcePasswordProxy(props.resource.id, { + password: values.password + }); + + if (response.error) { + setPasswordError(response.message); + return; + } + + const session = response.data!.session; + if (session) { + window.location.href = appendRequestToken( + props.redirect, + session + ); + } + } catch (e: any) { + console.error(e); + setPasswordError( + t("passwordErrorAuthenticate", { + defaultValue: "An unexpected error occurred. Please try again." + }) + ); + } finally { + setLoadingLogin(false); + } }; async function handleSSOAuth() { let isAllowed = false; try { - await api.get(`/resource/${props.resource.id}`); - isAllowed = true; + const response = await resourceAccessProxy(props.resource.id); + if (response.error) { + setAccessDenied(true); + } else { + isAllowed = true; + } } catch (e) { setAccessDenied(true); } diff --git a/src/components/ValidateOidcToken.tsx b/src/components/ValidateOidcToken.tsx index 6b453ee5..7ba8e145 100644 --- a/src/components/ValidateOidcToken.tsx +++ b/src/components/ValidateOidcToken.tsx @@ -17,6 +17,7 @@ import { Alert, AlertDescription } from "@/components/ui/alert"; import { Loader2, CheckCircle2, AlertCircle } from "lucide-react"; import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext"; import { useTranslations } from "next-intl"; +import { validateOidcUrlCallbackProxy } from "@app/actions/server"; type ValidateOidcTokenParams = { orgId: string; @@ -54,17 +55,27 @@ export default function ValidateOidcToken(props: ValidateOidcTokenParams) { } try { - const res = await api.post< - AxiosResponse - >(`/auth/idp/${props.idpId}/oidc/validate-callback`, { - code: props.code, - state: props.expectedState, - storedState: props.stateCookie - }); + const response = await validateOidcUrlCallbackProxy( + props.idpId, + props.code || "", + props.expectedState || "", + props.stateCookie || "" + ); - console.log(t('idpOidcTokenResponse'), res.data); + if (response.error) { + setError(response.message); + setLoading(false); + return; + } - const redirectUrl = res.data.data.redirectUrl; + const data = response.data; + if (!data) { + setError("Unable to validate OIDC token"); + setLoading(false); + return; + } + + const redirectUrl = data.redirectUrl; if (!redirectUrl) { router.push("/"); @@ -74,9 +85,9 @@ export default function ValidateOidcToken(props: ValidateOidcTokenParams) { await new Promise((resolve) => setTimeout(resolve, 100)); if (redirectUrl.startsWith("http")) { - window.location.href = res.data.data.redirectUrl; // this is validated by the parent using this component + window.location.href = data.redirectUrl; // this is validated by the parent using this component } else { - router.push(res.data.data.redirectUrl); + router.push(data.redirectUrl); } } catch (e) { setError(formatAxiosError(e, t('idpErrorOidcTokenValidating')));