mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-19 11:24:57 -05:00
test(sso): focus registered endpoint compatibility coverage
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user