From 5411fe23d7880b30a45c39ab123397d310dcc3a6 Mon Sep 17 00:00:00 2001 From: JOY <5027251+JOY@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:49:03 +0700 Subject: [PATCH 1/2] fix(auth): SSO auto-redirect plus an honest error page instead of the silent signup fallback Two commissioned auth UX fixes for the DOS ID single-provider flow: 1. Auto-redirect: when SSO is the only auth method (isGeneral + genericOauth), the /auth and /auth/login pages now start the DOS ID flow immediately (Redirecting to DOS ID... loader) instead of showing a page whose only content is one button. OauthProvider gains an autoStart prop: it fires gotoLogin on mount, falls back to the manual button if the link fetch fails, and clearing the error-page retry budget on a deliberately initiated flow. 2. Honest error state: a failed /oauth/:provider/exists exchange (state cookie mismatch, upstream token error) previously fell through to the signup form, so a broken sign-in looked like a fresh registration - the exact confusion reported on 2026-09-21 during the prod login outage. AuthErrorState now shows what happened (HTTP status, error message) with a loop-guarded retry: up to 2 automatic SSO restarts via sessionStorage budget, then a manual link so a persistent failure cannot ping-pong. The i18n strings use the fork's t(key, english-default) pattern consistent with the existing SSO strings on these pages. --- apps/frontend/src/components/auth/login.tsx | 2 +- .../auth/providers/oauth.provider.tsx | 54 ++++++++++- .../frontend/src/components/auth/register.tsx | 97 ++++++++++++++++++- 3 files changed, 145 insertions(+), 8 deletions(-) diff --git a/apps/frontend/src/components/auth/login.tsx b/apps/frontend/src/components/auth/login.tsx index 2ac62effbc..5d92d8691d 100644 --- a/apps/frontend/src/components/auth/login.tsx +++ b/apps/frontend/src/components/auth/login.tsx @@ -80,7 +80,7 @@ export function Login() {
{t(
'sso_description',
diff --git a/apps/frontend/src/components/auth/providers/oauth.provider.tsx b/apps/frontend/src/components/auth/providers/oauth.provider.tsx
index cf0a560dcd..e378f590e6 100644
--- a/apps/frontend/src/components/auth/providers/oauth.provider.tsx
+++ b/apps/frontend/src/components/auth/providers/oauth.provider.tsx
@@ -1,15 +1,23 @@
'use client';
-import { useCallback } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
import SafeImage from '@gitroom/react/helpers/safe.image';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import { useVariables } from '@gitroom/react/helpers/variable.context';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
-export const OauthProvider = () => {
+
+// Session key shared with the auth error state (register.tsx): a freshly
+// initiated SSO flow resets the error-page retry budget.
+export const DOS_OAUTH_RETRY_KEY = 'dos_oauth_retry_count';
+
+export const OauthProvider = ({ autoStart = false }: { autoStart?: boolean }) => {
const fetch = useFetch();
const { oauthLogoUrl, oauthDisplayName } = useVariables();
const t = useT();
- const gotoLogin = useCallback(async () => {
+ const [autoFailed, setAutoFailed] = useState(false);
+ const startedRef = useRef(false);
+
+ const gotoLogin = useCallback(async (): Promise
+ {t(
+ 'sign_in_failed_body',
+ 'We could not complete your sign-in. This is usually temporary - try again below.'
+ )}
+ {status ? ` (HTTP ${status})` : ''}
+ {message}
+ {t('already_have_an_account', 'Already Have An Account?')}
+
+ {t('sign_in', 'Sign In')}
+
+
{t(
'sso_description',
From f46220d799793b8eed2d5d3cab3035ece97f4ac1 Mon Sep 17 00:00:00 2001
From: JOY <5027251+JOY@users.noreply.github.com>
Date: Tue, 22 Sep 2026 15:06:55 +0700
Subject: [PATCH 2/2] fix(auth): cap the automatic SSO redirect against bounce
loops
Review round: an IdP error bounce can return to /auth WITHOUT a code, which
skips the exists-exchange error path entirely and re-fires the auto-start -
an uncontrolled /auth <-> IdP redirect storm is possible if the IdP ever
auto-returns. Cap it: OauthProvider's autoStart writes a sessionStorage
timestamp before navigating and refuses to auto-redirect again within 10s
(falls back to the manual button). Manual clicks are exempt.
Also per review: drop the dead useT in Register and the never-called
onRetry prop of AuthErrorState.
---
.../auth/providers/oauth.provider.tsx | 18 ++++++++++++++++++
apps/frontend/src/components/auth/register.tsx | 11 +----------
2 files changed, 19 insertions(+), 10 deletions(-)
diff --git a/apps/frontend/src/components/auth/providers/oauth.provider.tsx b/apps/frontend/src/components/auth/providers/oauth.provider.tsx
index e378f590e6..48e9b84eeb 100644
--- a/apps/frontend/src/components/auth/providers/oauth.provider.tsx
+++ b/apps/frontend/src/components/auth/providers/oauth.provider.tsx
@@ -10,6 +10,12 @@ import { useT } from '@gitroom/react/translation/get.transation.service.client';
// initiated SSO flow resets the error-page retry budget.
export const DOS_OAUTH_RETRY_KEY = 'dos_oauth_retry_count';
+// Cap for the automatic redirect only: if we auto-started a flow and are
+// back on an auth page without completing it within 10s (IdP error bounce,
+// remembered-deny, callback without code), show the manual button instead of
+// contributing to a browser/IdP redirect storm. Manual clicks are exempt.
+const DOS_OAUTH_AUTOSTART_TS_KEY = 'dos_oauth_autostart_ts';
+
export const OauthProvider = ({ autoStart = false }: { autoStart?: boolean }) => {
const fetch = useFetch();
const { oauthLogoUrl, oauthDisplayName } = useVariables();
@@ -40,10 +46,22 @@ export const OauthProvider = ({ autoStart = false }: { autoStart?: boolean }) =>
useEffect(() => {
if (!autoStart || startedRef.current) return;
startedRef.current = true;
+ const lastStart = Number(
+ window.sessionStorage.getItem(DOS_OAUTH_AUTOSTART_TS_KEY) || '0'
+ );
+ if (Date.now() - lastStart < 10_000) {
+ setAutoFailed(true);
+ return;
+ }
+ window.sessionStorage.setItem(
+ DOS_OAUTH_AUTOSTART_TS_KEY,
+ String(Date.now())
+ );
gotoLogin().then((ok) => {
if (!ok) {
// Auto-start could not even fetch the link - fall back to the
// manual button instead of a dead redirecting state.
+ window.sessionStorage.removeItem(DOS_OAUTH_AUTOSTART_TS_KEY);
setAutoFailed(true);
}
});
diff --git a/apps/frontend/src/components/auth/register.tsx b/apps/frontend/src/components/auth/register.tsx
index 8b1dfa26f5..e9498edf6e 100644
--- a/apps/frontend/src/components/auth/register.tsx
+++ b/apps/frontend/src/components/auth/register.tsx
@@ -32,7 +32,6 @@ type Inputs = {
export function Register() {
const getQuery = useSearchParams();
const fetch = useFetch();
- const t = useT();
const [provider] = useState(getQuery?.get('provider')?.toUpperCase() || 'GENERIC');
const [code, setCode] = useState(getQuery?.get('code') || '');
const [state] = useState(getQuery?.get('state') || '');
@@ -80,13 +79,7 @@ export function Register() {
}
}, [provider, code, state]);
if (error) {
- return (
-
+ {t('sign_in_failed', 'Sign-in failed')}
+
+