feat: remove hashed passwords feature spec and migrate to archive
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m6s
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m6s
fix: update login token expiration to 62 days chore: bump version to 1.0.5RC1 test: add isParentPersistent to LoginButton.spec.ts refactor: rename Assign Tasks button to Assign Chores in ParentView.vue refactor: rename Assign Tasks to Assign Chores in TaskAssignView.vue feat: add stay in parent mode checkbox and badge in LoginButton.vue test: enhance LoginButton.spec.ts with persistent mode tests test: add authGuard.spec.ts for logoutParent and enforceParentExpiry feat: implement parent mode expiry logic in auth.ts test: add auth.expiry.spec.ts for parent mode expiry tests chore: create template for feature specs
This commit is contained in:
@@ -1,44 +1,117 @@
|
||||
import { ref, watch } from 'vue'
|
||||
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)
|
||||
|
||||
// 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
|
||||
} else {
|
||||
localStorage.removeItem(PARENT_AUTH_KEY)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
localStorage.removeItem(PARENT_AUTH_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
export const isParentAuthenticated = ref(
|
||||
hasLocalStorage ? localStorage.getItem('isParentAuthenticated') === 'true' : false,
|
||||
)
|
||||
export const isUserLoggedIn = ref(false)
|
||||
export const isAuthReady = ref(false)
|
||||
export const currentUserId = ref('')
|
||||
let authSyncInitialized = false
|
||||
|
||||
watch(isParentAuthenticated, (val) => {
|
||||
if (hasLocalStorage && typeof localStorage.setItem === 'function') {
|
||||
localStorage.setItem('isParentAuthenticated', val ? 'true' : 'false')
|
||||
}
|
||||
})
|
||||
// --- Background expiry watcher ---
|
||||
let expiryWatcherIntervalId: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
export function authenticateParent() {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
applyParentLoggedOutState()
|
||||
broadcastParentLogoutEvent()
|
||||
}
|
||||
|
||||
function applyParentLoggedOutState() {
|
||||
parentAuthExpiresAt.value = null
|
||||
isParentPersistent.value = false
|
||||
isParentAuthenticated.value = false
|
||||
if (hasLocalStorage && typeof localStorage.removeItem === 'function') {
|
||||
localStorage.removeItem('isParentAuthenticated')
|
||||
localStorage.removeItem(PARENT_AUTH_KEY)
|
||||
}
|
||||
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
|
||||
isParentAuthenticated.value = false
|
||||
applyParentLoggedOutState()
|
||||
}
|
||||
|
||||
function applyLoggedOutState() {
|
||||
isUserLoggedIn.value = false
|
||||
currentUserId.value = ''
|
||||
logoutParent()
|
||||
applyParentLoggedOutState()
|
||||
}
|
||||
|
||||
function broadcastLogoutEvent() {
|
||||
@@ -64,6 +137,11 @@ export function initAuthSync() {
|
||||
if (!window.location.pathname.startsWith('/auth')) {
|
||||
window.location.href = '/auth/login'
|
||||
}
|
||||
} else if (payload?.type === 'parent_logout') {
|
||||
applyParentLoggedOutState()
|
||||
if (window.location.pathname.startsWith('/parent')) {
|
||||
window.location.href = '/child'
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore malformed sync events.
|
||||
|
||||
Reference in New Issue
Block a user