better-auth-organization-member 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +245 -0
- package/dist/client-B1z87oFG.d.mts +26 -0
- package/dist/client-B1z87oFG.d.mts.map +1 -0
- package/dist/client-PBBSie1a.mjs +11 -0
- package/dist/client-PBBSie1a.mjs.map +1 -0
- package/dist/client.d.mts +3 -0
- package/dist/client.d.ts +21 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +7 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +3 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4 -0
- package/dist/server-Big_p9zG.mjs +1017 -0
- package/dist/server-Big_p9zG.mjs.map +1 -0
- package/dist/server-uBIvbfMk.d.mts +119 -0
- package/dist/server-uBIvbfMk.d.mts.map +1 -0
- package/dist/server.d.mts +2 -0
- package/dist/server.d.ts +115 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +649 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +3 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# better-auth-organization-member
|
|
2
|
+
|
|
3
|
+
A Better Auth plugin that extends the organization plugin with additional member management capabilities.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Update Member Endpoint**: `/organization/update-member` - Update member information including role AND additional fields (firstName, lastName, avatar, and any custom fields)
|
|
8
|
+
- **Automatic Hook Injection**: Automatically adds `afterAcceptInvitation` hook to the organization plugin to transfer invitation data from invitations to member records
|
|
9
|
+
- **Before/After Update Hooks**: Lifecycle hooks for member updates
|
|
10
|
+
- **Full Type Inference**: Automatically infers member additional fields from organization plugin schema
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- `better-auth` >= 1.4.9
|
|
15
|
+
- `organization` plugin must be enabled
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
This is a workspace package. It's already included in the monorepo.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Server Setup
|
|
24
|
+
|
|
25
|
+
Simply add the plugin after the organization plugin. **No manual hooks required!**
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { betterAuth } from 'better-auth';
|
|
29
|
+
import { organization } from 'better-auth/plugins';
|
|
30
|
+
import { organizationMember } from 'better-auth-organization-member';
|
|
31
|
+
|
|
32
|
+
export const auth = betterAuth({
|
|
33
|
+
// ... other config
|
|
34
|
+
plugins: [
|
|
35
|
+
organization({
|
|
36
|
+
// organization config
|
|
37
|
+
schema: {
|
|
38
|
+
member: {
|
|
39
|
+
additionalFields: {
|
|
40
|
+
firstName: { type: 'string', input: true, required: false },
|
|
41
|
+
lastName: { type: 'string', input: true, required: false },
|
|
42
|
+
avatar: { type: 'string', input: true, required: false },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
invitation: {
|
|
46
|
+
additionalFields: {
|
|
47
|
+
firstName: { type: 'string', input: true, required: false },
|
|
48
|
+
lastName: { type: 'string', input: true, required: false },
|
|
49
|
+
avatar: { type: 'string', input: true, required: false },
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
// This plugin automatically injects the afterAcceptInvitation hook
|
|
55
|
+
organizationMember({
|
|
56
|
+
organizationMemberHooks: {
|
|
57
|
+
// Optional: Hook before member update
|
|
58
|
+
async beforeUpdateMember(data) {
|
|
59
|
+
console.log('Updating member:', data.member.id);
|
|
60
|
+
// You can modify the updates here
|
|
61
|
+
return {
|
|
62
|
+
data: {
|
|
63
|
+
...data.updates,
|
|
64
|
+
// Add custom logic
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
// Optional: Hook after member update
|
|
69
|
+
async afterUpdateMember(data) {
|
|
70
|
+
console.log('Member updated:', data.member.id);
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### What Happens Automatically
|
|
79
|
+
|
|
80
|
+
When the `organizationMember` plugin is added:
|
|
81
|
+
|
|
82
|
+
1. ✅ It automatically injects an `afterAcceptInvitation` hook into the organization plugin
|
|
83
|
+
2. ✅ When an invitation is accepted, the hook transfers `firstName`, `lastName`, and `avatar` from the invitation to the member record
|
|
84
|
+
3. ✅ No need to manually add hooks in your provider configuration!
|
|
85
|
+
|
|
86
|
+
### Client Usage
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { createAuthClient } from 'better-auth/client';
|
|
90
|
+
import { organizationMemberClient } from 'better-auth-organization-member/client';
|
|
91
|
+
|
|
92
|
+
const client = createAuthClient({
|
|
93
|
+
plugins: [organizationMemberClient()],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Update member information (role + additional fields)
|
|
97
|
+
await client.organization.updateMember({
|
|
98
|
+
memberId: 'member-id',
|
|
99
|
+
role: 'admin', // Can update role
|
|
100
|
+
firstName: 'John',
|
|
101
|
+
lastName: 'Doe',
|
|
102
|
+
avatar: 'https://example.com/avatar.jpg',
|
|
103
|
+
// any other custom fields defined in your member schema
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## API
|
|
108
|
+
|
|
109
|
+
### Endpoint: `/organization/update-member`
|
|
110
|
+
|
|
111
|
+
Updates member information in an organization. Follows the **exact same permission logic** as `/organization/update-member-role` but accepts additional fields.
|
|
112
|
+
|
|
113
|
+
**Method**: `POST`
|
|
114
|
+
|
|
115
|
+
**Body**:
|
|
116
|
+
```typescript
|
|
117
|
+
{
|
|
118
|
+
memberId: string; // Required: ID of the member to update
|
|
119
|
+
role: string | string[]; // Required: Role(s) to assign
|
|
120
|
+
organizationId?: string; // Optional: defaults to active organization
|
|
121
|
+
teamId?: string; // Optional: team ID (if teams are enabled)
|
|
122
|
+
// ... any additional fields defined in member.additionalFields
|
|
123
|
+
// Examples: firstName, lastName, avatar, etc.
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Response**:
|
|
128
|
+
```typescript
|
|
129
|
+
{
|
|
130
|
+
id: string;
|
|
131
|
+
userId: string;
|
|
132
|
+
organizationId: string;
|
|
133
|
+
role: string;
|
|
134
|
+
user: {
|
|
135
|
+
id: string;
|
|
136
|
+
email: string;
|
|
137
|
+
name: string | null;
|
|
138
|
+
image: string | null;
|
|
139
|
+
};
|
|
140
|
+
// ... all additional fields
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Permissions**: Same as `updateMemberRole` - requires `member:update` permission or owner/admin role.
|
|
145
|
+
|
|
146
|
+
**Role Logic** (same as `updateMemberRole`):
|
|
147
|
+
- ✅ Owners can update any member
|
|
148
|
+
- ✅ Admins with `member:update` permission can update members
|
|
149
|
+
- ❌ Non-creators cannot update creators
|
|
150
|
+
- ❌ Last owner cannot demote themselves
|
|
151
|
+
- ❌ Same permission checks as the built-in role update
|
|
152
|
+
|
|
153
|
+
## Hooks
|
|
154
|
+
|
|
155
|
+
### `beforeUpdateMember`
|
|
156
|
+
|
|
157
|
+
Called before a member's information is updated. Can modify the update data.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
beforeUpdateMember?: (data: {
|
|
161
|
+
member: Member;
|
|
162
|
+
updates: Record<string, any>;
|
|
163
|
+
user: User;
|
|
164
|
+
organization: Organization;
|
|
165
|
+
}) => Promise<void | { data: Record<string, any> }>;
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### `afterUpdateMember`
|
|
169
|
+
|
|
170
|
+
Called after a member's information is updated.
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
afterUpdateMember?: (data: {
|
|
174
|
+
member: Member;
|
|
175
|
+
previousData: Record<string, any>;
|
|
176
|
+
user: User;
|
|
177
|
+
organization: Organization;
|
|
178
|
+
}) => Promise<void>;
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Automatic `afterAcceptInvitation` Hook
|
|
182
|
+
|
|
183
|
+
This hook is **automatically injected** into the organization plugin when you add `organizationMember()`. It:
|
|
184
|
+
|
|
185
|
+
1. Extracts **all additional fields** from `invitation.additionalFields`
|
|
186
|
+
2. Automatically transfers them to the newly created member record
|
|
187
|
+
3. Works with any custom fields you define in both invitation and member schemas
|
|
188
|
+
4. Logs the update (or errors if they occur)
|
|
189
|
+
5. Does not fail the invitation acceptance if the update fails
|
|
190
|
+
|
|
191
|
+
## Comparison with `updateMemberRole`
|
|
192
|
+
|
|
193
|
+
### Built-in `updateMemberRole` (organization plugin)
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// Only updates the role field
|
|
197
|
+
await client.organization.updateMemberRole({
|
|
198
|
+
memberId: 'member-id',
|
|
199
|
+
role: 'admin',
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### This Plugin's `updateMember`
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// Updates role AND additional fields in one call
|
|
207
|
+
await client.organization.updateMember({
|
|
208
|
+
memberId: 'member-id',
|
|
209
|
+
role: 'admin', // ✅ Can update role
|
|
210
|
+
firstName: 'John', // ✅ Plus additional fields
|
|
211
|
+
lastName: 'Doe',
|
|
212
|
+
avatar: 'https://example.com/avatar.jpg',
|
|
213
|
+
customField: 'value', // ✅ Plus any custom fields
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Key Features:**
|
|
218
|
+
- ✅ **Exact same permission logic** as `updateMemberRole` (identical role checks)
|
|
219
|
+
- ✅ **Same validation** for role updates (creator protection, owner checks, etc.)
|
|
220
|
+
- ✅ **Additional field support** - Update member additional fields in the same request
|
|
221
|
+
- ✅ **Full type inference** - TypeScript autocomplete for all additional fields
|
|
222
|
+
- ✅ **Automatic schema validation** - Uses `toZodSchema` to validate additional fields
|
|
223
|
+
|
|
224
|
+
## Implementation Details
|
|
225
|
+
|
|
226
|
+
This plugin:
|
|
227
|
+
|
|
228
|
+
1. **Extends `updateMemberRole`**: Uses the exact same permission logic, role validation, and error handling as `updateMemberRole`, but accepts additional fields via `toZodSchema`
|
|
229
|
+
2. **Auto-injects hooks**: Uses the plugin's `init()` method to inject `afterAcceptInvitation` into the organization plugin's hooks
|
|
230
|
+
3. **Type-safe**: Full TypeScript support with `InferAdditionalFieldsFromPluginOptions` for automatic type inference of all additional fields
|
|
231
|
+
4. **Uses `getOrgAdapter`**: Leverages the organization plugin's adapter utilities for consistent behavior
|
|
232
|
+
5. **Dynamic field transfer**: Automatically detects and transfers all fields defined in `invitation.additionalFields` to member records
|
|
233
|
+
6. **Production-ready**: Follows Better Auth best practices and patterns
|
|
234
|
+
|
|
235
|
+
### How It Works
|
|
236
|
+
|
|
237
|
+
1. **At initialization**: The plugin reads the organization plugin's schema to get member additional fields
|
|
238
|
+
2. **Schema generation**: Uses `toZodSchema` to generate validation schema from `member.additionalFields`
|
|
239
|
+
3. **Endpoint creation**: Creates the `/organization/update-member` endpoint with merged schemas (base + additional fields)
|
|
240
|
+
4. **Hook injection**: Injects `afterAcceptInvitation` to automatically transfer invitation fields to member
|
|
241
|
+
5. **Type inference**: TypeScript automatically infers all additional fields for full autocomplete support
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
UNLICENSED
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { i as organizationMember } from "./server-uBIvbfMk.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/client.d.ts
|
|
4
|
+
interface OrganizationMemberClient {
|
|
5
|
+
updateMember: <T extends Record<string, any> = {}>(data: {
|
|
6
|
+
memberId: string;
|
|
7
|
+
organizationId?: string;
|
|
8
|
+
} & T) => Promise<{
|
|
9
|
+
data: any | null;
|
|
10
|
+
error: Error | null;
|
|
11
|
+
}>;
|
|
12
|
+
updateInvitation: <T extends Record<string, any> = {}>(data: {
|
|
13
|
+
invitationId: string;
|
|
14
|
+
organizationId?: string;
|
|
15
|
+
} & T) => Promise<{
|
|
16
|
+
data: any | null;
|
|
17
|
+
error: Error | null;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
declare const organizationMemberClient: () => {
|
|
21
|
+
id: string;
|
|
22
|
+
$InferServerPlugin: ReturnType<typeof organizationMember>;
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { organizationMemberClient as n, OrganizationMemberClient as t };
|
|
26
|
+
//# sourceMappingURL=client-B1z87oFG.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-B1z87oFG.d.mts","names":[],"sources":["../src/client.ts"],"sourcesContent":[],"mappings":";;;UAAiB,wBAAA;2BACU;;IADV,cAAA,CAAA,EAAA,MAAwB;EACd,CAAA,GAGrB,CAHqB,EAAA,GAGf,OAHe,CAAA;IAGrB,IAAA,EAAA,GAAA,GAAA,IAAA;IAEK,KAAA,EAAA,KAAA,GAAA,IAAA;EAFC,CAAA,CAAA;EAImB,gBAAA,EAAA,CAAA,UAAA,MAAA,CAAA,MAAA,EAAA,GAAA,CAAA,GAAA,CAAA,CAAA,CAAA,CAAA,IAAA,EAAA;IAGzB,YAAA,EAAA,MAAA;IAEK,cAAA,CAAA,EAAA,MAAA;EAFC,CAAA,GAAN,CAAM,EAAA,GAAA,OAAA,CAAA;IAAO,IAAA,EAAA,GAAA,GAAA,IAAA;IAMN,KAAA,EAJF,KAIE,GAAA,IAAA;;;cAAA;;sBAGiB,kBAI7B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-PBBSie1a.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["export interface OrganizationMemberClient {\n updateMember: <T extends Record<string, any> = {}>(data: {\n memberId: string;\n organizationId?: string;\n } & T) => Promise<{\n data: any | null;\n error: Error | null;\n }>;\n updateInvitation: <T extends Record<string, any> = {}>(data: {\n invitationId: string;\n organizationId?: string;\n } & T) => Promise<{\n data: any | null;\n error: Error | null;\n }>;\n}\n\nexport const organizationMemberClient = () => {\n return {\n id: \"organization-member\",\n $InferServerPlugin: {} as ReturnType<\n typeof import(\"./server\").organizationMember\n >,\n };\n};\n"],"mappings":";AAiBA,MAAa,iCAAiC;AAC5C,QAAO;EACL,IAAI;EACJ,oBAAoB,EAAE;EAGvB"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface OrganizationMemberClient {
|
|
2
|
+
updateMember: <T extends Record<string, any> = {}>(data: {
|
|
3
|
+
memberId: string;
|
|
4
|
+
organizationId?: string;
|
|
5
|
+
} & T) => Promise<{
|
|
6
|
+
data: any | null;
|
|
7
|
+
error: Error | null;
|
|
8
|
+
}>;
|
|
9
|
+
updateInvitation: <T extends Record<string, any> = {}>(data: {
|
|
10
|
+
invitationId: string;
|
|
11
|
+
organizationId?: string;
|
|
12
|
+
} & T) => Promise<{
|
|
13
|
+
data: any | null;
|
|
14
|
+
error: Error | null;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export declare const organizationMemberClient: () => {
|
|
18
|
+
id: string;
|
|
19
|
+
$InferServerPlugin: ReturnType<typeof import("./server").organizationMember>;
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACvC,YAAY,EAAE,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE;QACvD,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GAAG,CAAC,KAAK,OAAO,CAAC;QAChB,IAAI,EAAE,GAAG,GAAG,IAAI,CAAC;QACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;KACrB,CAAC,CAAC;IACH,gBAAgB,EAAE,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE;QAC3D,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GAAG,CAAC,KAAK,OAAO,CAAC;QAChB,IAAI,EAAE,GAAG,GAAG,IAAI,CAAC;QACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;KACrB,CAAC,CAAC;CACJ;AAED,eAAO,MAAM,wBAAwB;;wBAGP,UAAU,CAClC,cAAc,UAAU,EAAE,kBAAkB,CAC7C;CAEJ,CAAC"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAiBA,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,EAAE;IAC3C,OAAO;QACL,EAAE,EAAE,qBAAqB;QACzB,kBAAkB,EAAE,EAEnB;KACF,CAAC;AACJ,CAAC,CAAC"}
|
package/dist/client.mjs
ADDED
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { i as organizationMember, t as OrganizationMemberOptions } from "./server-uBIvbfMk.mjs";
|
|
2
|
+
import { n as organizationMemberClient, t as OrganizationMemberClient } from "./client-B1z87oFG.mjs";
|
|
3
|
+
export { type OrganizationMemberClient, type OrganizationMemberOptions, organizationMember, organizationMemberClient };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,YAAY,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AACpD,YAAY,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.mjs
ADDED