mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-19 23:53:17 -05:00
feat(organization): explicit organizationId in team endpoints (#5062)
Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
committed by
Alex Yang
co-authored by
Alex Yang
parent
f9727cadc1
commit
8f47015af2
@@ -912,6 +912,14 @@ const addTeamMemberBodySchema = z.object({
|
||||
description:
|
||||
"The user Id which represents the user to be added as a member.",
|
||||
}),
|
||||
|
||||
organizationId: z
|
||||
.string()
|
||||
.meta({
|
||||
description:
|
||||
"The organization ID which the team falls under. If not provided, it will default to the user's active organization.",
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const addTeamMember = <O extends OrganizationOptions>(options: O) =>
|
||||
@@ -967,7 +975,10 @@ export const addTeamMember = <O extends OrganizationOptions>(options: O) =>
|
||||
const session = ctx.context.session;
|
||||
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
|
||||
|
||||
if (!session.session.activeOrganizationId) {
|
||||
const organizationId =
|
||||
ctx.body.organizationId || session.session.activeOrganizationId;
|
||||
|
||||
if (!organizationId) {
|
||||
throw APIError.from(
|
||||
"BAD_REQUEST",
|
||||
ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION,
|
||||
@@ -976,7 +987,7 @@ export const addTeamMember = <O extends OrganizationOptions>(options: O) =>
|
||||
|
||||
const currentMember = await adapter.findMemberByOrgId({
|
||||
userId: session.user.id,
|
||||
organizationId: session.session.activeOrganizationId,
|
||||
organizationId: organizationId,
|
||||
});
|
||||
|
||||
if (!currentMember) {
|
||||
@@ -993,7 +1004,7 @@ export const addTeamMember = <O extends OrganizationOptions>(options: O) =>
|
||||
permissions: {
|
||||
member: ["update"],
|
||||
},
|
||||
organizationId: session.session.activeOrganizationId,
|
||||
organizationId: organizationId,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
@@ -1007,7 +1018,7 @@ export const addTeamMember = <O extends OrganizationOptions>(options: O) =>
|
||||
|
||||
const toBeAddedMember = await adapter.findMemberByOrgId({
|
||||
userId: ctx.body.userId,
|
||||
organizationId: session.session.activeOrganizationId,
|
||||
organizationId: organizationId,
|
||||
});
|
||||
|
||||
if (!toBeAddedMember) {
|
||||
@@ -1019,7 +1030,7 @@ export const addTeamMember = <O extends OrganizationOptions>(options: O) =>
|
||||
|
||||
const team = await adapter.findTeamById({
|
||||
teamId: ctx.body.teamId,
|
||||
organizationId: session.session.activeOrganizationId,
|
||||
organizationId: organizationId,
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
@@ -1029,9 +1040,7 @@ export const addTeamMember = <O extends OrganizationOptions>(options: O) =>
|
||||
);
|
||||
}
|
||||
|
||||
const organization = await adapter.findOrganizationById(
|
||||
session.session.activeOrganizationId,
|
||||
);
|
||||
const organization = await adapter.findOrganizationById(organizationId);
|
||||
if (!organization) {
|
||||
throw APIError.from(
|
||||
"BAD_REQUEST",
|
||||
@@ -1091,6 +1100,14 @@ const removeTeamMemberBodySchema = z.object({
|
||||
userId: z.coerce.string().meta({
|
||||
description: "The user which should be removed from the team.",
|
||||
}),
|
||||
|
||||
organizationId: z
|
||||
.string()
|
||||
.meta({
|
||||
description:
|
||||
"The organization ID which the team falls under. If not provided, it will default to the user's active organization.",
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const removeTeamMember = <O extends OrganizationOptions>(options: O) =>
|
||||
@@ -1132,7 +1149,10 @@ export const removeTeamMember = <O extends OrganizationOptions>(options: O) =>
|
||||
const session = ctx.context.session;
|
||||
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
|
||||
|
||||
if (!session.session.activeOrganizationId) {
|
||||
const organizationId =
|
||||
ctx.body.organizationId || session.session.activeOrganizationId;
|
||||
|
||||
if (!organizationId) {
|
||||
throw APIError.from(
|
||||
"BAD_REQUEST",
|
||||
ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION,
|
||||
@@ -1141,7 +1161,7 @@ export const removeTeamMember = <O extends OrganizationOptions>(options: O) =>
|
||||
|
||||
const currentMember = await adapter.findMemberByOrgId({
|
||||
userId: session.user.id,
|
||||
organizationId: session.session.activeOrganizationId,
|
||||
organizationId: organizationId,
|
||||
});
|
||||
|
||||
if (!currentMember) {
|
||||
@@ -1158,7 +1178,7 @@ export const removeTeamMember = <O extends OrganizationOptions>(options: O) =>
|
||||
permissions: {
|
||||
member: ["delete"],
|
||||
},
|
||||
organizationId: session.session.activeOrganizationId,
|
||||
organizationId: organizationId,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
@@ -1172,7 +1192,7 @@ export const removeTeamMember = <O extends OrganizationOptions>(options: O) =>
|
||||
|
||||
const toBeAddedMember = await adapter.findMemberByOrgId({
|
||||
userId: ctx.body.userId,
|
||||
organizationId: session.session.activeOrganizationId,
|
||||
organizationId: organizationId,
|
||||
});
|
||||
|
||||
if (!toBeAddedMember) {
|
||||
@@ -1184,7 +1204,7 @@ export const removeTeamMember = <O extends OrganizationOptions>(options: O) =>
|
||||
|
||||
const team = await adapter.findTeamById({
|
||||
teamId: ctx.body.teamId,
|
||||
organizationId: session.session.activeOrganizationId,
|
||||
organizationId: organizationId,
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
@@ -1194,9 +1214,7 @@ export const removeTeamMember = <O extends OrganizationOptions>(options: O) =>
|
||||
);
|
||||
}
|
||||
|
||||
const organization = await adapter.findOrganizationById(
|
||||
session.session.activeOrganizationId,
|
||||
);
|
||||
const organization = await adapter.findOrganizationById(organizationId);
|
||||
if (!organization) {
|
||||
throw APIError.from(
|
||||
"BAD_REQUEST",
|
||||
|
||||
@@ -248,6 +248,158 @@ describe("team", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("should update a team with explicit organizationId in data", async () => {
|
||||
// Get current session to ensure new org is different from active org
|
||||
const currentSession = await client.getSession({
|
||||
fetchOptions: { headers },
|
||||
});
|
||||
const currentActiveOrgId = (currentSession.data?.session as any)
|
||||
.activeOrganizationId;
|
||||
|
||||
// Create a new organization
|
||||
const newOrg = await client.organization.create({
|
||||
name: "New Test Organization",
|
||||
slug: "new-test-org",
|
||||
fetchOptions: { headers },
|
||||
});
|
||||
|
||||
expect(newOrg.data?.id).toBeDefined();
|
||||
const newOrgId = newOrg.data?.id as string;
|
||||
|
||||
// Ensure the new org is different from the session's active org
|
||||
expect(newOrgId).not.toBe(currentActiveOrgId);
|
||||
|
||||
// Create a team in the new organization
|
||||
const newTeam = await client.organization.createTeam(
|
||||
{
|
||||
name: "Team in New Org",
|
||||
organizationId: newOrgId,
|
||||
},
|
||||
{
|
||||
headers,
|
||||
},
|
||||
);
|
||||
|
||||
expect(newTeam.data?.id).toBeDefined();
|
||||
expect(newTeam.data?.organizationId).toBe(newOrgId);
|
||||
const newTeamId = newTeam.data?.id as string;
|
||||
|
||||
// Update the team with explicit organizationId in data
|
||||
const updatedTeam = await client.organization.updateTeam({
|
||||
teamId: newTeamId,
|
||||
data: {
|
||||
name: "Updated Team Name",
|
||||
organizationId: newOrgId,
|
||||
},
|
||||
fetchOptions: { headers },
|
||||
});
|
||||
|
||||
expect(updatedTeam.data?.name).toBe("Updated Team Name");
|
||||
expect(updatedTeam.data?.id).toBe(newTeamId);
|
||||
expect(updatedTeam.data?.organizationId).toBe(newOrgId);
|
||||
});
|
||||
|
||||
it("should add and remove team member with explicit organizationId", async () => {
|
||||
const testOrgId = organizationId;
|
||||
|
||||
// Create a new team specifically for this test
|
||||
const newTeamRes = await client.organization.createTeam(
|
||||
{
|
||||
name: "Team for Member Test",
|
||||
organizationId: testOrgId,
|
||||
},
|
||||
{
|
||||
headers,
|
||||
},
|
||||
);
|
||||
expect(newTeamRes.data?.id).toBeDefined();
|
||||
const testTeamId = newTeamRes.data?.id as string;
|
||||
|
||||
// Get the current user's ID
|
||||
const currentSession = await client.getSession({
|
||||
fetchOptions: { headers },
|
||||
});
|
||||
const currentUserId = currentSession.data?.user.id as string;
|
||||
|
||||
// Add current user to the team so they can list members
|
||||
await auth.api.addTeamMember({
|
||||
headers,
|
||||
body: {
|
||||
userId: currentUserId,
|
||||
teamId: testTeamId,
|
||||
organizationId: testOrgId,
|
||||
},
|
||||
});
|
||||
|
||||
// Create and add a new user
|
||||
const newUserHeaders = new Headers();
|
||||
const newUserRes = await client.signUp.email(
|
||||
{
|
||||
email: "teamuser@email.com",
|
||||
password: "password",
|
||||
name: "Team User",
|
||||
},
|
||||
{
|
||||
onSuccess: cookieSetter(newUserHeaders),
|
||||
},
|
||||
);
|
||||
|
||||
expect(newUserRes.data?.user.id).toBeDefined();
|
||||
const newUserId = newUserRes.data?.user.id as string;
|
||||
|
||||
// Add user to organization first
|
||||
await auth.api.addMember({
|
||||
body: {
|
||||
organizationId: testOrgId,
|
||||
userId: newUserId,
|
||||
role: "member",
|
||||
},
|
||||
});
|
||||
|
||||
// Add team member with explicit organizationId
|
||||
const addedMember = await auth.api.addTeamMember({
|
||||
headers,
|
||||
body: {
|
||||
userId: newUserId,
|
||||
teamId: testTeamId,
|
||||
organizationId: testOrgId,
|
||||
},
|
||||
});
|
||||
|
||||
expect(addedMember.teamId).toBe(testTeamId);
|
||||
expect(addedMember.userId).toBe(newUserId);
|
||||
|
||||
// Verify team member was added by listing team members
|
||||
const teamMembersBefore = await auth.api.listTeamMembers({
|
||||
headers,
|
||||
query: {
|
||||
teamId: testTeamId,
|
||||
},
|
||||
});
|
||||
expect(teamMembersBefore.some((m) => m.userId === newUserId)).toBe(true);
|
||||
|
||||
// Remove team member with explicit organizationId
|
||||
const removeRes = await auth.api.removeTeamMember({
|
||||
headers,
|
||||
body: {
|
||||
userId: newUserId,
|
||||
teamId: testTeamId,
|
||||
organizationId: testOrgId,
|
||||
},
|
||||
});
|
||||
|
||||
expect(removeRes.message).toBe("Team member removed successfully.");
|
||||
|
||||
// Verify team member was removed by listing team members again
|
||||
const teamMembersAfter = await auth.api.listTeamMembers({
|
||||
headers,
|
||||
query: {
|
||||
teamId: testTeamId,
|
||||
},
|
||||
});
|
||||
expect(teamMembersAfter.some((m) => m.userId === newUserId)).toBe(false);
|
||||
});
|
||||
|
||||
it("should not be allowed to invite a member to a team that's reached maximum members", async () => {
|
||||
const { auth, signInWithTestUser } = await getTestInstance({
|
||||
user: {
|
||||
|
||||
Reference in New Issue
Block a user