import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" import { getAuthUserApiV1UserAuthUserGet } from "@/client/sdk.gen"; import { impersonateApiV1SuperuserImpersonatePost } from "@/client/sdk.gen"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } export function getRandomId() { return Math.floor(Math.random() * 10_000); } export function debounce unknown>(func: T, wait: number): (...args: Parameters) => void { let timeout: NodeJS.Timeout | null = null; return function (...args: Parameters) { if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { func(...args); }, wait); }; } export async function getRedirectUrl(token: string, permissions: { id: string }[] = []) { try { const authUser = await getAuthUserApiV1UserAuthUserGet({ headers: { Authorization: `Bearer ${token}`, }, }); if (authUser.data?.is_superuser) { return "/superadmin"; } const hasAdminPermission = permissions.some(p => p.id === 'admin'); // If the user doesn't have admin permissions, redirect them to // usage page if (!hasAdminPermission) { return "/usage"; } return "/create-workflow"; } catch (error) { console.error("Failed to fetch auth user:", error); // Re-throw the error so the caller can handle it throw error; } } /** * -------------------------------------------------------------------------- * Cookie helpers * -------------------------------------------------------------------------- */ export function setStackRefreshCookie(refreshToken: string) { const expiryDate = new Date(); expiryDate.setFullYear(expiryDate.getFullYear() + 1); const isDograhDomain = window.location.hostname.endsWith('.dograh.com'); const cookieDomainPart = isDograhDomain ? '; domain=.dograh.com' : ''; document.cookie = `stack-refresh-${process.env.NEXT_PUBLIC_STACK_PROJECT_ID}=${refreshToken}; ` + `expires=${expiryDate.toUTCString()}; path=/` + `${cookieDomainPart}; secure; samesite=lax`; } /** * Centralised impersonation logic to avoid code duplication between pages. * * It performs the super-admin impersonate request, sets the cross-sub-domain * refresh cookie and optionally redirects the browser to the supplied path. */ export async function impersonateAsSuperadmin(params: { accessToken: string; userId?: number; providerUserId?: string; redirectPath?: string; /** * If true the browser opens the impersonated session in a **new tab** * (via `window.open`). Defaults to `false` which navigates in the current tab. */ openInNewTab?: boolean; }): Promise { const { accessToken, userId, providerUserId, redirectPath, openInNewTab = false } = params; // Build request body depending on which identifier we have. const body: Record = {}; if (userId !== undefined) { body.user_id = userId; } if (providerUserId !== undefined) { body.provider_user_id = providerUserId; } if (Object.keys(body).length === 0) { throw new Error('Either userId or providerUserId must be provided'); } const resp = await impersonateApiV1SuperuserImpersonatePost({ body, headers: { Authorization: `Bearer ${accessToken}`, }, }); const refreshToken = resp.data?.refresh_token; if (!refreshToken) { throw new Error('No refresh token returned from impersonate'); } // --------------------------------------------------------------------------------- // Instead of setting the cookie here (which would also affect the superadmin // sub-domain), redirect the browser to the dedicated impersonation helper route // (served from the target sub-domain, e.g. app.dograh.com). The route will set the // cookie for the *current* sub-domain only and then forward the user to the final // destination. // --------------------------------------------------------------------------------- // Determine the base URL that should handle the impersonation cookie. If we are on // superadmin.dograh.com we want to switch to app.dograh.com. For any other domain // (e.g. localhost, staging, or already on the app) we just keep the same origin. const appBaseUrl = window.location.origin.includes('superadmin.') ? window.location.origin.replace('superadmin.', 'app.') : window.location.origin; const finalRedirect = redirectPath ?? '/workflow'; // Build the redirect URL to the helper route, passing along the refresh token and // the final destination. const impersonateUrl = `${appBaseUrl}/impersonate?refresh_token=${encodeURIComponent( refreshToken, )}&redirect_path=${encodeURIComponent(finalRedirect)}`; if (openInNewTab) { window.open(impersonateUrl, '_blank'); } else { window.location.href = impersonateUrl; } }