Add shared device inactivity timeout to AuthGate

This commit is contained in:
jtricerolph 2026-07-13 11:53:04 +00:00
parent e4e6958ff9
commit e0ff800c6b

View file

@ -1,6 +1,13 @@
import { useEffect, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import type { User } from '../types' import type { User } from '../types'
const SHARED_TIMEOUT_MS = 10 * 60 * 1000
function isSharedDevice() {
return document.cookie.split(';').some(c => c.trim() === 'hnf_shared_device=1')
}
interface Props { interface Props {
children: (user: User) => React.ReactNode children: (user: User) => React.ReactNode
} }
@ -23,6 +30,7 @@ export function AuthGate({ children }: Props) {
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
const [error, setError] = useState('') const [error, setError] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
useEffect(() => { useEffect(() => {
fetch('/api/auth/verify?app=hk-planner', { credentials: 'include' }) fetch('/api/auth/verify?app=hk-planner', { credentials: 'include' })
@ -33,6 +41,30 @@ export function AuthGate({ children }: Props) {
.catch(() => setState('login')) .catch(() => setState('login'))
}, []) }, [])
useEffect(() => {
if (state !== 'authed' || !isSharedDevice()) return
async function forceLogout() {
await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' }).catch(() => {})
setUser(null)
setState('login')
}
function reset() {
if (timerRef.current) clearTimeout(timerRef.current)
timerRef.current = setTimeout(forceLogout, SHARED_TIMEOUT_MS)
}
const events = ['mousemove', 'keydown', 'click', 'touchstart'] as const
events.forEach(e => window.addEventListener(e, reset, { passive: true }))
reset()
return () => {
if (timerRef.current) clearTimeout(timerRef.current)
events.forEach(e => window.removeEventListener(e, reset))
}
}, [state])
async function login(e: React.FormEvent) { async function login(e: React.FormEvent) {
e.preventDefault() e.preventDefault()
setLoading(true) setLoading(true)