fix(auth): prevent unwanted anonymous session after logout by tracking session state

Track previous session existence to avoid minting a new anonymous user when a
session ends and the user is redirected to the sign-in page. This prevents
clobbering account-scoped preferences by ensuring bootstrapping does not occur
during the logout redirect flow.
This commit is contained in:
Richard R 2026-06-12 08:30:23 -06:00
parent b8621e64fb
commit 4bfd020125

View file

@ -102,6 +102,7 @@ export function AuthLoader({ children }: { children: ReactNode }) {
const [retryNonce, setRetryNonce] = useState(0); const [retryNonce, setRetryNonce] = useState(0);
const attemptedForNullSessionRef = useRef(false); const attemptedForNullSessionRef = useRef(false);
const clearingDisallowedAnonymousRef = useRef(false); const clearingDisallowedAnonymousRef = useRef(false);
const hadSessionRef = useRef(false);
const isAuthPage = pathname === '/signin' || pathname === '/signup'; const isAuthPage = pathname === '/signin' || pathname === '/signup';
// If the auth base URL changes, re-run the bootstrap logic. // If the auth base URL changes, re-run the bootstrap logic.
@ -116,6 +117,7 @@ export function AuthLoader({ children }: { children: ReactNode }) {
if (isPending) return; if (isPending) return;
if (session) { if (session) {
hadSessionRef.current = true;
if (!allowAnonymousAuthSessions && session.user.isAnonymous) { if (!allowAnonymousAuthSessions && session.user.isAnonymous) {
if (clearingDisallowedAnonymousRef.current) return; if (clearingDisallowedAnonymousRef.current) return;
clearingDisallowedAnonymousRef.current = true; clearingDisallowedAnonymousRef.current = true;
@ -154,11 +156,27 @@ export function AuthLoader({ children }: { children: ReactNode }) {
// preferences such as the changelog "last seen version" and re-showing the // preferences such as the changelog "last seen version" and re-showing the
// changelog on every fresh login. // changelog on every fresh login.
if (isAuthPage) { if (isAuthPage) {
hadSessionRef.current = false;
setIsAutoLoggingIn(false); setIsAutoLoggingIn(false);
setBootstrapError(null); setBootstrapError(null);
return; return;
} }
// A session just ended in this tab (sign-out or expiry) and we're still
// on an app page while the redirect to /signin is in flight. The session
// hook delivers `null` before `router.push('/signin')` finishes its
// server roundtrip, so `isAuthPage` is still false here — bootstrapping
// now would mint a replacement anonymous user that onLinkAccount links
// into the next login, clobbering account-scoped preferences. Redirect
// instead of bootstrapping; the ref resets once an auth page mounts.
if (hadSessionRef.current) {
setIsAutoLoggingIn(false);
setBootstrapError(null);
setIsRedirecting(true);
router.replace('/signin');
return;
}
// Avoid double-calling anonymous sign-in (e.g. React strict mode). // Avoid double-calling anonymous sign-in (e.g. React strict mode).
if (attemptedForNullSessionRef.current) return; if (attemptedForNullSessionRef.current) return;
attemptedForNullSessionRef.current = true; attemptedForNullSessionRef.current = true;