Skip to content

Commit 2246f12

Browse files
committed
fix: admin login CSRF, error handling, and FOUC
1 parent 2e8d10c commit 2246f12

3 files changed

Lines changed: 28 additions & 17 deletions

File tree

public/index.html

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@
1515
</head>
1616
<body>
1717

18-
<div id="app"></div>
18+
<div id="app">
19+
<div class="auth-page" style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:24px;">
20+
<div class="auth-card" style="text-align:center;padding:48px;">
21+
<span class="spinner" style="width:32px;height:32px;border-width:3px;margin:0 auto 16px;display:block;"></span>
22+
<p style="color:var(--text-secondary);">Loading...</p>
23+
</div>
24+
</div>
25+
</div>
1926

2027
<script src="/js/app.js?v=6"></script>
2128
<script src="/js/admin.js?v=6"></script>

public/js/admin.js

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -306,29 +306,33 @@ async function handleAdminLogin(e) {
306306
btn.innerHTML = '<span class="spinner"></span> Signing in...';
307307

308308
try {
309-
const capWidget = $a('#admin-login-form cap-widget');
310-
const capToken = capWidget?.token || '';
311-
const data = await adminApi('/login', {
312-
method: 'POST',
313-
body: JSON.stringify({
314-
email: $a('#admin-email').value,
315-
password: $a('#admin-password').value,
316-
capToken,
317-
}),
318-
});
309+
const capWidget = $a('#admin-login-form cap-widget');
310+
const capToken = capWidget?.token || '';
311+
const data = await adminApi('/login', {
312+
method: 'POST',
313+
body: JSON.stringify({
314+
email: $a('#admin-email').value,
315+
password: $a('#admin-password').value,
316+
capToken,
317+
}),
318+
});
319319
adminState.token = data.token;
320320
adminState.user = data.user;
321321
localStorage.setItem(ADMIN_STORAGE_KEY, data.token);
322322
history.replaceState({ adminPage: 'dashboard' }, '', '/admin/dashboard');
323323
renderAdminLayout();
324324
} catch (err) {
325-
if (errorEl) {
326-
errorEl.textContent = err.message;
327-
errorEl.classList.add('show');
325+
const currentErrorEl = $a('#admin-login-form .auth-error');
326+
if (currentErrorEl) {
327+
currentErrorEl.textContent = err.message;
328+
currentErrorEl.classList.add('show');
328329
}
329330
} finally {
330-
btn.disabled = false;
331-
btn.innerHTML = 'Sign In';
331+
const currentBtn = $a('#admin-login-btn');
332+
if (currentBtn) {
333+
currentBtn.disabled = false;
334+
currentBtn.innerHTML = 'Sign In';
335+
}
332336
}
333337
}
334338

server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ app.use((err, req, res, next) => {
178178
});
179179
app.use(cookieParser(process.env.COOKIE_SECRET));
180180

181-
const csrfExemptPaths = ['/api/auth/login', '/api/auth/register', '/api/auth/passkey/options', '/api/auth/passkey/verify', '/api/auth/passkeys/login/begin', '/api/auth/passkeys/login/complete', '/api/auth/passkeys/register/begin', '/api/auth/passkeys/register/complete', '/api/auth/totp/verify', '/api/auth/totp/recovery'];
181+
const csrfExemptPaths = ['/api/auth/login', '/api/auth/register', '/api/auth/passkey/options', '/api/auth/passkey/verify', '/api/auth/passkeys/login/begin', '/api/auth/passkeys/login/complete', '/api/auth/passkeys/register/begin', '/api/auth/passkeys/register/complete', '/api/auth/totp/verify', '/api/auth/totp/recovery', '/api/admin/login'];
182182
app.use((req, res, next) => {
183183
if (!req.path.startsWith('/api/')) return next();
184184
if (csrfExemptPaths.includes(req.path)) return next();

0 commit comments

Comments
 (0)