feat(organization): listUserInvitations adds the ability to list all invitations for a given user (#3121)

* feat(org): listUserInvitations

This PR introduces  in the org plugin.

* chore: lint
This commit is contained in:
Maxwell
2025-07-11 16:58:12 +10:00
committed by GitHub
parent c4c9530602
commit 3547c9d09a
5 changed files with 163 additions and 3 deletions

View File

@@ -560,7 +560,7 @@ await authClient.organization.getInvitation({
### List Invitations
To list all invitations you can use the `listInvitations` function provided by the client.
To list all invitations for a given organization you can use the `listInvitations` function provided by the client.
```ts title="auth-client.ts"
const invitations = await authClient.organization.listInvitations({
@@ -570,6 +570,30 @@ const invitations = await authClient.organization.listInvitations({
})
```
### List user invitations
To list all invitations for a given user you can use the `listUserInvitations` function provided by the client.
```ts title="auth-client.ts"
const invitations = await authClient.organization.listUserInvitations()
```
On the server, you can pass the user ID as a query parameter.
```ts title="api.ts"
const invitations = await auth.api.listUserInvitations({
query: {
email: "user@example.com"
}
})
```
<Callout type="warn">
The `email` query parameter is only available on the server to query for invitations for a specific user.
</Callout>
## Members
### Remove Member

View File

@@ -561,7 +561,13 @@ export const getOrgAdapter = (
});
return invitations;
},
listUserInvitations: async (email: string) => {
const invitations = await adapter.findMany<Invitation>({
model: "invitation",
where: [{ field: "email", value: email }],
});
return invitations;
},
createInvitation: async ({
invitation,
user,

View File

@@ -703,10 +703,11 @@ describe("organization", async (it) => {
password: userOverLimit.password,
name: userOverLimit.name,
});
const { res, headers: headers2 } = await signInWithUser(
const { headers: headers2 } = await signInWithUser(
userOverLimit2.email,
userOverLimit2.password,
);
await client.signUp.email(
{
email: userOverLimit2.email,
@@ -738,6 +739,92 @@ describe("organization", async (it) => {
});
expect(getFullOrganization.data?.members.length).toBe(6);
});
it("should allow listing invitations for an org", async () => {
const invitations = await client.organization.listInvitations({
query: {
organizationId: organizationId,
},
fetchOptions: {
headers: headers,
},
});
expect(invitations.data?.length).toBe(4);
});
it("should allow listing invitations for a user using authClient", async () => {
const rng = crypto.randomUUID();
const user = {
email: `${rng}@email.com`,
password: rng,
name: rng,
};
const rng2 = crypto.randomUUID();
const orgAdminUser = {
email: `${rng2}@email.com`,
password: rng2,
name: rng2,
};
await auth.api.signUpEmail({
body: user,
});
await auth.api.signUpEmail({
body: orgAdminUser,
});
const { headers: headers2, res: session } = await signInWithUser(
user.email,
user.password,
);
const { headers: adminHeaders, res: adminSession } = await signInWithUser(
orgAdminUser.email,
orgAdminUser.password,
);
const orgRng = crypto.randomUUID();
const org = await auth.api.createOrganization({
body: {
name: orgRng,
slug: orgRng,
},
headers: adminHeaders,
});
const invitation = await client.organization.inviteMember({
organizationId: org?.id,
email: user.email,
role: "member",
fetchOptions: {
headers: adminHeaders,
},
});
const userInvitations = await client.organization.listUserInvitations({
fetchOptions: {
headers: headers2,
},
});
expect(userInvitations.data?.[0].id).toBe(invitation.data?.id);
expect(userInvitations.data?.length).toBe(1);
});
it("should allow listing invitations for a user using server", async () => {
const orgInvitations = await client.organization.listInvitations({
fetchOptions: {
headers,
},
});
if (!orgInvitations.data?.[0].email) throw new Error("No email found");
const invitations = await auth.api.listUserInvitations({
query: {
email: orgInvitations.data?.[0].email,
},
});
expect(invitations?.length).toBe(
orgInvitations.data.filter(
(x) => x.email === orgInvitations.data?.[0].email,
).length,
);
});
});
describe("access control", async (it) => {

View File

@@ -16,6 +16,7 @@ import {
getInvitation,
listInvitations,
rejectInvitation,
listUserInvitations,
} from "./routes/crud-invites";
import {
addMember,
@@ -386,6 +387,7 @@ export const organization = <O extends OrganizationOptions>(options?: O) => {
acceptInvitation,
getInvitation,
rejectInvitation,
listUserInvitations,
checkOrganizationSlug,
addMember: addMember<O>(),
removeMember,

View File

@@ -745,3 +745,44 @@ export const listInvitations = createAuthEndpoint(
return ctx.json(invitations);
},
);
/**
* List all invitations recieved for a user
*/
export const listUserInvitations = createAuthEndpoint(
"/organization/list-user-invitations",
{
method: "GET",
use: [orgMiddleware],
query: z
.object({
email: z
.string({
description:
"The email of the user to list invitations for. This only works for server side API calls.",
})
.optional(),
})
.optional(),
},
async (ctx) => {
const session = await getSessionFromCtx(ctx);
if (ctx.request && ctx.query?.email) {
throw new APIError("BAD_REQUEST", {
message: "User email cannot be passed for client side API calls.",
});
}
const userEmail = session?.user.email || ctx.query?.email;
if (!userEmail) {
throw new APIError("BAD_REQUEST", {
message: "Missing session headers, or email query parameter.",
});
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const invitations = await adapter.listUserInvitations(userEmail);
return ctx.json(invitations);
},
);