fix(auth): убрать SSO-цикл на /auth/callback после replaceState
Docker / build (push) Failing after 18s

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-07-18 14:09:21 +07:00
co-authored by Cursor
parent 812aa614df
commit f26b2c8777
2 changed files with 19 additions and 8 deletions
+5 -1
View File
@@ -38,9 +38,13 @@ async function fetchApi<T>(path: string, options: RequestInit = {}): Promise<T>
}) })
if (!res.ok) { if (!res.ok) {
if (res.status === 401) { if (res.status === 401) {
// Avoid redirect storms: only hand off once per page load
const handoffKey = 'vps_auth_401_handoff'
const already = sessionStorage.getItem(handoffKey)
clearToken() clearToken()
const cfg = await ensureAuthConfig() const cfg = await ensureAuthConfig()
if (cfg.required || isAuthEnabled()) { if ((cfg.required || isAuthEnabled()) && !already) {
sessionStorage.setItem(handoffKey, '1')
redirectToPortalLogin(`${window.location.origin}/auth/callback`) redirectToPortalLogin(`${window.location.origin}/auth/callback`)
} }
} }
+14 -7
View File
@@ -2,6 +2,8 @@ import { createFileRoute, redirect } from '@tanstack/react-router'
import { import {
ensureAuthConfig, ensureAuthConfig,
firstAllowedPath, firstAllowedPath,
getClaims,
getToken,
parseHashToken, parseHashToken,
redirectToPortalLogin, redirectToPortalLogin,
setToken, setToken,
@@ -11,14 +13,19 @@ export const Route = createFileRoute('/auth/callback')({
beforeLoad: async () => { beforeLoad: async () => {
await ensureAuthConfig() await ensureAuthConfig()
const { accessToken } = parseHashToken(window.location.hash) const { accessToken } = parseHashToken(window.location.hash)
if (!accessToken) { if (accessToken) {
redirectToPortalLogin(`${window.location.origin}/auth/callback`) setToken(accessToken)
await new Promise(() => {}) // Do not replaceState to strip the hash here — that re-triggers beforeLoad
return // with an empty hash and sends the user back to the portal (SSO loop).
sessionStorage.removeItem('vps_auth_401_handoff')
throw redirect({ to: firstAllowedPath() })
} }
setToken(accessToken) // Already stored from a previous parse (e.g. remount) — finish handoff.
window.history.replaceState(null, '', '/auth/callback') if (getToken() && getClaims()) {
throw redirect({ to: firstAllowedPath() }) throw redirect({ to: firstAllowedPath() })
}
redirectToPortalLogin(`${window.location.origin}/auth/callback`)
await new Promise(() => {})
}, },
component: () => null, component: () => null,
}) })