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
+74
View File
@@ -0,0 +1,74 @@
import React, { useState } from 'react';
import { useLanguage } from '../contexts/LanguageContext';
interface LoginProps {
onLogin: () => void;
}
function Login({ onLogin }: LoginProps) {
const { t } = useLanguage();
const [pin, setPin] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setLoading(true);
try {
const res = await fetch('/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ pin }),
});
if (!res.ok) {
setError(t('login.invalid'));
setLoading(false);
return;
}
onLogin();
} catch {
setError(t('login.error'));
setLoading(false);
}
};
return (
<div className="login-page">
<div className="login-card">
<div className="login-brand">
<svg width="32" height="32" viewBox="0 0 24 24" fill="var(--accent)" aria-hidden="true">
<rect x="3" y="3" width="7" height="7" rx="1"/>
<rect x="14" y="3" width="7" height="4" rx="1"/>
<rect x="3" y="14" width="7" height="7" rx="1"/>
<rect x="14" y="11" width="7" height="10" rx="1"/>
</svg>
<h1>HiHala Data</h1>
</div>
<p className="login-subtitle">{t('login.subtitle')}</p>
<form onSubmit={handleSubmit}>
<input
type="password"
inputMode="numeric"
value={pin}
onChange={e => setPin(e.target.value)}
placeholder={t('login.placeholder')}
autoFocus
disabled={loading}
/>
{error && <p className="login-error">{error}</p>}
<button type="submit" disabled={loading || !pin}>
{loading ? '...' : t('login.submit')}
</button>
</form>
</div>
</div>
);
}
export default Login;