feat: Add Remember Me to admin login

- Added Remember Me checkbox to login form with 30-day session expiry
- Sessions default to 1 day; when remember_me=true, expire in 30 days
- User info stored in localStorage (persistent) vs sessionStorage (per-tab)
- initAuth() checks localStorage first, then sessionStorage for user info
This commit is contained in:
Leo
2026-06-01 13:10:26 -04:00
parent ed5e3ff9ec
commit 7a2d283379
2 changed files with 19 additions and 7 deletions
+4 -2
View File
@@ -834,9 +834,11 @@ def login(body: dict):
raise HTTPException(status_code=401, detail="Invalid username or password")
token = _generate_token()
remember_me = body.get("remember_me", False)
expiry = "+30 days" if remember_me else "+1 day"
conn.execute(
"INSERT INTO sessions (user_id, token, expires_at) VALUES (?, ?, datetime('now', '+1 day'))",
(row["id"], token),
"INSERT INTO sessions (user_id, token, expires_at) VALUES (?, ?, datetime('now', ?))",
(row["id"], token, expiry),
)
conn.commit()
_log_activity(conn, "login", "user", row["id"],
+15 -5
View File
@@ -937,6 +937,9 @@
<div id="loginError" class="login-error"></div>
<input id="loginUser" class="input-field" type="text" placeholder="Username" autocomplete="username">
<input id="loginPass" class="input-field" type="password" placeholder="Password" autocomplete="current-password">
<label style="display:flex;align-items:center;gap:6px;font-size:13px;color:var(--text2,#999);margin-top:4px;cursor:pointer;">
<input type="checkbox" id="loginRemember" checked> Remember me (30 days)
</label>
<button id="loginBtn" class="btn btn-primary btn-block" onclick="doLogin()">Sign In</button>
</div>
</div>
@@ -1822,6 +1825,7 @@ async function doLogin() {
const username = document.getElementById('loginUser').value.trim();
const password = document.getElementById('loginPass').value;
const loginBtn = document.getElementById('loginBtn');
const rememberMe = document.getElementById('loginRemember').checked;
if (!username || !password) {
showLoginError('Please enter username and password');
return;
@@ -1832,12 +1836,19 @@ async function doLogin() {
const data = await api('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
body: JSON.stringify({ username, password, remember_me: rememberMe }),
});
AppState.currentUser = { id: data.id, username: data.username, role: data.role };
AppState.authToken = data.token;
localStorage.setItem('adminToken', data.token);
sessionStorage.setItem('adminUser', JSON.stringify(AppState.currentUser));
const userInfo = JSON.stringify(AppState.currentUser);
if (rememberMe) {
localStorage.setItem('adminUser', userInfo);
sessionStorage.removeItem('adminUser');
} else {
sessionStorage.setItem('adminUser', userInfo);
localStorage.removeItem('adminUser');
}
document.getElementById('loginOverlay').classList.add('hidden');
updateUserUI();
switchPage('dashboard');
@@ -1875,9 +1886,8 @@ async function initAuth() {
try {
// Verify token by fetching a protected endpoint that returns user info
const users = await api('/api/users?limit=1');
// If we got here, token is valid — but we need user info.
// The login response stored user data, try sessionStorage fallback.
const savedUser = sessionStorage.getItem('adminUser');
// If we got here, token is valid — try localStorage first, then sessionStorage
const savedUser = localStorage.getItem('adminUser') || sessionStorage.getItem('adminUser');
if (savedUser) {
AppState.currentUser = JSON.parse(savedUser);
document.getElementById('loginOverlay').classList.add('hidden');