diff --git a/packages/sso/src/oidc.test.ts b/packages/sso/src/oidc.test.ts index 90b8333f5f..6d25d53d5a 100644 --- a/packages/sso/src/oidc.test.ts +++ b/packages/sso/src/oidc.test.ts @@ -1425,39 +1425,6 @@ describe("SSO shared redirectURI", async () => { expect(provider.redirectURI).not.toContain("shared-test"); }); - it("preserves a registered relative redirect URI with a trailing auth base slash", async () => { - const { auth: trailingBaseAuth, signInWithTestUser: signIn } = - await getTestInstance({ - baseURL: "http://localhost:3000/api/auth/", - trustedOrigins: ["http://localhost:8080"], - plugins: [ - sso({ redirectURI: "/sso/callback?source=a%2Fb" }), - organization(), - ], - }); - const { headers } = await signIn(); - const provider = await trailingBaseAuth.api.registerSSOProvider({ - body: { - issuer: server.issuer.url!, - domain: "relative-redirect.com", - providerId: "relative-redirect", - oidcConfig: { - clientId: "relative-redirect", - clientSecret: "relative-redirect-secret", - authorizationEndpoint: `${server.issuer.url}/authorize`, - tokenEndpoint: `${server.issuer.url}/token`, - jwksEndpoint: `${server.issuer.url}/jwks`, - discoveryEndpoint: `${server.issuer.url}/.well-known/openid-configuration`, - }, - }, - headers, - }); - - expect(provider.redirectURI).toBe( - "http://localhost:3000/api/auth//sso/callback?source=a%2Fb", - ); - }); - it("should use shared redirect URI in authorization URL", async () => { const headers = new Headers(); const res = await authClient.signIn.sso({ diff --git a/packages/sso/src/routes/helpers.test.ts b/packages/sso/src/routes/helpers.test.ts index 281d7df4a0..4478f70701 100644 --- a/packages/sso/src/routes/helpers.test.ts +++ b/packages/sso/src/routes/helpers.test.ts @@ -1,71 +1,16 @@ import { APIError } from "better-auth/api"; -import { getTestInstance } from "better-auth/test"; -import { describe, expect, it, vi } from "vitest"; -import { sso } from "../index"; +import { describe, expect, it } from "vitest"; import type { SAMLConfig } from "../types"; import { createSAMLPostForm, createSP, deriveSAMLIdentityProviderEntityID, deriveSAMLServiceProviderPolicy, - getSAMLServiceProviderURL, } from "./helpers"; const invalidSAMLBindingLocationMessage = "SAML POST binding location must be an absolute http or https URL"; -/** @see https://url.spec.whatwg.org/#percent-encoded-bytes */ -it("preserves the configured provider ID for a SAML logout lookup", async () => { - const { auth } = await getTestInstance({ - plugins: [sso({ saml: { enableSingleLogout: true } })], - }); - const context = await auth.$context; - const lookup = vi.spyOn(context.adapter, "findOne"); - await auth.handler( - new Request( - "http://localhost:3000/api/auth/sso/saml2/sp/slo/team%2Fmember?SAMLRequest=payload", - ), - ); - expect(lookup).toHaveBeenCalledWith( - expect.objectContaining({ - model: "ssoProvider", - where: [{ field: "providerId", value: "team%2Fmember" }], - }), - ); -}); - -describe("getSAMLServiceProviderURL", () => { - it("preserves the slash boundary of a registered ACS URL", () => { - expect( - getSAMLServiceProviderURL( - "https://Auth.Example.com:443/api/auth/", - "acs", - "acme:prod", - ), - ).toBe("https://Auth.Example.com:443/api/auth//sso/saml2/sp/acs/acme:prod"); - }); - - it.each([ - "acme:prod", - "okta@corp", - "a+b", - "team%2Fmember", - "okta%3Aacme", - ])("preserves registered ACS and SLO URLs for provider %s", (providerId) => { - for (const endpoint of ["acs", "slo"] as const) { - expect( - getSAMLServiceProviderURL( - "https://Auth.Example.com:443/api/auth", - endpoint, - providerId, - ), - ).toBe( - `https://Auth.Example.com:443/api/auth/sso/saml2/sp/${endpoint}/${providerId}`, - ); - } - }); -}); - function expectInvalidSAMLBindingLocation(action: string) { try { createSAMLPostForm(action, "SAMLResponse", "base64value"); @@ -175,11 +120,11 @@ describe("createSP assertion-signing metadata", () => { expect(provider.getMetadata()).toContain('WantAssertionsSigned="true"'); }); - it("preserves registered ACS and SLO slash boundaries in metadata", () => { + it("preserves registered endpoint identities in metadata", () => { const provider = createSP( configuration, - "https://service.example.com/api/auth/", - "team%20one", + "https://Auth.Example.com:443/api/auth/", + "acme:prod@team+dev%2Fmember", { sloOptions: { wantLogoutRequestSigned: false, @@ -190,10 +135,10 @@ describe("createSP assertion-signing metadata", () => { const metadata = provider.getMetadata(); expect(metadata).toContain( - "https://service.example.com/api/auth//sso/saml2/sp/acs/team%20one", + "https://Auth.Example.com:443/api/auth//sso/saml2/sp/acs/acme:prod@team+dev%2Fmember", ); expect(metadata).toContain( - "https://service.example.com/api/auth//sso/saml2/sp/slo/team%20one", + "https://Auth.Example.com:443/api/auth//sso/saml2/sp/slo/acme:prod@team+dev%2Fmember", ); }); diff --git a/packages/sso/src/saml/response-binding.test.ts b/packages/sso/src/saml/response-binding.test.ts index 218bf3f2cf..a9d80c1e16 100644 --- a/packages/sso/src/saml/response-binding.test.ts +++ b/packages/sso/src/saml/response-binding.test.ts @@ -1,5 +1,4 @@ import { describe, expect, it } from "vitest"; -import { getSAMLServiceProviderURL } from "../routes/helpers"; import { getSAMLPostAssertionConsumerServiceUrls, hasSAMLEncryptedAssertion, @@ -64,62 +63,6 @@ function validate(xml: string, expectedAudiences = [serviceProviderEntityId]) { } describe("validateSAMLResponseBinding", () => { - it("preserves the configured ACS slash boundary", () => { - const expectedRecipients = [ - getSAMLServiceProviderURL( - "https://Auth.Example.com:443/api/auth/", - "acs", - "acme:prod", - ), - ]; - const registeredURL = - "https://Auth.Example.com:443/api/auth//sso/saml2/sp/acs/acme:prod"; - expect(() => - validateSAMLResponseBinding( - buildSAMLResponse({ - recipient: registeredURL, - destination: registeredURL, - }), - { expectedAudiences: [serviceProviderEntityId], expectedRecipients }, - ), - ).not.toThrow(); - expect(() => - validateSAMLResponseBinding( - buildSAMLResponse({ - recipient: - "https://other.example.com/api/auth//sso/saml2/sp/acs/acme:prod", - }), - { expectedAudiences: [serviceProviderEntityId], expectedRecipients }, - ), - ).toThrow(/Recipient does not match/); - }); - - it.each([ - "acme:prod", - "okta@corp", - "a+b", - "team%2Fmember", - "okta%3Aacme", - ])("accepts the previously registered Recipient and Destination for %s", (providerId) => { - const registeredURL = `https://Auth.Example.com:443/api/auth/sso/saml2/sp/acs/${providerId}`; - const xml = buildSAMLResponse({ - recipient: registeredURL, - destination: registeredURL, - }); - expect(() => - validateSAMLResponseBinding(xml, { - expectedAudiences: [serviceProviderEntityId], - expectedRecipients: [ - getSAMLServiceProviderURL( - "https://Auth.Example.com:443/api/auth", - "acs", - providerId, - ), - ], - }), - ).not.toThrow(); - }); - it("accepts an assertion addressed to this Service Provider", () => { expect(() => validate(buildSAMLResponse())).not.toThrow(); });