mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-10 20:37:46 +00:00
Add API option to add member without invite (#378)
Co-authored-by: Bereket Engida <86073083+Bekacru@users.noreply.github.com> Co-authored-by: Bereket Engida <bekacru@gmail.com>
This commit is contained in:
@@ -393,6 +393,21 @@ To get the current member of the organization you can use the `organization.getA
|
||||
const member = await client.organization.getActiveMember()
|
||||
```
|
||||
|
||||
### Add Member
|
||||
|
||||
If you want to add a member directly to an organization without sending an invitation, you can use the `addMember` function which can only be invoked on the server.
|
||||
|
||||
```ts title="api.ts"
|
||||
import { auth } from "@/auth";
|
||||
|
||||
auth.api.addMember({
|
||||
body: {
|
||||
userId: "user-id",
|
||||
organizationId: "organization-id",
|
||||
role: "admin"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Access Control
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ export const getSessionFromCtx = async <
|
||||
return session as {
|
||||
session: S & Session;
|
||||
user: U & User;
|
||||
};
|
||||
} | null;
|
||||
};
|
||||
|
||||
export const sessionMiddleware = createAuthMiddleware(async (ctx) => {
|
||||
|
||||
@@ -302,6 +302,31 @@ describe("organization", async (it) => {
|
||||
expectTypeOf(auth.api.createOrganization).toBeFunction();
|
||||
expectTypeOf(auth.api.getInvitation).toBeFunction();
|
||||
});
|
||||
|
||||
it("should add member on the server directly", async () => {
|
||||
const newUser = await auth.api.signUpEmail({
|
||||
body: {
|
||||
email: "new-member@email.com",
|
||||
password: "password",
|
||||
name: "new member",
|
||||
},
|
||||
});
|
||||
const org = await auth.api.createOrganization({
|
||||
body: {
|
||||
name: "test",
|
||||
slug: "test",
|
||||
},
|
||||
headers,
|
||||
});
|
||||
const member = await auth.api.addMember({
|
||||
body: {
|
||||
organizationId: org?.id,
|
||||
userId: newUser.user.email,
|
||||
role: "admin",
|
||||
},
|
||||
});
|
||||
expect(member?.role).toBe("admin");
|
||||
});
|
||||
});
|
||||
|
||||
describe("access control", async (it) => {
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
rejectInvitation,
|
||||
} from "./routes/crud-invites";
|
||||
import {
|
||||
addMember,
|
||||
getActiveMember,
|
||||
removeMember,
|
||||
updateMemberRole,
|
||||
@@ -215,6 +216,7 @@ export const organization = <O extends OrganizationOptions>(options?: O) => {
|
||||
acceptInvitation,
|
||||
getInvitation,
|
||||
rejectInvitation,
|
||||
addMember: addMember<O>(),
|
||||
removeMember,
|
||||
updateMemberRole: updateMemberRole(options as O),
|
||||
getActiveMember,
|
||||
|
||||
@@ -4,7 +4,78 @@ import { getOrgAdapter } from "../adapter";
|
||||
import { orgMiddleware, orgSessionMiddleware } from "../call";
|
||||
import type { InferRolesFromOption, Member } from "../schema";
|
||||
import { APIError } from "better-call";
|
||||
import type { User } from "../../../db/schema";
|
||||
import { generateId } from "../../../utils";
|
||||
import type { OrganizationOptions } from "../organization";
|
||||
import { getSessionFromCtx } from "../../../api";
|
||||
|
||||
export const addMember = <O extends OrganizationOptions>() =>
|
||||
createAuthEndpoint(
|
||||
"/organization/add-member",
|
||||
{
|
||||
method: "POST",
|
||||
body: z.object({
|
||||
userId: z.string(),
|
||||
role: z.string() as unknown as InferRolesFromOption<O>,
|
||||
organizationId: z.string().optional(),
|
||||
}),
|
||||
use: [orgMiddleware],
|
||||
metadata: {
|
||||
SERVER_ONLY: true,
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const session = ctx.body.userId
|
||||
? await getSessionFromCtx<{
|
||||
session: {
|
||||
activeOrganizationId?: string;
|
||||
};
|
||||
}>(ctx).catch((e) => null)
|
||||
: null;
|
||||
const orgId =
|
||||
ctx.body.organizationId || session?.session.activeOrganizationId;
|
||||
if (!orgId) {
|
||||
return ctx.json(null, {
|
||||
status: 400,
|
||||
body: {
|
||||
message: "No active organization found!",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
|
||||
|
||||
const user = await ctx.context.internalAdapter.findUserById(
|
||||
ctx.body.userId,
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
throw new APIError("BAD_REQUEST", {
|
||||
message: "User not found!",
|
||||
});
|
||||
}
|
||||
|
||||
const alreadyMember = await adapter.findMemberByEmail({
|
||||
email: user.email,
|
||||
organizationId: orgId,
|
||||
});
|
||||
if (alreadyMember) {
|
||||
throw new APIError("BAD_REQUEST", {
|
||||
message: "User is already a member of this organization",
|
||||
});
|
||||
}
|
||||
|
||||
const createdMember = await adapter.createMember({
|
||||
id: generateId(),
|
||||
organizationId: orgId,
|
||||
userId: user.id,
|
||||
role: ctx.body.role as string,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
|
||||
return ctx.json(createdMember);
|
||||
},
|
||||
);
|
||||
|
||||
export const removeMember = createAuthEndpoint(
|
||||
"/organization/remove-member",
|
||||
|
||||
Reference in New Issue
Block a user