From 45bb84e9d072a19dc2a41c7dd87287418a5d63d7 Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Wed, 14 Oct 2026 10:13:20 -0700 Subject: [PATCH 1/3] docs: move the SAML ACS URL to /sp/acs and document IdP-initiated sign-in [ENG-1736] Better Auth 1.7.1 serves the ACS at /api/auth/sso/saml2/sp/acs/; the old /saml2/callback/ path answers 404. It also checks the assertion's Recipient and Destination against that URL, and refuses IdP-initiated sign-ins unless SSO_SAML_ALLOW_IDP_INITIATED=true, which the API already reads. The Audience is still SSO_SAML_ISSUER. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhM4wHR2xPP8bbrkXGaqUD --- docs/configuration.md | 3 ++- docs/sso-saml.md | 12 ++++++++++-- on-prem/.env.example | 4 +++- on-prem/CHANGELOG.md | 8 ++++++++ 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index a6821ad..72e187e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -99,11 +99,12 @@ Enable SAML single sign-on by setting the two required variables and mounting yo |----------|------|---------|-------------| | `SSO_SAML_IDP_METADATA_FILE` | string | _(empty)_ | Path to the mounted IdP metadata XML (e.g. `/etc/currents/sso/idp-metadata.xml`). **Required to enable SSO.** | | `SSO_SAML_ISSUER` | string | _(empty)_ | SP entityID / audience — a stable opaque identifier (e.g. `currents-onprem:your-org`). Must match the Audience / SP Entity ID configured in your IdP. **Required to enable SSO.** | -| `SSO_SAML_PROVIDER_ID` | string | `onprem-saml` | Provider id; recommend your IdP name (e.g. `okta`). Becomes the last path segment of the ACS callback URL. | +| `SSO_SAML_PROVIDER_ID` | string | `onprem-saml` | Provider id; recommend your IdP name (e.g. `okta`). Becomes the last path segment of the ACS URL, `/api/auth/sso/saml2/sp/acs/`. | | `SSO_SAML_DEFAULT_ROLE` | string | `member` | Role granted to auto-provisioned SSO users | | `SSO_ALLOWED_DOMAINS` | string | _(empty)_ | Comma-separated email domains allowed to sign in via SSO | | `SSO_SAML_AUTHN_REQUESTS_SIGNED` | bool | `false` | Sign outbound AuthnRequests (requires the SP key below) | | `SSO_SAML_SP_KEY_FILE` | string | _(empty)_ | Path to the mounted SP private key PEM (only when signing requests) | +| `SSO_SAML_ALLOW_IDP_INITIATED` | bool | `false` | Accept sign-ins started from the IdP's app dashboard. Off, such a sign-in is refused and users start from the Currents sign-in page | ### Docker Compose Configuration diff --git a/docs/sso-saml.md b/docs/sso-saml.md index fcd4467..94d185e 100644 --- a/docs/sso-saml.md +++ b/docs/sso-saml.md @@ -14,7 +14,7 @@ SSO turns on automatically once the two required variables are set and the IdP m You'll reuse these in both your IdP and your `.env`: -- **Provider id** (`SSO_SAML_PROVIDER_ID`) — set it to your IdP's service name, e.g. `okta`. It becomes the last path segment of the callback URL. +- **Provider id** (`SSO_SAML_PROVIDER_ID`) — set it to your IdP's service name, e.g. `okta`. It becomes the last path segment of the ACS URL. - **Issuer** (`SSO_SAML_ISSUER`) — a **stable, opaque** identifier that names this deployment as a SAML Service Provider. Use the form `currents-onprem:`, e.g. `currents-onprem:acme`. > ⚠️ **The issuer is not a URL.** Use a stable string like `currents-onprem:acme`. It only has to match the "Audience / SP Entity ID" you enter in your IdP — it does not need to resolve, and using your deployment URL is discouraged because that URL can change. @@ -25,13 +25,16 @@ Create a new **SAML 2.0** application. Using Okta as the example, set: | IdP field | Value | |-----------|-------| -| **Single Sign-On URL** (a.k.a. ACS / Assertion Consumer Service URL) | `/api/auth/sso/saml2/callback/okta` | +| **Single Sign-On URL** (a.k.a. ACS / Assertion Consumer Service URL) | `/api/auth/sso/saml2/sp/acs/okta` | +| **Recipient URL** and **Destination URL** | The Single Sign-On URL (Okta's default, "Use this for Recipient URL and Destination URL") | | **Audience URI** (a.k.a. SP Entity ID) | `currents-onprem:acme` | | **Name ID format** | `Persistent` | | **Application username** | Email | Replace `` with your deployment's base URL (the value of `APP_BASE_URL` in your `.env`, e.g. `https://currents.acme.com`), `okta` with your `SSO_SAML_PROVIDER_ID`, and `currents-onprem:acme` with your `SSO_SAML_ISSUER`. +Currents rejects an assertion whose Recipient or Destination is any URL other than the Single Sign-On URL. + Then **download the application's IdP metadata XML** (in Okta: the app's *Metadata URL*, or *SAML Setup Instructions → Identity Provider metadata*). This one file contains the IdP sign-on URL, entity ID, and signing certificate — Currents reads all of them from it. ## Step 3 — Provide the metadata file @@ -68,6 +71,8 @@ Go to your dashboard and enter your email on the sign-in screen. If your email i > New SSO users are added to your single on-prem organization automatically, with the role set by `SSO_SAML_DEFAULT_ROLE` (default `member`). The root admin account continues to sign in with its email + password. +Once SSO is on, Currents serves its SP metadata at `/api/auth/sso/saml2/sp/metadata?providerId=okta`, for IdPs that import it. + ## Optional configuration | Variable | Purpose | @@ -76,6 +81,7 @@ Go to your dashboard and enter your email on the sign-in screen. If your email i | `SSO_ALLOWED_DOMAINS` | Comma-separated allow-list of email domains, e.g. `acme.com,acme.io`. Defense-in-depth on top of your IdP. | | `SSO_SAML_AUTHN_REQUESTS_SIGNED` | Set `true` to sign outbound AuthnRequests. Requires an SP key (below). | | `SSO_SAML_SP_KEY_FILE` | Path to your SP private key PEM (place it in `./data/sso` too). Only needed for signed requests. | +| `SSO_SAML_ALLOW_IDP_INITIATED` | Set `true` to accept sign-ins started from the IdP's app dashboard (an Okta tile, the Entra My Apps portal). Off by default: Currents can't match such a sign-in to a request of its own, so it refuses it and users start from the Currents sign-in page. | ### Signing AuthnRequests (advanced) @@ -101,5 +107,7 @@ The metadata file is the single source for the IdP sign-on URL, entity ID, and s - **Sign-in isn't redirecting to the IdP** — confirm both `SSO_SAML_IDP_METADATA_FILE` and `SSO_SAML_ISSUER` are set and the file exists at the mounted path; SSO is off until both are present. Check the API logs at startup for SSO config validation errors. - **IdP rejects the request / "audience mismatch"** — the **Audience / SP Entity ID** in your IdP must exactly equal `SSO_SAML_ISSUER`. - **Assertion signature invalid** — the metadata XML is stale or from the wrong app; re-download it from the IdP and replace the file. +- **"Recipient does not match" or "Destination does not match"** — the Recipient or Destination URL in your IdP is not the Single Sign-On URL above. Set both to it. +- **Sign-in from the IdP's app dashboard fails, sign-in from Currents works** — set `SSO_SAML_ALLOW_IDP_INITIATED=true`, or have users start from the Currents sign-in page. See the [Configuration Reference](./configuration.md#saml-sso-optional) for the full list of SSO variables. diff --git a/on-prem/.env.example b/on-prem/.env.example index 159cbbb..edb980a 100644 --- a/on-prem/.env.example +++ b/on-prem/.env.example @@ -182,7 +182,8 @@ OAUTH_DYNAMIC_CLIENT_REGISTRATION_ENABLED=true # # Setup summary: # 1. Create a SAML app in your IdP with: -# ACS / Single Sign-On URL: /api/auth/sso/saml2/callback/okta +# ACS / Single Sign-On URL: /api/auth/sso/saml2/sp/acs/okta +# (also the Recipient and Destination URL) # Audience / SP Entity ID: currents-onprem:your-org # 2. mkdir -p ./data/sso, then download the app's metadata XML to # ./data/sso/idp-metadata.xml (mounted read-only to /etc/currents/sso). @@ -200,6 +201,7 @@ OAUTH_DYNAMIC_CLIENT_REGISTRATION_ENABLED=true # SSO_ALLOWED_DOMAINS=acme.com,acme.io # restrict SSO to these email domains # SSO_SAML_AUTHN_REQUESTS_SIGNED=false # sign AuthnRequests (requires the SP key below) # SSO_SAML_SP_KEY_FILE=/etc/currents/sso/sp-key.pem +# SSO_SAML_ALLOW_IDP_INITIATED=false # accept sign-ins started from the IdP's app dashboard # ============================================================================= # Docker Compose Configuration diff --git a/on-prem/CHANGELOG.md b/on-prem/CHANGELOG.md index c931e46..9b3d4e4 100644 --- a/on-prem/CHANGELOG.md +++ b/on-prem/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Breaking Changes +- **SAML SSO: the ACS URL moves** from `/api/auth/sso/saml2/callback/` to `/api/auth/sso/saml2/sp/acs/`. The old path answers `404`, so SSO sign-in fails until your IdP sends assertions to the new one. The root admin's email and password sign-in is unaffected. Schedule the IdP change for the upgrade window: + 1. In the IdP's SAML app, set the Single Sign-On / ACS URL (Entra ID: Reply URL) to the new URL. Set the Recipient and Destination URLs to it too; Okta does this by default. Currents rejects an assertion whose Recipient or Destination is any other URL. + 2. Keep the Audience / SP Entity ID. It must still equal `SSO_SAML_ISSUER`, which doesn't change. + 3. Sign in from the Currents sign-in page to confirm. +- **SAML SSO: sign-ins started from the IdP's app dashboard** (an Okta tile, the Entra My Apps portal) are refused unless `SSO_SAML_ALLOW_IDP_INITIATED=true`. Sign-ins started from the Currents sign-in page work either way. + ### Upgrade Notes - The scheduler runs this release's database migrations when it starts, which can take several minutes on a large dataset. Check that the scheduler's last startup in `docker compose logs scheduler` ends with `Startup tasks complete` (see [Database migration issues](../docs/upgrading.md#database-migration-issues)). If it logs an error instead, such as a ClickHouse `Timeout error.`, the remaining migrations don't run until the scheduler starts again: run `docker compose restart scheduler`. Finished migrations are skipped, and the run backfills skip the runs they already updated. @@ -20,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - `MCP_ENABLED` (default `true`), `MCP_ORIGINS` — remote MCP at `/mcp` - `OAUTH_PROVIDER_ENABLED`, `OAUTH_DYNAMIC_CLIENT_REGISTRATION_ENABLED`, `OAUTH_CIMD_ENABLED`, `OAUTH_HTTP_ALLOWED_HOSTS`, `OAUTH_JWKS_URL` — sign-in to remote MCP with a Currents account. `.env.example` turns the provider on; an upgraded `.env` without `OAUTH_PROVIDER_ENABLED=true` keeps it off. It needs an `https` `APP_BASE_URL`, or `http` on `localhost` (or, for an evaluation, a `.localhost` name in `OAUTH_HTTP_ALLOWED_HOSTS`, with `OAUTH_JWKS_URL` on `localhost`): with `http` on any other host the API refuses to start, so set `OAUTH_PROVIDER_ENABLED=false` - `API_BASE_URL` — public API origin, default `APP_BASE_URL` +- `SSO_SAML_ALLOW_IDP_INITIATED` — accept SAML sign-ins started from the IdP's app dashboard. Default `false` - `DC_RUSTFS_DELETE_ARTIFACTS_AFTER_DAYS` — delete artifacts in the bundled RustFS after this many days. Unset keeps them ### Changed Environment Variables From 832bb09b90d0dd5af2515871701700fa9166fad1 Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Wed, 14 Oct 2026 10:58:23 -0700 Subject: [PATCH 2/3] docs: name the Okta checkbox, and say the migrations are not downtime [ENG-1736] "Okta does this by default" did not say what Okta does. The upgrade note now also says the services keep serving while the scheduler migrates, and which reads can leave out older runs until it is done. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhM4wHR2xPP8bbrkXGaqUD --- docs/sso-saml.md | 2 +- on-prem/CHANGELOG.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sso-saml.md b/docs/sso-saml.md index 94d185e..125b93e 100644 --- a/docs/sso-saml.md +++ b/docs/sso-saml.md @@ -26,7 +26,7 @@ Create a new **SAML 2.0** application. Using Okta as the example, set: | IdP field | Value | |-----------|-------| | **Single Sign-On URL** (a.k.a. ACS / Assertion Consumer Service URL) | `/api/auth/sso/saml2/sp/acs/okta` | -| **Recipient URL** and **Destination URL** | The Single Sign-On URL (Okta's default, "Use this for Recipient URL and Destination URL") | +| **Recipient URL** and **Destination URL** | The Single Sign-On URL. In Okta, leave "Use this for Recipient URL and Destination URL" checked (the default) | | **Audience URI** (a.k.a. SP Entity ID) | `currents-onprem:acme` | | **Name ID format** | `Persistent` | | **Application username** | Email | diff --git a/on-prem/CHANGELOG.md b/on-prem/CHANGELOG.md index 9b3d4e4..c4991c1 100644 --- a/on-prem/CHANGELOG.md +++ b/on-prem/CHANGELOG.md @@ -8,13 +8,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Breaking Changes - **SAML SSO: the ACS URL moves** from `/api/auth/sso/saml2/callback/` to `/api/auth/sso/saml2/sp/acs/`. The old path answers `404`, so SSO sign-in fails until your IdP sends assertions to the new one. The root admin's email and password sign-in is unaffected. Schedule the IdP change for the upgrade window: - 1. In the IdP's SAML app, set the Single Sign-On / ACS URL (Entra ID: Reply URL) to the new URL. Set the Recipient and Destination URLs to it too; Okta does this by default. Currents rejects an assertion whose Recipient or Destination is any other URL. + 1. In the IdP's SAML app, set the Single Sign-On / ACS URL (Entra ID: Reply URL) to the new URL. Set the Recipient and Destination URLs to the new URL as well. In Okta, leave "Use this for Recipient URL and Destination URL" checked (the default) and both follow the Single Sign-On URL. Currents rejects an assertion whose Recipient or Destination is any other URL. 2. Keep the Audience / SP Entity ID. It must still equal `SSO_SAML_ISSUER`, which doesn't change. 3. Sign in from the Currents sign-in page to confirm. - **SAML SSO: sign-ins started from the IdP's app dashboard** (an Okta tile, the Entra My Apps portal) are refused unless `SSO_SAML_ALLOW_IDP_INITIATED=true`. Sign-ins started from the Currents sign-in page work either way. ### Upgrade Notes -- The scheduler runs this release's database migrations when it starts, which can take several minutes on a large dataset. Check that the scheduler's last startup in `docker compose logs scheduler` ends with `Startup tasks complete` (see [Database migration issues](../docs/upgrading.md#database-migration-issues)). If it logs an error instead, such as a ClickHouse `Timeout error.`, the remaining migrations don't run until the scheduler starts again: run `docker compose restart scheduler`. Finished migrations are skipped, and the run backfills skip the runs they already updated. +- The scheduler runs this release's database migrations when it starts, which can take several minutes on a large dataset. This is not downtime: the dashboard, API and run recording keep working. Until the migrations finish, filtering runs by status and searching by CI build ID can leave out runs recorded before the upgrade, and new results can take longer to process. Check that the scheduler's last startup in `docker compose logs scheduler` ends with `Startup tasks complete` (see [Database migration issues](../docs/upgrading.md#database-migration-issues)). If it logs an error instead, such as a ClickHouse `Timeout error.`, the remaining migrations don't run until the scheduler starts again: run `docker compose restart scheduler`. Finished migrations are skipped, and the run backfills skip the runs they already updated. ### Compose File Changes - The bundled RustFS image is `rustfs/rustfs:1.0.1` (requires `./scripts/generate-compose.sh` if using custom templates) From 9583641e86a04b9ccc1435c5026e312e5abbb64c Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Wed, 14 Oct 2026 11:45:09 -0700 Subject: [PATCH 3/3] docs: name the provider id in the ACS and SP metadata URLs [ENG-1736] The .env.example summary gave /acs/okta while SSO_SAML_PROVIDER_ID defaults to onprem-saml, and the SP metadata URL read as if okta were literal. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhM4wHR2xPP8bbrkXGaqUD --- docs/sso-saml.md | 2 +- on-prem/.env.example | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sso-saml.md b/docs/sso-saml.md index 125b93e..53d9232 100644 --- a/docs/sso-saml.md +++ b/docs/sso-saml.md @@ -71,7 +71,7 @@ Go to your dashboard and enter your email on the sign-in screen. If your email i > New SSO users are added to your single on-prem organization automatically, with the role set by `SSO_SAML_DEFAULT_ROLE` (default `member`). The root admin account continues to sign in with its email + password. -Once SSO is on, Currents serves its SP metadata at `/api/auth/sso/saml2/sp/metadata?providerId=okta`, for IdPs that import it. +Once SSO is on, Currents serves its SP metadata at `/api/auth/sso/saml2/sp/metadata?providerId=okta` (with your `SSO_SAML_PROVIDER_ID`), for IdPs that import it. ## Optional configuration diff --git a/on-prem/.env.example b/on-prem/.env.example index edb980a..ae9b839 100644 --- a/on-prem/.env.example +++ b/on-prem/.env.example @@ -182,7 +182,7 @@ OAUTH_DYNAMIC_CLIENT_REGISTRATION_ENABLED=true # # Setup summary: # 1. Create a SAML app in your IdP with: -# ACS / Single Sign-On URL: /api/auth/sso/saml2/sp/acs/okta +# ACS / Single Sign-On URL: /api/auth/sso/saml2/sp/acs/ # (also the Recipient and Destination URL) # Audience / SP Entity ID: currents-onprem:your-org # 2. mkdir -p ./data/sso, then download the app's metadata XML to