This commit is contained in:
@@ -7,19 +7,43 @@ import { KittyGame } from './components/KittyGame';
|
|||||||
import { FlappyCat } from './components/FlappyCat';
|
import { FlappyCat } from './components/FlappyCat';
|
||||||
import { useAuth } from './hooks/useAuth';
|
import { useAuth } from './hooks/useAuth';
|
||||||
|
|
||||||
// Eksportujemy typ, aby inne pliki mogły go użyć
|
|
||||||
export type View = 'home' | 'login' | 'history' | 'jump-game' | 'flappy-game';
|
export type View = 'home' | 'login' | 'history' | 'jump-game' | 'flappy-game';
|
||||||
|
|
||||||
|
const getSubdomain = () => {
|
||||||
|
const hostname = window.location.hostname;
|
||||||
|
const parts = hostname.split('.');
|
||||||
|
if (parts.length <= 2) return null;
|
||||||
|
return parts[0];
|
||||||
|
};
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [url, setUrl] = useState('');
|
const [url, setUrl] = useState('');
|
||||||
const [view, setView] = useState<View>('home');
|
const [view, setView] = useState<View>('home');
|
||||||
const { isAuthenticated, logout } = useAuth();
|
const { isAuthenticated, logout } = useAuth();
|
||||||
|
const subdomain = getSubdomain();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STAN POCHODNY (Derived State)
|
||||||
|
* Rozwiązuje błąd "cascading renders". Jeśli użytkownik jest na subdomenie
|
||||||
|
* i nie jest zalogowany, automatycznie renderujemy widok logowania,
|
||||||
|
* ale nie nadpisujemy stanu 'view' w nieskończoność.
|
||||||
|
*/
|
||||||
|
const activeView = (subdomain && !isAuthenticated) ? 'login' : view;
|
||||||
|
|
||||||
const renderView = () => {
|
const renderView = () => {
|
||||||
switch (view) {
|
switch (activeView) {
|
||||||
case 'login':
|
case 'login':
|
||||||
return <LoginView onBack={() => setView('home')} onSuccess={() => setView('home')} />;
|
return (
|
||||||
|
<LoginView
|
||||||
|
onBack={() => setView('home')}
|
||||||
|
onSuccess={() => setView('home')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case 'history':
|
case 'history':
|
||||||
|
// Strażnik dostępu dla widoku historii
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
return <LoginView onBack={() => setView('home')} onSuccess={() => setView('home')} />;
|
||||||
|
}
|
||||||
return <HistoryView onBack={() => setView('home')} />;
|
return <HistoryView onBack={() => setView('home')} />;
|
||||||
case 'jump-game':
|
case 'jump-game':
|
||||||
return <KittyGame onBack={() => setView('home')} />;
|
return <KittyGame onBack={() => setView('home')} />;
|
||||||
@@ -37,7 +61,12 @@ function App() {
|
|||||||
isAuthenticated={isAuthenticated}
|
isAuthenticated={isAuthenticated}
|
||||||
onLogout={logout}
|
onLogout={logout}
|
||||||
/>
|
/>
|
||||||
<main>{renderView()}</main>
|
<main>
|
||||||
|
{/* Jeśli użytkownik jest zalogowany (SSO), activeView od razu
|
||||||
|
pokaże Generator, zamiast LoginView.
|
||||||
|
*/}
|
||||||
|
{renderView()}
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,21 @@ import type { AuthResponse } from '../types/auth';
|
|||||||
|
|
||||||
const TOKEN_KEY = 'ktty_shared_token';
|
const TOKEN_KEY = 'ktty_shared_token';
|
||||||
|
|
||||||
// 1. DYNAMICZNE USTALANIE DOMENY
|
/**
|
||||||
|
* Konfiguracja ciasteczek dla SSO.
|
||||||
|
* Na localhost ciasteczka domenowe nie działają, więc używamy undefined.
|
||||||
|
* Na produkcji używamy '.ktty.is', co pozwala współdzielić sesję między subdomenami.
|
||||||
|
*/
|
||||||
const getCookieConfig = () => {
|
const getCookieConfig = () => {
|
||||||
const hostname = window.location.hostname;
|
const hostname = window.location.hostname;
|
||||||
|
const isLocal = hostname === 'localhost' || hostname === '127.0.0.1';
|
||||||
|
|
||||||
// Jeśli testujesz na localhost, nie ustawiamy atrybutu 'domain'
|
return {
|
||||||
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
domain: isLocal ? undefined : '.ktty.is',
|
||||||
return { domain: undefined, secure: false };
|
secure: !isLocal, // Wymagane HTTPS na produkcji
|
||||||
}
|
sameSite: 'Lax' as const,
|
||||||
|
expires: 7 // Token ważny przez 7 dni
|
||||||
// Na produkcji używamy domeny nadrzędnej z kropką
|
};
|
||||||
// To pozwoli s.ktty.is i ktty.is widzieć to samo ciasteczko
|
|
||||||
return { domain: '.ktty.is', secure: true };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSubdomain = (): string | null => {
|
const getSubdomain = (): string | null => {
|
||||||
@@ -31,7 +34,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
const config = getCookieConfig();
|
const config = getCookieConfig();
|
||||||
const subdomain = getSubdomain();
|
const subdomain = getSubdomain();
|
||||||
|
|
||||||
// Inicjalizacja stanu bezpośrednio z ciasteczka
|
// Inicjalizacja stanu: sprawdzamy ciasteczko natychmiast przy ładowaniu strony.
|
||||||
|
// Dzięki temu SSO działa bez opóźnień.
|
||||||
const [token, setToken] = useState<string | null>(() => Cookies.get(TOKEN_KEY) || null);
|
const [token, setToken] = useState<string | null>(() => Cookies.get(TOKEN_KEY) || null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -42,6 +46,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const hashedPassword = await sha512(pass);
|
const hashedPassword = await sha512(pass);
|
||||||
|
// Vite Proxy przekaże to na https://www.ktty.is/api/...
|
||||||
const fullUrl = `/api/v1/user/${endpoint}`;
|
const fullUrl = `/api/v1/user/${endpoint}`;
|
||||||
|
|
||||||
const response = await fetch(fullUrl, {
|
const response = await fetch(fullUrl, {
|
||||||
@@ -57,24 +62,18 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
const data: AuthResponse = await response.json();
|
const data: AuthResponse = await response.json();
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(data?.error || data?.message || 'Błąd logowania');
|
throw new Error(data?.error || data?.message || 'Błąd autoryzacji');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data?.token) {
|
if (data?.token) {
|
||||||
// 2. ZAPIS Z DYNAMICZNĄ KONFIGURACJĄ
|
// Zapisujemy token z flagą domain: '.ktty.is'
|
||||||
Cookies.set(TOKEN_KEY, data.token, {
|
Cookies.set(TOKEN_KEY, data.token, config);
|
||||||
domain: config.domain,
|
|
||||||
expires: 7, // wydłużamy do 7 dni dla wygody
|
|
||||||
secure: config.secure,
|
|
||||||
sameSite: 'lax'
|
|
||||||
});
|
|
||||||
setToken(data.token);
|
setToken(data.token);
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
let errorMessage = 'Wystąpił nieoczekiwany błąd';
|
const msg = err instanceof Error ? err.message : 'Wystąpił nieoczekiwany błąd';
|
||||||
if (err instanceof Error) errorMessage = err.message;
|
setError(msg);
|
||||||
setError(errorMessage);
|
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -82,7 +81,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
}, [subdomain, config]);
|
}, [subdomain, config]);
|
||||||
|
|
||||||
const logout = useCallback(() => {
|
const logout = useCallback(() => {
|
||||||
// 3. USUNIĘCIE MUSI MIEĆ TĘ SAMĄ DOMENĘ CO ZAPIS
|
// Usuwamy ciasteczko z tej samej domeny, na której zostało zapisane
|
||||||
Cookies.remove(TOKEN_KEY, { domain: config.domain });
|
Cookies.remove(TOKEN_KEY, { domain: config.domain });
|
||||||
setToken(null);
|
setToken(null);
|
||||||
}, [config]);
|
}, [config]);
|
||||||
|
|||||||
Reference in New Issue
Block a user