feat(organization): explicit organizationId in team endpoints (#5062)

Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
Yanqi Zong
2026-03-13 23:46:35 -07:00
committed by Alex Yang
co-authored by Alex Yang
parent f9727cadc1
commit 8f47015af2
2 changed files with 186 additions and 16 deletions
@@ -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: {