elemes/frontend/src/lib/stores/auth.ts

121 lines
3.1 KiB
TypeScript

/**
* Shared authentication state using Svelte writable stores.
*
* Usage in .svelte files:
* import { authToken, authStudentName, authLoggedIn, auth } from '$stores/auth';
* // Use $authLoggedIn, $authStudentName, $authToken for reactive values
* // Use auth.init(), auth.login(), auth.logout() for actions
*/
import { writable, get } from 'svelte/store';
import { validateToken, login as apiLogin, logout as apiLogout } from '$services/api';
const STORAGE_KEY = 'student_token';
const LAST_ACTIVE_KEY = 'student_last_active';
const MAX_INACTIVITY = 24 * 60 * 60 * 1000; // 1 day in ms
export const authToken = writable('');
export const authStudentName = writable('');
export const authLoggedIn = writable(false);
export const authIsTeacher = writable(false);
function clearAllCookies() {
if (typeof document === 'undefined') return;
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i];
const eqPos = cookie.indexOf('=');
const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
}
}
function clearAuthData() {
clearAllCookies();
localStorage.removeItem(STORAGE_KEY);
localStorage.removeItem(LAST_ACTIVE_KEY);
sessionStorage.clear();
}
function updateLastActive() {
if (typeof window !== 'undefined') {
localStorage.setItem(LAST_ACTIVE_KEY, Date.now().toString());
}
}
export const auth = {
/** Current token value (non-reactive). */
get token() { return get(authToken); },
get isLoggedIn() { return get(authLoggedIn); },
/** Restore session from localStorage on app mount. */
async init() {
if (typeof window === 'undefined') return;
const saved = localStorage.getItem(STORAGE_KEY);
const lastActive = localStorage.getItem(LAST_ACTIVE_KEY);
if (!saved) return;
// Check for 1 day inactivity
if (lastActive) {
const inactiveTime = Date.now() - parseInt(lastActive, 10);
if (inactiveTime > MAX_INACTIVITY) {
clearAuthData();
return;
}
}
try {
const res = await validateToken(saved);
if (res.success && res.student_name) {
authToken.set(saved);
authStudentName.set(res.student_name);
authLoggedIn.set(true);
authIsTeacher.set(res.is_teacher ?? false);
updateLastActive();
} else {
clearAuthData();
}
} catch {
clearAuthData();
}
},
async login(inputToken: string) {
clearAuthData();
const res = await apiLogin(inputToken);
if (res.success && res.student_name) {
authToken.set(inputToken);
authStudentName.set(res.student_name);
authLoggedIn.set(true);
authIsTeacher.set(res.is_teacher ?? false);
localStorage.setItem(STORAGE_KEY, inputToken);
updateLastActive();
}
return res;
},
async logout() {
try {
await apiLogout();
} catch {
// ignore logout failure, proceed to clear local state
}
authToken.set('');
authStudentName.set('');
authLoggedIn.set(false);
authIsTeacher.set(false);
clearAuthData();
location.reload();
},
/** Update activity timestamp. Call this on user interactions. */
recordActivity() {
if (get(authLoggedIn)) {
updateLastActive();
}
}
};