supbase
This commit is contained in:
parent
56164fdb31
commit
a20bb1b6e9
@ -7,7 +7,7 @@ import { queryClient } from "@/lib/queryClient";
|
||||
import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom";
|
||||
import { AuthProvider, useAuth } from "@/hooks/useAuth";
|
||||
import { AuthProvider as OidcProvider } from "react-oidc-context";
|
||||
import { WebStorageStateStore } from "oidc-client-ts";
|
||||
import { userManager } from "@/lib/oidc";
|
||||
|
||||
import { LogProvider } from "@/contexts/LogContext";
|
||||
|
||||
@ -274,27 +274,13 @@ const App = () => {
|
||||
initFormatDetection();
|
||||
}, []);
|
||||
|
||||
const oidcConfig = {
|
||||
authority: "https://auth.polymech.info",
|
||||
client_id: "367440527605432321",
|
||||
redirect_uri: window.location.origin + "/authz", // Where Zitadel sends the code back to
|
||||
// Must appear in Zitadel app → Post Logout URIs (same allowlist pattern as redirect_uri; bare origin alone often fails).
|
||||
post_logout_redirect_uri: window.location.origin + "/authz",
|
||||
response_type: "code",
|
||||
scope: "openid profile email",
|
||||
loadUserInfo: true, // Specifically instruct the client to fetch /userinfo
|
||||
// Store tokens in localStorage instead of sessionStorage so they survive new tabs
|
||||
userStore: new WebStorageStateStore({ store: window.localStorage }),
|
||||
onSigninCallback: () => {
|
||||
// Clean up the URL after successful login
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<HelmetProvider>
|
||||
<SWRConfig value={{ provider: () => new Map() }}>
|
||||
<OidcProvider {...oidcConfig}>
|
||||
<OidcProvider
|
||||
userManager={userManager}
|
||||
onSigninCallback={() => window.history.replaceState({}, document.title, window.location.pathname)}
|
||||
>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider>
|
||||
<LogProvider>
|
||||
|
||||
@ -1,310 +1,121 @@
|
||||
import { createContext, useContext, useEffect, useState, useRef } from 'react';
|
||||
import { User, Session } from '@supabase/supabase-js';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { fetchUserRoles, notifyServerLogout } from '@/modules/user/client-user';
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { useAuth as useOidcAuth } from 'react-oidc-context';
|
||||
import { fetchUserRoles } from '@/modules/user/client-user';
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface AuthUser {
|
||||
id: string; // Zitadel numeric sub
|
||||
email: string | undefined;
|
||||
user_metadata: {
|
||||
display_name?: string;
|
||||
username?: string;
|
||||
full_name?: string;
|
||||
};
|
||||
app_metadata: Record<string, unknown>;
|
||||
aud: string;
|
||||
}
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
session: Session | null;
|
||||
roles: string[];
|
||||
loading: boolean;
|
||||
signUp: (email: string, password: string, username: string, displayName: string) => Promise<{ data: any; error: any }>;
|
||||
signIn: (email: string, password: string) => Promise<{ error: any }>;
|
||||
signInWithGithub: () => Promise<{ error: any }>;
|
||||
signInWithGoogle: () => Promise<{ error: any }>;
|
||||
resetPassword: (email: string) => Promise<{ error: any }>;
|
||||
signOut: () => Promise<void>;
|
||||
user: AuthUser | null;
|
||||
/** Always null — Zitadel uses tokens, not Supabase sessions */
|
||||
session: null;
|
||||
roles: string[];
|
||||
loading: boolean;
|
||||
isAuthenticated: boolean;
|
||||
signIn: () => Promise<void>;
|
||||
signOut: () => Promise<void>;
|
||||
/** @deprecated Use signIn() — redirects to Zitadel */
|
||||
signUp: (...args: any[]) => Promise<{ data: null; error: Error }>;
|
||||
/** @deprecated Use signIn() — redirects to Zitadel */
|
||||
signInWithGithub: () => Promise<{ error: null }>;
|
||||
/** @deprecated Use signIn() — redirects to Zitadel */
|
||||
signInWithGoogle: () => Promise<{ error: null }>;
|
||||
/** @deprecated Reset password via Zitadel portal */
|
||||
resetPassword: (...args: any[]) => Promise<{ error: Error }>;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
// ─── Provider ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [roles, setRoles] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const loadingRef = useRef(true); // Track loading state for timeouts/callbacks without closure staleness
|
||||
const { toast } = useToast();
|
||||
const oidcAuth = useOidcAuth();
|
||||
const [roles, setRoles] = useState<string[]>([]);
|
||||
const [rolesLoading, setRolesLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadingRef.current = loading;
|
||||
}, [loading]);
|
||||
useEffect(() => {
|
||||
if (oidcAuth.isLoading) return;
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
let cleanup: (() => void) | undefined;
|
||||
if (!oidcAuth.isAuthenticated || !oidcAuth.user) {
|
||||
setRoles([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Simplified and robust auth initialization
|
||||
const initAuth = async () => {
|
||||
const startTime = Date.now();
|
||||
const MIN_LOADING_TIME = 500;
|
||||
const sub = oidcAuth.user.profile.sub;
|
||||
|
||||
// 1. Setup listener *before* checking session to catch any immediate events
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange(
|
||||
async (event, session) => {
|
||||
if (mounted) {
|
||||
setSession(session);
|
||||
setUser(session?.user ?? null);
|
||||
|
||||
if (session?.user) {
|
||||
// OPTIMIZATION: Check for cached roles to unblock UI immediately
|
||||
const cachedRoles = localStorage.getItem(`polymech-roles-${session.user.id}`);
|
||||
if (cachedRoles) {
|
||||
try {
|
||||
const parsedRoles = JSON.parse(cachedRoles);
|
||||
if (mounted) setRoles(parsedRoles);
|
||||
// If we have cached roles, we can stop loading immediately (after min time)
|
||||
// We will still fetch fresh roles in background
|
||||
if (loadingRef.current) {
|
||||
const elapsed = Date.now() - startTime;
|
||||
if (elapsed < MIN_LOADING_TIME) {
|
||||
await new Promise(r => setTimeout(r, MIN_LOADING_TIME - elapsed));
|
||||
}
|
||||
if (mounted) setLoading(false);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse cached roles', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch fresh roles independently
|
||||
fetchUserRoles(session.user.id).then(roles => {
|
||||
if (!mounted) return;
|
||||
|
||||
// Update cache
|
||||
localStorage.setItem(`polymech-roles-${session.user.id}`, JSON.stringify(roles));
|
||||
|
||||
// Compare with current roles to avoid unnecessary re-renders if possible?
|
||||
// For now just set them. React handles strict equality checks on state setters usually,
|
||||
// but arrays are new references.
|
||||
// Let's just set it.
|
||||
if (mounted) setRoles(roles);
|
||||
|
||||
// If we were still loading (no cache), stop loading now
|
||||
if (loadingRef.current) {
|
||||
if (mounted) setLoading(false);
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('Error fetching user roles:', err);
|
||||
// If we were still loading and failed, we should stop loading (roles=[], guest?)
|
||||
if (loadingRef.current && !cachedRoles) {
|
||||
if (mounted) setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
if (mounted) {
|
||||
setRolesLoading(true);
|
||||
fetchUserRoles(sub)
|
||||
.then(r => setRoles(r as string[]))
|
||||
.catch(err => {
|
||||
console.error('[AuthProvider] Failed to fetch roles:', err);
|
||||
setRoles([]);
|
||||
if (loadingRef.current) setLoading(false);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => setRolesLoading(false));
|
||||
}, [oidcAuth.isAuthenticated, oidcAuth.isLoading]);
|
||||
|
||||
const user: AuthUser | null = oidcAuth.user
|
||||
? {
|
||||
id: oidcAuth.user.profile.sub,
|
||||
email: oidcAuth.user.profile.email,
|
||||
user_metadata: {
|
||||
display_name: oidcAuth.user.profile.name,
|
||||
full_name: oidcAuth.user.profile.name,
|
||||
username: oidcAuth.user.profile.preferred_username,
|
||||
},
|
||||
app_metadata: {},
|
||||
aud: String(oidcAuth.user.profile.aud ?? ''),
|
||||
}
|
||||
}
|
||||
);
|
||||
: null;
|
||||
|
||||
// 2. Trigger initial check, but don't block narrowly on it
|
||||
// The onAuthStateChange often fires INITIAL_SESSION immediately.
|
||||
|
||||
const safetyTimeout = setTimeout(() => {
|
||||
if (mounted && loadingRef.current) {
|
||||
console.warn('Auth initialization safety timeout (5s) - forcing app load');
|
||||
setLoading(false);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
try {
|
||||
const { error } = await supabase.auth.getSession();
|
||||
if (error) throw error;
|
||||
} catch (err) {
|
||||
console.error("getSession failed or timed out", err);
|
||||
// If this fails, the safety timeout or event listener (with null session) should handle it
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearTimeout(safetyTimeout);
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
};
|
||||
|
||||
initAuth().then(c => { cleanup = c; });
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
if (cleanup) cleanup();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const signUp = async (email: string, password: string, username: string, displayName: string) => {
|
||||
const redirectUrl = `${window.location.origin}/`;
|
||||
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
emailRedirectTo: redirectUrl,
|
||||
data: {
|
||||
username,
|
||||
display_name: displayName
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast({
|
||||
title: "Sign up failed",
|
||||
description: error.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: "Check your email",
|
||||
description: "Please check your email to confirm your account.",
|
||||
});
|
||||
}
|
||||
|
||||
return { data, error };
|
||||
};
|
||||
|
||||
const signIn = async (email: string, password: string) => {
|
||||
const { error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast({
|
||||
title: "Sign in failed",
|
||||
description: error.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
|
||||
return { error };
|
||||
};
|
||||
|
||||
const signInWithGithub = async () => {
|
||||
const { error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'github',
|
||||
options: {
|
||||
redirectTo: `${window.location.origin}/`
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast({
|
||||
title: "Sign in failed",
|
||||
description: error.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
|
||||
return { error };
|
||||
};
|
||||
|
||||
const signInWithGoogle = async () => {
|
||||
const redirectTo = `${window.location.origin}/`;
|
||||
const { error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'google',
|
||||
options: {
|
||||
redirectTo
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast({
|
||||
title: "Sign in failed",
|
||||
description: error.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
|
||||
return { error };
|
||||
};
|
||||
|
||||
const resetPassword = async (email: string) => {
|
||||
const baseUrl = import.meta.env.VITE_REDIRECT_URL || window.location.origin;
|
||||
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
||||
redirectTo: `${baseUrl}/auth/update-password`,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast({
|
||||
title: 'Reset failed',
|
||||
description: error.message,
|
||||
variant: 'destructive',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: 'Check your email',
|
||||
description: 'A password reset link has been sent to your email.',
|
||||
});
|
||||
}
|
||||
|
||||
return { error };
|
||||
};
|
||||
|
||||
const signOut = async () => {
|
||||
// 1. Grab current token before signing out (we need it to notify the server)
|
||||
let token: string | null = null;
|
||||
try {
|
||||
const { data } = await supabase.auth.getSession();
|
||||
token = data?.session?.access_token ?? null;
|
||||
} catch { /* ignore — we'll still clean up */ }
|
||||
|
||||
// 2. Notify server to flush its auth cache (fire-and-forget, don't block on errors)
|
||||
if (token) {
|
||||
notifyServerLogout(token);
|
||||
}
|
||||
|
||||
// 3. Attempt Supabase signOut (may fail with 503 if auth service is down)
|
||||
try {
|
||||
await supabase.auth.signOut();
|
||||
} catch (err) {
|
||||
console.warn('Supabase signOut failed (forcing local cleanup):', err);
|
||||
}
|
||||
|
||||
// 4. Force-clear localStorage auth token regardless of Supabase response
|
||||
// The key format is: sb-<project-ref>-auth-token
|
||||
for (const key of Object.keys(localStorage)) {
|
||||
if (key.startsWith('sb-') && key.endsWith('-auth-token')) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Clear cached roles
|
||||
if (user?.id) {
|
||||
localStorage.removeItem(`polymech-roles-${user.id}`);
|
||||
}
|
||||
|
||||
// 6. Reset React state — user is now logged out
|
||||
setUser(null);
|
||||
setSession(null);
|
||||
setRoles([]);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{
|
||||
user,
|
||||
session,
|
||||
roles,
|
||||
loading,
|
||||
signUp,
|
||||
signIn,
|
||||
signInWithGithub,
|
||||
signInWithGoogle,
|
||||
resetPassword,
|
||||
signOut,
|
||||
}}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
user,
|
||||
session: null,
|
||||
roles,
|
||||
loading: oidcAuth.isLoading || rolesLoading,
|
||||
isAuthenticated: oidcAuth.isAuthenticated,
|
||||
signIn: () => oidcAuth.signinRedirect(),
|
||||
signOut: () => oidcAuth.signoutRedirect(),
|
||||
signUp: async () => ({
|
||||
data: null,
|
||||
error: new Error('Sign up via Zitadel — use the /authz page'),
|
||||
}),
|
||||
signInWithGithub: async () => {
|
||||
await oidcAuth.signinRedirect();
|
||||
return { error: null };
|
||||
},
|
||||
signInWithGoogle: async () => {
|
||||
await oidcAuth.signinRedirect();
|
||||
return { error: null };
|
||||
},
|
||||
resetPassword: async () => ({
|
||||
error: new Error('Reset password via the Zitadel portal'),
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// ─── Hook ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return context;
|
||||
const context = useContext(AuthContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
@ -5,7 +5,7 @@ import { useFeedCache } from '@/contexts/FeedCacheContext';
|
||||
import { augmentFeedPosts, FeedPost } from '@/modules/posts/client-posts';
|
||||
import { searchContent, searchContentStream } from '@/modules/search/client-search';
|
||||
import { getCurrentLang } from '@/i18n';
|
||||
import { fetchWithDeduplication } from '@/lib/db';
|
||||
import { fetchWithDeduplication, getAuthToken } from '@/lib/db';
|
||||
import { fetchFeed } from '@/modules/feed/client-feed';
|
||||
|
||||
const { supabase } = await import('@/integrations/supabase/client');
|
||||
@ -120,8 +120,7 @@ export const useFeedData = ({
|
||||
try {
|
||||
// 0. Search source — use streaming API
|
||||
if (source === 'search' && sourceId) {
|
||||
const client = supabaseClient || supabase;
|
||||
const { data: { session } } = await client.auth.getSession();
|
||||
const session = { access_token: await getAuthToken() };
|
||||
|
||||
// Close any existing stream
|
||||
if (streamRef.current) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { queryClient } from './queryClient';
|
||||
import { supabase as defaultSupabase } from "@/integrations/supabase/client";
|
||||
import { userManager } from './oidc';
|
||||
|
||||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
|
||||
@ -72,8 +73,8 @@ export const fetchWithDeduplication = async <T>(
|
||||
export const serverUrl = import.meta.env.VITE_SERVER_IMAGE_API_URL || window.location.origin;
|
||||
|
||||
export const getAuthToken = async (): Promise<string | undefined> => {
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
return sessionData?.session?.access_token;
|
||||
const user = await userManager.getUser();
|
||||
return user?.access_token ?? undefined;
|
||||
};
|
||||
|
||||
/** Helper function to get authorization headers */
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
import { createImage as createImageGoogle, editImage as editImageGoogle } from '@/image-api';
|
||||
import { createImageWithReplicate, editImageWithReplicate } from '@/lib/replicate';
|
||||
//import { createImageWithReplicate, editImageWithReplicate } from '@/lib/replicate';
|
||||
import { createImageWithBria, editImageWithBria } from '@/lib/bria';
|
||||
import { createImageWithAimlApi, editImageWithAimlApi } from '@/lib/aimlapi';
|
||||
|
||||
@ -329,9 +329,6 @@ export const createImage = async (
|
||||
case 'google':
|
||||
return await createImageGoogle(prompt, modelName, apiKey, aspectRatio, resolution, enableSearchGrounding, enableImageSearch);
|
||||
|
||||
case 'replicate':
|
||||
return await createImageWithReplicate(prompt, modelName, apiKey);
|
||||
|
||||
case 'bria':
|
||||
return await createImageWithBria(prompt, modelName, apiKey);
|
||||
|
||||
@ -382,9 +379,6 @@ export const editImage = async (
|
||||
case 'google':
|
||||
return await editImageGoogle(prompt, imageFiles, modelName, apiKey, aspectRatio, resolution, enableSearchGrounding, enableImageSearch);
|
||||
|
||||
case 'replicate':
|
||||
return await editImageWithReplicate(prompt, imageFiles, modelName, apiKey);
|
||||
|
||||
case 'bria':
|
||||
return await editImageWithBria(prompt, imageFiles, modelName, apiKey);
|
||||
|
||||
|
||||
15
packages/ui/src/lib/oidc.ts
Normal file
15
packages/ui/src/lib/oidc.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { UserManager, WebStorageStateStore } from 'oidc-client-ts';
|
||||
|
||||
const authority = import.meta.env.VITE_ZITADEL_AUTHORITY as string;
|
||||
const client_id = import.meta.env.VITE_ZITADEL_CLIENT_ID as string;
|
||||
|
||||
export const userManager = new UserManager({
|
||||
authority,
|
||||
client_id,
|
||||
redirect_uri: window.location.origin + '/authz',
|
||||
post_logout_redirect_uri: window.location.origin + '/authz',
|
||||
response_type: 'code',
|
||||
scope: 'openid profile email',
|
||||
loadUserInfo: true,
|
||||
userStore: new WebStorageStateStore({ store: window.localStorage }),
|
||||
});
|
||||
@ -519,9 +519,8 @@ export const markdownScraperTool = (addLog: LogFunction = defaultLog): RunnableT
|
||||
try {
|
||||
addLog('info', '[SEARCH-TOOLS] markdown_scraper called', { url: args.url });
|
||||
|
||||
const { supabase } = await import('@/integrations/supabase/client');
|
||||
const session = await supabase.auth.getSession();
|
||||
const token = session.data.session?.access_token;
|
||||
const { getAuthToken } = await import('@/lib/db');
|
||||
const token = await getAuthToken();
|
||||
|
||||
const res = await fetch('/api/scrape/markdown', {
|
||||
method: 'POST',
|
||||
@ -782,9 +781,8 @@ export const webSearchTool = (
|
||||
num: String(num),
|
||||
});
|
||||
|
||||
const { supabase } = await import('@/integrations/supabase/client');
|
||||
const session = await supabase.auth.getSession();
|
||||
const token = session.data.session?.access_token;
|
||||
const { getAuthToken } = await import('@/lib/db');
|
||||
const token = await getAuthToken();
|
||||
|
||||
// Call server-side proxy (avoids CORS, hides API key)
|
||||
const res = await fetch(`/api/serpapi/search?${params.toString()}`, {
|
||||
|
||||
@ -173,11 +173,9 @@ export function useChatEngine(namespace = 'chat') {
|
||||
const headers: Record<string, string> = {};
|
||||
// Reuse the auth pattern from useChatEngine's existing getClient
|
||||
try {
|
||||
const { supabase } = await import('@/integrations/supabase/client');
|
||||
const { data } = await supabase.auth.getSession();
|
||||
if (data?.session?.access_token) {
|
||||
headers['Authorization'] = `Bearer ${data.session.access_token}`;
|
||||
}
|
||||
const { getAuthToken } = await import('@/lib/db');
|
||||
const token = await getAuthToken();
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
} catch { }
|
||||
const apiBase = import.meta.env.VITE_SERVER_IMAGE_API_URL || '';
|
||||
const res = await fetch(`${apiBase}/api/vfs/read/${mount}/${clean}`, { headers });
|
||||
@ -257,9 +255,8 @@ export function useChatEngine(namespace = 'chat') {
|
||||
// Read from local session for proxy backend auth
|
||||
let token: string | undefined = undefined;
|
||||
try {
|
||||
const { supabase } = await import('@/integrations/supabase/client');
|
||||
const { data } = await supabase.auth.getSession();
|
||||
token = data?.session?.access_token;
|
||||
const { getAuthToken } = await import('@/lib/db');
|
||||
token = await getAuthToken();
|
||||
} catch { }
|
||||
|
||||
if (!token) return null;
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
import { z } from 'zod';
|
||||
import type { RunnableToolFunctionWithParse } from 'openai/lib/RunnableFunction';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { getAuthToken } from '@/lib/db';
|
||||
|
||||
type LogFunction = (level: 'info' | 'warn' | 'error' | 'debug', message: string, data?: any) => void;
|
||||
const defaultLog: LogFunction = (level, message, data) => console.log(`[FS-TOOLS][${level}] ${message}`, data ?? '');
|
||||
@ -17,8 +17,7 @@ const default_mount = 'home';
|
||||
// ── Auth helper ──────────────────────────────────────────────────────────
|
||||
|
||||
const getToken = async (): Promise<string | null> => {
|
||||
const { data } = await supabase.auth.getSession();
|
||||
return data?.session?.access_token || null;
|
||||
return (await getAuthToken()) ?? null;
|
||||
};
|
||||
|
||||
const vfsFetch = async (method: string, op: string, subpath?: string, body?: string): Promise<Response> => {
|
||||
|
||||
@ -26,8 +26,8 @@ export interface Campaign {
|
||||
// ─── Auth helper ──────────────────────────────────────────────────────────────
|
||||
|
||||
async function authHeaders(contentType?: string): Promise<HeadersInit> {
|
||||
const { data } = await defaultSupabase.auth.getSession();
|
||||
const token = data.session?.access_token;
|
||||
const { getAuthToken } = await import('@/lib/db');
|
||||
const token = await getAuthToken();
|
||||
const h: HeadersInit = {};
|
||||
if (token) h['Authorization'] = `Bearer ${token}`;
|
||||
if (contentType) h['Content-Type'] = contentType;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { supabase as defaultSupabase } from "@/integrations/supabase/client";
|
||||
import { fetchWithDeduplication } from "@/lib/db";
|
||||
import { fetchWithDeduplication, getAuthToken } from "@/lib/db";
|
||||
import { getCurrentLang } from '@/i18n';
|
||||
|
||||
// --- Category Management ---
|
||||
@ -50,8 +49,7 @@ export const fetchCategories = async (options?: { parentSlug?: string; includeCh
|
||||
|
||||
export const createCategory = async (category: Partial<Category> & { parentId?: string; relationType?: string }) => {
|
||||
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
@ -65,8 +63,7 @@ export const createCategory = async (category: Partial<Category> & { parentId?:
|
||||
};
|
||||
|
||||
export const updateCategory = async (id: string, updates: Partial<Category>) => {
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
@ -80,8 +77,7 @@ export const updateCategory = async (id: string, updates: Partial<Category>) =>
|
||||
};
|
||||
|
||||
export const deleteCategory = async (id: string) => {
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = {};
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
|
||||
@ -53,8 +53,8 @@ export interface ContactGroup {
|
||||
// ─── Auth helper ──────────────────────────────────────────────────────────────
|
||||
|
||||
async function authHeaders(contentType?: string): Promise<HeadersInit> {
|
||||
const { data } = await defaultSupabase.auth.getSession();
|
||||
const token = data.session?.access_token;
|
||||
const { getAuthToken } = await import('@/lib/db');
|
||||
const token = await getAuthToken();
|
||||
const h: HeadersInit = {};
|
||||
if (token) h['Authorization'] = `Bearer ${token}`;
|
||||
if (contentType) h['Content-Type'] = contentType;
|
||||
|
||||
@ -9,8 +9,8 @@ const SERVER_URL = import.meta.env.VITE_SERVER_IMAGE_API_URL || '';
|
||||
const API_BASE = '/api/contacts/mailboxes';
|
||||
|
||||
async function authHeaders(contentType?: string): Promise<HeadersInit> {
|
||||
const { data } = await defaultSupabase.auth.getSession();
|
||||
const token = data.session?.access_token;
|
||||
const { getAuthToken } = await import('@/lib/db');
|
||||
const token = await getAuthToken();
|
||||
const h: HeadersInit = {};
|
||||
if (token) h['Authorization'] = `Bearer ${token}`;
|
||||
if (contentType) h['Content-Type'] = contentType;
|
||||
|
||||
@ -10,10 +10,8 @@ const API_BASE = import.meta.env.VITE_API_URL || '';
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function getAuthToken(): Promise<string | null> {
|
||||
// Dynamically import to avoid circular deps
|
||||
const { supabase } = await import('@/integrations/supabase/client');
|
||||
const { data } = await supabase.auth.getSession();
|
||||
return data.session?.access_token || null;
|
||||
const { getAuthToken: getZitadelToken } = await import('@/lib/db');
|
||||
return (await getZitadelToken()) ?? null;
|
||||
}
|
||||
|
||||
async function authFetch(path: string, options: RequestInit = {}): Promise<Response> {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { supabase as defaultSupabase } from "@/integrations/supabase/client";
|
||||
import { z } from "zod";
|
||||
import { fetchWithDeduplication, invalidateCache } from "@/lib/db";
|
||||
import { fetchWithDeduplication, invalidateCache, getAuthToken } from "@/lib/db";
|
||||
|
||||
// --- i18n ---
|
||||
|
||||
@ -25,8 +24,7 @@ export interface Glossary {
|
||||
|
||||
export const translateText = async (text: string, srcLang: string, dstLang: string, glossaryId?: string, format?: 'text' | 'markdown') => {
|
||||
// POST /api/i18n/translate
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
@ -64,8 +62,7 @@ export const fetchGlossaries = async () => {
|
||||
|
||||
export const createGlossary = async (name: string, srcLang: string, dstLang: string, entries: Record<string, string>) => {
|
||||
// POST /api/i18n/glossaries
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
@ -93,8 +90,7 @@ export const createGlossary = async (name: string, srcLang: string, dstLang: str
|
||||
|
||||
export const deleteGlossary = async (id: string) => {
|
||||
// DELETE /api/i18n/glossaries/:id
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = {};
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
@ -171,8 +167,7 @@ export interface WidgetTranslation {
|
||||
export type WidgetTranslationInput = Omit<WidgetTranslation, 'id' | 'created_at' | 'updated_at'>;
|
||||
|
||||
const authHeaders = async (): Promise<HeadersInit> => {
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
return headers;
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
import { supabase as defaultSupabase } from "@/integrations/supabase/client";
|
||||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
import { fetchWithDeduplication } from "@/lib/db";
|
||||
import { fetchWithDeduplication, getAuthToken } from "@/lib/db";
|
||||
|
||||
export const createLayout = async (layoutData: any, client?: SupabaseClient) => {
|
||||
const supabase = client || defaultSupabase;
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json'
|
||||
@ -28,13 +25,8 @@ export const createLayout = async (layoutData: any, client?: SupabaseClient) =>
|
||||
export const getLayout = async (layoutId: string, client?: SupabaseClient) => {
|
||||
const key = `layout-${layoutId}`;
|
||||
return fetchWithDeduplication(key, async () => {
|
||||
const supabase = client || defaultSupabase;
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
const res = await fetch(`/api/layouts/${layoutId}`, {
|
||||
@ -61,13 +53,8 @@ export const getLayouts = async (filters?: { type?: string, visibility?: string,
|
||||
|
||||
const key = `layouts-${params.toString()}`;
|
||||
return fetchWithDeduplication(key, async () => {
|
||||
const supabase = client || defaultSupabase;
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
const res = await fetch(`/api/layouts?${params.toString()}`, {
|
||||
@ -86,8 +73,7 @@ export const getLayouts = async (filters?: { type?: string, visibility?: string,
|
||||
};
|
||||
export const updateLayoutMeta = async (layoutId: string, metaUpdates: any) => {
|
||||
// Fetch current layout to merge meta
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
@ -110,9 +96,7 @@ export const updateLayoutMeta = async (layoutId: string, metaUpdates: any) => {
|
||||
return await updateRes.json();
|
||||
};
|
||||
export const updateLayout = async (layoutId: string, layoutData: any, client?: SupabaseClient) => {
|
||||
const supabase = client || defaultSupabase;
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json'
|
||||
@ -134,9 +118,7 @@ export const updateLayout = async (layoutId: string, layoutData: any, client?: S
|
||||
|
||||
|
||||
export const deleteLayout = async (layoutId: string, client?: SupabaseClient) => {
|
||||
const supabase = client || defaultSupabase;
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json'
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { getAuthToken } from "@/lib/db";
|
||||
import { toast } from "sonner";
|
||||
import { translate, getCurrentLang } from "@/i18n";
|
||||
|
||||
@ -21,11 +22,7 @@ export function useEmailActions({ page, orgSlug }: UseEmailActionsParams) {
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
import("@/integrations/supabase/client").then(({ supabase }) => {
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
setAuthToken(session?.access_token || null);
|
||||
});
|
||||
});
|
||||
getAuthToken().then(token => setAuthToken(token ?? null));
|
||||
}, []);
|
||||
|
||||
const handleSendEmail = async () => {
|
||||
|
||||
@ -2,7 +2,7 @@ import { supabase as defaultSupabase } from "@/integrations/supabase/client";
|
||||
import { UserProfile } from "@/modules/posts/views/types";
|
||||
import { MediaType, MediaItem } from "@/types";
|
||||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
import { fetchWithDeduplication } from "@/lib/db";
|
||||
import { fetchWithDeduplication, getAuthToken } from "@/lib/db";
|
||||
|
||||
export interface FeedPost {
|
||||
id: string; // Post ID
|
||||
@ -40,13 +40,9 @@ export const fetchPostDetailsAPI = async (id: string, options: { sizes?: string,
|
||||
// Let's assume we need to handle auth here or use a helper that does.
|
||||
// To keep it simple for now, we'll import `supabase` and get session.
|
||||
|
||||
const { supabase } = await import('@/integrations/supabase/client');
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
|
||||
const token = await getAuthToken();
|
||||
const headers: Record<string, string> = {};
|
||||
if (session?.access_token) {
|
||||
headers['Authorization'] = `Bearer ${session.access_token}`;
|
||||
}
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
const res = await fetch(url, { headers });
|
||||
if (!res.ok) {
|
||||
@ -95,9 +91,7 @@ export const fetchFullPost = async (postId: string, client?: SupabaseClient) =>
|
||||
};
|
||||
|
||||
export const deletePost = async (id: string, client?: SupabaseClient) => {
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
|
||||
const token = await getAuthToken();
|
||||
if (!token) throw new Error('No active session');
|
||||
|
||||
const response = await fetch(`/api/posts/${id}`, {
|
||||
@ -114,9 +108,7 @@ export const deletePost = async (id: string, client?: SupabaseClient) => {
|
||||
return await response.json();
|
||||
};
|
||||
export const createPost = async (postData: { title: string, description?: string, settings?: any, meta?: any }) => {
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
|
||||
const token = await getAuthToken();
|
||||
if (!token) throw new Error('No active session');
|
||||
|
||||
const response = await fetch(`/api/posts`, {
|
||||
@ -136,9 +128,7 @@ export const createPost = async (postData: { title: string, description?: string
|
||||
};
|
||||
|
||||
export const updatePostDetails = async (postId: string, updates: { title?: string, description?: string, settings?: any, meta?: any }, client?: SupabaseClient) => {
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
|
||||
const token = await getAuthToken();
|
||||
if (!token) throw new Error('No active session');
|
||||
|
||||
const response = await fetch(`/api/posts/${postId}`, {
|
||||
@ -255,9 +245,7 @@ export const augmentFeedPosts = (posts: any[]): FeedPost[] => {
|
||||
};
|
||||
|
||||
export const updatePostMeta = async (postId: string, meta: any, client?: SupabaseClient) => {
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
|
||||
const token = await getAuthToken();
|
||||
if (!token) throw new Error('No active session');
|
||||
|
||||
const response = await fetch(`/api/posts/${postId}`, {
|
||||
|
||||
@ -1,10 +1,4 @@
|
||||
import { supabase } from "@/integrations/supabase/client";
|
||||
|
||||
/** Helper to get auth token */
|
||||
const getAuthToken = async () => {
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
return sessionData.session?.access_token;
|
||||
};
|
||||
import { getAuthToken } from "@/lib/db";
|
||||
|
||||
const API_BASE = import.meta.env.VITE_SERVER_IMAGE_API_URL || '';
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { supabase } from "@/integrations/supabase/client";
|
||||
import { fetchWithDeduplication, invalidateCache } from "@/lib/db";
|
||||
import { fetchWithDeduplication, invalidateCache, getAuthToken } from "@/lib/db";
|
||||
|
||||
export interface TypeDefinition {
|
||||
id: string;
|
||||
@ -43,8 +42,7 @@ export const fetchTypes = async (options?: {
|
||||
if (options?.parentTypeId) params.append('parentTypeId', options.parentTypeId);
|
||||
if (options?.visibility) params.append('visibility', options.visibility);
|
||||
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = {};
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
@ -59,8 +57,7 @@ export const fetchTypes = async (options?: {
|
||||
export const fetchTypeById = async (id: string) => {
|
||||
const key = `type-${id}`;
|
||||
return fetchWithDeduplication(key, async () => {
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = {};
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
@ -81,8 +78,7 @@ export const createType = async (typeData: any) => {
|
||||
// Client side transaction is not easy.
|
||||
// Let's use the API endpoint: POST /api/types
|
||||
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
@ -106,8 +102,7 @@ export const createType = async (typeData: any) => {
|
||||
};
|
||||
|
||||
export const updateType = async (id: string, updates: any) => {
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
@ -132,8 +127,7 @@ export const updateType = async (id: string, updates: any) => {
|
||||
};
|
||||
|
||||
export const deleteType = async (id: string) => {
|
||||
const { data: sessionData } = await supabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getAuthToken();
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { supabase as defaultSupabase } from "@/integrations/supabase/client";
|
||||
import { UserProfile } from "@/modules/posts/views/types";
|
||||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
import { fetchWithDeduplication } from "@/lib/db";
|
||||
import { fetchWithDeduplication, getAuthToken as getZitadelToken } from "@/lib/db";
|
||||
|
||||
const serverUrl = (path: string) => {
|
||||
const baseUrl = import.meta.env.VITE_SERVER_IMAGE_API_URL || window.location.origin;
|
||||
@ -319,8 +319,7 @@ export const updateProfileAPI = async (profileData: {
|
||||
avatar_url?: string | null;
|
||||
settings?: any;
|
||||
}): Promise<any> => {
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getZitadelToken();
|
||||
if (!token) throw new Error('Not authenticated');
|
||||
|
||||
const res = await fetch(serverUrl('/api/profile'), {
|
||||
@ -346,8 +345,7 @@ export const updateUserEmail = async (newEmail: string): Promise<void> => {
|
||||
// =============================================
|
||||
|
||||
const getAuthToken = async (): Promise<string> => {
|
||||
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
||||
const token = sessionData.session?.access_token;
|
||||
const token = await getZitadelToken();
|
||||
if (!token) throw new Error('Not authenticated');
|
||||
return token;
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
* Upload Utility Functions
|
||||
*/
|
||||
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { getAuthToken } from '@/lib/db';
|
||||
|
||||
// Helper to upload internal video
|
||||
export const uploadInternalVideo = async (
|
||||
@ -10,8 +10,7 @@ export const uploadInternalVideo = async (
|
||||
userId: string,
|
||||
onProgress?: (progress: number) => void
|
||||
): Promise<{ dbId: string; thumbnailUrl: string; meta: any }> => {
|
||||
// Get auth token before creating the XHR
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
const token = await getAuthToken();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const serverUrl = import.meta.env.VITE_SERVER_IMAGE_API_URL;
|
||||
@ -21,9 +20,7 @@ export const uploadInternalVideo = async (
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', `${serverUrl}/api/videos/upload?userId=${userId}&title=${encodeURIComponent(title)}&preset=original`);
|
||||
if (session?.access_token) {
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${session.access_token}`);
|
||||
}
|
||||
if (token) xhr.setRequestHeader('Authorization', `Bearer ${token}`);
|
||||
|
||||
xhr.upload.onprogress = (e) => {
|
||||
if (e.lengthComputable && onProgress) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user