Files
OpenSquawk/app/middleware/require-auth.ts
itsrubberduck 8585170885 fix(sso): hand the issuer /auth/callback so the code gets redeemed
The auth guard asked the issuer to come back to the page the visitor wanted.
The issuer appends ?code= to whatever URL it is given, but only /auth/callback
redeems a code — so it arrived on the target page, sat there unread, the guard
found no session and bounced back for a fresh code. The browser ping-ponged
between the two hosts until the user gave up.

The guard now hands over /auth/callback and carries the wanted page in its
redirect parameter, which is exactly what that page already expected. A spent
code in the URL is dropped rather than carried along, so a stale link cannot
turn into a redemption error one hop later.

URL building moved into shared/utils/ssoHandoff.ts because the same mistake
existed twice — the retry button on the callback page had it too — and because
a redirect loop deserves a regression test that does not need a browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 19:14:20 +02:00

39 lines
1.1 KiB
TypeScript

import { defineNuxtRouteMiddleware, navigateTo, useRuntimeConfig } from '#app'
import { useAuthStore } from '~/stores/auth'
import { buildIssuerLoginUrl } from '~~/shared/utils/ssoHandoff'
export default defineNuxtRouteMiddleware(async (to) => {
const config = useRuntimeConfig()
// AUTH_MODE=open: a self-hosted instance has no login, so there is nothing to
// guard. The server resolves every request to the single local identity.
if (config.public.authMode === 'open') {
return
}
const auth = useAuthStore()
if (!auth.accessToken) {
await auth.tryRefresh().catch(() => false)
}
if (!auth.initialized || !auth.user) {
await auth.fetchUser().catch(() => null)
}
if (auth.user) return
const target = to.fullPath || '/'
const issuer = String(config.public.authIssuer || '').replace(/\/+$/, '')
// No issuer configured means website and app still share an origin — the
// local /login page is the login page.
if (!issuer) {
return navigateTo(`/login?redirect=${encodeURIComponent(target)}`)
}
return navigateTo(buildIssuerLoginUrl(issuer, window.location.origin, target), {
external: true,
})
})