(null);
useEffect(() => {
if (code) {
load();
}
}, []);
const load = useCallback(async () => {
+ setError(null);
try {
const response = await fetch(
`/auth/oauth/${provider?.toUpperCase() || 'GENERIC'}/exists`,
@@ -54,21 +59,28 @@ export function Register() {
}
);
if (!response.ok) {
- setShow(true);
+ // The exchange failed server-side. Never masquerade this failure as
+ // a fresh signup: surface it with a loop-guarded retry instead.
+ setError({ status: response.status, message: '' });
return;
}
const data = await response.json();
if (data?.token) {
+ window.sessionStorage.removeItem(DOS_OAUTH_RETRY_KEY);
setCode(data.token);
setShow(true);
} else {
+ window.sessionStorage.removeItem(DOS_OAUTH_RETRY_KEY);
window.location.href = '/';
}
} catch (e) {
console.error('Failed to verify oauth code:', e);
- setShow(true);
+ setError({ message: (e as Error)?.message || '' });
}
}, [provider, code, state]);
+ if (error) {
+ return ;
+ }
if (!code && !getQuery?.get('provider')) {
return ;
}
@@ -79,6 +91,74 @@ export function Register() {
);
}
+
+// A failed OAuth exchange (state cookie mismatch, upstream token error, ...)
+// used to fall through to the signup form, so a broken sign-in looked like a
+// fresh registration - the exact confusion reported on 2026-09-21. This state
+// shows what happened and offers a loop-guarded retry: up to RETRY_LIMIT
+// automatic SSO restarts (a live id.dos.me session makes that one click),
+// then a manual link so a persistent failure cannot ping-pong forever.
+const RETRY_LIMIT = 2;
+
+function AuthErrorState({
+ status,
+ message,
+}: {
+ status?: number;
+ message: string;
+}) {
+ const t = useT();
+ const fetch = useFetch();
+ const attempts = Number(window.sessionStorage.getItem(DOS_OAUTH_RETRY_KEY) || '0');
+ const retry = useCallback(async () => {
+ try {
+ window.sessionStorage.setItem(DOS_OAUTH_RETRY_KEY, String(attempts + 1));
+ const response = await fetch('/auth/oauth/GENERIC');
+ if (response.ok) {
+ window.location.href = await response.text();
+ return;
+ }
+ } catch (e) {
+ console.error('Failed to restart the SSO flow:', e);
+ }
+ window.location.href = '/auth/login';
+ }, [attempts]);
+ return (
+
+
+ {t('sign_in_failed', 'Sign-in failed')}
+
+
+ {t(
+ 'sign_in_failed_body',
+ 'We could not complete your sign-in. This is usually temporary - try again below.'
+ )}
+ {status ? ` (HTTP ${status})` : ''}
+