Skip to content

Commit 0b6d5d5

Browse files
committed
chore(deps): upgrade better-auth to 1.7.12
Moves better-auth, @better-auth/oauth-provider, @better-auth/sso and @better-auth/stripe (and packages/auth) from 1.6.27 to 1.7.12, which carries the 1.7.7 fixes for the magic-link, OAuth proxy, device-authorization and rate-limit advisories. Each place 1.7 changes behavior Sim relies on is adapted so connects, sign-ins, the MCP OAuth provider and enterprise SSO keep working. Dependencies - @better-auth/core calls z.validate, added in zod 4.5; the root override keeps zod on 4.3.6, so a version-pinned patch restores 1.6's safeParse form. - The global @xmldom/xmldom 0.8.15 override is dropped: @better-auth/sso needs 0.9.12, and samlify, xml-crypto, mammoth, officeparser and plist keep 0.8.15. - The oauth-provider 1.6.27 resource patch is obsolete and removed. Integration connectors - Generic OAuth no longer serves /oauth2/*; the registered /api/auth/oauth2/callback/:id URIs are forwarded to core's /callback/:id, so no provider registration changes. Clients link through linkSocial. - Custom getToken uses the registered redirect URI; Google connectors use explicit endpoints instead of discovery; scope rewrites move into the token exchange; reserved authorization params (scope, nonce) are passed as options; pkce stays off where it was off. - Connector callbacks only complete an authenticated link and never sign up or sign in a user; linking counts the profile email as verified. - Account hooks identify connector callbacks by the account's provider, since hook contexts carry the route pattern. MCP OAuth provider - Resources stay out of 1.7's static resource model via a signed sim_resource parameter; public clients are recognized by token_endpoint_auth_method none; dynamic registration infers the application type; registrations keep their requested scopes; DPoP is refused and metadata advertises only what Sim honors; access tokens revoked at sign-out are rejected. - Migration 0447 adds 1.7's columns and tables expand-only, so 1.6 and 1.7 pods share the schema during rollout. Runtime schema validation is off because migrations own Sim's schema. Enterprise SSO - The legacy SAML ACS URL is forwarded to /sso/saml2/sp/acs/:id, so IdPs need no change; new providers advertise the new URL. - Registration matches 1.7's strict schemas (no mapping.id; idpMetadata as metadata or entityID) and checks 1.7's sign-in policy on save. - Migration 0448 spells out entityID for certificate-only providers and clears wantAssertionsSigned where the SP metadata never asked for signed assertions, keeping 1.6's accept-any-IdP-signature policy. - POST sign-in/email-otp is refused: 1.7 deletes every linked account of an unverified user there.
1 parent 9e2afe0 commit 0b6d5d5

75 files changed

Lines changed: 65603 additions & 694 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/desktop/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ The main process and two preloads are bundled by esbuild into `dist/main.cjs`, `
6767
2. `apps/sim/app/desktop/auth/page.tsx` requires a browser session (redirects through `/login?callbackUrl=…`) and renders a **Continue** gesture gate; only that click mints a one-time token (`POST /api/desktop/auth/handoff`) and sends the browser to the loopback (`http://127.0.0.1:<port>/auth/callback?token=…&state=…`, RFC 8252 §7.3). The gate is the security boundary — state and port are attacker-choosable in a crafted link, so a bare GET must never mint. The loopback is the single hand-back channel: no OS scheme registration, works identically in dev and packaged builds. Interception of loopback is mitigated the way PKCE mitigates it: the token is single-use, short-TTL, and bound to a 128-bit state the app compares in constant time. (RFC 8252's *most*-preferred callback is claimed-`https` / macOS universal links, which bind the OS to a verified app identity — a future hardening step that needs an associated-domains entitlement + `apple-app-site-association` on the origin.)
6868
3. The loopback fires the callback in the main process: the app validates the state, then a renderer in the app partition POSTs the token to `/api/auth/one-time-token/verify` (same-origin ⇒ trustedOrigins/CSRF pass; better-auth sets the session cookie and burns the token) and loads `/workspace`.
6969
- **The app gets its own session, never the browser's.** The token is minted by `POST /api/desktop/auth/handoff` (`apps/sim/lib/auth/desktop-handoff.ts`), which creates a *new* session row for the user and points the one-time token at that. Better-auth's own `generateOneTimeToken` binds to the *calling* session, so using it directly made the app and the authorizing browser share one row: signing out of either deleted the row the other was still presenting, and — because the session cookie cache is not revalidated against the database — the survivor kept looking signed in while every database-backed session resolution failed (the socket handshake logged `Session not found` and 401-looped). A session per device is what better-auth's own device authorization grant does and what RFC 8252 assumes: sign-out, revocation, and expiry apply to one surface at a time. **Do not "simplify" this back to `generateOneTimeToken`.**
70-
- Integration connects are **same-window redirects** (`client.oauth2.link`), not popups. Unknown provider hosts stay in-window (lenient default); Google/Microsoft connects get a native dialog offering to finish in the browser — the browser is signed in after the login handoff, tokens land server-side, the app just refreshes.
70+
- Integration connects are **same-window redirects** (`client.linkSocial`), not popups. Unknown provider hosts stay in-window (lenient default); Google/Microsoft connects get a native dialog offering to finish in the browser — the browser is signed in after the login handoff, tokens land server-side, the app just refreshes.
7171
- The MCP OAuth popup (`mcp-oauth-*`) is allowed as a same-partition child so `window.opener.postMessage` keeps working.
7272
- Sign-out **revokes server-side first** (`POST /api/auth/sign-out` from the app-origin renderer — a main-process `session.fetch` would be rejected by better-auth's origin check), then clears cookies/localStorage/IndexedDB/cache/service workers plus any pending handoff state. The revoke is not optional: since the app owns its own session row, clearing the partition alone would strand a live 30-day credential that nothing can revoke. Two signals trigger teardown: the `/login?fromLogout=true` navigation (fast path) **and** deletion of the better-auth session cookie confirmed by a `get-session` probe (robust backstop — catches every sign-out path, not just the settings one, and rotation can't cause a false teardown). API 401s (probe-confirmed) surface a native re-auth prompt.
7373

‎apps/docs/content/docs/cli/organizations.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,7 @@ sim organizations sso providers save [options]
912912
| `--domain <value>` | Yes | Email domain already verified by this organization. |
913913
| `--jit-provisioning-enabled` | No | Allow SSO sign-in to provision organization membership, subject to eligibility and available seats. Defaults to true. |
914914
| `--no-jit-provisioning-enabled` | No | Send --jit-provisioning-enabled as false. |
915-
| `--mapping <json\|@file>` | No | Identity-provider claims mapped to user fields. Defaults to &#123;"id":"sub","email":"email","name":"name","image":"picture"&#125;. (JSON, or @path / @- to read a file or stdin). |
915+
| `--mapping <json\|@file>` | No | Identity-provider claims mapped to user fields. Defaults to &#123;"email":"email","name":"name","image":"picture"&#125;. (JSON, or @path / @- to read a file or stdin). |
916916
| `--client-id <value>` | Conditional | Identity provider client identifier. Available when providerType is oidc. Required when providerType is oidc. |
917917
| `--client-secret <value\|@file>` | Conditional | Write-only OIDC client secret; the redacted marker from providers get preserves an existing secret. Passing it inline exposes it to shell history and process listings. Required when --provider-type is oidc (@path / @- reads a file or stdin verbatim, including trailing newlines; @@value for a literal leading @). |
918918
| `--scopes <json\|@file>` | No | OIDC scopes; offline_access is omitted. Available when providerType is oidc. Defaults to ["openid","profile","email"]. (JSON, or @path / @- to read a file or stdin). |
@@ -926,7 +926,7 @@ sim organizations sso providers save [options]
926926
| `--jwks-endpoint <value>` | No | Optional signing-key endpoint; otherwise resolved through issuer discovery. Available when providerType is oidc. |
927927
| `--entry-point <value>` | Conditional | Identity provider SAML sign-in endpoint. Available when providerType is saml. Required when providerType is saml. |
928928
| `--cert <value>` | Conditional | Identity provider signing certificate. Available when providerType is saml. Required when providerType is saml. |
929-
| `--callback-url <value>` | No | SAML callback URL; defaults to this provider’s Sim callback. Available when providerType is saml. |
929+
| `--callback-url <value>` | No | SAML ACS URL advertised to the identity provider; defaults to this provider’s Sim ACS URL. Available when providerType is saml. |
930930
| `--audience <value>` | No | SAML audience; omission preserves the saved value. Available when providerType is saml. |
931931
| `--want-assertions-signed` | No | Require signed assertions; omission preserves the saved value. Available when providerType is saml. |
932932
| `--no-want-assertions-signed` | No | Send --want-assertions-signed as false. |

‎apps/docs/content/docs/cli/reference.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4179,7 +4179,7 @@ sim organizations sso providers save [options]
41794179
| `--domain <value>` | Yes | Email domain already verified by this organization. |
41804180
| `--jit-provisioning-enabled` | No | Allow SSO sign-in to provision organization membership, subject to eligibility and available seats. Defaults to true. |
41814181
| `--no-jit-provisioning-enabled` | No | Send --jit-provisioning-enabled as false. |
4182-
| `--mapping <json\|@file>` | No | Identity-provider claims mapped to user fields. Defaults to &#123;"id":"sub","email":"email","name":"name","image":"picture"&#125;. (JSON, or @path / @- to read a file or stdin). |
4182+
| `--mapping <json\|@file>` | No | Identity-provider claims mapped to user fields. Defaults to &#123;"email":"email","name":"name","image":"picture"&#125;. (JSON, or @path / @- to read a file or stdin). |
41834183
| `--client-id <value>` | Conditional | Identity provider client identifier. Available when providerType is oidc. Required when providerType is oidc. |
41844184
| `--client-secret <value\|@file>` | Conditional | Write-only OIDC client secret; the redacted marker from providers get preserves an existing secret. Passing it inline exposes it to shell history and process listings. Required when --provider-type is oidc (@path / @- reads a file or stdin verbatim, including trailing newlines; @@value for a literal leading @). |
41854185
| `--scopes <json\|@file>` | No | OIDC scopes; offline_access is omitted. Available when providerType is oidc. Defaults to ["openid","profile","email"]. (JSON, or @path / @- to read a file or stdin). |
@@ -4193,7 +4193,7 @@ sim organizations sso providers save [options]
41934193
| `--jwks-endpoint <value>` | No | Optional signing-key endpoint; otherwise resolved through issuer discovery. Available when providerType is oidc. |
41944194
| `--entry-point <value>` | Conditional | Identity provider SAML sign-in endpoint. Available when providerType is saml. Required when providerType is saml. |
41954195
| `--cert <value>` | Conditional | Identity provider signing certificate. Available when providerType is saml. Required when providerType is saml. |
4196-
| `--callback-url <value>` | No | SAML callback URL; defaults to this provider’s Sim callback. Available when providerType is saml. |
4196+
| `--callback-url <value>` | No | SAML ACS URL advertised to the identity provider; defaults to this provider’s Sim ACS URL. Available when providerType is saml. |
41974197
| `--audience <value>` | No | SAML audience; omission preserves the saved value. Available when providerType is saml. |
41984198
| `--want-assertions-signed` | No | Require signed assertions; omission preserves the saved value. Available when providerType is saml. |
41994199
| `--no-want-assertions-signed` | No | Send --want-assertions-signed as false. |

‎apps/docs/content/docs/platform/enterprise/sso.mdx‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ Use OIDC when your provider supports OpenID Connect, or SAML 2.0 for a SAML conn
4949
| **Domain** | The verified email domain served by this connection, such as `acme.com` |
5050
| **On first SSO sign-in** | **Automatic** adds new organization members; **Invite only** authenticates without adding membership. See [membership behavior](#membership-and-account-linking). |
5151

52-
For OIDC, also enter **Client ID** and **Client secret**. Sim discovers authorization, token, UserInfo, and JWKS endpoints from `{issuer}/.well-known/openid-configuration`. Under **Advanced options**, you can set **Scopes** (default `openid,profile,email`) and override authorization, token, or JWKS endpoints if discovery does not supply usable values.
52+
For OIDC, also enter **Client ID** and **Client secret**. Sim discovers authorization, token, UserInfo, and JWKS endpoints from `{issuer}/.well-known/openid-configuration`. The issuer must match the `iss` claim of the provider's ID tokens exactly. Under **Advanced options**, you can set **Scopes** (default `openid,profile,email`) and override authorization, token, or JWKS endpoints if discovery does not supply usable values.
5353

54-
For SAML, also enter **Entry point URL**, the provider's SSO service URL, and **Identity provider certificate**, its Base64 X.509 certificate. Under **Advanced options**, you can configure Audience, Callback URL override, signed-assertion requirements, NameID format, and IdP metadata XML. Metadata does not replace the required certificate.
54+
For SAML, also enter **Entry point URL**, the provider's SSO service URL, and **Identity provider certificate**, its Base64 X.509 certificate. Under **Advanced options**, you can configure Audience, Callback URL override, signed-assertion requirements, NameID format, and IdP metadata XML. Metadata does not replace the required certificate. When signed assertions are required, the identity provider must sign the assertion itself; a signature on the response alone is rejected.
5555

56-
**Attribute mapping** lets either protocol override the email, name, and stable user-ID claim names. Leave a mapping blank to use the protocol default.
56+
**Attribute mapping** lets either protocol override the email and name claim names. Leave a mapping blank to use the protocol default. Sim identifies each user by the OIDC `sub` claim or the SAML NameID, so choose a NameID format that stays stable for each person.
5757

5858
### Register the callback URL
5959

@@ -62,9 +62,9 @@ Copy **Callback URL** for OIDC or **ACS URL (Reply URL)** for SAML, then registe
6262
| Protocol | Callback format |
6363
| --- | --- |
6464
| OIDC | `https://<your-sim-domain>/api/auth/sso/callback/{provider-id}` |
65-
| SAML | `https://<your-sim-domain>/api/auth/sso/saml2/callback/{provider-id}` |
65+
| SAML | `https://<your-sim-domain>/api/auth/sso/saml2/sp/acs/{provider-id}` |
6666

67-
Sim Cloud uses `www.sim.ai`; self-hosted deployments use their public domain. Use your chosen **Provider ID** in both systems. For SAML, register the **SP Entity ID** as the provider's audience/Entity ID. If you set a **Callback URL override**, Sim's copyable ACS URL uses that override.
67+
Sim Cloud uses `www.sim.ai`; self-hosted deployments use their public domain. SAML providers saved before the ACS URL changed keep `/api/auth/sso/saml2/callback/{provider-id}`, which still works, so their identity provider needs no change. Use your chosen **Provider ID** in both systems. For SAML, register the **SP Entity ID** as the provider's audience/Entity ID. If you set a **Callback URL override**, Sim's copyable ACS URL uses that override.
6868

6969
## Provider guides
7070

@@ -186,7 +186,7 @@ See [Microsoft SAML setup](https://learn.microsoft.com/en-us/entra/identity/ente
186186
3. Set **Relying party identifier** to Sim's **SP Entity ID**. Sim Cloud normally shows `https://www.sim.ai`; copy the value from your own deployment.
187187
4. Add a **SAML Assertion Consumer Service** endpoint with HTTP POST binding:
188188
```
189-
https://<your-sim-domain>/api/auth/sso/saml2/callback/acme-adfs
189+
https://<your-sim-domain>/api/auth/sso/saml2/sp/acs/acme-adfs
190190
```
191191
5. Under **Certificates**, export the **Token-signing certificate** using **View Certificate → Details → Copy to File → Base-64 encoded X.509 (.CER)**. Paste the text, including its header and footer, into Sim.
192192
6. Copy the **Federation Service identifier** from **Service → Federation Service Properties**, and the SSO endpoint, such as `https://adfs.acme.com/adfs/ls`.

‎apps/docs/openapi-v2-resources.json‎

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23482,7 +23482,6 @@
2348223482
},
2348323483
"mapping": {
2348423484
"default": {
23485-
"id": "sub",
2348623485
"email": "email",
2348723486
"name": "name",
2348823487
"image": "picture"
@@ -23491,8 +23490,7 @@
2349123490
"type": "object",
2349223491
"properties": {
2349323492
"id": {
23494-
"default": "sub",
23495-
"description": "Claim holding the stable identity identifier.",
23493+
"description": "Deprecated and ignored: users are identified by the OIDC sub claim or the SAML NameID.",
2349623494
"type": "string",
2349723495
"minLength": 1,
2349823496
"maxLength": 255
@@ -23513,7 +23511,7 @@
2351323511
},
2351423512
"image": {
2351523513
"default": "picture",
23516-
"description": "Claim holding the avatar URL.",
23514+
"description": "Claim holding the avatar URL; OIDC only.",
2351723515
"type": "string",
2351823516
"minLength": 1,
2351923517
"maxLength": 255
@@ -23623,7 +23621,6 @@
2362323621
},
2362423622
"mapping": {
2362523623
"default": {
23626-
"id": "sub",
2362723624
"email": "email",
2362823625
"name": "name",
2362923626
"image": "picture"
@@ -23632,8 +23629,7 @@
2363223629
"type": "object",
2363323630
"properties": {
2363423631
"id": {
23635-
"default": "sub",
23636-
"description": "Claim holding the stable identity identifier.",
23632+
"description": "Deprecated and ignored: users are identified by the OIDC sub claim or the SAML NameID.",
2363723633
"type": "string",
2363823634
"minLength": 1,
2363923635
"maxLength": 255
@@ -23654,7 +23650,7 @@
2365423650
},
2365523651
"image": {
2365623652
"default": "picture",
23657-
"description": "Claim holding the avatar URL.",
23653+
"description": "Claim holding the avatar URL; OIDC only.",
2365823654
"type": "string",
2365923655
"minLength": 1,
2366023656
"maxLength": 255
@@ -23675,7 +23671,7 @@
2367523671
"description": "Identity provider signing certificate."
2367623672
},
2367723673
"callbackUrl": {
23678-
"description": "SAML callback URL; defaults to this provider’s Sim callback.",
23674+
"description": "SAML ACS URL advertised to the identity provider; defaults to this provider’s Sim ACS URL.",
2367923675
"type": "string",
2368023676
"maxLength": 2048,
2368123677
"format": "uri"

‎apps/sim/app/(auth)/oauth/sign-in/route.test.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ describe('OAuth login bridge', () => {
5959
expect(callback.searchParams.has('ba_iat')).toBe(false)
6060
})
6161

62+
it('restores the client resource the authorize route handed the provider', async () => {
63+
const response = await GET(
64+
request(
65+
'client_id=mcp-client&scope=api%3Aread&sim_resource=https%3A%2F%2Fsim.test%2Fapi%2Fmcp&sig=signed'
66+
)
67+
)
68+
const { callback } = redirectParts(response)
69+
70+
expect(callback.searchParams.get('resource')).toBe('https://sim.test/api/mcp')
71+
expect(callback.searchParams.has('sim_resource')).toBe(false)
72+
})
73+
6274
it('uses the same redirect precedence as the form and requires authentication for OAuth', async () => {
6375
const destination = new URL('/login', 'https://sim.test')
6476
destination.searchParams.set('callbackUrl', '/api/auth/oauth2/authorize?client_id=sim-cli')

‎apps/sim/app/(auth)/oauth/sign-in/route.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type NextRequest, NextResponse } from 'next/server'
2+
import { OAUTH_AUTHORIZED_RESOURCE_PARAM } from '@/lib/auth/oauth-resource'
23
import { isAuthDisabled, isRegistrationDisabled } from '@/lib/core/config/env-flags'
34
import { getBaseUrl } from '@/lib/core/utils/urls'
45
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
@@ -45,6 +46,13 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
4546

4647
const params = new URLSearchParams(request.nextUrl.search)
4748
for (const name of SIGNED_QUERY_PARAMS) params.delete(name)
49+
/**
50+
* The authorize route renamed the client's `resource` for the provider;
51+
* restore it so the resumed request is validated again.
52+
*/
53+
const authorizedResource = params.get(OAUTH_AUTHORIZED_RESOURCE_PARAM)
54+
params.delete(OAUTH_AUTHORIZED_RESOURCE_PARAM)
55+
if (authorizedResource !== null) params.set('resource', authorizedResource)
4856
const requiresLogin = consumeInteractivePrompt(params)
4957

5058
const authorizeUrl = `/api/auth/oauth2/authorize?${params.toString()}`

0 commit comments

Comments
 (0)