fix(auth): route supabase auth via caddy and harden session setup
This commit is contained in:
parent
bdd5ff768e
commit
3cc12c8b48
|
|
@ -1,5 +1,7 @@
|
|||
chat.serverbob.org {
|
||||
encode gzip
|
||||
@supabaseAuth path /auth/v1/*
|
||||
reverse_proxy @supabaseAuth pulse-kong:8000
|
||||
reverse_proxy pulse:4991
|
||||
tls zax@serverbob.org
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { logDebug } from '@/helpers/browser-logger';
|
|||
import { getHostFromServer } from '@/helpers/get-file-url';
|
||||
import { applyServerPreferences } from '@/lib/preferences-apply';
|
||||
import { seedPreferencesFromLocalStorage } from '@/lib/preferences-seed';
|
||||
import { getAccessToken } from '@/lib/supabase';
|
||||
import { cleanup, connectToTRPC, getHomeTRPCClient } from '@/lib/trpc';
|
||||
import { type TPublicServerSettings, type TServerInfo } from '@pulse/shared';
|
||||
import { store } from '../store';
|
||||
|
|
@ -71,6 +72,11 @@ export const connect = async () => {
|
|||
throw new Error('Failed to fetch server info');
|
||||
}
|
||||
|
||||
const accessToken = await getAccessToken();
|
||||
if (!accessToken) {
|
||||
throw new Error('Missing authentication token');
|
||||
}
|
||||
|
||||
const host = getHostFromServer();
|
||||
const trpc = await connectToTRPC(host);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
setLocalStorageItem
|
||||
} from '@/helpers/storage';
|
||||
import { useForm } from '@/hooks/use-form';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import { getAccessToken, initSupabase, supabase } from '@/lib/supabase';
|
||||
import type { Provider } from '@supabase/supabase-js';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
|
@ -62,6 +62,38 @@ const Connect = memo(() => {
|
|||
return invite || undefined;
|
||||
}, []);
|
||||
|
||||
const ensureSupabaseReady = useCallback(() => {
|
||||
if (supabase) return true;
|
||||
if (info?.supabaseUrl && info?.supabaseAnonKey) {
|
||||
initSupabase(info.supabaseUrl, info.supabaseAnonKey);
|
||||
return !!supabase;
|
||||
}
|
||||
return false;
|
||||
}, [info?.supabaseUrl, info?.supabaseAnonKey]);
|
||||
|
||||
const establishSession = useCallback(
|
||||
async (accessToken: string, refreshToken: string) => {
|
||||
const { error } = await supabase.auth.setSession({
|
||||
access_token: accessToken,
|
||||
refresh_token: refreshToken
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(`Failed to store session: ${error.message}`);
|
||||
}
|
||||
|
||||
// Ensure token is visible to the ws connectionParams path before connect().
|
||||
for (let i = 0; i < 5; i += 1) {
|
||||
const token = await getAccessToken();
|
||||
if (token) return;
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
}
|
||||
|
||||
throw new Error('Failed to read authentication token after login');
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const onRememberCredentialsChange = useCallback(
|
||||
(checked: boolean) => {
|
||||
loginForm.onChange('rememberCredentials', checked);
|
||||
|
|
@ -81,6 +113,10 @@ const Connect = memo(() => {
|
|||
setLoading(true);
|
||||
|
||||
try {
|
||||
if (!ensureSupabaseReady()) {
|
||||
throw new Error('Authentication client is not initialized');
|
||||
}
|
||||
|
||||
const url = getUrlFromServer();
|
||||
const response = await fetch(`${url}/login`, {
|
||||
method: 'POST',
|
||||
|
|
@ -102,10 +138,7 @@ const Connect = memo(() => {
|
|||
refreshToken: string;
|
||||
};
|
||||
|
||||
await supabase.auth.setSession({
|
||||
access_token: data.accessToken,
|
||||
refresh_token: data.refreshToken
|
||||
});
|
||||
await establishSession(data.accessToken, data.refreshToken);
|
||||
|
||||
if (loginForm.values.rememberCredentials) {
|
||||
setLocalStorageItem(LocalStorageKey.EMAIL, loginForm.values.email);
|
||||
|
|
@ -137,12 +170,16 @@ const Connect = memo(() => {
|
|||
setLoading(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [loginForm.values, loginForm.setErrors, inviteCode]);
|
||||
}, [loginForm.values, loginForm.setErrors, inviteCode, ensureSupabaseReady, establishSession]);
|
||||
|
||||
const onRegisterClick = useCallback(async () => {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
if (!ensureSupabaseReady()) {
|
||||
throw new Error('Authentication client is not initialized');
|
||||
}
|
||||
|
||||
const url = getUrlFromServer();
|
||||
const response = await fetch(`${url}/register`, {
|
||||
method: 'POST',
|
||||
|
|
@ -166,10 +203,7 @@ const Connect = memo(() => {
|
|||
refreshToken: string;
|
||||
};
|
||||
|
||||
await supabase.auth.setSession({
|
||||
access_token: data.accessToken,
|
||||
refresh_token: data.refreshToken
|
||||
});
|
||||
await establishSession(data.accessToken, data.refreshToken);
|
||||
|
||||
await connect();
|
||||
|
||||
|
|
@ -197,10 +231,15 @@ const Connect = memo(() => {
|
|||
setLoading(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [registerForm.values, registerForm.setErrors, inviteCode]);
|
||||
}, [registerForm.values, registerForm.setErrors, inviteCode, ensureSupabaseReady, establishSession]);
|
||||
|
||||
const onOAuthClick = useCallback(
|
||||
async (provider: string) => {
|
||||
if (!ensureSupabaseReady()) {
|
||||
toast.error('Authentication client is not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
const redirectTo = new URL(window.location.origin);
|
||||
|
||||
if (inviteCode) {
|
||||
|
|
@ -214,7 +253,7 @@ const Connect = memo(() => {
|
|||
}
|
||||
});
|
||||
},
|
||||
[inviteCode]
|
||||
[inviteCode, ensureSupabaseReady]
|
||||
);
|
||||
|
||||
const logoSrc = useMemo(() => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue