feat: implement force logout event and update navigation redirects
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 1m37s
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 1m37s
This commit is contained in:
@@ -14,6 +14,10 @@ from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
||||
from api.utils import sanitize_email
|
||||
from config.paths import get_user_image_dir
|
||||
from events.sse import send_event_to_user
|
||||
from events.types.event import Event
|
||||
from events.types.event_types import EventType
|
||||
from events.types.payload import Payload
|
||||
|
||||
from api.error_codes import (
|
||||
MISSING_FIELDS, EMAIL_EXISTS, MISSING_TOKEN, INVALID_TOKEN, TOKEN_TIMESTAMP_MISSING,
|
||||
@@ -359,6 +363,9 @@ def reset_password():
|
||||
# Invalidate ALL refresh tokens for this user
|
||||
refresh_tokens_db.remove(TokenQuery.user_id == user.id)
|
||||
|
||||
# Notify all active sessions (other tabs/devices) to sign out immediately
|
||||
send_event_to_user(user.id, Event(EventType.FORCE_LOGOUT.value, Payload({})))
|
||||
|
||||
resp = jsonify({'message': 'Password has been reset'})
|
||||
_clear_auth_cookies(resp)
|
||||
return resp, 200
|
||||
|
||||
@@ -27,3 +27,5 @@ class EventType(Enum):
|
||||
CHORE_SCHEDULE_MODIFIED = "chore_schedule_modified"
|
||||
CHORE_TIME_EXTENDED = "chore_time_extended"
|
||||
CHILD_CHORE_CONFIRMATION = "child_chore_confirmation"
|
||||
|
||||
FORCE_LOGOUT = "force_logout"
|
||||
|
||||
@@ -28,7 +28,7 @@ function handleUnauthorizedResponse(): void {
|
||||
unauthorizedRedirectHandler()
|
||||
return
|
||||
}
|
||||
window.location.assign('/auth')
|
||||
window.location.assign('/')
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useBackendEvents } from '@/common/backendEvents'
|
||||
import { currentUserId } from '@/stores/auth'
|
||||
import { currentUserId, logoutUser } from '@/stores/auth'
|
||||
import { eventBus } from '@/common/eventBus'
|
||||
|
||||
const userId = ref(currentUserId.value)
|
||||
const router = useRouter()
|
||||
|
||||
watch(currentUserId, (id) => {
|
||||
userId.value = id
|
||||
@@ -11,6 +14,19 @@ watch(currentUserId, (id) => {
|
||||
|
||||
// Always call useBackendEvents in setup, passing the reactive userId
|
||||
useBackendEvents(userId)
|
||||
|
||||
function handleForceLogout() {
|
||||
logoutUser()
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
eventBus.on('force_logout', handleForceLogout)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
eventBus.off('force_logout', handleForceLogout)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- Sticky nav -->
|
||||
<nav class="landing-nav" :class="{ 'landing-nav-scrolled': scrolled }">
|
||||
<div class="nav-inner">
|
||||
<img src="/images/c_logo.png" alt="Chorly" class="nav-logo" />
|
||||
<img src="/images/c_logo.png" alt="Chorly" class="nav-logo" @click="scrollToTop" />
|
||||
<button class="nav-signin" @click="goToLogin">Sign In</button>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -34,6 +34,10 @@ function goToLogin() {
|
||||
router.push({ name: 'Login' })
|
||||
}
|
||||
|
||||
function scrollToTop() {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('scroll', onScroll, { passive: true })
|
||||
})
|
||||
@@ -82,6 +86,7 @@ onUnmounted(() => {
|
||||
.nav-logo {
|
||||
height: 34px;
|
||||
filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.35));
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-signin {
|
||||
|
||||
@@ -341,7 +341,7 @@ function handleDeleteSuccess() {
|
||||
}).finally(() => {
|
||||
// Clear client-side auth and redirect, regardless of logout response
|
||||
logoutUser()
|
||||
router.push('/auth/login')
|
||||
router.push('/')
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ async function signOut() {
|
||||
try {
|
||||
await fetch('/api/auth/logout', { method: 'POST' })
|
||||
logoutUser()
|
||||
router.push('/auth')
|
||||
router.push('/')
|
||||
} catch {
|
||||
// Optionally show error
|
||||
}
|
||||
|
||||
@@ -21,10 +21,8 @@ const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const handleBack = () => {
|
||||
// route to the auth landing page instead of using browser history
|
||||
router.push({ name: 'AuthLanding' }).catch(() => {
|
||||
// fallback to a safe path if named route isn't available
|
||||
window.location.href = '/auth'
|
||||
router.push({ name: 'LandingPage' }).catch(() => {
|
||||
window.location.href = '/'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user