mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-04 10:52:17 +02:00
Initial Commit 🚀 🚀
This commit is contained in:
commit
4f2a629340
444 changed files with 76863 additions and 0 deletions
148
ui/src/lib/utils.ts
Normal file
148
ui/src/lib/utils.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
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<T extends (...args: unknown[]) => unknown>(func: T, wait: number): (...args: Parameters<T>) => void {
|
||||
let timeout: NodeJS.Timeout | null = null;
|
||||
|
||||
return function (...args: Parameters<T>) {
|
||||
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<void> {
|
||||
const { accessToken, userId, providerUserId, redirectPath, openInNewTab = false } = params;
|
||||
|
||||
// Build request body depending on which identifier we have.
|
||||
const body: Record<string, unknown> = {};
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue