better-auth-invite-plugin 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sandy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,629 @@
1
+
2
+ # 👥 Better Auth Invite Plugin
3
+
4
+ [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
6
+ [![npm](https://img.shields.io/npm/v/better-auth-invite-plugin?logo=npm)](https://www.npmjs.com/package/better-auth-invite-plugin/)
7
+ [![Better Auth Plugin](https://img.shields.io/badge/Better_Auth-Plugin-blue?logo=better-auth)](https://www.better-auth.com/docs/concepts/plugins)
8
+
9
+ A plugin for Better Auth that adds an invitation system, allowing you to create, send, and manage invites for user sign-ups or role upgrades.
10
+
11
+ ## ⚙️ Features
12
+
13
+ - Keep track of who created and who accepted the invite.
14
+ - Create and manage invitation codes to control user sign-ups.
15
+ - Send invitations via email, provide a shareable URL, or generate an invitation code.
16
+ - Automatically assign or upgrade roles when invites are used.
17
+ - Track each invitation's usage and enforce maximum uses.
18
+ - Support multiple token types, including default, code, or custom tokens.
19
+ - Store tokens securely in browser cookies for seamless activation.
20
+ - Fully customize behavior for redirects, token expiration, and email handling.
21
+ - Built with security in mind to prevent unauthorized invite usage.
22
+ - Show the invitee a welcome page or role upgrade page after signing up or upgrading their role.
23
+ ## 💾 Installation
24
+
25
+ Install the plugin
26
+
27
+ <details>
28
+ <summary>npm</summary>
29
+
30
+ ```bash
31
+ npm install better-auth-invite-plugin
32
+ ```
33
+
34
+ </details>
35
+
36
+ <details>
37
+ <summary>pnpm</summary>
38
+
39
+ ```bash
40
+ pnpm add better-auth-invite-plugin
41
+ ```
42
+
43
+ </details>
44
+
45
+ <details>
46
+ <summary>yarn</summary>
47
+
48
+ ```bash
49
+ yarn add better-auth-invite-plugin
50
+ ```
51
+
52
+ </details>
53
+
54
+ ## 🗄️ Server-Side Setup
55
+
56
+ Start by importing `invite` in your `betterAuth` configuration.
57
+
58
+ **Important:** `defaultRoleForSignupWithoutInvite` from the invite plugin should match `defaultRole` from the admin plugin
59
+
60
+ Basic example:
61
+
62
+ ```ts
63
+ import invite from "better-auth-invite-plugin";
64
+
65
+ export const auth = betterAuth({
66
+ //... other options
67
+ database,
68
+ plugins: {
69
+ adminPlugin({
70
+ ac,
71
+ roles: { user, admin },
72
+ defaultRole: "user",
73
+ }),
74
+ invite({
75
+ defaultRoleForSignupWithoutInvite: "user",
76
+ defaultMaxUses: 1,
77
+ defaultRedirectToSignUp: "/auth/sign-up",
78
+ defaultRedirectToSignIn: "/auth/sign-in",
79
+ defaultRedirectAfterUpgrade: "/auth/invited",
80
+ sendUserInvitation: async ({ email, role, url }) => {
81
+ void sendInvitationEmail(role as RoleType, email, url);
82
+ },
83
+ })
84
+ },
85
+ emailAndPassword: {
86
+ enabled: true
87
+ }
88
+ });
89
+ ```
90
+
91
+ If you want, you can use a lot more options, here's an example using all the options
92
+
93
+ <details>
94
+ <summary>Advanced example</summary>
95
+
96
+ ```ts
97
+ import invite from "better-auth-invite-plugin";
98
+
99
+ export const auth = betterAuth({
100
+ //... other options
101
+ database,
102
+ plugins: {
103
+ adminPlugin({
104
+ ac,
105
+ roles: { user, admin, owner },
106
+ defaultRole: "user",
107
+ }),
108
+ invite({
109
+ defaultRoleForSignupWithoutInvite: "user", // The default role for users that signed up without an invite
110
+ getDate: () => new Date(), // Don't know why would you change the getDate function, but you can if you want
111
+ canCreateInvite: (inviteUser, inviterUser) => { // Can a user create an invite? By default it checks that the inviter role isn't defaultRoleForSignupWithoutInvite
112
+ if (!inviterUser.role) return false;
113
+
114
+ const RoleHierarchy = {
115
+ user: 1,
116
+ admin: 2,
117
+ owner: 3,
118
+ } as const;
119
+
120
+ return ( // If the inviter isn't trying to invite a user with a higer role than his, he can create the invite
121
+ RoleHierarchy[inviterUser.role as RoleType] >=
122
+ RoleHierarchy[inviteUser.role as RoleType]
123
+ );
124
+ },
125
+ canAcceptInvite: ({ user, newAccount }) => { // Can a user accept an invite? By default he can accept an invite
126
+ return newAccount; // Can only accept an invite to create an account, they cannot upgrade their role
127
+ },
128
+ generateToken: () => { // If you want you can create your own custom tokens
129
+ return String(Math.floor(Math.random() * 9) + 1); // Totally not ideal, since this only allows 9 different tokens
130
+ }
131
+ defaultTokenType: "token", // Token is recomended for email invites
132
+ defaultRedirectToSignUp: "/auth/sign-up", // The url to sign up the user
133
+ defaultRedirectToSignIn: "/auth/sign-in", // The url to sign in the user
134
+ defaultRedirectAfterUpgrade: "/auth/invited", // You should put a welcome message on this page
135
+ defaultShareInviterName: true, // Will be passed as an argument to the defaultRedirectAfterUpgrade
136
+ defaultMaxUses: 1,
137
+ defaultSenderResponse: "url", // If no email is provider, the sender will receive a URL to share with friends!
138
+ defaultSenderResponseRedirect: "signUp", // If no email is provided and defaultSenderResponse is "url", the user will be redirected to the sign-up page when they open that URL
139
+ customCookiePrefix: "my-app", // It defaults to better auth config advanced.cookiePrefix or simply "better-auth"
140
+ customCookieName: "{prefix}_invite", // Change your cookie name, you can use {prefix}, it'll be replaced with customCookiePrefix
141
+ sendUserInvitation: async ({ email, role, url }) => { // Implement your logic to send an email
142
+ void sendInvitationEmail(role as RoleType, email, url);
143
+ },
144
+ invitationTokenExpiresIn: 3600, // The token is only valid for 1 hour (in seconds)
145
+ onInvitationUsed: async ({ user, newAccount }) => {
146
+ // Send the user an email after they use an invitation
147
+ if (newAccount) // If it's a new account send them a welcome email
148
+ return void sendWelcomeEmail(user.name, user.email);
149
+
150
+ // If it's not a new account, send them an email telling them their new role
151
+ void sendRoleUpgraeEmail(user.name, user.email, user.role);
152
+ },
153
+ schema: { // Customize the table and column names
154
+ invite: {
155
+ fields: {
156
+ token: "invite_token"
157
+ },
158
+ },
159
+ },
160
+ })
161
+ },
162
+ emailAndPassword: {
163
+ enabled: true
164
+ }
165
+ });
166
+ ```
167
+
168
+ </details>
169
+
170
+ ## ⚙️ Invite Options
171
+
172
+ <details>
173
+ <summary>Click to expand</summary>
174
+
175
+ ? = Optional
176
+
177
+ | Property | Type | Description | Default |
178
+ | :------- | :--- | :---------- | :------ |
179
+ | `defaultRoleForSignupWithoutInvite` | `string` | The role that users signing up without an invitation should have. | — |
180
+ | `getDate?` | `() => Date` | Function to generate the date. | `() => new Date()` |
181
+ | `canCreateInvite?` | `(inviteUser: {email?: string; role: string}, inviterUser: UserWithRole) => boolean` | Function that runs before creating an invite. | — |
182
+ | `canAcceptInvite?` | `(data: {user: UserWithRole, newAccount: boolean}) => boolean` | Function that runs before accepting an invite. | — |
183
+ | `generateToken?` | `() => string` | Function to generate a custom token. | — |
184
+ | `defaultTokenType?` | `"token" \| "code" \| "custom"` | Default token type. | `token` |
185
+ | `defaultRedirectToSignUp` | `string` | URL to redirect the user to sign up. | — |
186
+ | `defaultRedirectToSignIn` | `string` | URL to redirect the user to sign in. | — |
187
+ | `defaultRedirectAfterUpgrade` | `string` | URL to redirect the user after upgrading role. | — |
188
+ | `defaultShareInviterName?` | `boolean` | Whether the inviter's name is shared with the invitee by default. | `true` |
189
+ | `defaultMaxUses` | `number` | Max number of uses for an invite. | `1` |
190
+ | `defaultSenderResponse?` | `"token" \| "url"` | How the sender receives the token if no email is provided. | `token` |
191
+ | `defaultSenderResponseRedirect?` | `"signUp" \| "signIn"` | Where to redirect the user if no email is provided. | `signUp` |
192
+ | `customCookiePrefix?` | `string` | Custom cookie prefix. | `better-auth` |
193
+ | `customCookieName?` | `string` | Custom cookie name, `{prefix}` can be used. | `{prefix}.invite-token` |
194
+ | `sendUserInvitation?` | `(data: {email: string; role: string; url: string; token: string}, request?: Request) => Promise<void>` | Function to send the invitation email. | — |
195
+ | `invitationTokenExpiresIn?` | `number` | Number of seconds the token is valid for. | `3600` |
196
+ | `onInvitationUsed?` | `(data: { user: UserWithRole, newAccount: boolean}, request?: Request) => Promise<void>` | Callback when an invite is used. | — |
197
+ | `schema?` | `InferOptionSchema<InviteSchema>` | Custom schema for the invite plugin. | — |
198
+
199
+ </details>
200
+
201
+ ## 🖥️ Client-Side Setup
202
+
203
+ Import the `inviteClient` plugin and add it to your `betterAuth` configuration.
204
+
205
+ ```ts
206
+ import { inviteClient } from "better-auth-invite-plugin";
207
+
208
+ const client = createClient({
209
+ //... other options
210
+ plugins: [
211
+ inviteClient()
212
+ ],
213
+ });
214
+ ```
215
+
216
+ ## 💡 Usage/Examples
217
+
218
+ <h3 id="creating-invites"></h3>
219
+
220
+ ### 1. Creating Invites
221
+ Authenticated users can create invite codes. You can create an invite on the client or on the server.
222
+
223
+ <details>
224
+ <summary>Create an invite on the server</summary>
225
+
226
+ Use `authClient.invite.create` to create one.
227
+
228
+ ```ts
229
+ import { client } from "@/lib/auth-client";
230
+
231
+ const { data, error } = await client.invite.create({
232
+ // Here you put the options
233
+ role: "admin"
234
+ });
235
+
236
+ if (error) {
237
+ console.error("Failed to create invite:", error);
238
+ return;
239
+ }
240
+
241
+ if (data) {
242
+ console.log("Invite token created:", data.token);
243
+ // Example response: { data: { status: true, message: "test", token: "invite-123" }, error: null }
244
+ return data.token;
245
+ }
246
+ ```
247
+ </details>
248
+
249
+ <details>
250
+ <summary>Create an invite on the client</summary>
251
+
252
+ Use `authClient.invite.create` to create one.
253
+
254
+ ```ts
255
+ import { auth } from "@/lib/auth";
256
+
257
+ const { data, error } = await auth.api.createInvite({
258
+ headers: ..., // The user headers, req.headers on api, await headers() on NextJS
259
+ body: { // In the body you put the options
260
+ role: "admin"
261
+ }
262
+ });
263
+
264
+ if (error) {
265
+ console.error("Failed to create invite:", error);
266
+ return;
267
+ }
268
+
269
+ if (data) {
270
+ console.log("Invite token created:", data.token);
271
+ // Example response: { data: { status: true, message: "test", token: "invite-123" }, error: null }
272
+ return data.token;
273
+ }
274
+ ```
275
+ </details>
276
+
277
+ #### [Create invite options](#create-invite-options)
278
+
279
+ <h3 id="activating-invites"></h3>
280
+
281
+ ### 2. Activating Invites
282
+
283
+ When a user receives an invite code, he needs to activate it.
284
+ If the user recives an email, the link they recive automaticly activates the invite.
285
+ Also you can activate an invite on the client or on the server (manually).
286
+
287
+ <details>
288
+ <summary>Activating an invite manually</summary>
289
+
290
+ To manually activate an invite, you can use one of the following methods depending on whether you are working on the server or client side.
291
+
292
+ <details>
293
+ <summary>Create an invite on the server</summary>
294
+
295
+ Use `authClient.invite.activate` to create one.
296
+
297
+ ```ts
298
+ import { client } from "@/lib/auth-client";
299
+
300
+ async function activateInvite(token: string) {
301
+ const { data, error } = await client.invite.activate({
302
+ token,
303
+ callbackURL: "/auth/sign-up" // Where to redirect the user
304
+ });
305
+
306
+ if (error) {
307
+ console.error("Failed to activate invite:", error);
308
+ // Handle error (e.g., code invalid, expired, already used)
309
+ return false;
310
+ }
311
+
312
+ // On successful activation, a cookie named '{your-app-name}.invite-code'
313
+ // is set in the user's browser. This cookie will be used during sign-up.
314
+ console.log("Invite activated successfully.");
315
+ return true;
316
+ }
317
+ ```
318
+ </details>
319
+
320
+ <details>
321
+ <summary>Create an invite on the client</summary>
322
+
323
+ Use `auth.api.activateInvite` to create one.
324
+
325
+ ```ts
326
+ import { auth } from "@/lib/auth";
327
+
328
+ async function activateInvite(token: string) {
329
+ const { data, error } = await auth.api.activateInvite({
330
+ headers: ..., // The usera headers, req.headers on api, await headers() on NextJS
331
+ body: { // In the body you put the options
332
+ token,
333
+ callbackURL: "/auth/sign-up" // Where to redirect the user
334
+ }
335
+ });
336
+
337
+ if (error) {
338
+ console.error("Failed to activate invite:", error);
339
+ // Handle error (e.g., code invalid, expired, already used)
340
+ return false;
341
+ }
342
+
343
+ // On successful activation, a cookie named '{your-app-name}.invite-code'
344
+ // is set in the user's browser. This cookie will be used during sign-up.
345
+ console.log("Invite activated successfully.");
346
+ return true;
347
+ }
348
+ ```
349
+ </details>
350
+
351
+ </details>
352
+
353
+ <details>
354
+ <summary>Activating an invite automaticly</summary>
355
+
356
+ When you create an invite (see ["Creating Invites"](#creating-invites)) and provide an email.
357
+
358
+ <details>
359
+ <summary>Example</summary>
360
+
361
+ ```ts
362
+ import { client } from "@/lib/auth-client";
363
+
364
+ const { data, error } = await client.invite.create({
365
+ email: "test@test.com"
366
+ });
367
+ ```
368
+ </details>
369
+
370
+ An email will be sent to that user. The system checks whether a user with that email already exists:
371
+
372
+ - If the user does **not** exist, the email invites them to **sign up** and create an account.
373
+ - If the user **already exists**, the email invites them to **upgrade their role**.
374
+
375
+ When the user follows the link, the token is automatically activated. After completing the action (creating an account or upgrading their role), they are redirected to `redirectToAfterUpgrade` to see their new account or updated role.
376
+ </details>
377
+
378
+ #### [Activate invite options](#activate-invite-options)
379
+
380
+ ### 3. Signing up
381
+
382
+ The invite system works alongside the standard sign-up and sign-in flow. The outcome depends on whether the user has an active invite.
383
+
384
+ #### How it works
385
+
386
+ - When an invite is activated, the token is saved in the user's browser cookie.
387
+ - A hook runs after key authentication endpoints (like `/sign-up/email`, `/sign-in/email`, `/verify-email`, and social callbacks).
388
+ - The hook validates the token, checks expiration and max uses, and marks the invite as used.
389
+ - The user's role is upgraded if applicable.
390
+ - The cookie is cleared after the invite is consumed.
391
+ - The user is redirected to `defaultRedirectAfterUpgrade` to see their new role or welcome page.
392
+
393
+ #### Scenario 1: Using an Active Invite
394
+
395
+ 1. **Activate Invite:** The user activates an invite code (see ["Activating Invites"](#activating-invites)), which sets a `{your-app-name}.invite-code` cookie.
396
+ 2. **Sign Up / Sign In:** The user completes the normal sign-up or sign-in process (e.g., using email and password).
397
+ 3. **Role Upgrade:** If a valid `{your-app-name}.invite-code` cookie exists:
398
+ - The invite is validated.
399
+ - The user's role is upgraded.
400
+ - The invite is marked as used in the database.
401
+ - The `{your-app-name}.invite-code` cookie is cleared.
402
+
403
+ <details>
404
+ <summary>Example Code</summary>
405
+
406
+ ```ts
407
+ import { client } from "@/lib/auth-client";
408
+
409
+ // Using the client with an active invite
410
+ async function signUpWithInvite(email, password, name) {
411
+ const { data, error } = await client.signUp.email({ email, password, name });
412
+
413
+ if (error) {
414
+ console.error("Sign-up failed:", error);
415
+ return;
416
+ }
417
+
418
+ if (data) {
419
+ console.log(
420
+ "Sign-up successful. Role should now be updated:",
421
+ data.user
422
+ );
423
+ // data.user contains the new user object with the upgraded role
424
+ // data.token contains the session token
425
+ }
426
+ }
427
+ ```
428
+ </details>
429
+
430
+ #### Scenario 2: Without an Invite
431
+
432
+ 1. **Sign Up:** The user signs up without activating an invite or if the activated invite is invalid, expired, or already used.
433
+ 2. **Default Role:**
434
+ - The user is created with the default role defined in the admin plugin.
435
+ - The invite system does not affect this user.
436
+
437
+ This approach allows capturing user interest even if invites are limited. Roles can be upgraded later using a valid invite or administrative actions.
438
+
439
+ <details>
440
+ <summary>Example Code</summary>
441
+
442
+ ```ts
443
+ import { client } from "@/lib/auth-client";
444
+
445
+ // Using the client without an invite
446
+ async function signUpWithoutInvite(email, password, name) {
447
+ const { data, error } = await client.signUp.email({ email, password, name });
448
+
449
+ if (error) {
450
+ console.error("Sign-up failed:", error);
451
+ return;
452
+ }
453
+
454
+ if (data) {
455
+ console.log(
456
+ "Sign-up successful. Role should be roleForSignupWithoutInvite:",
457
+ data.user
458
+ );
459
+ // data.user contains the new user object with the default role
460
+ // data.token contains the session token
461
+ }
462
+ }
463
+ ```
464
+ </details>
465
+
466
+ ## 🔒 Security
467
+
468
+ The invite system provides several built-in mechanisms to ensure secure management of invitations and role upgrades.
469
+
470
+ ### Token Types
471
+
472
+ Each invite has a token, which is used to validate and track its use:
473
+
474
+ - Token - Default type. A long, random string generated with generateId(24) from better-auth. Recommended for email and url invitations.
475
+ - Code - Shorter, human-readable codes. Generated with random alphanumeric characters. Use with care, as they are easier to guess.
476
+ - Custom - Allows defining your own token generator function via generateToken.
477
+
478
+ ### Invite Permissions
479
+
480
+ - Creating invites: By default, a user cannot create an invite if their role is defaultRoleForSignupWithoutInvite.
481
+ - This behavior can be customized using the canCreateInvite function in the invite options.
482
+
483
+ ### Accepting invites
484
+
485
+ - By default, all users can accept invites
486
+ - This behavior can be customized using the canAcceptInvite function in the invite options.
487
+
488
+ ### Expiration and Usage Limits
489
+
490
+ - Each invite has an expiration date (expiresAt) and a maximum number of uses (maxUses), preventing unlimited sharing or misuse.
491
+ - Expired invites or invites that have reached their usage limit are automatically rejected and deleted.
492
+
493
+ ### Secure Delivery
494
+
495
+ - When sending invites via email, the system uses configured functions (sendUserInvitation or sendUserRoleUpgrade) to securely deliver the token or link.
496
+ - Invites that were send to a certain email can only be used by that exact email.
497
+ - The token is stored in an HTTP-only cookie when the invite is activated, protecting it from client-side access.
498
+
499
+ ### Best Practices
500
+
501
+ - Use the default token type for most invitations, as it provides the highest entropy.
502
+ - Ensure canCreateInvite is properly configured to prevent users from inviting others to roles above their own.
503
+ - Never expose the token in client-side code or logs.
504
+ - Monitor maxUses and expiresAt to avoid old invites being exploited.
505
+ ## 🧩 API Reference
506
+
507
+ <details>
508
+ <summary>POST /invite/create (Create an invite)</summary>
509
+
510
+ It creates a token using all the configuration, if you call this
511
+ endpoint with an email, it will send them an email to create their
512
+ account or to upgrade their role.
513
+
514
+ ```http
515
+ POST /invite/create
516
+ ```
517
+
518
+ <h3 id="create-invite-options"></h3>
519
+ ? = Optional
520
+
521
+ | Parameter | Type | Description | Default value | Where |
522
+ | :-------- | :--- | :---------- | :------------ | :----- |
523
+ | `role` | `string` | The role to give the invited user. | — | `BODY` |
524
+ | `email?` | `email` | The email address of the user to send a invitation email to. | — | `BODY` |
525
+ | `tokenType?` | `"token" \| "code" \| "custom"` | Type of token to use, 24 character token, 6 digit code or custom options.generateToken. | `options.defaultTokenType` | `BODY` |
526
+ | `redirectToSignUp?` | `string` | The URL to redirect the user to create their account. | `options.defaultRedirectTo` | `BODY` |
527
+ | `redirectToSignIn?` | `string` | The URL to redirect the user to upgrade their role. | `options.defaultRedirectToSignIn` | `BODY` |
528
+ | `maxUses?` | `number` | The number of times an invitation can be used. | `options.defaultMaxUses` | `BODY` |
529
+ | `expiresIn?` | `number` | Number of seconds the invitation token is valid for. | `options.invitationTokenExpiresIn` | `BODY` |
530
+ | `redirectToAfterUpgrade?` | `string` | The URL to redirect the user to after upgrade their role (if the user is already logged in). | `options.defaultRedirectAfterUpgrade` | `BODY` |
531
+ | `shareInviterName?` | `boolean` | Whether the inviter's name should be shared with the invitee. | options.defaultShareInviterName | `BODY` |
532
+ | `senderResponse?` | `"token" \| "url"` | How should the sender receive the token (sender only receives a token if no email is provided) | `options.defaultSenderResponse` | `BODY` |
533
+ | `senderResponseRedirect?` | `"signUp" \| "signIn"` | Where should we redirect the user? (only if no email is provided) | `options.defaultSenderResponseRedirect` | `BODY` |
534
+
535
+ </details>
536
+
537
+ <details>
538
+ <summary>POST /invite/activate (Activate an invite)</summary>
539
+
540
+ It saves the token in the user's cookie, for later use in a hook,
541
+ but if user is already signed in, it will only consume the token
542
+ and upgrade their role.
543
+
544
+ ```http
545
+ POST /invite/activate
546
+ ```
547
+
548
+ <h3 id="activate-invite-options"></h3>
549
+ ? = Optional
550
+
551
+ | Parameter | Type | Description | Default value | Where |
552
+ | :-------- | :--- | :---------- | :------------ | :----- |
553
+ | `token` | `string` | The invitation token. | — | `BODY` |
554
+ | `redirectTo?` | `string` | Where to redirect the user to sign in/up. | — | `BODY` |
555
+
556
+ </details>
557
+
558
+
559
+ <details>
560
+ <summary>GET /invite/:token (Activate an invite callback)</summary>
561
+
562
+ It saves the token in the user's cookie, for later use in a hook,
563
+ but if user is already signed in, it will only consume the token
564
+ and upgrade their role.
565
+
566
+ This endpoint is meant to be used as a **callback**.
567
+ It is the URL sent in invitation emails and when senderResponse is set to "url"
568
+
569
+ Unlike `POST /invite/activate`, this endpoint uses **GET**, and is intended to be called via a URL and should not be called directly from the API (auth.api.activateInvite or authClient.invite.activate)
570
+
571
+ ```http
572
+ GET /invite/:token
573
+ ```
574
+
575
+ <h3 id="activate-invite-options"></h3>
576
+ ? = Optional
577
+
578
+ | Parameter | Type | Description | Default value | Where |
579
+ | :-------- | :--- | :---------- | :------------ | :----- |
580
+ | `token` | `string` | The invitation token. | — | `URL` |
581
+ | `redirectTo?` | `string` | Where to redirect the user to sign in/up. | — | `QUERY` |
582
+
583
+ </details>
584
+
585
+ ## Database Schema
586
+
587
+ <details>
588
+ <summary>Invite Table</summary>
589
+
590
+ ? = optional
591
+
592
+ ### `invite` table
593
+
594
+ | Column | Type | Description | References |
595
+ |--------|------|-------------|------------|
596
+ | `token` | `string` | The unique invite code. | — |
597
+ | `createdAt` | `date` | Timestamp when the invite was created. | — |
598
+ | `expiresAt` | `date` | Timestamp when the invite expires. | — |
599
+ | `maxUses` | `number` | Maximum number of times the invite can be used. | — |
600
+ | `createdByUserId?` | `string` | ID of the user who created the invite. | `user.id` |
601
+ | `callbackURL` | `string` | URL to redirect the user after activating the invite. | — |
602
+ | `redirectToAfterUpgrade` | `string` | URL to redirect the user after their role is upgraded. | — |
603
+ | `shareInviterName` | `boolean` | Whether to share the inviter's name with the invitee. | — |
604
+ | `email?` | `string` | Email of the invited user. Optional if sending a URL directly. | — |
605
+ | `role` | `string` | Role to assign to the invited user. | — |
606
+ </details>
607
+
608
+ <details>
609
+ <summary>InviteUse Table</summary>
610
+
611
+ ? = optional
612
+
613
+ ### `invite_use` table
614
+
615
+ | Column | Type | Description | References |
616
+ |--------|------|-------------|------------|
617
+ | `inviteId` | `string` | ID of the invite being used. | `invite.token` |
618
+ | `usedAt` | `date` | Timestamp when the invite was used. | — |
619
+ | `usedByUserId?` | `string` | ID of the user who used the invite. | `user.id` |
620
+ </details>
621
+
622
+ ## Acknowledgements
623
+
624
+ - Partially based in [better auth invite from bard](https://github.com/bard/better-auth-invite)
625
+
626
+ ## License
627
+
628
+ [MIT](https://choosealicense.com/licenses/mit/)
629
+
package/dist/body.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import * as z from "zod";
2
+ export declare const createInviteBodySchema: z.ZodObject<{
3
+ role: z.ZodString;
4
+ email: z.ZodOptional<z.ZodEmail>;
5
+ tokenType: z.ZodOptional<z.ZodEnum<{
6
+ token: "token";
7
+ code: "code";
8
+ custom: "custom";
9
+ }>>;
10
+ redirectToSignUp: z.ZodOptional<z.ZodString>;
11
+ redirectToSignIn: z.ZodOptional<z.ZodString>;
12
+ maxUses: z.ZodOptional<z.ZodNumber>;
13
+ expiresIn: z.ZodOptional<z.ZodNumber>;
14
+ redirectToAfterUpgrade: z.ZodOptional<z.ZodString>;
15
+ shareInviterName: z.ZodOptional<z.ZodBoolean>;
16
+ senderResponse: z.ZodOptional<z.ZodEnum<{
17
+ token: "token";
18
+ url: "url";
19
+ }>>;
20
+ senderResponseRedirect: z.ZodOptional<z.ZodEnum<{
21
+ signUp: "signUp";
22
+ signIn: "signIn";
23
+ }>>;
24
+ }, z.core.$strip>;