diff --git a/AGENTS.md b/AGENTS.md
index bec2dcfec..675b662bb 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -300,7 +300,7 @@ Positions are stored and sent as **WGS84** longitude and latitude, but users rea
**Signing in.** The site's status, read before anything renders, sets one way to sign in:
- **Username and password** (`require_idp` off): the DAS OAuth password grant.
-- **Auth0** (`require_idp` on): through the organization's identity provider, or EarthRanger Identity where it has none. These sites are mid-migration, so an account not linked yet goes to the server's account linker. A site may also offer a **managed user** button, for accounts in its own Auth0 connection.
+- **Auth0** (`require_idp` on): through EarthRanger Identity. These sites are mid-migration, so an account not linked yet goes to the server's account linker. A site may also offer a **managed user** button, for accounts in its own Auth0 connection.
Either way the client gets an access token, Auth0's used as is, kept in a cookie and in Redux and sent as a `Bearer` header.
diff --git a/public/locales/en-US/login.json b/public/locales/en-US/login.json
index fa24a1c67..e76a47598 100644
--- a/public/locales/en-US/login.json
+++ b/public/locales/en-US/login.json
@@ -9,7 +9,6 @@
"helpText": "Questions? Reach out to your site admin."
},
"errorAlert": {
- "accessDeniedNotAuthorized": "Access denied: Your account is not authorized for this organization. Please contact your administrator.",
"accessDeniedNoPermission": "Access denied: You do not have permission to access this application.",
"authenticationFailed": "Authentication failed: Please check your credentials and try again.",
"authenticationError": "Authentication error: {{errorDescription}}",
@@ -28,7 +27,6 @@
"eulaLinkLabel": "EarthRanger EULA (opens in a new tab)",
"loginButton": "Log in",
"loginButtonEmail": "Sign in with email",
- "loginButtonIdp": "Sign in",
"loginButtonLoadingLabel": "Loading",
"loginButtonManagedUser": "Sign in as a managed user",
"passwordLabel": "Password",
diff --git a/public/locales/es/login.json b/public/locales/es/login.json
index b7889253e..2faf32920 100644
--- a/public/locales/es/login.json
+++ b/public/locales/es/login.json
@@ -9,7 +9,6 @@
"helpText": "¿Tiene preguntas? Comuníquese con el administrador de su sitio."
},
"errorAlert": {
- "accessDeniedNotAuthorized": "Acceso denegado: Su cuenta no está autorizada para esta organización. Por favor contacte a su administrador.",
"accessDeniedNoPermission": "Acceso denegado: No tiene permiso para acceder a esta aplicación.",
"authenticationFailed": "Autenticación fallida: Por favor verifique sus credenciales e intente de nuevo.",
"authenticationError": "Error de autenticación: {{errorDescription}}",
@@ -28,7 +27,6 @@
"eulaLinkLabel": "EULA de EarthRanger (se abre en una nueva pestaña)",
"loginButton": "Iniciar sesión",
"loginButtonEmail": "Iniciar sesión con correo electrónico",
- "loginButtonIdp": "Iniciar sesión",
"loginButtonLoadingLabel": "Cargando",
"loginButtonManagedUser": "Iniciar sesión como usuario gestionado",
"passwordLabel": "Contraseña",
diff --git a/public/locales/fr/login.json b/public/locales/fr/login.json
index 2f421f40b..5772ccb8a 100644
--- a/public/locales/fr/login.json
+++ b/public/locales/fr/login.json
@@ -9,7 +9,6 @@
"helpText": "Des questions ? Contactez l'administrateur de votre site."
},
"errorAlert": {
- "accessDeniedNotAuthorized": "Accès refusé : Votre compte n'est pas autorisé pour cette organisation. Veuillez contacter votre administrateur.",
"accessDeniedNoPermission": "Accès refusé : Vous n'avez pas la permission d'accéder à cette application.",
"authenticationFailed": "Authentification échouée : Veuillez vérifier vos identifiants et réessayer.",
"authenticationError": "Erreur d'authentification : {{errorDescription}}",
@@ -28,7 +27,6 @@
"eulaLinkLabel": "EULA EarthRanger (s'ouvre dans un nouvel onglet)",
"loginButton": "Se Connecter",
"loginButtonEmail": "Se connecter avec un e-mail",
- "loginButtonIdp": "Se Connecter",
"loginButtonLoadingLabel": "Chargement",
"loginButtonManagedUser": "Se connecter en tant qu'utilisateur géré",
"passwordLabel": "Mot de passe",
diff --git a/public/locales/ne-NP/login.json b/public/locales/ne-NP/login.json
index cb7773542..b27afd2d4 100644
--- a/public/locales/ne-NP/login.json
+++ b/public/locales/ne-NP/login.json
@@ -9,7 +9,6 @@
"helpText": "प्रश्नहरू छन्? आफ्नो साइट प्रशासकलाई सम्पर्क गर्नुहोस्।"
},
"errorAlert": {
- "accessDeniedNotAuthorized": "पहुँच अस्वीकार गरियो: तपाईंको खाता यस संस्थाको लागि अधिकृत छैन। कृपया आफ्नो प्रशासकलाई सम्पर्क गर्नुहोस्।",
"accessDeniedNoPermission": "पहुँच अस्वीकार गरियो: तपाईंसँग यो अनुप्रयोग पहुँच गर्ने अनुमति छैन।",
"authenticationFailed": "प्रमाणीकरण असफल भयो: कृपया आफ्नो प्रमाणहरू जाँच गर्नुहोस् र पुनः प्रयास गर्नुहोस्।",
"authenticationError": "प्रमाणीकरण त्रुटि: {{errorDescription}}",
@@ -28,7 +27,6 @@
"eulaLinkLabel": "अर्थरेन्जर EULA (नयाँ ट्याबमा खुल्छ)",
"loginButton": "लग इन",
"loginButtonEmail": "इमेलबाट साइन इन",
- "loginButtonIdp": "साइन इन",
"loginButtonLoadingLabel": "लोड हुँदैछ",
"loginButtonManagedUser": "व्यवस्थापित प्रयोगकर्ताको रूपमा साइन इन",
"passwordLabel": "पासवर्ड",
diff --git a/public/locales/pt/login.json b/public/locales/pt/login.json
index 96ba87603..591cd6c0b 100644
--- a/public/locales/pt/login.json
+++ b/public/locales/pt/login.json
@@ -9,7 +9,6 @@
"helpText": "Dúvidas? Entre em contato com o administrador do seu site."
},
"errorAlert": {
- "accessDeniedNotAuthorized": "Acesso negado: Sua conta não está autorizada para esta organização. Entre em contato com o administrador.",
"accessDeniedNoPermission": "Acesso negado: Você não tem permissão para acessar esta aplicação.",
"authenticationFailed": "Falha na autenticação: Verifique suas credenciais e tente novamente.",
"authenticationError": "Erro de autenticação: {{errorDescription}}",
@@ -28,7 +27,6 @@
"eulaLinkLabel": "EULA EarthRanger (abre em uma nova guia)",
"loginButton": "Conecte-se",
"loginButtonEmail": "Conecte-se com e-mail",
- "loginButtonIdp": "Conecte-se",
"loginButtonLoadingLabel": "Carregando",
"loginButtonManagedUser": "Conecte-se como usuário gerenciado",
"passwordLabel": "Senha",
diff --git a/public/locales/sw/login.json b/public/locales/sw/login.json
index 1ed590c60..32886fe74 100644
--- a/public/locales/sw/login.json
+++ b/public/locales/sw/login.json
@@ -9,7 +9,6 @@
"helpText": "Una maswali? Wasiliana na msimamizi wa tovuti yako."
},
"errorAlert": {
- "accessDeniedNotAuthorized": "Ufikiaji umekataliwa: Akaunti yako haijaruhusiwa kwa shirika hili. Tafadhali wasiliana na msimamizi wako.",
"accessDeniedNoPermission": "Ufikiaji umekataliwa: Huna ruhusa ya kufikia programu hii.",
"authenticationFailed": "Uthibitishaji umeshindwa: Tafadhali hakikisha maelezo yako na jaribu tena.",
"authenticationError": "Kosa la uthibitishaji: {{errorDescription}}",
@@ -28,7 +27,6 @@
"eulaLinkLabel": "EarthRanger EULA (hufungua kwenye kichupo kipya)",
"loginButton": "Ingia",
"loginButtonEmail": "Ingia kwa barua pepe",
- "loginButtonIdp": "Ingia",
"loginButtonLoadingLabel": "Inapakia",
"loginButtonManagedUser": "Ingia kama mtumiaji anayesimamiwa",
"passwordLabel": "Nenosiri",
diff --git a/src/Auth0TokenManager/accountLinkingGate.integration.test.js b/src/Auth0TokenManager/accountLinkingGate.integration.test.js
index e3da2226f..29ca0572f 100644
--- a/src/Auth0TokenManager/accountLinkingGate.integration.test.js
+++ b/src/Auth0TokenManager/accountLinkingGate.integration.test.js
@@ -74,7 +74,7 @@ describe('Auth0 error redirect reaches the login page', () => {
}),
{
data: { token: { access_token: null } },
- view: { systemConfig: { require_idp: true, idp_org_id: null } },
+ view: { systemConfig: { require_idp: true } },
},
applyMiddleware(thunk, promiseMiddleware),
);
@@ -158,7 +158,7 @@ describe('post-callback account-linking gate', () => {
}),
{
data: { token: { access_token: null } },
- view: { systemConfig: { require_idp: true, idp_org_id: null } }, // common-DB site
+ view: { systemConfig: { require_idp: true } },
},
applyMiddleware(thunk, promiseMiddleware),
);
diff --git a/src/Auth0TokenManager/index.js b/src/Auth0TokenManager/index.js
index d6d26f8e5..4164f323a 100644
--- a/src/Auth0TokenManager/index.js
+++ b/src/Auth0TokenManager/index.js
@@ -26,7 +26,6 @@ const Auth0TokenManager = () => {
const navigate = useNavigate();
const existingToken = useSelector((state) => state.data.token?.access_token);
- const idpOrgId = useSelector((state) => state.view.systemConfig?.idp_org_id);
const requireIdp = useSelector((state) => !!state.view.systemConfig?.require_idp);
const { isAuthenticated, getAccessTokenSilently, logout } = useAuth0();
@@ -64,8 +63,7 @@ const Auth0TokenManager = () => {
return;
}
- // Account-linking gate — common-DB path only; org-scoped (rcuksa) sites skip it.
- if (requireIdp && !idpOrgId?.trim()) {
+ if (requireIdp) {
const { result, linkUrl } = await checkAccountLinked(safe);
if (result === GATE_RESULT.UNLINKED) {
@@ -134,7 +132,7 @@ const Auth0TokenManager = () => {
}
};
ensureIdpToken();
- }, [dispatch, existingToken, getAccessTokenSilently, idpOrgId, isAuthenticated, logout, requireIdp, navigate, location.search]);
+ }, [dispatch, existingToken, getAccessTokenSilently, isAuthenticated, logout, requireIdp, navigate, location.search]);
return null;
};
diff --git a/src/Auth0TokenManager/index.test.js b/src/Auth0TokenManager/index.test.js
index 137e8d183..32831c976 100644
--- a/src/Auth0TokenManager/index.test.js
+++ b/src/Auth0TokenManager/index.test.js
@@ -55,7 +55,7 @@ describe('Auth0TokenManager', () => {
useSelector.mockImplementation((selector) => {
const state = {
data: { token: { access_token: null } },
- view: { systemConfig: { require_idp: true, idp_org_id: null } }
+ view: { systemConfig: { require_idp: true } }
};
return selector(state);
});
@@ -350,7 +350,7 @@ describe('Auth0TokenManager', () => {
});
});
- test('org-scoped (idp_org_id set): skips the gate and authenticates', async () => {
+ test('runs the gate on a site whose status response still reports an organization ID', async () => {
useSelector.mockImplementation((selector) => selector({
data: { token: { access_token: null } },
view: { systemConfig: { require_idp: true, idp_org_id: 'org_abc' } },
@@ -359,9 +359,9 @@ describe('Auth0TokenManager', () => {
renderAfterCallback();
await waitFor(() => {
- expect(applyAccessToken).toHaveBeenCalledWith(VALID_TOKEN);
+ expect(checkAccountLinked).toHaveBeenCalledWith(VALID_TOKEN);
});
- expect(checkAccountLinked).not.toHaveBeenCalled();
+ expect(applyAccessToken).toHaveBeenCalledWith(VALID_TOKEN);
});
});
});
diff --git a/src/Login/index.js b/src/Login/index.js
index cc833d7de..b0c1b048b 100644
--- a/src/Login/index.js
+++ b/src/Login/index.js
@@ -55,44 +55,37 @@ const LoginPage = () => {
const [formErrors, setFormErrors] = useState({ username: null, password: null });
const [isLoading, setIsLoading] = useState(false);
- const idpOrgId = systemConfig?.idp_org_id?.trim() || null;
const isEULAEnabled = !!systemConfig?.[SYSTEM_CONFIG_FLAGS.EULA];
const requireIdp = !!systemConfig?.require_idp;
const siteSlug = systemConfig?.site_slug?.trim() || null;
// No slug means no connection on the redirect, which would sign the user into the
- // common database. Org-scoped sites skip the gate that catches an unmapped one.
- const canSignInAsManagedUser = !!systemConfig?.support_managed_users
- && !!siteSlug
- && !idpOrgId;
+ // common database.
+ const canSignInAsManagedUser = !!systemConfig?.support_managed_users && !!siteSlug;
const onAuth0Login = useCallback(async () => {
try {
await auth0LoginWithRedirect({
- authorizationParams: buildAuth0AuthorizationParams(appConfig.auth0.audience, idpOrgId),
+ authorizationParams: { audience: appConfig.auth0.audience },
});
} catch (_error) {
setAlert({ key: 'errorAlert.signInFailed' });
}
- }, [auth0LoginWithRedirect, idpOrgId]);
+ }, [auth0LoginWithRedirect]);
const onManagedUserLogin = useCallback(async () => {
markManagedUserLoginAttempt();
try {
await auth0LoginWithRedirect({
- authorizationParams: buildAuth0AuthorizationParams(
- appConfig.auth0.audience,
- idpOrgId,
- siteSlug,
- ),
+ authorizationParams: buildAuth0AuthorizationParams(appConfig.auth0.audience, siteSlug),
});
} catch (_error) {
// No redirect happened, so there is no attempt left to attribute.
takeManagedUserLoginAttempt();
setAlert({ key: 'errorAlert.signInFailed' });
}
- }, [auth0LoginWithRedirect, idpOrgId, siteSlug]);
+ }, [auth0LoginWithRedirect, siteSlug]);
const onFormSubmit = useCallback(async (event) => {
event.preventDefault();
@@ -181,9 +174,7 @@ const LoginPage = () => {
return { key: 'errorAlert.managedUserSignInFailed' };
}
if (auth0Error === 'access_denied') {
- return auth0ErrorDescription?.includes('not part of the')
- ? { key: 'errorAlert.accessDeniedNotAuthorized' }
- : { key: 'errorAlert.accessDeniedNoPermission' };
+ return { key: 'errorAlert.accessDeniedNoPermission' };
}
if (auth0Error === 'unauthorized') {
return { key: 'errorAlert.authenticationFailed' };
@@ -215,11 +206,10 @@ const LoginPage = () => {
{t('title')}
- {/* Auth0 migration guidance: shown only on common-DB sites (require_idp with
- no idp_org_id). "Sign in with email" below auto-drives EarthRanger
+ {/* Auth0 migration guidance: "Sign in with email" below drives EarthRanger
Identity; users who have not converted their account yet are linked to
- the server account linker. Org-scoped sites show no box. */}
- {requireIdp && !idpOrgId && (
+ the server account linker. */}
+ {requireIdp && (
{t('auth0Info.title')}
@@ -253,7 +243,7 @@ const LoginPage = () => {
>
{isAuth0Loading
?
- : t(idpOrgId ? 'loginButtonIdp' : 'loginButtonEmail')}
+ : t('loginButtonEmail')}
{canSignInAsManagedUser && (
diff --git a/src/Login/index.test.js b/src/Login/index.test.js
index 3b1965e5d..7a05870a2 100644
--- a/src/Login/index.test.js
+++ b/src/Login/index.test.js
@@ -94,12 +94,12 @@ describe('Login', () => {
test('shows the Auth0 sign-in button and hides local credentials when IDP login is required', () => {
store = mockStore({
data: { eula: { eula_url: '' } },
- view: { systemConfig: { require_idp: true, idp_org_id: 'org_abc' } },
+ view: { systemConfig: { require_idp: true } },
});
renderLogin();
- const signIn = screen.getByRole('button', { name: 'Sign in' });
+ const signIn = screen.getByRole('button', { name: 'Sign in with email' });
expect(signIn).toBeVisible();
expect(signIn).toHaveAttribute('type', 'button');
expect(signIn).toBeEnabled();
@@ -108,71 +108,52 @@ describe('Login', () => {
expect(screen.queryByLabelText('Username')).not.toBeInTheDocument();
});
- test('calls loginWithRedirect with audience and organization when the user clicks Sign in', async () => {
+ test('calls loginWithRedirect with the audience when the user clicks Sign in with email', async () => {
store = mockStore({
data: { eula: { eula_url: '' } },
- view: { systemConfig: { require_idp: true, idp_org_id: 'org_abc' } },
+ view: { systemConfig: { require_idp: true } },
});
loginWithRedirect.mockResolvedValue(undefined);
renderLogin();
- await userEvent.click(screen.getByRole('button', { name: 'Sign in' }));
+ await userEvent.click(screen.getByRole('button', { name: 'Sign in with email' }));
expect(loginWithRedirect).toHaveBeenCalledWith({
- authorizationParams: {
- audience: appConfig.auth0.audience,
- organization: 'org_abc',
- },
+ authorizationParams: { audience: appConfig.auth0.audience },
});
});
- test('disables the Auth0 sign-in button and shows a loading state while Auth0 reports loading', () => {
+ test('sends no organization param on a site whose status response still reports an organization ID', async () => {
store = mockStore({
data: { eula: { eula_url: '' } },
view: { systemConfig: { require_idp: true, idp_org_id: 'org_abc' } },
});
- useAuth0.mockReturnValue({ loginWithRedirect, isLoading: true });
+ loginWithRedirect.mockResolvedValue(undefined);
renderLogin();
- const button = screen.getByRole('button', { name: 'Loading' });
- expect(button).toHaveAttribute('type', 'button');
- expect(button).toHaveAttribute('aria-busy', 'true');
- expect(button).toHaveAttribute('aria-label', 'Loading');
- expect(button).toBeDisabled();
- });
+ await userEvent.click(screen.getByRole('button', { name: 'Sign in with email' }));
- test('shows the common-DB "Sign in with email" button and no configuration error when IDP is required without an organization ID', () => {
- store = mockStore({
- data: { eula: { eula_url: '' } },
- view: { systemConfig: { require_idp: true } },
+ expect(loginWithRedirect).toHaveBeenCalledWith({
+ authorizationParams: { audience: appConfig.auth0.audience },
});
-
- renderLogin();
-
- const signIn = screen.getByRole('button', { name: 'Sign in with email' });
- expect(signIn).toBeVisible();
- expect(signIn).toHaveAttribute('type', 'button');
- expect(signIn).toBeEnabled();
- expect(screen.queryByText('Identity provider organization is not configured.')).not.toBeInTheDocument();
- expect(screen.queryByLabelText('Username')).not.toBeInTheDocument();
});
- test('calls loginWithRedirect with audience and no organization when the user clicks Sign in with email', async () => {
+ test('disables the Auth0 sign-in button and shows a loading state while Auth0 reports loading', () => {
store = mockStore({
data: { eula: { eula_url: '' } },
view: { systemConfig: { require_idp: true } },
});
- loginWithRedirect.mockResolvedValue(undefined);
+ useAuth0.mockReturnValue({ loginWithRedirect, isLoading: true });
renderLogin();
- await userEvent.click(screen.getByRole('button', { name: 'Sign in with email' }));
-
- expect(loginWithRedirect).toHaveBeenCalledWith({
- authorizationParams: { audience: appConfig.auth0.audience },
- });
+ const button = screen.getByRole('button', { name: 'Loading' });
+ expect(button).toHaveAttribute('type', 'button');
+ expect(button).toHaveAttribute('aria-busy', 'true');
+ expect(button).toHaveAttribute('aria-label', 'Loading');
+ expect(button).toBeDisabled();
});
describe('managed-user sign-in', () => {
@@ -247,15 +228,23 @@ describe('Login', () => {
expect(screen.queryByRole('button', { name: 'Sign in as a managed user' })).not.toBeInTheDocument();
});
- test('hides the managed-user path on an org-scoped site, where the unmapped-user guard does not run', () => {
+ test('sends no organization param on a site whose status response still reports an organization ID', async () => {
store = mockStore({
data: { eula: { eula_url: '' } },
view: { systemConfig: { ...managedUserSystemConfig, idp_org_id: 'org_abc' } },
});
+ loginWithRedirect.mockResolvedValue(undefined);
renderLogin();
- expect(screen.queryByRole('button', { name: 'Sign in as a managed user' })).not.toBeInTheDocument();
+ await userEvent.click(screen.getByRole('button', { name: 'Sign in as a managed user' }));
+
+ expect(loginWithRedirect).toHaveBeenCalledWith({
+ authorizationParams: {
+ audience: appConfig.auth0.audience,
+ connection: 'gdl-zoo',
+ },
+ });
});
test('points at the managed-user option from the migration guidance when it is offered', () => {
@@ -581,7 +570,7 @@ describe('Login', () => {
});
describe('Auth0 sign-in info box', () => {
- test('renders the info box on a common-DB Auth0 site (require_idp without an organization ID)', () => {
+ test('renders the info box on an Auth0 site', () => {
store = mockStore({
data: { eula: { eula_url: '' } },
view: { systemConfig: { require_idp: true } },
@@ -607,33 +596,19 @@ describe('Login', () => {
expect(screen.getByText('Questions? Reach out to your site admin.')).toBeVisible();
});
- test('treats a whitespace-only organization ID as common-DB and still renders the info box', () => {
+ test('renders the info box on a site whose status response still reports an organization ID', () => {
store = mockStore({
data: { eula: { eula_url: '' } },
- view: { systemConfig: { require_idp: true, idp_org_id: ' ' } },
+ view: { systemConfig: { require_idp: true, idp_org_id: 'org_abc' } },
});
renderLogin();
expect(screen.getByRole('heading', { level: 2 })).toBeVisible();
expect(screen.getByRole('link', { name: 'Convert your account' })).toBeVisible();
- // Whitespace-only org id is treated as no org -> common-DB "Sign in with email".
expect(screen.getByRole('button', { name: 'Sign in with email' })).toBeVisible();
});
- test('does not render the info box on an org-scoped Auth0 site (require_idp with an organization ID)', () => {
- store = mockStore({
- data: { eula: { eula_url: '' } },
- view: { systemConfig: { require_idp: true, idp_org_id: 'org_abc' } },
- });
-
- renderLogin();
-
- expect(screen.queryByRole('heading', { level: 2 })).not.toBeInTheDocument();
- expect(screen.queryByRole('link', { name: 'Convert your account' })).not.toBeInTheDocument();
- expect(screen.getByRole('button', { name: 'Sign in' })).toBeVisible();
- });
-
test('does not render the info box on a site without Auth0 configured', () => {
store = mockStore({
data: { eula: { eula_url: '' } },
@@ -652,13 +627,13 @@ describe('Login', () => {
test('shows a sign-in failure alert when loginWithRedirect rejects', async () => {
store = mockStore({
data: { eula: { eula_url: '' } },
- view: { systemConfig: { require_idp: true, idp_org_id: 'org_abc' } },
+ view: { systemConfig: { require_idp: true } },
});
loginWithRedirect.mockRejectedValue(new Error('Auth0 failed'));
renderLogin();
- await userEvent.click(screen.getByRole('button', { name: 'Sign in' }));
+ await userEvent.click(screen.getByRole('button', { name: 'Sign in with email' }));
await waitFor(() => {
const alert = screen.getByText('Sign-in failed. Please try again.');
@@ -974,20 +949,7 @@ describe('Login', () => {
expect(fetchEula).toHaveBeenCalled();
});
- test('shows the organization-access alert when the callback URL includes access_denied with a membership-related description', () => {
- renderLogin({
- initialEntries: ['/login?error=access_denied&error_description=user+is+not+part+of+the+org'],
- });
-
- const alert = screen.getByText(
- 'Access denied: Your account is not authorized for this organization. Please contact your administrator.',
- );
- expect(alert).toBeVisible();
- expect(alert).toHaveAttribute('role', 'alert');
- expect(alert).toHaveClass(loginStyles.alertMessage);
- });
-
- test('shows the generic access-denied alert when access_denied has no membership-related description', () => {
+ test('shows the access-denied alert when the callback URL includes an access_denied error', () => {
renderLogin({
initialEntries: ['/login?error=access_denied&error_description=User+cancelled+login'],
});
diff --git a/src/ducks/system-config/index.js b/src/ducks/system-config/index.js
index f6f13865e..1a14413d0 100644
--- a/src/ducks/system-config/index.js
+++ b/src/ducks/system-config/index.js
@@ -30,7 +30,6 @@ export const setSystemConfigFromSystemStatus = (systemStatus) => (dispatch) => {
[SYSTEM_CONFIG_FLAGS.SUBJECTS]: systemStatus[SYSTEM_CONFIG_FLAGS.SUBJECTS] ?? true,
[SYSTEM_CONFIG_FLAGS.TABLEAU]: systemStatus[SYSTEM_CONFIG_FLAGS.TABLEAU] ?? true,
[SYSTEM_CONFIG_FLAGS.GEO_SPAN]: systemStatus[SYSTEM_CONFIG_FLAGS.GEO_SPAN] ?? null,
- idp_org_id: systemStatus.idp_org_id || null,
previewFeatures: systemStatus.preview_features || {},
require_idp: !!systemStatus.require_idp,
site_slug: systemStatus.site_slug || null,
@@ -77,7 +76,6 @@ export const INITIAL_STATE = {
[SYSTEM_CONFIG_FLAGS.SUBJECTS]: false,
[SYSTEM_CONFIG_FLAGS.TABLEAU]: false,
[SYSTEM_CONFIG_FLAGS.GEO_SPAN]: null,
- idp_org_id: null,
previewFeatures: {},
require_idp: null,
site_slug: null,
diff --git a/src/ducks/system-config/index.test.js b/src/ducks/system-config/index.test.js
index e2e1314cb..be3e2013e 100644
--- a/src/ducks/system-config/index.test.js
+++ b/src/ducks/system-config/index.test.js
@@ -51,6 +51,7 @@ describe('Ducks - System config', () => {
[SYSTEM_CONFIG_FLAGS.SUBJECTS]: true,
[SYSTEM_CONFIG_FLAGS.TABLEAU]: true,
geoPermissionsEnabled: true,
+ idp_org_id: 'org_abc',
preview_features: { community_input_admin_enabled: true },
show_track_days: true,
site_name: 'Site name',
@@ -76,7 +77,6 @@ describe('Ducks - System config', () => {
[SYSTEM_CONFIG_FLAGS.SPATIAL_FEATURES]: true,
[SYSTEM_CONFIG_FLAGS.SUBJECTS]: true,
[SYSTEM_CONFIG_FLAGS.TABLEAU]: true,
- idp_org_id: null,
previewFeatures: {
community_input_admin_enabled: true,
},
@@ -248,7 +248,6 @@ describe('Ducks - System config', () => {
[SYSTEM_CONFIG_FLAGS.SPATIAL_FEATURES]: true,
[SYSTEM_CONFIG_FLAGS.SUBJECTS]: true,
[SYSTEM_CONFIG_FLAGS.TABLEAU]: true,
- idp_org_id: null,
previewFeatures: { community_input_admin_enabled: true },
require_idp: null,
showTrackDays: true,
diff --git a/src/hooks/useAuthRecovery.js b/src/hooks/useAuthRecovery.js
index 79f151269..d92637b12 100644
--- a/src/hooks/useAuthRecovery.js
+++ b/src/hooks/useAuthRecovery.js
@@ -1,11 +1,9 @@
import { useEffect } from 'react';
import { useAuth0 } from '@auth0/auth0-react';
-import { useSelector } from 'react-redux';
import { useLocation } from 'react-router';
import appConfig from '../config';
import { registerAuthRecovery } from '../utils/auth-recovery';
-import { buildAuth0AuthorizationParams } from '../utils/auth0';
import { setIntendedPostAuth0SuccessRoute } from '../utils/auth';
/**
@@ -15,7 +13,6 @@ import { setIntendedPostAuth0SuccessRoute } from '../utils/auth';
const useAuthRecovery = () => {
const { getAccessTokenSilently, loginWithRedirect } = useAuth0();
const { pathname, search } = useLocation();
- const idpOrgId = useSelector((state) => state.view.systemConfig?.idp_org_id);
useEffect(() => {
registerAuthRecovery({
@@ -26,7 +23,7 @@ const useAuthRecovery = () => {
setIntendedPostAuth0SuccessRoute(`${pathname}${search}`);
await loginWithRedirect({
authorizationParams: {
- ...buildAuth0AuthorizationParams(appConfig.auth0.audience, idpOrgId),
+ audience: appConfig.auth0.audience,
...(acrValues ? { acr_values: acrValues } : {}),
...(maxAge ? { max_age: maxAge } : {}),
},
@@ -34,7 +31,7 @@ const useAuthRecovery = () => {
return new Promise(() => {});
},
});
- }, [getAccessTokenSilently, loginWithRedirect, idpOrgId, pathname, search]);
+ }, [getAccessTokenSilently, loginWithRedirect, pathname, search]);
};
export default useAuthRecovery;
diff --git a/src/hooks/useAuthRecovery.test.js b/src/hooks/useAuthRecovery.test.js
index 0a21825cf..f4738b9eb 100644
--- a/src/hooks/useAuthRecovery.test.js
+++ b/src/hooks/useAuthRecovery.test.js
@@ -1,6 +1,5 @@
import { renderHook } from '@testing-library/react';
import { useAuth0 } from '@auth0/auth0-react';
-import { useSelector } from 'react-redux';
import { useLocation } from 'react-router';
import useAuthRecovery from './useAuthRecovery';
@@ -8,7 +7,6 @@ import { registerAuthRecovery } from '../utils/auth-recovery';
import { setIntendedPostAuth0SuccessRoute } from '../utils/auth';
jest.mock('@auth0/auth0-react');
-jest.mock('react-redux', () => ({ useSelector: jest.fn() }));
jest.mock('react-router', () => ({ __esModule: true, useLocation: jest.fn() }));
jest.mock('../utils/auth-recovery', () => ({ registerAuthRecovery: jest.fn() }));
jest.mock('../utils/auth', () => ({ setIntendedPostAuth0SuccessRoute: jest.fn() }));
@@ -24,7 +22,6 @@ describe('useAuthRecovery', () => {
getAccessTokenSilently = jest.fn();
loginWithRedirect = jest.fn().mockResolvedValue(undefined);
useAuth0.mockReturnValue({ getAccessTokenSilently, loginWithRedirect });
- useSelector.mockReturnValue(null); // idp_org_id (common-DB site)
useLocation.mockReturnValue({ pathname: '/events/123', search: '?foo=bar' });
});
@@ -43,11 +40,11 @@ describe('useAuthRecovery', () => {
expect(setIntendedPostAuth0SuccessRoute).toHaveBeenCalledWith('/events/123?foo=bar');
expect(loginWithRedirect).toHaveBeenCalledWith({
- authorizationParams: expect.objectContaining({
+ authorizationParams: {
audience: 'https://api.example',
acr_values: 'urn:mfa',
max_age: '3600',
- }),
+ },
});
});
diff --git a/src/i18n.js b/src/i18n.js
index 053ac3e8a..8c89c9b7d 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -7,7 +7,7 @@ import LocalStorageBackend from 'i18next-localstorage-backend';
import { SUPPORTED_LANGUAGES } from './constants';
-const I18N_FILES_VERSION = '1.76';
+const I18N_FILES_VERSION = '1.77';
const preloadNamespaces = [
'components',
diff --git a/src/utils/auth.test.js b/src/utils/auth.test.js
index abfd5b6b8..db1933383 100644
--- a/src/utils/auth.test.js
+++ b/src/utils/auth.test.js
@@ -29,7 +29,7 @@ describe('auth utils', () => {
});
test('returns true when require_idp is true (loaded)', () => {
- const systemConfig = { require_idp: true, sitename: 'Test Site', idp_org_id: 'org_123' };
+ const systemConfig = { require_idp: true, sitename: 'Test Site' };
expect(isSystemConfigLoaded(systemConfig)).toBe(true);
});
});
diff --git a/src/utils/auth0.js b/src/utils/auth0.js
index 16069c0a9..c0be8f7f6 100644
--- a/src/utils/auth0.js
+++ b/src/utils/auth0.js
@@ -3,16 +3,12 @@ export const hasAuth0CallbackParams = (searchParams) => {
return urlParams.has('code') && (urlParams.has('state') || urlParams.has('error'));
};
-// Both params are omitted when blank so Auth0 falls back to the tenant's Default
-// Directory; a connection opts out of it. Carrying both at once is an artifact of
-// the unfinished migration away from organizations, left unguarded on purpose —
-// the combination cannot arise once that finishes.
-export const buildAuth0AuthorizationParams = (audience, idpOrgId, connection) => {
- const org = idpOrgId?.trim();
+// The connection is omitted when blank so Auth0 falls back to the tenant's Default
+// Directory; naming one opts out of it.
+export const buildAuth0AuthorizationParams = (audience, connection) => {
const namedConnection = connection?.trim();
return {
audience,
- ...(org ? { organization: org } : {}),
...(namedConnection ? { connection: namedConnection } : {}),
};
};
diff --git a/src/utils/auth0.test.js b/src/utils/auth0.test.js
index 2bc7ea49e..66bd42d42 100644
--- a/src/utils/auth0.test.js
+++ b/src/utils/auth0.test.js
@@ -51,72 +51,32 @@ describe('auth0 utils', () => {
describe('buildAuth0AuthorizationParams', () => {
const AUDIENCE = 'https://example.org/api';
- test('forwards the organization when an idp org id is provided', () => {
- expect(buildAuth0AuthorizationParams(AUDIENCE, 'org_abc')).toEqual({
- audience: AUDIENCE,
- organization: 'org_abc',
- });
- });
-
- test('omits the organization entirely when the idp org id is null', () => {
- const params = buildAuth0AuthorizationParams(AUDIENCE, null);
- expect(params).toEqual({ audience: AUDIENCE });
- expect(params).not.toHaveProperty('organization');
- });
-
- test('omits the organization entirely when the idp org id is an empty string', () => {
- const params = buildAuth0AuthorizationParams(AUDIENCE, '');
- expect(params).toEqual({ audience: AUDIENCE });
- expect(params).not.toHaveProperty('organization');
- });
-
- test('omits the organization entirely when the idp org id is only whitespace', () => {
- const params = buildAuth0AuthorizationParams(AUDIENCE, ' ');
- expect(params).toEqual({ audience: AUDIENCE });
- expect(params).not.toHaveProperty('organization');
- });
-
- test('forwards a trimmed organization when the idp org id has surrounding whitespace', () => {
- expect(buildAuth0AuthorizationParams(AUDIENCE, ' org_abc ')).toEqual({
- audience: AUDIENCE,
- organization: 'org_abc',
- });
- });
-
test('forwards the connection when one is provided', () => {
- expect(buildAuth0AuthorizationParams(AUDIENCE, null, 'gdl-zoo')).toEqual({
+ expect(buildAuth0AuthorizationParams(AUDIENCE, 'gdl-zoo')).toEqual({
audience: AUDIENCE,
connection: 'gdl-zoo',
});
});
test('forwards a trimmed connection when it has surrounding whitespace', () => {
- expect(buildAuth0AuthorizationParams(AUDIENCE, null, ' gdl-zoo ')).toEqual({
+ expect(buildAuth0AuthorizationParams(AUDIENCE, ' gdl-zoo ')).toEqual({
audience: AUDIENCE,
connection: 'gdl-zoo',
});
});
test('omits the connection entirely when it is not provided', () => {
- const params = buildAuth0AuthorizationParams(AUDIENCE, null);
+ const params = buildAuth0AuthorizationParams(AUDIENCE);
expect(params).toEqual({ audience: AUDIENCE });
expect(params).not.toHaveProperty('connection');
});
test('omits the connection entirely when it is null, empty, or only whitespace', () => {
[null, '', ' '].forEach((connection) => {
- const params = buildAuth0AuthorizationParams(AUDIENCE, null, connection);
+ const params = buildAuth0AuthorizationParams(AUDIENCE, connection);
expect(params).toEqual({ audience: AUDIENCE });
expect(params).not.toHaveProperty('connection');
});
});
-
- test('forwards both the organization and the connection instead of special-casing their overlap', () => {
- expect(buildAuth0AuthorizationParams(AUDIENCE, 'org_abc', 'gdl-zoo')).toEqual({
- audience: AUDIENCE,
- organization: 'org_abc',
- connection: 'gdl-zoo',
- });
- });
});
});