mirror of
https://github.com/better-auth/better-auth.git
synced 2026-08-23 17:58:47 -05:00
fix(sync): address behavioral review findings
This commit is contained in:
@@ -1530,6 +1530,8 @@ await authClient.signIn.sso({
|
||||
|
||||
For unsolicited **IdP-initiated** flows where `signIn.sso()` is not used (and therefore no client-side `callbackURL` is set), configure `idpInitiatedCallbackUrl` in the provider's `samlConfig` or in the global `saml` plugin options. The provider setting takes precedence over the global setting, and the same fallback applies to validation error redirects. Better Auth-generated `RelayState` success and error callbacks retain higher priority. If neither fallback is configured, the existing redirect behavior is preserved.
|
||||
|
||||
When updating a provider, pass `null` for `samlConfig.idpInitiatedCallbackUrl` to remove its override and fall back to the global setting.
|
||||
|
||||
The callback route supports both GET and POST methods automatically, so you don't need to create any additional route handlers in your framework.
|
||||
|
||||
### Additional Provider Fields
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
"url": "git+https://github.com/better-auth/better-auth.git"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "git rev-parse --git-dir >/dev/null 2>&1 && lefthook install --reset-hooks-path || echo \"skipping lefthook (not a git repo)\"",
|
||||
"prepare": "if git rev-parse --git-dir >/dev/null 2>&1; then lefthook install --reset-hooks-path; else echo \"skipping lefthook (not a git repo)\"; fi",
|
||||
"build": "turbo build --filter=./packages/*",
|
||||
"dev": "turbo dev --filter=./packages/* --filter=!./packages/cli",
|
||||
"clean": "turbo clean --filter=./packages/* && rm -rf node_modules",
|
||||
|
||||
@@ -1096,7 +1096,7 @@ describe("lastLoginMethod", async () => {
|
||||
consentGiven = true;
|
||||
|
||||
// Second login with consent - cookie should be set
|
||||
await client.signOut();
|
||||
await client.signOut({ fetchOptions: { headers: headers1 } });
|
||||
const headers2 = new Headers();
|
||||
await client.signIn.email(
|
||||
{
|
||||
|
||||
@@ -440,7 +440,7 @@ function dbFieldToRequestBodyProperty(field: DBFieldAttribute): OpenAPISchema {
|
||||
return { type: "string", format: "date-time" };
|
||||
}
|
||||
if (field.type === "json") {
|
||||
return { type: "object", additionalProperties: true };
|
||||
return {};
|
||||
}
|
||||
if (field.type === "string[]") {
|
||||
return { type: "array", items: { type: "string" } };
|
||||
|
||||
@@ -402,6 +402,37 @@ describe("open-api", async () => {
|
||||
expect(schemas["User"]!.required).toContain("scores");
|
||||
});
|
||||
|
||||
/**
|
||||
* @see https://github.com/better-auth/better-auth/issues/10430
|
||||
*/
|
||||
it("should allow every JSON value in additional field request bodies", async () => {
|
||||
const { auth } = await getTestInstance(
|
||||
{
|
||||
plugins: [openAPI()],
|
||||
user: {
|
||||
additionalFields: {
|
||||
metadata: {
|
||||
type: "json",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ disableTestUser: true },
|
||||
);
|
||||
const schema = await auth.api.generateOpenAPISchema();
|
||||
const paths = schema.paths as Record<string, Path>;
|
||||
|
||||
const signUpSchema = getPostRequestBody(paths, "/sign-up/email").content[
|
||||
"application/json"
|
||||
].schema;
|
||||
expect(getSchemaProperty(signUpSchema, "metadata")).toEqual({});
|
||||
|
||||
const updateUserSchema = getPostRequestBody(paths, "/update-user").content[
|
||||
"application/json"
|
||||
].schema;
|
||||
expect(getSchemaProperty(updateUserSchema, "metadata")).toEqual({});
|
||||
});
|
||||
|
||||
it("should include additionalFields on sign-up and update-user request bodies", async () => {
|
||||
const schema = await auth.api.generateOpenAPISchema();
|
||||
const paths = schema.paths as Record<string, Path>;
|
||||
|
||||
@@ -167,6 +167,7 @@ async function generateAction(opts: any) {
|
||||
);
|
||||
return;
|
||||
}
|
||||
await removeGeneratedStub();
|
||||
|
||||
let adapter: DBAdapter;
|
||||
if (options.adapter) {
|
||||
|
||||
@@ -128,6 +128,69 @@ export const auth = betterAuth({
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @see https://github.com/better-auth/better-auth/issues/10136
|
||||
*/
|
||||
it("should remove a config import stub before generating a new Prisma schema", async () => {
|
||||
const cacheDir = path.join(
|
||||
process.cwd(),
|
||||
"node_modules",
|
||||
".cache",
|
||||
"generate-output-",
|
||||
);
|
||||
fs.mkdirSync(path.dirname(cacheDir), { recursive: true });
|
||||
const tmpDir = fs.mkdtempSync(cacheDir);
|
||||
const outputPath = path.join(tmpDir, "schema.ts");
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, "auth.ts"),
|
||||
`import schema from "./schema";
|
||||
import { betterAuth } from "better-auth";
|
||||
|
||||
export const auth = betterAuth({
|
||||
secret: "test-secret",
|
||||
baseURL: "http://localhost:3000",
|
||||
});
|
||||
|
||||
export const __schema = schema;
|
||||
`,
|
||||
);
|
||||
|
||||
try {
|
||||
await execFileAsync(
|
||||
process.execPath,
|
||||
[
|
||||
cliPath,
|
||||
"generate",
|
||||
"--cwd",
|
||||
tmpDir,
|
||||
"--config",
|
||||
"auth.ts",
|
||||
"--adapter",
|
||||
"prisma",
|
||||
"--dialect",
|
||||
"sqlite",
|
||||
"--output",
|
||||
"schema.ts",
|
||||
"--yes",
|
||||
],
|
||||
{
|
||||
cwd: tmpDir,
|
||||
env: {
|
||||
...process.env,
|
||||
BETTER_AUTH_TELEMETRY_DISABLED: "true",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const schema = fs.readFileSync(outputPath, "utf-8");
|
||||
expect(schema).toContain("generator client");
|
||||
expect(schema).toContain("datasource db");
|
||||
expect(schema).toContain("model User");
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should generate prisma schema", async () => {
|
||||
|
||||
@@ -1068,6 +1068,47 @@ kBGIJYs=
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* @see https://github.com/better-auth/better-auth/issues/10329
|
||||
*/
|
||||
it("should clear a provider-level IdP-initiated callback URL", async () => {
|
||||
const { auth, getAuthHeaders, data } = createTestAuth(false);
|
||||
const headers = await getAuthHeaders({
|
||||
email: "owner@example.com",
|
||||
password: "password123",
|
||||
name: "Owner",
|
||||
});
|
||||
|
||||
await auth.api.registerSSOProvider({
|
||||
body: {
|
||||
providerId: "my-saml-provider",
|
||||
issuer: "https://idp.example.com",
|
||||
domain: "example.com",
|
||||
samlConfig: {
|
||||
entryPoint: "https://idp.example.com/sso",
|
||||
cert: TEST_CERT,
|
||||
idpInitiatedCallbackUrl: "/dashboard",
|
||||
idpMetadata: { entityID: "https://idp.example.com" },
|
||||
},
|
||||
},
|
||||
headers,
|
||||
});
|
||||
|
||||
const updated = await auth.api.updateSSOProvider({
|
||||
body: {
|
||||
providerId: "my-saml-provider",
|
||||
samlConfig: { idpInitiatedCallbackUrl: null },
|
||||
},
|
||||
headers,
|
||||
});
|
||||
|
||||
expect(updated.samlConfig?.idpInitiatedCallbackUrl).toBeUndefined();
|
||||
const storedConfig = safeJsonParse<SAMLConfig>(
|
||||
data.ssoProvider[0]!.samlConfig!,
|
||||
);
|
||||
expect(storedConfig?.idpInitiatedCallbackUrl).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should perform partial update on OIDC provider", async () => {
|
||||
const { auth, getAuthHeaders, createOIDCProviderData, data } =
|
||||
createTestAuth(false);
|
||||
|
||||
@@ -590,8 +590,9 @@ function parseAndValidateConfig<T>(
|
||||
|
||||
type SAMLConfigUpdate = Omit<
|
||||
Partial<SAMLConfig>,
|
||||
"idpMetadata" | "spMetadata"
|
||||
"idpInitiatedCallbackUrl" | "idpMetadata" | "spMetadata"
|
||||
> & {
|
||||
idpInitiatedCallbackUrl?: string | null | undefined;
|
||||
idpMetadata?: Partial<SAMLIdentityProviderMetadata> | undefined;
|
||||
spMetadata?: Partial<NonNullable<SAMLConfig["spMetadata"]>> | undefined;
|
||||
};
|
||||
@@ -631,7 +632,9 @@ function mergeSAMLConfig(
|
||||
audience: updates.audience ?? current.audience,
|
||||
callbackUrl: updates.callbackUrl ?? current.callbackUrl,
|
||||
idpInitiatedCallbackUrl:
|
||||
updates.idpInitiatedCallbackUrl ?? current.idpInitiatedCallbackUrl,
|
||||
updates.idpInitiatedCallbackUrl === null
|
||||
? undefined
|
||||
: (updates.idpInitiatedCallbackUrl ?? current.idpInitiatedCallbackUrl),
|
||||
wantAssertionsSigned:
|
||||
updates.wantAssertionsSigned ?? current.wantAssertionsSigned,
|
||||
authnRequestsSigned:
|
||||
|
||||
@@ -326,6 +326,7 @@ const updateSSOProviderBodySchema = z.object({
|
||||
.omit({ idpMetadata: true })
|
||||
.partial()
|
||||
.extend({
|
||||
idpInitiatedCallbackUrl: samlRedirectUrlSchema.nullable().optional(),
|
||||
idpMetadata: samlIdentityProviderMetadataUpdateSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
|
||||
Reference in New Issue
Block a user