feat: add PIN-based login with server-side cookie sessions
Deploy HiHala Dashboard / deploy (push) Successful in 6s

- Server: POST /auth/login (verify PIN, set httpOnly cookie)
- Server: GET /auth/check, POST /auth/logout
- Client: Login page shown when not authenticated
- Session persists 7 days via httpOnly cookie
- PIN stored server-side only (ADMIN_PIN env var)
- Dashboard loads data only after successful auth

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
fahed
2026-03-31 22:02:34 +03:00
parent c99f2abe10
commit 8cf6f9eedd
12 changed files with 331 additions and 4 deletions
+38 -3
View File
@@ -4,6 +4,7 @@ import { BrowserRouter as Router, Routes, Route, Link, useLocation } from 'react
const Dashboard = lazy(() => import('./components/Dashboard'));
const Comparison = lazy(() => import('./components/Comparison'));
const Settings = lazy(() => import('./components/Settings'));
import Login from './components/Login';
import LoadingSkeleton from './components/shared/LoadingSkeleton';
import { fetchData, getCacheStatus, refreshData } from './services/dataService';
import { fetchSeasons } from './services/seasonsService';
@@ -36,6 +37,7 @@ interface DataSource {
function App() {
const { t, dir, switchLanguage } = useLanguage();
const [authenticated, setAuthenticated] = useState<boolean | null>(null);
const [data, setData] = useState<MuseumRecord[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [refreshing, setRefreshing] = useState<boolean>(false);
@@ -105,16 +107,49 @@ function App() {
setSeasons(s);
}, []);
// Check auth on mount
useEffect(() => {
loadData();
loadSeasons();
fetch('/auth/check', { credentials: 'include' })
.then(r => r.json())
.then(d => {
setAuthenticated(d.authenticated);
if (d.authenticated) {
loadData();
loadSeasons();
}
})
.catch(() => setAuthenticated(false));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleLogin = () => {
setAuthenticated(true);
loadData();
loadSeasons();
};
const handleRefresh = () => {
loadData(true);
};
// Auth check loading
if (authenticated === null) {
return (
<div className="app" dir={dir}>
<LoadingSkeleton />
</div>
);
}
// Not authenticated — show login
if (!authenticated) {
return (
<div className="app" dir={dir}>
<Login onLogin={handleLogin} />
</div>
);
}
if (loading) {
return (
<div className="app" dir={dir}>