fix(organization): apply membershipLimit to listMembers user fetch (#10342)

This commit is contained in:
Maxwell
2026-07-12 18:58:21 +00:00
committed by GitHub
parent 7508940376
commit bae71988ab
3 changed files with 90 additions and 0 deletions
@@ -0,0 +1,5 @@
---
"better-auth": patch
---
Fix `organization.listMembers` failing with "User not found for member" for orgs with more than ~100 members by applying the same membership limit to the users query.
@@ -219,6 +219,8 @@ export const getOrgAdapter = <O extends OrganizationOptions>(
],
}),
]);
// Prisma/Drizzle default findMany to ~100 when limit is omitted.
// Bound by the members result so every joined user row is fetched.
const users = await adapter.findMany<User>({
model: "user",
where: [
@@ -228,6 +230,7 @@ export const getOrgAdapter = <O extends OrganizationOptions>(
operator: "in",
},
],
limit: members[0].length,
});
return {
members: members[0].map((member) => {
@@ -288,6 +288,88 @@ describe("listMembers", async () => {
});
});
/**
* @see https://github.com/better-auth/better-auth/issues/9407
*/
describe("listMembers with >100 members", async () => {
const { auth, signInWithTestUser } = await getTestInstance({
plugins: [
organization({
membershipLimit: 500,
}),
],
});
const ctx = await auth.$context;
const { headers } = await signInWithTestUser();
const client = createAuthClient({
plugins: [organizationClient()],
baseURL: "http://localhost:3000/api/auth",
fetchOptions: {
customFetchImpl: async (url, init) => {
return auth.handler(new Request(url, init));
},
},
});
const org = await client.organization.create({
name: "large-org",
slug: "large-org",
fetchOptions: {
headers,
},
});
// Owner + 110 members = 111 total (>100 default adapter limit)
for (let i = 0; i < 110; i++) {
const user = await ctx.adapter.create({
model: "user",
data: {
email: `large-org-${i}@test.com`,
name: `large-org-${i}`,
},
});
await auth.api.addMember({
body: {
organizationId: org.data?.id as string,
userId: user.id,
role: "member",
},
});
}
// Prisma/Drizzle default findMany to ~100 rows when limit is omitted.
// Apply that default after seeding so setup is unaffected, then assert
// listMembers still returns every member user.
const originalFindMany = ctx.adapter.findMany.bind(ctx.adapter);
ctx.adapter.findMany = async (data) => {
return originalFindMany({
...data,
limit: data.limit ?? 100,
});
};
it("should list all members when membershipLimit is above the adapter default", async () => {
const members = await client.organization.listMembers({
fetchOptions: {
headers,
},
query: {
organizationId: org.data?.id as string,
},
});
expect(members.error).toBeNull();
expect(members.data?.members.length).toBe(111);
expect(members.data?.total).toBe(111);
expect(
members.data?.members.every(
(member) =>
typeof member.user?.id === "string" &&
typeof member.user?.email === "string",
),
).toBe(true);
});
});
describe("updateMemberRole", async () => {
const { auth, signInWithTestUser, customFetchImpl } = await getTestInstance({
plugins: [organization()],