better-auth-lead 0.4.2 → 0.5.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 CHANGED
@@ -56,6 +56,8 @@ const authClient = createAuthClient({
56
56
 
57
57
  ### Subscribe
58
58
 
59
+ Provide an `email` to subscribe an anonymous lead:
60
+
59
61
  ```ts
60
62
  // POST /lead/subscribe
61
63
  const { data, error } = await authClient.lead.subscribe({
@@ -67,6 +69,19 @@ const { data, error } = await authClient.lead.subscribe({
67
69
  });
68
70
  ```
69
71
 
72
+ Or omit `email` to subscribe the currently authenticated user. The lead is associated to the session user's `id` (a valid session cookie is required):
73
+
74
+ ```ts
75
+ // POST /lead/subscribe
76
+ const { data, error } = await authClient.lead.subscribe({
77
+ metadata: {
78
+ preferences: 'engineering',
79
+ },
80
+ });
81
+ ```
82
+
83
+ If neither `email` nor an active session is provided, the endpoint responds with `400 Bad Request` (`EMAIL_OR_SESSION_REQUIRED`).
84
+
70
85
  ### Verify
71
86
 
72
87
  ```ts
@@ -80,13 +95,26 @@ await authClient.lead.verify({
80
95
 
81
96
  ### Unsubscribe
82
97
 
98
+ The unsubscribe endpoint is designed for [RFC 8058](https://www.rfc-editor.org/rfc/rfc8058) one-click unsubscribe. The signed `token` is embedded in the `unsubscribeUrl` provided to `sendConfirmationEmail` and should be used in `List-Unsubscribe` email headers — email clients (Gmail, Apple Mail, Yahoo Mail) will POST to this URL automatically when the user clicks "Unsubscribe".
99
+
100
+ ```ts
101
+ // POST /lead/unsubscribe?token=<signed-token>
102
+ const { data, error } = await authClient.lead.unsubscribe({
103
+ query: { token },
104
+ });
105
+ ```
106
+
107
+ For an authenticated user (e.g. from a "Manage preferences" page in your app), use the session-based endpoint. It requires a valid session and deletes the lead associated with the session user's `id`:
108
+
83
109
  ```ts
84
- // POST /lead/unsubscribe
85
- const { data, error } = await authClient.lead.unsubscribe({ id: 'lead-id' });
110
+ // POST /lead/unsubscribe-session
111
+ const { data, error } = await authClient.lead.unsubscribeSession();
86
112
  ```
87
113
 
88
114
  ### Resend
89
115
 
116
+ Resend the confirmation email by `email`:
117
+
90
118
  ```ts
91
119
  // POST /lead/resend
92
120
  const { data, error } = await authClient.lead.resend({
@@ -94,25 +122,82 @@ const { data, error } = await authClient.lead.resend({
94
122
  });
95
123
  ```
96
124
 
125
+ Or omit `email` to resend for the currently authenticated user (lead is looked up by the session user's `id`):
126
+
127
+ ```ts
128
+ // POST /lead/resend
129
+ const { data, error } = await authClient.lead.resend();
130
+ ```
131
+
132
+ If neither `email` nor an active session is provided, the endpoint responds with `400 Bad Request` (`EMAIL_OR_SESSION_REQUIRED`).
133
+
97
134
  ### Update
98
135
 
136
+ Update the metadata of the lead associated with the currently authenticated user. Requires a valid session — the lead is looked up by the session user's `id`:
137
+
99
138
  ```ts
100
139
  // POST /lead/update
101
140
  const { data, error } = await authClient.lead.update({
102
- id: 'lead-id',
103
141
  metadata: {
104
142
  preferences: 'ai',
105
143
  },
106
144
  });
107
145
  ```
108
146
 
109
- ### Email Verification
147
+ If no session is present the endpoint responds with `401 Unauthorized`.
148
+
149
+ ### List (admin)
150
+
151
+ Optional admin endpoint to list all leads. Requires the better-auth [`admin`](https://www.better-auth.com/docs/plugins/admin) plugin to be registered, and must be opted in via `admin.enabled`:
152
+
153
+ ```ts
154
+ // server/auth.ts
155
+ import { betterAuth } from 'better-auth';
156
+ import { admin } from 'better-auth/plugins';
157
+ import { lead } from 'better-auth-lead';
158
+
159
+ export const auth = betterAuth({
160
+ plugins: [
161
+ admin(),
162
+ lead({
163
+ admin: {
164
+ enabled: true,
165
+ // Optional. Roles allowed to call /lead/list. Default: ['admin'].
166
+ // Checked against session.user.role (admin plugin supports
167
+ // comma-separated roles).
168
+ roles: ['admin', 'editor'],
169
+ },
170
+ }),
171
+ ],
172
+ });
173
+ ```
174
+
175
+ ```ts
176
+ // GET /lead/list?limit=100&offset=0
177
+ const { data, error } = await authClient.lead.list({
178
+ query: {
179
+ limit: 100, // optional, default 100, max 1000
180
+ offset: 0, // optional, default 0
181
+ },
182
+ });
183
+ ```
184
+
185
+ The response includes the page of `leads`, the `total` number of leads in the database, and the resolved `limit` and `offset` for client-side pagination.
110
186
 
111
- To enable email verification, you need to pass a function that sends a verification email with a link. The `sendVerificationEmail` takes a data object with the following properties:
187
+ Responses:
188
+
189
+ - `404 Not Found` (`ADMIN_PLUGIN_REQUIRED`) if the admin plugin is not registered.
190
+ - `403 Forbidden` (`FORBIDDEN`) if the session user's role is not in `admin.roles`.
191
+ - `401 Unauthorized` if no session is present.
192
+
193
+ ### Email Confirmation
194
+
195
+ To enable double opt-in email confirmation, pass a `sendConfirmationEmail` function. It receives a data object with:
112
196
 
113
197
  - `lead`: The lead object.
114
- - `url`: The URL to send to the user which contains the token.
115
- - `token`: A verification token used to complete the email verification.
198
+ - `email`: The lead's email address.
199
+ - `url`: The URL containing the confirmation token to send to the user.
200
+ - `token`: The confirmation token used to complete the verification.
116
201
  - `unsubscribeUrl`: The endpoint URL for one-click unsubscribe (RFC 8058). Use this in `List-Unsubscribe` email headers.
117
202
 
118
203
  and a `request` object as the second parameter.
@@ -126,22 +211,22 @@ import { sendEmail } from './email'; // your email sending function
126
211
  export const auth = betterAuth({
127
212
  plugins: [
128
213
  lead({
129
- sendVerificationEmail: async ({ lead, url, token, unsubscribeUrl }) => {
130
- const { verificationEmailSentAt } = lead;
214
+ sendConfirmationEmail: async ({ lead, email, url, token, unsubscribeUrl }) => {
215
+ const { confirmationSentAt } = lead;
131
216
  if (
132
- verificationEmailSentAt &&
133
- Date.now() - verificationEmailSentAt.getTime() < 60 * 1000 // 1 minute
217
+ confirmationSentAt &&
218
+ Date.now() - confirmationSentAt.getTime() < 60 * 1000 // 1 minute
134
219
  ) {
135
220
  console.log(
136
- `Skipping sending verification email to ${lead.email} because a recent email was already sent.`,
221
+ `Skipping sending confirmation email to ${email} because a recent email was already sent.`,
137
222
  );
138
223
  return false;
139
224
  }
140
225
 
141
226
  void sendEmail({
142
- to: lead.email,
143
- subject: 'Newsletter: Verify your email address',
144
- text: `Click the link to verify your email: ${url}`,
227
+ to: email,
228
+ subject: 'Newsletter: Confirm your subscription',
229
+ text: `Click the link to confirm your subscription: ${url}`,
145
230
  // One-click unsubscribe headers (RFC 8058)
146
231
  // Supported by Gmail, Apple Mail, and Yahoo Mail.
147
232
  headers: {
@@ -152,9 +237,9 @@ export const auth = betterAuth({
152
237
 
153
238
  return true;
154
239
  },
155
- onEmailVerified: async ({ lead }) => {
156
- // do something when a lead's email is verified
157
- console.log(`Lead ${lead.email} has been verified!`);
240
+ onConfirmed: async ({ lead }) => {
241
+ // do something when a lead confirms their subscription
242
+ console.log(`Lead ${lead} has confirmed their subscription!`);
158
243
  },
159
244
  }),
160
245
  ],
@@ -163,7 +248,7 @@ export const auth = betterAuth({
163
248
 
164
249
  > Avoid awaiting the email sending to prevent timing attacks.
165
250
 
166
- Additionally, you can provide an `onEmailVerified` callback to execute logic after a lead's email is verified.
251
+ Additionally, you can provide an `onConfirmed` callback to execute logic after a lead confirms their subscription.
167
252
 
168
253
  ### Metadata Validation
169
254
 
@@ -214,6 +299,11 @@ await authClient.lead.subscribe({
214
299
  email: 'user@example.com',
215
300
  metadata: { preferences: 'engineering' },
216
301
  });
302
+
303
+ // or for the currently authenticated user (omit email)
304
+ await authClient.lead.subscribe({
305
+ metadata: { preferences: 'engineering' },
306
+ });
217
307
  ```
218
308
 
219
309
  ## Schema
@@ -222,29 +312,33 @@ await authClient.lead.subscribe({
222
312
 
223
313
  Table name: `lead`
224
314
 
225
- |  Field |  Type |  Key |  Description |
226
- | ----------------------- | ------- | ------ | ------------------------------------------------- |
227
- | id | string | pk | Unique identifier for each lead |
228
- | email | string | unique | Email address of the lead |
229
- | emailVerified | boolean | | Whether the email is verified |
230
- | verificationEmailSentAt | Date | ? | Timestamp of when the verification email was sent |
231
- | metadata | json | ? | Additional data about the lead |
232
- | createdAt | date | | Timestamp of lead creation |
233
- | updatedAt | date | | Timestamp of last update |
315
+ | Field | Type | Key | Description |
316
+ | ------------------ | ------- | ------ | ------------------------------------------------- |
317
+ | id | string | pk | Unique identifier for each lead |
318
+ | email | string? | unique | Email address of the lead (optional) |
319
+ | userId | string? | unique | ID of an associated better-auth user (optional) |
320
+ | confirmed | boolean | | Whether the lead has confirmed their subscription |
321
+ | confirmationSentAt | Date | ? | Timestamp of when the confirmation email was sent |
322
+ | metadata | json | ? | Additional data about the lead |
323
+ | createdAt | date | | Timestamp of lead creation |
324
+ | updatedAt | date | | Timestamp of last update |
234
325
 
235
326
  #### Prisma
236
327
 
237
328
  ```prisma
238
329
  model Lead {
239
- id String @id
240
- createdAt DateTime @default(now())
241
- updatedAt DateTime @updatedAt
242
- email String
243
- emailVerified Boolean @default(false)
244
- verificationEmailSentAt DateTime?
245
- metadata String?
330
+ id String @id
331
+ createdAt DateTime @default(now())
332
+ updatedAt DateTime @updatedAt
333
+ email String?
334
+ userId String?
335
+ confirmed Boolean @default(false)
336
+ confirmationSentAt DateTime?
337
+ metadata String?
338
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
246
339
 
247
340
  @@unique([email])
341
+ @@unique([userId])
248
342
  @@map("lead")
249
343
  }
250
344
  ```
package/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { r as LeadOptions, t as lead } from "./index-BRWNSNS3.mjs";
1
+ import { r as LeadOptions, t as lead } from "./index-DOhc8vla.mjs";
2
2
  import { StandardSchemaV1 } from "better-auth";
3
3
 
4
4
  //#region src/client.d.ts
@@ -19,16 +19,25 @@ declare const lead$1: {
19
19
  };
20
20
  email: {
21
21
  type: "string";
22
- required: true;
22
+ required: false;
23
23
  unique: true;
24
24
  };
25
- emailVerified: {
25
+ userId: {
26
+ type: "string";
27
+ required: false;
28
+ unique: true;
29
+ references: {
30
+ model: string;
31
+ field: string;
32
+ };
33
+ };
34
+ confirmed: {
26
35
  type: "boolean";
27
36
  defaultValue: false;
28
37
  required: true;
29
38
  input: false;
30
39
  };
31
- verificationEmailSentAt: {
40
+ confirmationSentAt: {
32
41
  type: "date";
33
42
  required: false;
34
43
  input: false;
@@ -48,30 +57,32 @@ interface LeadOptions {
48
57
  * @param data the data object
49
58
  * @param request the request object
50
59
  */
51
- sendVerificationEmail?: (
60
+ sendConfirmationEmail?: (
52
61
  /**
53
- * @param lead the lead to send the verification email to
54
- * @param url the verification url
55
- * @param token the verification token
62
+ * @param lead the lead to send the confirmation email to
63
+ * @param email the email address to send the confirmation to
64
+ * @param url the confirmation url
65
+ * @param token the confirmation token
56
66
  * @param unsubscribeUrl the one-click unsubscribe URL (RFC 8058) to include in List-Unsubscribe headers
57
67
  */
58
68
 
59
69
  data: {
60
70
  lead: Lead;
71
+ email: string;
61
72
  url: string;
62
73
  token: string;
63
74
  unsubscribeUrl: string;
64
75
  }, request?: Request) => Promise<boolean>;
65
- onEmailVerified?: (
76
+ onConfirmed?: (
66
77
  /**
67
- * @param lead the lead that was verified
78
+ * @param lead the lead that confirmed their subscription
68
79
  */
69
80
 
70
81
  data: {
71
82
  lead: Lead;
72
83
  }, request?: Request) => Promise<void>;
73
84
  /**
74
- * Number of seconds the verification token is
85
+ * Number of seconds the confirmation token is
75
86
  * valid for.
76
87
  * @default 3600 seconds (1 hour)
77
88
  */
@@ -105,6 +116,25 @@ interface LeadOptions {
105
116
  metadata?: {
106
117
  validationSchema?: StandardSchemaV1;
107
118
  };
119
+ /**
120
+ * Admin-only endpoints. Requires the better-auth `admin` plugin to be
121
+ * registered. When enabled, exposes `GET /lead/list` which returns all
122
+ * leads to users whose role matches `admin.roles`.
123
+ */
124
+ admin?: {
125
+ /**
126
+ * Enable admin endpoints (e.g. `/lead/list`).
127
+ * @default false
128
+ */
129
+ enabled?: boolean;
130
+ /**
131
+ * Roles allowed to call admin endpoints. The check is performed against
132
+ * `session.user.role` (added by the admin plugin), which may contain a
133
+ * comma-separated list of roles.
134
+ * @default ['admin']
135
+ */
136
+ roles?: string[];
137
+ };
108
138
  }
109
139
  interface Lead {
110
140
  /**
@@ -113,12 +143,17 @@ interface Lead {
113
143
  id: string;
114
144
  createdAt: Date;
115
145
  updatedAt: Date;
116
- email: string;
117
- emailVerified: boolean;
118
- verificationEmailSentAt: Date | null;
146
+ email: string | null;
147
+ userId: string | null;
148
+ confirmed: boolean;
149
+ confirmationSentAt: Date | null;
119
150
  metadata?: string;
120
151
  }
121
- type LeadPayload = Omit<Lead, 'id' | 'createdAt' | 'updatedAt' | 'emailVerified' | 'verificationEmailSentAt'>;
152
+ type LeadPayload = {
153
+ email?: string | null;
154
+ userId?: string | null;
155
+ metadata?: string;
156
+ };
122
157
  //#endregion
123
158
  //#region src/index.d.ts
124
159
  declare const lead: <O extends LeadOptions>(options?: O) => {
@@ -141,16 +176,25 @@ declare const lead: <O extends LeadOptions>(options?: O) => {
141
176
  };
142
177
  email: {
143
178
  type: "string";
144
- required: true;
179
+ required: false;
145
180
  unique: true;
146
181
  };
147
- emailVerified: {
182
+ userId: {
183
+ type: "string";
184
+ required: false;
185
+ unique: true;
186
+ references: {
187
+ model: string;
188
+ field: string;
189
+ };
190
+ };
191
+ confirmed: {
148
192
  type: "boolean";
149
193
  defaultValue: false;
150
194
  required: true;
151
195
  input: false;
152
196
  };
153
- verificationEmailSentAt: {
197
+ confirmationSentAt: {
154
198
  type: "date";
155
199
  required: false;
156
200
  input: false;
@@ -163,16 +207,51 @@ declare const lead: <O extends LeadOptions>(options?: O) => {
163
207
  };
164
208
  };
165
209
  endpoints: {
210
+ list?: import("better-auth").StrictEndpoint<"/lead/list", {
211
+ method: "GET";
212
+ query: import("zod").ZodObject<{
213
+ limit: import("zod").ZodOptional<import("zod").ZodCoercedNumber<unknown>>;
214
+ offset: import("zod").ZodOptional<import("zod").ZodCoercedNumber<unknown>>;
215
+ }, import("better-auth").$strip>;
216
+ use: ((inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<{
217
+ session: {
218
+ session: Record<string, any> & {
219
+ id: string;
220
+ createdAt: Date;
221
+ updatedAt: Date;
222
+ userId: string;
223
+ expiresAt: Date;
224
+ token: string;
225
+ ipAddress?: string | null | undefined;
226
+ userAgent?: string | null | undefined;
227
+ };
228
+ user: Record<string, any> & {
229
+ id: string;
230
+ createdAt: Date;
231
+ updatedAt: Date;
232
+ email: string;
233
+ emailVerified: boolean;
234
+ name: string;
235
+ image?: string | null | undefined;
236
+ };
237
+ };
238
+ }>)[];
239
+ }, {
240
+ leads: Lead[];
241
+ total: number;
242
+ limit: number;
243
+ offset: number;
244
+ }> | undefined;
166
245
  subscribe: import("better-auth").StrictEndpoint<"/lead/subscribe", {
167
246
  method: "POST";
168
247
  body: import("zod").ZodObject<{
169
- email: import("zod").ZodString;
248
+ email: import("zod").ZodOptional<import("zod").ZodString>;
170
249
  metadata: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
171
250
  }, import("better-auth").$strip>;
172
251
  metadata: {
173
252
  $Infer: {
174
253
  body: {
175
- email: string;
254
+ email?: string;
176
255
  metadata?: (O extends {
177
256
  metadata: {
178
257
  validationSchema: import("better-auth").StandardSchemaV1<unknown, infer Out>;
@@ -197,13 +276,44 @@ declare const lead: <O extends LeadOptions>(options?: O) => {
197
276
  query: import("zod").ZodObject<{
198
277
  token: import("zod").ZodString;
199
278
  }, import("better-auth").$strip>;
279
+ metadata: {
280
+ allowedMediaTypes: never[];
281
+ };
282
+ }, {
283
+ status: boolean;
284
+ }>;
285
+ unsubscribeSession: import("better-auth").StrictEndpoint<"/lead/unsubscribe-session", {
286
+ method: "POST";
287
+ use: ((inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<{
288
+ session: {
289
+ session: Record<string, any> & {
290
+ id: string;
291
+ createdAt: Date;
292
+ updatedAt: Date;
293
+ userId: string;
294
+ expiresAt: Date;
295
+ token: string;
296
+ ipAddress?: string | null | undefined;
297
+ userAgent?: string | null | undefined;
298
+ };
299
+ user: Record<string, any> & {
300
+ id: string;
301
+ createdAt: Date;
302
+ updatedAt: Date;
303
+ email: string;
304
+ emailVerified: boolean;
305
+ name: string;
306
+ image?: string | null | undefined;
307
+ };
308
+ };
309
+ }>)[];
200
310
  }, {
201
311
  status: boolean;
202
312
  }>;
203
313
  resend: import("better-auth").StrictEndpoint<"/lead/resend", {
204
314
  method: "POST";
205
315
  body: import("zod").ZodObject<{
206
- email: import("zod").ZodString;
316
+ email: import("zod").ZodOptional<import("zod").ZodString>;
207
317
  }, import("better-auth").$strip>;
208
318
  }, {
209
319
  status: boolean;
@@ -211,13 +321,34 @@ declare const lead: <O extends LeadOptions>(options?: O) => {
211
321
  update: import("better-auth").StrictEndpoint<"/lead/update", {
212
322
  method: "POST";
213
323
  body: import("zod").ZodObject<{
214
- id: import("zod").ZodString;
215
324
  metadata: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
216
325
  }, import("better-auth").$strip>;
326
+ use: ((inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<{
327
+ session: {
328
+ session: Record<string, any> & {
329
+ id: string;
330
+ createdAt: Date;
331
+ updatedAt: Date;
332
+ userId: string;
333
+ expiresAt: Date;
334
+ token: string;
335
+ ipAddress?: string | null | undefined;
336
+ userAgent?: string | null | undefined;
337
+ };
338
+ user: Record<string, any> & {
339
+ id: string;
340
+ createdAt: Date;
341
+ updatedAt: Date;
342
+ email: string;
343
+ emailVerified: boolean;
344
+ name: string;
345
+ image?: string | null | undefined;
346
+ };
347
+ };
348
+ }>)[];
217
349
  metadata: {
218
350
  $Infer: {
219
351
  body: {
220
- id: string;
221
352
  metadata?: (O extends {
222
353
  metadata: {
223
354
  validationSchema: import("better-auth").StandardSchemaV1<unknown, infer Out>;
@@ -237,12 +368,15 @@ declare const lead: <O extends LeadOptions>(options?: O) => {
237
368
  max: number;
238
369
  }[];
239
370
  $ERROR_CODES: {
371
+ FORBIDDEN: import("better-auth").RawError<"FORBIDDEN">;
240
372
  INVALID_EMAIL: import("better-auth").RawError<"INVALID_EMAIL">;
241
373
  INVALID_TOKEN: import("better-auth").RawError<"INVALID_TOKEN">;
242
374
  TOKEN_EXPIRED: import("better-auth").RawError<"TOKEN_EXPIRED">;
243
375
  INVALID_METADATA: import("better-auth").RawError<"INVALID_METADATA">;
376
+ EMAIL_OR_SESSION_REQUIRED: import("better-auth").RawError<"EMAIL_OR_SESSION_REQUIRED">;
377
+ ADMIN_PLUGIN_REQUIRED: import("better-auth").RawError<"ADMIN_PLUGIN_REQUIRED">;
244
378
  };
245
379
  };
246
380
  //#endregion
247
381
  export { LeadPayload as i, Lead as n, LeadOptions as r, lead as t };
248
- //# sourceMappingURL=index-BRWNSNS3.d.mts.map
382
+ //# sourceMappingURL=index-DOhc8vla.d.mts.map
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { i as LeadPayload, n as Lead, r as LeadOptions, t as lead } from "./index-BRWNSNS3.mjs";
1
+ import { i as LeadPayload, n as Lead, r as LeadOptions, t as lead } from "./index-DOhc8vla.mjs";
2
2
  export { type Lead, type LeadOptions, type LeadPayload, lead };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { BASE_ERROR_CODES, defineErrorCodes } from "better-auth";
2
- import { APIError, createAuthEndpoint, createEmailVerificationToken } from "better-auth/api";
2
+ import { APIError, createAuthEndpoint, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
3
3
  import { SignJWT, jwtVerify } from "jose";
4
4
  import { JWTExpired } from "jose/errors";
5
5
  import * as z from "zod";
@@ -9,12 +9,15 @@ const LEAD_ERROR_CODES = defineErrorCodes({
9
9
  INVALID_EMAIL: "Invalid email",
10
10
  INVALID_TOKEN: "Invalid token",
11
11
  TOKEN_EXPIRED: "Token expired",
12
- INVALID_METADATA: "Invalid metadata"
12
+ INVALID_METADATA: "Invalid metadata",
13
+ EMAIL_OR_SESSION_REQUIRED: "Email or session is required",
14
+ ADMIN_PLUGIN_REQUIRED: "Admin plugin is required",
15
+ FORBIDDEN: "Forbidden"
13
16
  });
14
17
  //#endregion
15
18
  //#region src/routes.ts
16
19
  const subscribeSchema = z.object({
17
- email: z.string().meta({ description: "Email address of the lead" }),
20
+ email: z.string().optional().meta({ description: "Email address of the lead" }),
18
21
  metadata: z.record(z.string(), z.any()).optional().meta({ description: "Additional metadata to store with the lead" })
19
22
  });
20
23
  const subscribe = (options) => createAuthEndpoint("/lead/subscribe", {
@@ -22,52 +25,76 @@ const subscribe = (options) => createAuthEndpoint("/lead/subscribe", {
22
25
  body: subscribeSchema,
23
26
  metadata: { $Infer: { body: {} } }
24
27
  }, async (ctx) => {
25
- const { email } = ctx.body;
26
- if (!z.email().safeParse(email).success) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.INVALID_EMAIL);
28
+ const email = ctx.body.email;
27
29
  const metadata = validateMetadata(options, ctx.body.metadata, ctx.context.logger);
28
- const normalizedEmail = email.toLowerCase();
30
+ let identifierType;
31
+ let leadIdentifier;
32
+ let leadEmail;
33
+ let createData;
34
+ if (email) {
35
+ if (!z.email().safeParse(email).success) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.INVALID_EMAIL);
36
+ leadIdentifier = email.toLowerCase();
37
+ identifierType = "email";
38
+ leadEmail = leadIdentifier;
39
+ createData = {
40
+ email: leadIdentifier,
41
+ metadata: metadata ? JSON.stringify(metadata) : void 0
42
+ };
43
+ } else {
44
+ const session = await getSessionFromCtx(ctx);
45
+ if (!session) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.EMAIL_OR_SESSION_REQUIRED);
46
+ leadIdentifier = session.user.id;
47
+ identifierType = "user";
48
+ leadEmail = session.user.email;
49
+ createData = {
50
+ userId: leadIdentifier,
51
+ metadata: metadata ? JSON.stringify(metadata) : void 0
52
+ };
53
+ }
54
+ const whereField = identifierType === "email" ? "email" : "userId";
29
55
  let lead = await ctx.context.adapter.findOne({
30
56
  model: "lead",
31
57
  where: [{
32
- field: "email",
33
- value: normalizedEmail
58
+ field: whereField,
59
+ value: leadIdentifier
34
60
  }]
35
61
  });
36
62
  if (!lead) try {
37
63
  lead = await ctx.context.adapter.create({
38
64
  model: "lead",
39
- data: {
40
- email: normalizedEmail,
41
- metadata: metadata ? JSON.stringify(metadata) : void 0
42
- }
65
+ data: createData
43
66
  });
44
67
  } catch (e) {
45
68
  ctx.context.logger.info("Error creating lead");
46
69
  lead = await ctx.context.adapter.findOne({
47
70
  model: "lead",
48
71
  where: [{
49
- field: "email",
50
- value: normalizedEmail
72
+ field: whereField,
73
+ value: leadIdentifier
51
74
  }]
52
75
  });
53
76
  }
54
- if (options.sendVerificationEmail && lead && !lead.emailVerified) {
55
- const token = await createEmailVerificationToken(ctx.context.secret, normalizedEmail, void 0, options.expiresIn ?? 3600);
77
+ if (options.sendConfirmationEmail && lead && !lead.confirmed) {
78
+ const token = await createConfirmationToken(ctx.context.secret, {
79
+ identifier: leadIdentifier,
80
+ type: identifierType
81
+ }, options.expiresIn ?? 3600);
56
82
  const url = `${ctx.context.baseURL}/lead/verify?token=${token}`;
57
83
  const unsubscribeToken = await createUnsubscribeToken(ctx.context.secret, lead.id, options.unsubscribeExpiresIn);
58
84
  const unsubscribeUrl = `${ctx.context.baseURL}/lead/unsubscribe?token=${unsubscribeToken}`;
59
- if (await options.sendVerificationEmail({
85
+ if (await options.sendConfirmationEmail({
60
86
  lead,
87
+ email: leadEmail,
61
88
  url,
62
89
  token,
63
90
  unsubscribeUrl
64
91
  }, ctx.request)) await ctx.context.adapter.update({
65
92
  model: "lead",
66
93
  where: [{
67
- field: "email",
68
- value: normalizedEmail
94
+ field: whereField,
95
+ value: leadIdentifier
69
96
  }],
70
- update: { verificationEmailSentAt: /* @__PURE__ */ new Date() }
97
+ update: { confirmationSentAt: /* @__PURE__ */ new Date() }
71
98
  });
72
99
  }
73
100
  return ctx.json({ status: true });
@@ -85,32 +112,37 @@ const verify = (options) => createAuthEndpoint("/lead/verify", {
85
112
  if (e instanceof JWTExpired) throw APIError.from("UNAUTHORIZED", LEAD_ERROR_CODES.TOKEN_EXPIRED);
86
113
  throw APIError.from("UNAUTHORIZED", LEAD_ERROR_CODES.INVALID_TOKEN);
87
114
  }
88
- const parsed = subscribeSchema.parse(jwt.payload);
115
+ const parsed = z.object({
116
+ identifier: z.string(),
117
+ type: z.enum(["email", "user"])
118
+ }).parse(jwt.payload);
119
+ const whereField = parsed.type === "user" ? "userId" : "email";
89
120
  let lead = await ctx.context.adapter.findOne({
90
121
  model: "lead",
91
122
  where: [{
92
- field: "email",
93
- value: parsed.email
123
+ field: whereField,
124
+ value: parsed.identifier
94
125
  }]
95
126
  });
96
127
  if (!lead) return ctx.json({ status: true });
97
- if (lead.emailVerified) return ctx.json({ status: true });
128
+ if (lead.confirmed) return ctx.json({ status: true });
98
129
  lead = await ctx.context.adapter.update({
99
130
  model: "lead",
100
131
  where: [{
101
- field: "email",
102
- value: parsed.email
132
+ field: whereField,
133
+ value: parsed.identifier
103
134
  }],
104
- update: { emailVerified: true }
135
+ update: { confirmed: true }
105
136
  });
106
137
  if (!lead) return ctx.json({ status: true });
107
- if (options.onEmailVerified) await ctx.context.runInBackgroundOrAwait(options.onEmailVerified({ lead }, ctx.request));
138
+ if (options.onConfirmed) await ctx.context.runInBackgroundOrAwait(options.onConfirmed({ lead }, ctx.request));
108
139
  return ctx.json({ status: true });
109
140
  });
110
141
  const unsubscribeQuerySchema = z.object({ token: z.string().meta({ description: "Signed unsubscribe token" }) });
111
142
  const unsubscribe = (options) => createAuthEndpoint("/lead/unsubscribe", {
112
143
  method: "POST",
113
- query: unsubscribeQuerySchema
144
+ query: unsubscribeQuerySchema,
145
+ metadata: { allowedMediaTypes: [] }
114
146
  }, async (ctx) => {
115
147
  let payload;
116
148
  try {
@@ -136,71 +168,137 @@ const unsubscribe = (options) => createAuthEndpoint("/lead/unsubscribe", {
136
168
  });
137
169
  return ctx.json({ status: true });
138
170
  });
139
- const resendSchema = z.object({ email: z.string().meta({ description: "Email address to resend the verification email to" }) });
171
+ const unsubscribeSession = (_options) => createAuthEndpoint("/lead/unsubscribe-session", {
172
+ method: "POST",
173
+ use: [sessionMiddleware]
174
+ }, async (ctx) => {
175
+ const userId = ctx.context.session.user.id;
176
+ if (!await ctx.context.adapter.findOne({
177
+ model: "lead",
178
+ where: [{
179
+ field: "userId",
180
+ value: userId
181
+ }]
182
+ })) return ctx.json({ status: true });
183
+ await ctx.context.adapter.delete({
184
+ model: "lead",
185
+ where: [{
186
+ field: "userId",
187
+ value: userId
188
+ }]
189
+ });
190
+ return ctx.json({ status: true });
191
+ });
192
+ const resendSchema = z.object({ email: z.string().optional().meta({ description: "Email address to resend the verification email to" }) });
140
193
  const resend = (options) => createAuthEndpoint("/lead/resend", {
141
194
  method: "POST",
142
195
  body: resendSchema
143
196
  }, async (ctx) => {
144
- const { email } = ctx.body;
145
- if (!z.email().safeParse(email).success) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.INVALID_EMAIL);
146
- const normalizedEmail = email.toLowerCase();
197
+ const email = ctx.body.email;
198
+ let identifierType;
199
+ let leadIdentifier;
200
+ let leadEmail;
201
+ if (email) {
202
+ if (!z.email().safeParse(email).success) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.INVALID_EMAIL);
203
+ leadIdentifier = email.toLowerCase();
204
+ identifierType = "email";
205
+ leadEmail = leadIdentifier;
206
+ } else {
207
+ const session = await getSessionFromCtx(ctx);
208
+ if (!session) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.EMAIL_OR_SESSION_REQUIRED);
209
+ leadIdentifier = session.user.id;
210
+ identifierType = "user";
211
+ leadEmail = session.user.email;
212
+ }
213
+ const whereField = identifierType === "email" ? "email" : "userId";
147
214
  const lead = await ctx.context.adapter.findOne({
148
215
  model: "lead",
149
216
  where: [{
150
- field: "email",
151
- value: normalizedEmail
217
+ field: whereField,
218
+ value: leadIdentifier
152
219
  }]
153
220
  });
154
221
  if (!lead) return ctx.json({ status: true });
155
- if (options.sendVerificationEmail && lead && !lead.emailVerified) {
156
- const token = await createEmailVerificationToken(ctx.context.secret, normalizedEmail, void 0, options.expiresIn ?? 3600);
222
+ if (options.sendConfirmationEmail && !lead.confirmed) {
223
+ const token = await createConfirmationToken(ctx.context.secret, {
224
+ identifier: leadIdentifier,
225
+ type: identifierType
226
+ }, options.expiresIn ?? 3600);
157
227
  const url = `${ctx.context.baseURL}/lead/verify?token=${token}`;
158
228
  const unsubscribeToken = await createUnsubscribeToken(ctx.context.secret, lead.id, options.unsubscribeExpiresIn);
159
229
  const unsubscribeUrl = `${ctx.context.baseURL}/lead/unsubscribe?token=${unsubscribeToken}`;
160
- if (await options.sendVerificationEmail({
230
+ if (await options.sendConfirmationEmail({
161
231
  lead,
232
+ email: leadEmail,
162
233
  url,
163
234
  token,
164
235
  unsubscribeUrl
165
236
  }, ctx.request)) await ctx.context.adapter.update({
166
237
  model: "lead",
167
238
  where: [{
168
- field: "email",
169
- value: normalizedEmail
239
+ field: whereField,
240
+ value: leadIdentifier
170
241
  }],
171
- update: { verificationEmailSentAt: /* @__PURE__ */ new Date() }
242
+ update: { confirmationSentAt: /* @__PURE__ */ new Date() }
172
243
  });
173
244
  }
174
245
  return ctx.json({ status: true });
175
246
  });
176
- const updateSchema = z.object({
177
- id: z.string().meta({ description: "The id of the lead to update" }),
178
- metadata: z.record(z.string(), z.any()).optional().meta({ description: "Additional metadata to store with the lead" })
179
- });
247
+ const updateSchema = z.object({ metadata: z.record(z.string(), z.any()).optional().meta({ description: "Additional metadata to store with the lead" }) });
180
248
  const update = (options) => createAuthEndpoint("/lead/update", {
181
249
  method: "POST",
182
250
  body: updateSchema,
251
+ use: [sessionMiddleware],
183
252
  metadata: { $Infer: { body: {} } }
184
253
  }, async (ctx) => {
185
- const { id } = ctx.body;
254
+ const userId = ctx.context.session.user.id;
186
255
  if (!await ctx.context.adapter.findOne({
187
256
  model: "lead",
188
257
  where: [{
189
- field: "id",
190
- value: id
258
+ field: "userId",
259
+ value: userId
191
260
  }]
192
261
  })) return ctx.json({ status: true });
193
262
  const metadata = validateMetadata(options, ctx.body.metadata, ctx.context.logger);
194
263
  await ctx.context.adapter.update({
195
264
  model: "lead",
196
265
  where: [{
197
- field: "id",
198
- value: id
266
+ field: "userId",
267
+ value: userId
199
268
  }],
200
269
  update: { metadata: metadata ? JSON.stringify(metadata) : void 0 }
201
270
  });
202
271
  return ctx.json({ status: true });
203
272
  });
273
+ const listQuerySchema = z.object({
274
+ limit: z.coerce.number().meta({ description: "The number of lead to return" }).optional(),
275
+ offset: z.coerce.number().meta({ description: "The offset to start from" }).optional()
276
+ });
277
+ const list = (options) => createAuthEndpoint("/lead/list", {
278
+ method: "GET",
279
+ query: listQuerySchema,
280
+ use: [sessionMiddleware]
281
+ }, async (ctx) => {
282
+ if (!ctx.context.hasPlugin("admin")) throw APIError.from("NOT_FOUND", LEAD_ERROR_CODES.ADMIN_PLUGIN_REQUIRED);
283
+ const allowedRoles = options.admin?.roles ?? ["admin"];
284
+ if (!(ctx.context.session.user.role ?? "").split(",").map((r) => r.trim()).filter(Boolean).some((r) => allowedRoles.includes(r))) throw APIError.from("FORBIDDEN", LEAD_ERROR_CODES.FORBIDDEN);
285
+ const limit = ctx.query.limit ?? 100;
286
+ const offset = ctx.query.offset ?? 0;
287
+ const [leads, total] = await Promise.all([ctx.context.adapter.findMany({
288
+ model: "lead",
289
+ limit,
290
+ offset
291
+ }), ctx.context.adapter.count({ model: "lead" })]);
292
+ return ctx.json({
293
+ leads,
294
+ total,
295
+ limit,
296
+ offset
297
+ });
298
+ });
299
+ async function createConfirmationToken(secret, payload, expiresIn) {
300
+ return new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(Math.floor(Date.now() / 1e3) + expiresIn).sign(new TextEncoder().encode(secret));
301
+ }
204
302
  async function createUnsubscribeToken(secret, leadId, expiresIn) {
205
303
  const jwt = new SignJWT({ id: leadId }).setProtectedHeader({ alg: "HS256" }).setIssuedAt();
206
304
  if (expiresIn !== void 0) jwt.setExpirationTime(Math.floor(Date.now() / 1e3) + expiresIn);
@@ -234,16 +332,25 @@ const lead$1 = { lead: { fields: {
234
332
  },
235
333
  email: {
236
334
  type: "string",
237
- required: true,
335
+ required: false,
238
336
  unique: true
239
337
  },
240
- emailVerified: {
338
+ userId: {
339
+ type: "string",
340
+ required: false,
341
+ unique: true,
342
+ references: {
343
+ model: "user",
344
+ field: "id"
345
+ }
346
+ },
347
+ confirmed: {
241
348
  type: "boolean",
242
349
  defaultValue: false,
243
350
  required: true,
244
351
  input: false
245
352
  },
246
- verificationEmailSentAt: {
353
+ confirmationSentAt: {
247
354
  type: "date",
248
355
  required: false,
249
356
  input: false
@@ -259,16 +366,19 @@ const getSchema = (options) => {
259
366
  //#endregion
260
367
  //#region src/index.ts
261
368
  const lead = (options = {}) => {
369
+ const endpoints = {
370
+ subscribe: subscribe(options),
371
+ verify: verify(options),
372
+ unsubscribe: unsubscribe(options),
373
+ unsubscribeSession: unsubscribeSession(options),
374
+ resend: resend(options),
375
+ update: update(options),
376
+ ...options.admin?.enabled ? { list: list(options) } : {}
377
+ };
262
378
  return {
263
379
  id: "lead",
264
380
  schema: getSchema(options),
265
- endpoints: {
266
- subscribe: subscribe(options),
267
- verify: verify(options),
268
- unsubscribe: unsubscribe(options),
269
- resend: resend(options),
270
- update: update(options)
271
- },
381
+ endpoints,
272
382
  options,
273
383
  rateLimit: [{
274
384
  pathMatcher: (path) => ["/lead/subscribe", "/lead/resend"].includes(path),
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["lead"],"sources":["../src/error-codes.ts","../src/routes.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["import { defineErrorCodes } from 'better-auth';\n\nexport const LEAD_ERROR_CODES = defineErrorCodes({\n INVALID_EMAIL: 'Invalid email',\n INVALID_TOKEN: 'Invalid token',\n TOKEN_EXPIRED: 'Token expired',\n INVALID_METADATA: 'Invalid metadata',\n});\n","import { BASE_ERROR_CODES, type InternalLogger, type StandardSchemaV1 } from 'better-auth';\nimport { APIError, createAuthEndpoint, createEmailVerificationToken } from 'better-auth/api';\nimport { SignJWT, jwtVerify } from 'jose';\nimport type { JWTPayload, JWTVerifyResult } from 'jose';\nimport { JWTExpired } from 'jose/errors';\nimport * as z from 'zod';\n\nimport { LEAD_ERROR_CODES } from './error-codes';\nimport type { Lead, LeadOptions, LeadPayload } from './type';\n\ntype InferMetadata<O extends LeadOptions> = O extends {\n metadata: { validationSchema: StandardSchemaV1<unknown, infer Out> };\n}\n ? Out\n : Record<string, any>;\n\nconst subscribeSchema = z.object({\n email: z.string().meta({\n description: 'Email address of the lead',\n }),\n metadata: z.record(z.string(), z.any()).optional().meta({\n description: 'Additional metadata to store with the lead',\n }),\n});\n\nexport const subscribe = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/subscribe',\n {\n method: 'POST',\n body: subscribeSchema,\n metadata: {\n $Infer: {\n body: {} as {\n email: string;\n metadata?: InferMetadata<O>;\n },\n },\n },\n },\n async (ctx) => {\n const { email } = ctx.body;\n\n const isValidEmail = z.email().safeParse(email);\n if (!isValidEmail.success) {\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.INVALID_EMAIL);\n }\n\n const metadata = validateMetadata(\n options,\n ctx.body.metadata as Record<string, any> | undefined,\n ctx.context.logger,\n );\n\n const normalizedEmail = email.toLowerCase();\n\n let lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [\n {\n field: 'email',\n value: normalizedEmail,\n },\n ],\n });\n\n if (!lead) {\n try {\n lead = await ctx.context.adapter.create<LeadPayload, Lead>({\n model: 'lead',\n data: {\n email: normalizedEmail,\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n },\n });\n } catch (e) {\n ctx.context.logger.info('Error creating lead');\n lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [\n {\n field: 'email',\n value: normalizedEmail,\n },\n ],\n });\n }\n }\n\n if (options.sendVerificationEmail && lead && !lead.emailVerified) {\n const token = await createEmailVerificationToken(\n ctx.context.secret,\n normalizedEmail,\n undefined,\n options.expiresIn ?? 3600,\n );\n const url = `${ctx.context.baseURL}/lead/verify?token=${token}`;\n const unsubscribeToken = await createUnsubscribeToken(\n ctx.context.secret,\n lead.id,\n options.unsubscribeExpiresIn,\n );\n const unsubscribeUrl = `${ctx.context.baseURL}/lead/unsubscribe?token=${unsubscribeToken}`;\n\n const sent = await options.sendVerificationEmail(\n {\n lead,\n url,\n token,\n unsubscribeUrl,\n },\n ctx.request,\n );\n\n if (sent) {\n await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [{ field: 'email', value: normalizedEmail }],\n update: { verificationEmailSentAt: new Date() },\n });\n }\n }\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst verifySchema = z.object({\n token: z.string().meta({\n description: 'The token to verify the email',\n }),\n});\n\nexport const verify = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/verify',\n {\n method: 'GET',\n query: verifySchema,\n },\n async (ctx) => {\n const { token } = ctx.query;\n\n let jwt: JWTVerifyResult<JWTPayload>;\n try {\n jwt = await jwtVerify(token, new TextEncoder().encode(ctx.context.secret), {\n algorithms: ['HS256'],\n });\n } catch (e) {\n if (e instanceof JWTExpired) {\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.TOKEN_EXPIRED);\n }\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.INVALID_TOKEN);\n }\n\n const parsed = subscribeSchema.parse(jwt.payload);\n\n let lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [\n {\n field: 'email',\n value: parsed.email,\n },\n ],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n if (lead.emailVerified) {\n return ctx.json({\n status: true,\n });\n }\n\n lead = await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [\n {\n field: 'email',\n value: parsed.email,\n },\n ],\n update: {\n emailVerified: true,\n },\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n if (options.onEmailVerified) {\n await ctx.context.runInBackgroundOrAwait(options.onEmailVerified({ lead }, ctx.request));\n }\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst unsubscribeQuerySchema = z.object({\n token: z.string().meta({\n description: 'Signed unsubscribe token',\n }),\n});\n\nexport const unsubscribe = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/unsubscribe',\n {\n method: 'POST',\n query: unsubscribeQuerySchema,\n },\n async (ctx) => {\n let payload: JWTPayload;\n try {\n const result = await jwtVerify(\n ctx.query.token,\n new TextEncoder().encode(ctx.context.secret),\n { algorithms: ['HS256'] },\n );\n payload = result.payload;\n } catch (e) {\n if (e instanceof JWTExpired) {\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.TOKEN_EXPIRED);\n }\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.INVALID_TOKEN);\n }\n\n const id = payload['id'] as string;\n\n const lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [\n {\n field: 'id',\n value: id,\n },\n ],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n await ctx.context.adapter.delete({\n model: 'lead',\n where: [\n {\n field: 'id',\n value: id,\n },\n ],\n });\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst resendSchema = z.object({\n email: z.string().meta({\n description: 'Email address to resend the verification email to',\n }),\n});\n\nexport const resend = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/resend',\n {\n method: 'POST',\n body: resendSchema,\n },\n async (ctx) => {\n const { email } = ctx.body;\n\n const isValidEmail = z.email().safeParse(email);\n if (!isValidEmail.success) {\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.INVALID_EMAIL);\n }\n\n const normalizedEmail = email.toLowerCase();\n\n const lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [\n {\n field: 'email',\n value: normalizedEmail,\n },\n ],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n if (options.sendVerificationEmail && lead && !lead.emailVerified) {\n const token = await createEmailVerificationToken(\n ctx.context.secret,\n normalizedEmail,\n undefined,\n options.expiresIn ?? 3600,\n );\n const url = `${ctx.context.baseURL}/lead/verify?token=${token}`;\n const unsubscribeToken = await createUnsubscribeToken(\n ctx.context.secret,\n lead.id,\n options.unsubscribeExpiresIn,\n );\n const unsubscribeUrl = `${ctx.context.baseURL}/lead/unsubscribe?token=${unsubscribeToken}`;\n\n const sent = await options.sendVerificationEmail(\n {\n lead,\n url,\n token,\n unsubscribeUrl,\n },\n ctx.request,\n );\n\n if (sent) {\n await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [{ field: 'email', value: normalizedEmail }],\n update: { verificationEmailSentAt: new Date() },\n });\n }\n }\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst updateSchema = z.object({\n id: z.string().meta({\n description: 'The id of the lead to update',\n }),\n metadata: z.record(z.string(), z.any()).optional().meta({\n description: 'Additional metadata to store with the lead',\n }),\n});\n\nexport const update = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/update',\n {\n method: 'POST',\n body: updateSchema,\n metadata: {\n $Infer: {\n body: {} as {\n id: string;\n metadata?: InferMetadata<O>;\n },\n },\n },\n },\n async (ctx) => {\n const { id } = ctx.body;\n\n const lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [\n {\n field: 'id',\n value: id,\n },\n ],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n const metadata = validateMetadata(\n options,\n ctx.body.metadata as Record<string, any> | undefined,\n ctx.context.logger,\n );\n\n await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [\n {\n field: 'id',\n value: id,\n },\n ],\n update: {\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n },\n });\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nasync function createUnsubscribeToken(secret: string, leadId: string, expiresIn?: number) {\n const jwt = new SignJWT({ id: leadId }).setProtectedHeader({ alg: 'HS256' }).setIssuedAt();\n if (expiresIn !== undefined) {\n jwt.setExpirationTime(Math.floor(Date.now() / 1000) + expiresIn);\n }\n return jwt.sign(new TextEncoder().encode(secret));\n}\n\nfunction validateMetadata(\n options: LeadOptions,\n metadata: Record<string, any> | undefined,\n logger: InternalLogger,\n) {\n if (!metadata || !options.metadata?.validationSchema) {\n return metadata;\n }\n const validationResult = options.metadata.validationSchema['~standard'].validate(metadata);\n\n if (validationResult instanceof Promise) {\n throw APIError.from('INTERNAL_SERVER_ERROR', BASE_ERROR_CODES.ASYNC_VALIDATION_NOT_SUPPORTED);\n }\n\n if (validationResult.issues) {\n logger.error('Invalid metadata', validationResult.issues);\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.INVALID_METADATA);\n }\n\n return validationResult.value as Record<string, any>;\n}\n","import { type BetterAuthPluginDBSchema } from 'better-auth';\nimport { mergeSchema } from 'better-auth/db';\n\nimport type { LeadOptions } from './type';\n\nexport const lead = {\n lead: {\n fields: {\n createdAt: {\n type: 'date',\n defaultValue: () => new Date(),\n required: true,\n input: false,\n },\n updatedAt: {\n type: 'date',\n defaultValue: () => new Date(),\n onUpdate: () => new Date(),\n required: true,\n input: false,\n },\n email: {\n type: 'string',\n required: true,\n unique: true,\n },\n emailVerified: {\n type: 'boolean',\n defaultValue: false,\n required: true,\n input: false,\n },\n verificationEmailSentAt: {\n type: 'date',\n required: false,\n input: false,\n },\n metadata: {\n type: 'string',\n required: false,\n },\n },\n },\n} satisfies BetterAuthPluginDBSchema;\n\nexport const getSchema = <O extends LeadOptions>(options: O) => {\n return mergeSchema(lead, options.schema);\n};\n","import type { BetterAuthPlugin } from 'better-auth';\n\nimport { LEAD_ERROR_CODES } from './error-codes';\nimport { resend, subscribe, unsubscribe, update, verify } from './routes';\nimport { getSchema } from './schema';\nimport type { LeadOptions } from './type';\n\nexport const lead = <O extends LeadOptions>(options: O = {} as O) => {\n return {\n id: 'lead',\n schema: getSchema(options),\n endpoints: {\n subscribe: subscribe(options),\n verify: verify(options),\n unsubscribe: unsubscribe(options),\n resend: resend(options),\n update: update(options),\n },\n options: options as NoInfer<O>,\n rateLimit: [\n {\n pathMatcher: (path) => ['/lead/subscribe', '/lead/resend'].includes(path),\n window: options.rateLimit?.window ?? 10,\n max: options.rateLimit?.max ?? 3,\n },\n ],\n $ERROR_CODES: LEAD_ERROR_CODES,\n } satisfies BetterAuthPlugin;\n};\n\nexport type * from './type';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["lead"],"sources":["../src/error-codes.ts","../src/routes.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["import { defineErrorCodes } from 'better-auth';\n\nexport const LEAD_ERROR_CODES = defineErrorCodes({\n INVALID_EMAIL: 'Invalid email',\n INVALID_TOKEN: 'Invalid token',\n TOKEN_EXPIRED: 'Token expired',\n INVALID_METADATA: 'Invalid metadata',\n EMAIL_OR_SESSION_REQUIRED: 'Email or session is required',\n ADMIN_PLUGIN_REQUIRED: 'Admin plugin is required',\n FORBIDDEN: 'Forbidden',\n});\n","import { BASE_ERROR_CODES, type InternalLogger, type StandardSchemaV1 } from 'better-auth';\nimport {\n APIError,\n createAuthEndpoint,\n getSessionFromCtx,\n sessionMiddleware,\n} from 'better-auth/api';\nimport { SignJWT, jwtVerify } from 'jose';\nimport type { JWTPayload, JWTVerifyResult } from 'jose';\nimport { JWTExpired } from 'jose/errors';\nimport * as z from 'zod';\n\nimport { LEAD_ERROR_CODES } from './error-codes';\nimport type { Lead, LeadOptions, LeadPayload } from './type';\n\ntype InferMetadata<O extends LeadOptions> = O extends {\n metadata: { validationSchema: StandardSchemaV1<unknown, infer Out> };\n}\n ? Out\n : Record<string, any>;\n\ntype IdentifierType = 'email' | 'user';\n\nconst subscribeSchema = z.object({\n email: z.string().optional().meta({\n description: 'Email address of the lead',\n }),\n metadata: z.record(z.string(), z.any()).optional().meta({\n description: 'Additional metadata to store with the lead',\n }),\n});\n\nexport const subscribe = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/subscribe',\n {\n method: 'POST',\n body: subscribeSchema,\n metadata: {\n $Infer: {\n body: {} as {\n email?: string;\n metadata?: InferMetadata<O>;\n },\n },\n },\n },\n async (ctx) => {\n const email = ctx.body.email;\n\n const metadata = validateMetadata(\n options,\n ctx.body.metadata as Record<string, any> | undefined,\n ctx.context.logger,\n );\n\n let identifierType: IdentifierType;\n let leadIdentifier: string;\n let leadEmail: string;\n let createData: LeadPayload;\n\n if (email) {\n const isValidEmail = z.email().safeParse(email);\n if (!isValidEmail.success) {\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.INVALID_EMAIL);\n }\n\n leadIdentifier = email.toLowerCase();\n identifierType = 'email';\n leadEmail = leadIdentifier;\n createData = {\n email: leadIdentifier,\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n };\n } else {\n const session = await getSessionFromCtx(ctx);\n\n if (!session) {\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.EMAIL_OR_SESSION_REQUIRED);\n }\n\n leadIdentifier = session.user.id;\n identifierType = 'user';\n leadEmail = session.user.email;\n createData = {\n userId: leadIdentifier,\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n };\n }\n\n const whereField = identifierType === 'email' ? 'email' : 'userId';\n\n let lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [{ field: whereField, value: leadIdentifier }],\n });\n\n if (!lead) {\n try {\n lead = await ctx.context.adapter.create<LeadPayload, Lead>({\n model: 'lead',\n data: createData,\n });\n } catch (e) {\n ctx.context.logger.info('Error creating lead');\n lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [{ field: whereField, value: leadIdentifier }],\n });\n }\n }\n\n if (options.sendConfirmationEmail && lead && !lead.confirmed) {\n const token = await createConfirmationToken(\n ctx.context.secret,\n { identifier: leadIdentifier, type: identifierType },\n options.expiresIn ?? 3600,\n );\n const url = `${ctx.context.baseURL}/lead/verify?token=${token}`;\n const unsubscribeToken = await createUnsubscribeToken(\n ctx.context.secret,\n lead.id,\n options.unsubscribeExpiresIn,\n );\n const unsubscribeUrl = `${ctx.context.baseURL}/lead/unsubscribe?token=${unsubscribeToken}`;\n\n const sent = await options.sendConfirmationEmail(\n {\n lead,\n email: leadEmail,\n url,\n token,\n unsubscribeUrl,\n },\n ctx.request,\n );\n\n if (sent) {\n await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [{ field: whereField, value: leadIdentifier }],\n update: { confirmationSentAt: new Date() },\n });\n }\n }\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst verifySchema = z.object({\n token: z.string().meta({\n description: 'The token to verify the email',\n }),\n});\n\nexport const verify = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/verify',\n {\n method: 'GET',\n query: verifySchema,\n },\n async (ctx) => {\n const { token } = ctx.query;\n\n let jwt: JWTVerifyResult<JWTPayload>;\n try {\n jwt = await jwtVerify(token, new TextEncoder().encode(ctx.context.secret), {\n algorithms: ['HS256'],\n });\n } catch (e) {\n if (e instanceof JWTExpired) {\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.TOKEN_EXPIRED);\n }\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.INVALID_TOKEN);\n }\n\n const confirmationPayloadSchema = z.object({\n identifier: z.string(),\n type: z.enum(['email', 'user']),\n });\n const parsed = confirmationPayloadSchema.parse(jwt.payload);\n\n const whereField = parsed.type === 'user' ? 'userId' : 'email';\n\n let lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [{ field: whereField, value: parsed.identifier }],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n if (lead.confirmed) {\n return ctx.json({\n status: true,\n });\n }\n\n lead = await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [{ field: whereField, value: parsed.identifier }],\n update: {\n confirmed: true,\n },\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n if (options.onConfirmed) {\n await ctx.context.runInBackgroundOrAwait(options.onConfirmed({ lead }, ctx.request));\n }\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst unsubscribeQuerySchema = z.object({\n token: z.string().meta({\n description: 'Signed unsubscribe token',\n }),\n});\n\nexport const unsubscribe = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/unsubscribe',\n {\n method: 'POST',\n query: unsubscribeQuerySchema,\n metadata: {\n // Empty array overrides the router-level JSON-only restriction,\n // allowing POST with no body (required for RFC 8058 one-click unsubscribe).\n allowedMediaTypes: [],\n },\n },\n async (ctx) => {\n let payload: JWTPayload;\n try {\n const result = await jwtVerify(\n ctx.query.token,\n new TextEncoder().encode(ctx.context.secret),\n { algorithms: ['HS256'] },\n );\n payload = result.payload;\n } catch (e) {\n if (e instanceof JWTExpired) {\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.TOKEN_EXPIRED);\n }\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.INVALID_TOKEN);\n }\n\n const id = payload['id'] as string;\n\n const lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [\n {\n field: 'id',\n value: id,\n },\n ],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n await ctx.context.adapter.delete({\n model: 'lead',\n where: [\n {\n field: 'id',\n value: id,\n },\n ],\n });\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nexport const unsubscribeSession = <O extends LeadOptions>(_options: O) =>\n createAuthEndpoint(\n '/lead/unsubscribe-session',\n {\n method: 'POST',\n use: [sessionMiddleware],\n },\n async (ctx) => {\n const userId = ctx.context.session.user.id;\n\n const lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [{ field: 'userId', value: userId }],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n await ctx.context.adapter.delete({\n model: 'lead',\n where: [{ field: 'userId', value: userId }],\n });\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst resendSchema = z.object({\n email: z.string().optional().meta({\n description: 'Email address to resend the verification email to',\n }),\n});\n\nexport const resend = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/resend',\n {\n method: 'POST',\n body: resendSchema,\n },\n async (ctx) => {\n const email = ctx.body.email;\n\n let identifierType: IdentifierType;\n let leadIdentifier: string;\n let leadEmail: string;\n\n if (email) {\n const isValidEmail = z.email().safeParse(email);\n if (!isValidEmail.success) {\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.INVALID_EMAIL);\n }\n\n leadIdentifier = email.toLowerCase();\n identifierType = 'email';\n leadEmail = leadIdentifier;\n } else {\n const session = await getSessionFromCtx(ctx);\n\n if (!session) {\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.EMAIL_OR_SESSION_REQUIRED);\n }\n leadIdentifier = session.user.id;\n identifierType = 'user';\n leadEmail = session.user.email;\n }\n\n const whereField = identifierType === 'email' ? 'email' : 'userId';\n\n const lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [{ field: whereField, value: leadIdentifier }],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n if (options.sendConfirmationEmail && !lead.confirmed) {\n const token = await createConfirmationToken(\n ctx.context.secret,\n { identifier: leadIdentifier, type: identifierType },\n options.expiresIn ?? 3600,\n );\n const url = `${ctx.context.baseURL}/lead/verify?token=${token}`;\n const unsubscribeToken = await createUnsubscribeToken(\n ctx.context.secret,\n lead.id,\n options.unsubscribeExpiresIn,\n );\n const unsubscribeUrl = `${ctx.context.baseURL}/lead/unsubscribe?token=${unsubscribeToken}`;\n\n const sent = await options.sendConfirmationEmail(\n {\n lead,\n email: leadEmail,\n url,\n token,\n unsubscribeUrl,\n },\n ctx.request,\n );\n\n if (sent) {\n await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [{ field: whereField, value: leadIdentifier }],\n update: { confirmationSentAt: new Date() },\n });\n }\n }\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst updateSchema = z.object({\n metadata: z.record(z.string(), z.any()).optional().meta({\n description: 'Additional metadata to store with the lead',\n }),\n});\n\nexport const update = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/update',\n {\n method: 'POST',\n body: updateSchema,\n use: [sessionMiddleware],\n metadata: {\n $Infer: {\n body: {} as {\n metadata?: InferMetadata<O>;\n },\n },\n },\n },\n async (ctx) => {\n const userId = ctx.context.session.user.id;\n\n const lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [{ field: 'userId', value: userId }],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n const metadata = validateMetadata(\n options,\n ctx.body.metadata as Record<string, any> | undefined,\n ctx.context.logger,\n );\n\n await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [{ field: 'userId', value: userId }],\n update: {\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n },\n });\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst listQuerySchema = z.object({\n limit: z.coerce\n .number()\n .meta({\n description: 'The number of lead to return',\n })\n .optional(),\n offset: z.coerce\n .number()\n .meta({\n description: 'The offset to start from',\n })\n .optional(),\n});\n\nexport const list = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/list',\n {\n method: 'GET',\n query: listQuerySchema,\n use: [sessionMiddleware],\n },\n async (ctx) => {\n if (!ctx.context.hasPlugin('admin')) {\n throw APIError.from('NOT_FOUND', LEAD_ERROR_CODES.ADMIN_PLUGIN_REQUIRED);\n }\n\n const allowedRoles = options.admin?.roles ?? ['admin'];\n const userRole = (ctx.context.session.user as { role?: string }).role ?? '';\n const userRoles = userRole\n .split(',')\n .map((r) => r.trim())\n .filter(Boolean);\n const hasRole = userRoles.some((r) => allowedRoles.includes(r));\n\n if (!hasRole) {\n throw APIError.from('FORBIDDEN', LEAD_ERROR_CODES.FORBIDDEN);\n }\n\n const limit = ctx.query.limit ?? 100;\n const offset = ctx.query.offset ?? 0;\n\n const [leads, total] = await Promise.all([\n ctx.context.adapter.findMany<Lead>({\n model: 'lead',\n limit,\n offset,\n }),\n ctx.context.adapter.count({ model: 'lead' }),\n ]);\n\n return ctx.json({ leads, total, limit, offset });\n },\n );\n\nasync function createConfirmationToken(\n secret: string,\n payload: { identifier: string; type: IdentifierType },\n expiresIn: number,\n) {\n return new SignJWT(payload)\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(Math.floor(Date.now() / 1000) + expiresIn)\n .sign(new TextEncoder().encode(secret));\n}\n\nasync function createUnsubscribeToken(secret: string, leadId: string, expiresIn?: number) {\n const jwt = new SignJWT({ id: leadId }).setProtectedHeader({ alg: 'HS256' }).setIssuedAt();\n if (expiresIn !== undefined) {\n jwt.setExpirationTime(Math.floor(Date.now() / 1000) + expiresIn);\n }\n return jwt.sign(new TextEncoder().encode(secret));\n}\n\nfunction validateMetadata(\n options: LeadOptions,\n metadata: Record<string, any> | undefined,\n logger: InternalLogger,\n) {\n if (!metadata || !options.metadata?.validationSchema) {\n return metadata;\n }\n const validationResult = options.metadata.validationSchema['~standard'].validate(metadata);\n\n if (validationResult instanceof Promise) {\n throw APIError.from('INTERNAL_SERVER_ERROR', BASE_ERROR_CODES.ASYNC_VALIDATION_NOT_SUPPORTED);\n }\n\n if (validationResult.issues) {\n logger.error('Invalid metadata', validationResult.issues);\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.INVALID_METADATA);\n }\n\n return validationResult.value as Record<string, any>;\n}\n","import { type BetterAuthPluginDBSchema } from 'better-auth';\nimport { mergeSchema } from 'better-auth/db';\n\nimport type { LeadOptions } from './type';\n\nexport const lead = {\n lead: {\n fields: {\n createdAt: {\n type: 'date',\n defaultValue: () => new Date(),\n required: true,\n input: false,\n },\n updatedAt: {\n type: 'date',\n defaultValue: () => new Date(),\n onUpdate: () => new Date(),\n required: true,\n input: false,\n },\n email: {\n type: 'string',\n required: false,\n unique: true,\n },\n userId: {\n type: 'string',\n required: false,\n unique: true,\n references: {\n model: 'user',\n field: 'id',\n },\n },\n confirmed: {\n type: 'boolean',\n defaultValue: false,\n required: true,\n input: false,\n },\n confirmationSentAt: {\n type: 'date',\n required: false,\n input: false,\n },\n metadata: {\n type: 'string',\n required: false,\n },\n },\n },\n} satisfies BetterAuthPluginDBSchema;\n\nexport const getSchema = <O extends LeadOptions>(options: O) => {\n return mergeSchema(lead, options.schema);\n};\n","import type { BetterAuthPlugin } from 'better-auth';\n\nimport { LEAD_ERROR_CODES } from './error-codes';\nimport { list, resend, subscribe, unsubscribe, unsubscribeSession, update, verify } from './routes';\nimport { getSchema } from './schema';\nimport type { LeadOptions } from './type';\n\nexport const lead = <O extends LeadOptions>(options: O = {} as O) => {\n const endpoints = {\n subscribe: subscribe(options),\n verify: verify(options),\n unsubscribe: unsubscribe(options),\n unsubscribeSession: unsubscribeSession(options),\n resend: resend(options),\n update: update(options),\n ...(options.admin?.enabled ? { list: list(options) } : {}),\n };\n\n return {\n id: 'lead',\n schema: getSchema(options),\n endpoints,\n options: options as NoInfer<O>,\n rateLimit: [\n {\n pathMatcher: (path) => ['/lead/subscribe', '/lead/resend'].includes(path),\n window: options.rateLimit?.window ?? 10,\n max: options.rateLimit?.max ?? 3,\n },\n ],\n $ERROR_CODES: LEAD_ERROR_CODES,\n } satisfies BetterAuthPlugin;\n};\n\nexport type * from './type';\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-auth-lead",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Better Auth Lead plugin",
5
5
  "homepage": "https://github.com/marcjulian/better-auth-plugins#readme",
6
6
  "bugs": {