add server action proxies

This commit is contained in:
miloschwartz
2025-09-25 17:14:25 -07:00
parent 21f0cd6e3f
commit e555d3c496
9 changed files with 663 additions and 164 deletions

View File

@@ -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."
}

394
src/actions/server.ts Normal file
View File

@@ -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<T>(
url: string,
method: "GET" | "POST",
body?: any,
additionalHeaders: Record<string, string> = {}
): Promise<ResponseT<T>> {
// Get existing cookies to forward
const allCookies = await cookies();
const cookieHeader = allCookies.toString();
const headers: Record<string, string> = {
"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<ResponseT<LoginResponse>> {
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<LoginResponse>(url, "POST", request);
}
export async function securityKeyStartProxy(
request: SecurityKeyStartRequest
): Promise<ResponseT<SecurityKeyStartResponse>> {
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<SecurityKeyStartResponse>(url, "POST", request);
}
export async function securityKeyVerifyProxy(
request: SecurityKeyVerifyRequest,
tempSessionId: string
): Promise<ResponseT<SecurityKeyVerifyResponse>> {
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<SecurityKeyVerifyResponse>(
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<ResponseT<ResourcePasswordResponse>> {
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<ResourcePasswordResponse>(url, "POST", request);
}
export async function resourcePincodeProxy(
resourceId: number,
request: ResourcePincodeRequest
): Promise<ResponseT<ResourcePincodeResponse>> {
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<ResourcePincodeResponse>(url, "POST", request);
}
export async function resourceWhitelistProxy(
resourceId: number,
request: ResourceWhitelistRequest
): Promise<ResponseT<ResourceWhitelistResponse>> {
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<ResourceWhitelistResponse>(
url,
"POST",
request
);
}
export async function resourceAccessProxy(
resourceId: number
): Promise<ResponseT<ResourceAccessResponse>> {
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<ResourceAccessResponse>(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<ResponseT<ValidateOidcUrlCallbackResponse>> {
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<ValidateOidcUrlCallbackResponse>(url, "POST", {
code: code,
state: expectedState,
storedState: stateCookie
});
}
export async function generateOidcUrlProxy(
idpId: number,
redirect: string,
orgId?: string
): Promise<ResponseT<GenerateOidcUrlResponse>> {
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<GenerateOidcUrlResponse>(url, "POST", {
redirectUrl: redirect || "/"
});
}

View File

@@ -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: {
<Link
href={
!searchParams.redirect
? `/auth/signup`
: `/auth/signup?redirect=${redirectUrl}`
? `/auth/login`
: `/auth/login?redirect=${redirectUrl}`
}
className="underline"
>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -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<InitialSetupCompleteResponse>
>(`/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");
}

View File

@@ -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<GenerateOidcUrlResponse>
>(`/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);
}
}
}

View File

@@ -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<AxiosResponse<LoginResponse>>(
"/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<AxiosResponse<GenerateOidcUrlResponse>>(
`/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) {
<div className="text-center">
<Link
href={`/auth/reset-password${form.getValues().email ? `?email=${form.getValues().email}` : ""}`}
href={`${env.app.dashboardUrl}/auth/reset-password${form.getValues().email ? `?email=${form.getValues().email}` : ""}`}
className="text-sm text-muted-foreground"
>
{t("passwordForgot")}

View File

@@ -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<AxiosResponse<AuthWithWhitelistResponse>>(
`/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<typeof pinSchema>) => {
setLoadingLogin(true);
api.post<AxiosResponse<AuthWithPasswordResponse>>(
`/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<typeof passwordSchema>) => {
setLoadingLogin(true);
api.post<AxiosResponse<AuthWithPasswordResponse>>(
`/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<typeof pinSchema>) => {
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<typeof passwordSchema>) => {
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);
}

View File

@@ -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<ValidateOidcUrlCallbackResponse>
>(`/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')));