2026-03-19 15:06:32 +03:00
|
|
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
|
|
|
|
import './App.css'
|
|
|
|
|
|
|
|
|
|
|
|
type UserProfile = {
|
|
|
|
|
|
sub: string
|
|
|
|
|
|
name?: string
|
|
|
|
|
|
email?: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 15:47:21 +03:00
|
|
|
|
const NO_AUTH_STORAGE_KEY = 'furumiNodePlayer.runWithoutAuth'
|
|
|
|
|
|
|
2026-03-19 15:06:32 +03:00
|
|
|
|
function App() {
|
|
|
|
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
|
|
const [user, setUser] = useState<UserProfile | null>(null)
|
|
|
|
|
|
const [error, setError] = useState<string | null>(null)
|
2026-03-19 15:47:21 +03:00
|
|
|
|
const [runWithoutAuth, setRunWithoutAuth] = useState(() => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return window.localStorage.getItem(NO_AUTH_STORAGE_KEY) === '1'
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-03-19 15:06:32 +03:00
|
|
|
|
|
|
|
|
|
|
const apiBase = useMemo(() => import.meta.env.VITE_API_BASE_URL ?? '', [])
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-03-19 15:47:21 +03:00
|
|
|
|
if (runWithoutAuth) {
|
|
|
|
|
|
setError(null)
|
|
|
|
|
|
setUser({ sub: 'noauth', name: 'No Auth' })
|
|
|
|
|
|
setLoading(false)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 15:06:32 +03:00
|
|
|
|
const loadMe = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${apiBase}/api/me`, {
|
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (response.status === 401) {
|
|
|
|
|
|
setUser(null)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
throw new Error(`Request failed with status ${response.status}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await response.json()
|
|
|
|
|
|
setUser(data.user ?? null)
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
setError(err instanceof Error ? err.message : 'Failed to load session')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void loadMe()
|
2026-03-19 15:47:21 +03:00
|
|
|
|
}, [apiBase, runWithoutAuth])
|
2026-03-19 15:06:32 +03:00
|
|
|
|
|
|
|
|
|
|
const loginUrl = `${apiBase}/api/login`
|
|
|
|
|
|
const logoutUrl = `${apiBase}/api/logout`
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<main className="page">
|
|
|
|
|
|
<section className="card">
|
|
|
|
|
|
<h1>OIDC Login</h1>
|
|
|
|
|
|
<p className="subtitle">Авторизация обрабатывается на Express сервере.</p>
|
|
|
|
|
|
|
2026-03-19 15:47:21 +03:00
|
|
|
|
<div className="settings">
|
|
|
|
|
|
<label className="toggle">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={runWithoutAuth}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const next = e.target.checked
|
|
|
|
|
|
setRunWithoutAuth(next)
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (next) window.localStorage.setItem(NO_AUTH_STORAGE_KEY, '1')
|
|
|
|
|
|
else window.localStorage.removeItem(NO_AUTH_STORAGE_KEY)
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// ignore
|
|
|
|
|
|
}
|
|
|
|
|
|
setLoading(true)
|
|
|
|
|
|
setUser(null)
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span>Запускать без авторизации</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-19 15:06:32 +03:00
|
|
|
|
{loading && <p>Проверяю сессию...</p>}
|
|
|
|
|
|
{error && <p className="error">Ошибка: {error}</p>}
|
|
|
|
|
|
|
2026-03-19 15:47:21 +03:00
|
|
|
|
{!loading && runWithoutAuth && (
|
|
|
|
|
|
<p className="hint">
|
|
|
|
|
|
Режим без авторизации включён. Для входа отключи настройку выше.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-03-19 15:06:32 +03:00
|
|
|
|
{!loading && !user && (
|
|
|
|
|
|
<a className="btn" href={loginUrl}>
|
|
|
|
|
|
Войти через OIDC
|
|
|
|
|
|
</a>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{!loading && user && (
|
|
|
|
|
|
<div className="profile">
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<strong>ID:</strong> {user.sub}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
{user.name && (
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<strong>Имя:</strong> {user.name}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{user.email && (
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<strong>Email:</strong> {user.email}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
2026-03-19 15:47:21 +03:00
|
|
|
|
{!runWithoutAuth && (
|
|
|
|
|
|
<a className="btn ghost" href={logoutUrl}>
|
|
|
|
|
|
Выйти
|
|
|
|
|
|
</a>
|
|
|
|
|
|
)}
|
2026-03-19 15:06:32 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</main>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default App
|