fix(routing): catch the paths the split left pointing at nothing

Three dead ends, one cause: these routes resolved to pages that stayed on the
website, and nothing here answered them any more.

/start — this page became the app's front page in the split (it is index.vue
now), so every pre-split bookmark and the website's own redirect landed on a
404. Caught here so the app's old URLs work without waiting for the website to
be redeployed.

/login — the app has no login by design, identity comes from the issuer. But
five call sites still send people there: logout, the live-atc session guard,
the bridge pairing screen. In the monorepo that path was the website's login
form; since the split it was nothing, so signing out dropped the user on a
blank page. It is a forwarder to the issuer, not a login form.

/logout — after clearing the app session it sent the user to /login, which
under SSO means an issuer where they are still signed in: they would be handed
a fresh code and land straight back inside. It now signs them out at the issuer
too, which bumps tokenVersion and invalidates every refresh token.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
itsrubberduck
2026-07-28 19:37:27 +02:00
parent e8ceedf408
commit 32f3998d6c
2 changed files with 40 additions and 1 deletions

28
app/pages/login.vue Normal file
View File

@@ -0,0 +1,28 @@
<script setup lang="ts">
import { buildIssuerLoginUrl } from '~~/shared/utils/ssoHandoff'
/**
* Not a login page — the app has none by design, identity comes from the
* issuer. This only exists because /login is where the code still sends people
* (logout, the live-atc session guard, the bridge pairing screen), and in the
* monorepo that path resolved to the website's login form. Since the split it
* resolved to nothing, so signing out dropped the user on a 404.
*/
const route = useRoute()
const config = useRuntimeConfig()
definePageMeta({ layout: false })
const issuer = String(config.public.authIssuer || '').replace(/\/+$/, '')
const raw = String(route.query.redirect || '/')
// Only same-origin paths — never bounce onward to an absolute URL from the query.
const target = raw.startsWith('/') && !raw.startsWith('//') ? raw : '/'
if (issuer) {
await navigateTo(buildIssuerLoginUrl(issuer, window.location.origin, target), { external: true })
}
else {
// AUTH_MODE=open: no login exists at all, every request is the local identity.
await navigateTo(target, { replace: true })
}
</script>

View File

@@ -21,6 +21,7 @@ import { useAuthStore } from '~/stores/auth'
const router = useRouter()
const auth = useAuthStore()
const config = useRuntimeConfig()
useHead({
title: 'Logout OpenSquawk',
@@ -31,7 +32,17 @@ useHead({
onMounted(async () => {
await auth.logout()
router.replace('/login')
// Sign out at the issuer too. Sending the user to the app's own /login would
// only forward them back to an issuer where they are still signed in — they
// would be handed a fresh code and land straight back inside, which is not
// what "log out" means.
const issuer = String(config.public.authIssuer || '').replace(/\/+$/, '')
if (issuer) {
return navigateTo(`${issuer}/logout`, { external: true })
}
router.replace('/')
})
</script>