test: testing
All checks were successful
Update changelog / changelog (push) Successful in 28s

This commit is contained in:
Pc
2026-01-04 14:36:29 +01:00
parent 3c1d66ba48
commit 13909c46f6
2 changed files with 54 additions and 26 deletions

View File

@@ -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>
); );
} }

View File

@@ -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]);