"use server"; import { cookies, headers as reqHeaders } from "next/headers"; import { ResponseT } from "@server/types/Response"; import { pullEnv } from "@app/lib/pullEnv"; type CookieOptions = { path?: string; httpOnly?: boolean; secure?: boolean; sameSite?: "lax" | "strict" | "none"; expires?: Date; maxAge?: number; domain?: string; }; function parseSetCookieString( setCookie: string, host?: 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 env = pullEnv(); 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; } } if (!options.domain) { const d = host ? host.split(":")[0] // strip port if present : new URL(env.app.dashboardUrl).hostname; if (d) { options.domain = d; } } 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 headersList = await reqHeaders(); const host = headersList.get("host"); const xForwardedFor = headersList.get("x-forwarded-for"); const headers: Record = { "Content-Type": "application/json", "X-CSRF-Token": "x-csrf-protection", ...(xForwardedFor ? { "X-Forwarded-For": xForwardedFor } : {}), ...(cookieHeader && { Cookie: cookieHeader }), ...additionalHeaders }; let res: Response; try { res = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : undefined, cache: "no-store" }); } 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, host || undefined ); 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; resourceGuid?: 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, forceLogin?: boolean ): Promise> { const serverPort = process.env.SERVER_EXTERNAL_PORT; const url = `http://localhost:${serverPort}/api/v1/auth/login${forceLogin ? "?forceLogin=true" : ""}`; console.log("Making login request to:", url); return await makeApiRequest(url, "POST", request); } export async function securityKeyStartProxy( request: SecurityKeyStartRequest, forceLogin?: boolean ): Promise> { const serverPort = process.env.SERVER_EXTERNAL_PORT; const url = `http://localhost:${serverPort}/api/v1/auth/security-key/authenticate/start${forceLogin ? "?forceLogin=true" : ""}`; console.log("Making security key start request to:", url); return await makeApiRequest(url, "POST", request); } export async function securityKeyVerifyProxy( request: SecurityKeyVerifyRequest, tempSessionId: string, forceLogin?: boolean ): Promise> { const serverPort = process.env.SERVER_EXTERNAL_PORT; const url = `http://localhost:${serverPort}/api/v1/auth/security-key/authenticate/verify${forceLogin ? "?forceLogin=true" : ""}`; 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, forceLogin?: boolean ): Promise> { const serverPort = process.env.SERVER_EXTERNAL_PORT; const queryParams = new URLSearchParams(); if (orgId) { queryParams.append("orgId", orgId); } if (forceLogin) { queryParams.append("forceLogin", "true"); } const queryString = queryParams.toString(); const url = `http://localhost:${serverPort}/api/v1/auth/idp/${idpId}/oidc/generate-url${queryString ? `?${queryString}` : ""}`; console.log("Making OIDC URL generation request to:", url); return await makeApiRequest(url, "POST", { redirectUrl: redirect || "/" }); }