diff --git a/backend/__tests__/integration/oidcSso.test.js b/backend/__tests__/integration/oidcSso.test.js index b7f97c87..7524a6b7 100644 --- a/backend/__tests__/integration/oidcSso.test.js +++ b/backend/__tests__/integration/oidcSso.test.js @@ -38,6 +38,10 @@ describe('OIDC SSO (#798)', () => { beforeAll(async () => { process.env.JWT_SECRET = process.env.JWT_SECRET || 'oidc-test-secret'; + // The redirect_uri derives from the public base URL — pin it explicitly: + // CI has no backend/.env, and getFrontendBaseUrl() returning '' makes + // buildAuthorizationRequest fail (by design) with OIDC_BAD_CONFIG. + process.env.FRONTEND_URL = 'http://localhost:5199'; ({ db, cleanup } = await bootCrmDb()); idp = new MockOidcProvider(); diff --git a/backend/src/routes/adminSettings.js b/backend/src/routes/adminSettings.js index 7f4c60dc..161245c3 100644 --- a/backend/src/routes/adminSettings.js +++ b/backend/src/routes/adminSettings.js @@ -390,6 +390,10 @@ router.get('/sso', adminAuth, requirePermission('settings.view'), async (req, re try { const oidcService = require('../services/oidcService'); const cfg = await oidcService.getOidcConfig(); + // No public base URL configured → surface an empty redirect_uri rather + // than failing the whole settings read; the login route refuses to start + // the flow in that state anyway (OIDC_BAD_CONFIG). + const redirectUri = await oidcService.getRedirectUri().catch(() => ''); res.json({ oidc_enabled: cfg.enabled, oidc_issuer_url: cfg.issuerUrl || '', @@ -399,7 +403,7 @@ router.get('/sso', adminAuth, requirePermission('settings.view'), async (req, re oidc_default_role: cfg.defaultRole, oidc_button_label: cfg.buttonLabel || '', oidc_scopes: cfg.scopes, - redirect_uri: await oidcService.getRedirectUri(), + redirect_uri: redirectUri, }); } catch (error) { logger.error('Failed to read SSO settings', { error: error.message }); diff --git a/backend/src/services/oidcService.js b/backend/src/services/oidcService.js index 4e2887ee..bac4651a 100644 --- a/backend/src/services/oidcService.js +++ b/backend/src/services/oidcService.js @@ -153,6 +153,14 @@ async function getClient(cfg) { async function getRedirectUri() { const { getFrontendBaseUrl } = require('../utils/frontendUrl'); const base = (await getFrontendBaseUrl()).replace(/\/$/, ''); + if (!base) { + // Without a public base URL the redirect_uri would be relative — the IdP + // would reject it with an opaque error on ITS side. Fail here with a + // clear config message instead. + const err = new Error('FRONTEND_URL (or the general_site_url setting) must be set for SSO'); + err.code = 'OIDC_BAD_CONFIG'; + throw err; + } return `${base}/api/auth/admin/sso/callback`; }