Files
chore/frontend/vue-app/src/stores/auth.ts
Ryan Kegel 861b3dc9d4
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 1m48s
Refactor code structure and remove redundant sections for improved readability and maintainability
2026-04-10 17:44:37 -04:00

176 lines
5.3 KiB
TypeScript

import { ref } from 'vue'
const hasLocalStorage =
typeof localStorage !== 'undefined' && typeof localStorage.getItem === 'function'
const AUTH_SYNC_EVENT_KEY = 'authSyncEvent'
const PARENT_AUTH_KEY = 'parentAuth'
const PARENT_AUTH_EXPIRY_NON_PERSISTENT = 60_000 // 1 minute
const PARENT_AUTH_EXPIRY_PERSISTENT = 172_800_000 // 2 days
// --- Parent auth expiry state ---
export const isParentAuthenticated = ref(false)
export const isParentPersistent = ref(false)
export const parentAuthExpiresAt = ref<number | null>(null)
// --- Background expiry watcher ---
// Declared before the init block so startParentExpiryWatcher is safe to call during restore.
let expiryWatcherIntervalId: ReturnType<typeof setInterval> | null = null
function runExpiryCheck() {
if (parentAuthExpiresAt.value !== null && Date.now() >= parentAuthExpiresAt.value) {
applyParentLoggedOutState()
if (typeof window !== 'undefined') {
window.location.href = '/child'
}
}
}
export function startParentExpiryWatcher() {
stopParentExpiryWatcher()
expiryWatcherIntervalId = setInterval(runExpiryCheck, 15_000)
}
export function stopParentExpiryWatcher() {
if (expiryWatcherIntervalId !== null) {
clearInterval(expiryWatcherIntervalId)
expiryWatcherIntervalId = null
}
}
// Restore persistent parent auth from localStorage on store init
if (hasLocalStorage) {
try {
const stored = localStorage.getItem(PARENT_AUTH_KEY)
if (stored) {
const parsed = JSON.parse(stored) as { expiresAt: number }
if (parsed.expiresAt && Date.now() < parsed.expiresAt) {
parentAuthExpiresAt.value = parsed.expiresAt
isParentPersistent.value = true
isParentAuthenticated.value = true
startParentExpiryWatcher()
} else {
localStorage.removeItem(PARENT_AUTH_KEY)
}
}
} catch {
localStorage.removeItem(PARENT_AUTH_KEY)
}
}
export const isUserLoggedIn = ref(false)
export const isAuthReady = ref(false)
export const currentUserId = ref('')
export const suppressForceLogout = ref(false)
let authSyncInitialized = false
/**
* Explicitly checks whether parent auth has expired and clears it if so.
* Called by the router guard before allowing /parent routes.
*/
export function enforceParentExpiry() {
runExpiryCheck()
}
export function authenticateParent(persistent: boolean) {
const duration = persistent ? PARENT_AUTH_EXPIRY_PERSISTENT : PARENT_AUTH_EXPIRY_NON_PERSISTENT
parentAuthExpiresAt.value = Date.now() + duration
isParentPersistent.value = persistent
isParentAuthenticated.value = true
if (persistent && hasLocalStorage && typeof localStorage.setItem === 'function') {
localStorage.setItem(PARENT_AUTH_KEY, JSON.stringify({ expiresAt: parentAuthExpiresAt.value }))
}
startParentExpiryWatcher()
}
export function logoutParent() {
if (hasLocalStorage && typeof localStorage.removeItem === 'function') {
localStorage.removeItem(PARENT_AUTH_KEY)
}
applyParentLoggedOutState()
broadcastParentLogoutEvent()
}
function applyParentLoggedOutState() {
parentAuthExpiresAt.value = null
isParentPersistent.value = false
isParentAuthenticated.value = false
stopParentExpiryWatcher()
}
function broadcastParentLogoutEvent() {
if (!hasLocalStorage || typeof localStorage.setItem !== 'function') return
localStorage.setItem(
AUTH_SYNC_EVENT_KEY,
JSON.stringify({ type: 'parent_logout', at: Date.now() }),
)
}
export function loginUser() {
isUserLoggedIn.value = true
// Always start in child mode after login. Also explicitly remove persistent parent
// auth from localStorage so a fresh login always starts in child mode.
if (hasLocalStorage && typeof localStorage.removeItem === 'function') {
localStorage.removeItem(PARENT_AUTH_KEY)
}
applyParentLoggedOutState()
}
function applyLoggedOutState() {
isUserLoggedIn.value = false
currentUserId.value = ''
applyParentLoggedOutState()
}
function broadcastLogoutEvent() {
if (!hasLocalStorage || typeof localStorage.setItem !== 'function') return
localStorage.setItem(AUTH_SYNC_EVENT_KEY, JSON.stringify({ type: 'logout', at: Date.now() }))
}
export function logoutUser() {
applyLoggedOutState()
broadcastLogoutEvent()
}
export function initAuthSync() {
if (authSyncInitialized || typeof window === 'undefined') return
authSyncInitialized = true
window.addEventListener('storage', (event) => {
if (event.key !== AUTH_SYNC_EVENT_KEY || !event.newValue) return
try {
const payload = JSON.parse(event.newValue)
if (payload?.type === 'logout') {
applyLoggedOutState()
if (!window.location.pathname.startsWith('/auth') && window.location.pathname !== '/') {
window.location.href = '/'
}
} else if (payload?.type === 'parent_logout') {
applyParentLoggedOutState()
if (window.location.pathname.startsWith('/parent')) {
window.location.href = '/child'
}
}
} catch {
// Ignore malformed sync events.
}
})
}
export async function checkAuth() {
try {
const res = await fetch('/api/auth/me', { method: 'GET' })
if (res.ok) {
const data = await res.json()
currentUserId.value = data.id
isUserLoggedIn.value = true
} else {
isUserLoggedIn.value = false
currentUserId.value = ''
}
} catch {
isUserLoggedIn.value = false
currentUserId.value = ''
}
isAuthReady.value = true
}