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 api.utils import sanitize_email
|
||||||
from config.paths import get_user_image_dir
|
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 (
|
from api.error_codes import (
|
||||||
MISSING_FIELDS, EMAIL_EXISTS, MISSING_TOKEN, INVALID_TOKEN, TOKEN_TIMESTAMP_MISSING,
|
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
|
# Invalidate ALL refresh tokens for this user
|
||||||
refresh_tokens_db.remove(TokenQuery.user_id == user.id)
|
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'})
|
resp = jsonify({'message': 'Password has been reset'})
|
||||||
_clear_auth_cookies(resp)
|
_clear_auth_cookies(resp)
|
||||||
return resp, 200
|
return resp, 200
|
||||||
|
|||||||
@@ -27,3 +27,5 @@ class EventType(Enum):
|
|||||||
CHORE_SCHEDULE_MODIFIED = "chore_schedule_modified"
|
CHORE_SCHEDULE_MODIFIED = "chore_schedule_modified"
|
||||||
CHORE_TIME_EXTENDED = "chore_time_extended"
|
CHORE_TIME_EXTENDED = "chore_time_extended"
|
||||||
CHILD_CHORE_CONFIRMATION = "child_chore_confirmation"
|
CHILD_CHORE_CONFIRMATION = "child_chore_confirmation"
|
||||||
|
|
||||||
|
FORCE_LOGOUT = "force_logout"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function handleUnauthorizedResponse(): void {
|
|||||||
unauthorizedRedirectHandler()
|
unauthorizedRedirectHandler()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
window.location.assign('/auth')
|
window.location.assign('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<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 { 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 userId = ref(currentUserId.value)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
watch(currentUserId, (id) => {
|
watch(currentUserId, (id) => {
|
||||||
userId.value = id
|
userId.value = id
|
||||||
@@ -11,6 +14,19 @@ watch(currentUserId, (id) => {
|
|||||||
|
|
||||||
// Always call useBackendEvents in setup, passing the reactive userId
|
// Always call useBackendEvents in setup, passing the reactive userId
|
||||||
useBackendEvents(userId)
|
useBackendEvents(userId)
|
||||||
|
|
||||||
|
function handleForceLogout() {
|
||||||
|
logoutUser()
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
eventBus.on('force_logout', handleForceLogout)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
eventBus.off('force_logout', handleForceLogout)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<!-- Sticky nav -->
|
<!-- Sticky nav -->
|
||||||
<nav class="landing-nav" :class="{ 'landing-nav-scrolled': scrolled }">
|
<nav class="landing-nav" :class="{ 'landing-nav-scrolled': scrolled }">
|
||||||
<div class="nav-inner">
|
<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>
|
<button class="nav-signin" @click="goToLogin">Sign In</button>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -34,6 +34,10 @@ function goToLogin() {
|
|||||||
router.push({ name: 'Login' })
|
router.push({ name: 'Login' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scrollToTop() {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('scroll', onScroll, { passive: true })
|
window.addEventListener('scroll', onScroll, { passive: true })
|
||||||
})
|
})
|
||||||
@@ -82,6 +86,7 @@ onUnmounted(() => {
|
|||||||
.nav-logo {
|
.nav-logo {
|
||||||
height: 34px;
|
height: 34px;
|
||||||
filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.35));
|
filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.35));
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-signin {
|
.nav-signin {
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ function handleDeleteSuccess() {
|
|||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
// Clear client-side auth and redirect, regardless of logout response
|
// Clear client-side auth and redirect, regardless of logout response
|
||||||
logoutUser()
|
logoutUser()
|
||||||
router.push('/auth/login')
|
router.push('/')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ async function signOut() {
|
|||||||
try {
|
try {
|
||||||
await fetch('/api/auth/logout', { method: 'POST' })
|
await fetch('/api/auth/logout', { method: 'POST' })
|
||||||
logoutUser()
|
logoutUser()
|
||||||
router.push('/auth')
|
router.push('/')
|
||||||
} catch {
|
} catch {
|
||||||
// Optionally show error
|
// Optionally show error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,8 @@ const router = useRouter()
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
// route to the auth landing page instead of using browser history
|
router.push({ name: 'LandingPage' }).catch(() => {
|
||||||
router.push({ name: 'AuthLanding' }).catch(() => {
|
window.location.href = '/'
|
||||||
// fallback to a safe path if named route isn't available
|
|
||||||
window.location.href = '/auth'
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user