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:
+4
-2
@@ -834,9 +834,11 @@ def login(body: dict):
|
|||||||
raise HTTPException(status_code=401, detail="Invalid username or password")
|
raise HTTPException(status_code=401, detail="Invalid username or password")
|
||||||
|
|
||||||
token = _generate_token()
|
token = _generate_token()
|
||||||
|
remember_me = body.get("remember_me", False)
|
||||||
|
expiry = "+30 days" if remember_me else "+1 day"
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO sessions (user_id, token, expires_at) VALUES (?, ?, datetime('now', '+1 day'))",
|
"INSERT INTO sessions (user_id, token, expires_at) VALUES (?, ?, datetime('now', ?))",
|
||||||
(row["id"], token),
|
(row["id"], token, expiry),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
_log_activity(conn, "login", "user", row["id"],
|
_log_activity(conn, "login", "user", row["id"],
|
||||||
|
|||||||
+15
-5
@@ -937,6 +937,9 @@
|
|||||||
<div id="loginError" class="login-error"></div>
|
<div id="loginError" class="login-error"></div>
|
||||||
<input id="loginUser" class="input-field" type="text" placeholder="Username" autocomplete="username">
|
<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">
|
<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>
|
<button id="loginBtn" class="btn btn-primary btn-block" onclick="doLogin()">Sign In</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1822,6 +1825,7 @@ async function doLogin() {
|
|||||||
const username = document.getElementById('loginUser').value.trim();
|
const username = document.getElementById('loginUser').value.trim();
|
||||||
const password = document.getElementById('loginPass').value;
|
const password = document.getElementById('loginPass').value;
|
||||||
const loginBtn = document.getElementById('loginBtn');
|
const loginBtn = document.getElementById('loginBtn');
|
||||||
|
const rememberMe = document.getElementById('loginRemember').checked;
|
||||||
if (!username || !password) {
|
if (!username || !password) {
|
||||||
showLoginError('Please enter username and password');
|
showLoginError('Please enter username and password');
|
||||||
return;
|
return;
|
||||||
@@ -1832,12 +1836,19 @@ async function doLogin() {
|
|||||||
const data = await api('/api/auth/login', {
|
const data = await api('/api/auth/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
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.currentUser = { id: data.id, username: data.username, role: data.role };
|
||||||
AppState.authToken = data.token;
|
AppState.authToken = data.token;
|
||||||
localStorage.setItem('adminToken', 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');
|
document.getElementById('loginOverlay').classList.add('hidden');
|
||||||
updateUserUI();
|
updateUserUI();
|
||||||
switchPage('dashboard');
|
switchPage('dashboard');
|
||||||
@@ -1875,9 +1886,8 @@ async function initAuth() {
|
|||||||
try {
|
try {
|
||||||
// Verify token by fetching a protected endpoint that returns user info
|
// Verify token by fetching a protected endpoint that returns user info
|
||||||
const users = await api('/api/users?limit=1');
|
const users = await api('/api/users?limit=1');
|
||||||
// If we got here, token is valid — but we need user info.
|
// If we got here, token is valid — try localStorage first, then sessionStorage
|
||||||
// The login response stored user data, try sessionStorage fallback.
|
const savedUser = localStorage.getItem('adminUser') || sessionStorage.getItem('adminUser');
|
||||||
const savedUser = sessionStorage.getItem('adminUser');
|
|
||||||
if (savedUser) {
|
if (savedUser) {
|
||||||
AppState.currentUser = JSON.parse(savedUser);
|
AppState.currentUser = JSON.parse(savedUser);
|
||||||
document.getElementById('loginOverlay').classList.add('hidden');
|
document.getElementById('loginOverlay').classList.add('hidden');
|
||||||
|
|||||||
Reference in New Issue
Block a user