better-auth-lead 0.4.3 → 0.6.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 +174 -34
- package/dist/client.d.mts +1 -1
- package/dist/index-BX1SlAVz.d.mts +472 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +246 -59
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/dist/index-P6m89iLp.d.mts +0 -251
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BASE_ERROR_CODES, defineErrorCodes } from "better-auth";
|
|
2
|
-
import {
|
|
2
|
+
import { whereOperators } from "better-auth/adapters";
|
|
3
|
+
import { APIError, createAuthEndpoint, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
|
|
3
4
|
import { SignJWT, jwtVerify } from "jose";
|
|
4
5
|
import { JWTExpired } from "jose/errors";
|
|
5
6
|
import * as z from "zod";
|
|
@@ -9,12 +10,16 @@ const LEAD_ERROR_CODES = defineErrorCodes({
|
|
|
9
10
|
INVALID_EMAIL: "Invalid email",
|
|
10
11
|
INVALID_TOKEN: "Invalid token",
|
|
11
12
|
TOKEN_EXPIRED: "Token expired",
|
|
12
|
-
INVALID_METADATA: "Invalid metadata"
|
|
13
|
+
INVALID_METADATA: "Invalid metadata",
|
|
14
|
+
EMAIL_OR_SESSION_REQUIRED: "Email or session is required",
|
|
15
|
+
ADMIN_PLUGIN_REQUIRED: "Admin plugin is required",
|
|
16
|
+
FORBIDDEN: "Forbidden",
|
|
17
|
+
LEAD_NOT_FOUND: "Lead not found"
|
|
13
18
|
});
|
|
14
19
|
//#endregion
|
|
15
20
|
//#region src/routes.ts
|
|
16
21
|
const subscribeSchema = z.object({
|
|
17
|
-
email: z.string().meta({ description: "Email address of the lead" }),
|
|
22
|
+
email: z.string().optional().meta({ description: "Email address of the lead" }),
|
|
18
23
|
metadata: z.record(z.string(), z.any()).optional().meta({ description: "Additional metadata to store with the lead" })
|
|
19
24
|
});
|
|
20
25
|
const subscribe = (options) => createAuthEndpoint("/lead/subscribe", {
|
|
@@ -22,52 +27,76 @@ const subscribe = (options) => createAuthEndpoint("/lead/subscribe", {
|
|
|
22
27
|
body: subscribeSchema,
|
|
23
28
|
metadata: { $Infer: { body: {} } }
|
|
24
29
|
}, async (ctx) => {
|
|
25
|
-
const
|
|
26
|
-
if (!z.email().safeParse(email).success) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.INVALID_EMAIL);
|
|
30
|
+
const email = ctx.body.email;
|
|
27
31
|
const metadata = validateMetadata(options, ctx.body.metadata, ctx.context.logger);
|
|
28
|
-
|
|
32
|
+
let identifierType;
|
|
33
|
+
let leadIdentifier;
|
|
34
|
+
let leadEmail;
|
|
35
|
+
let createData;
|
|
36
|
+
if (email) {
|
|
37
|
+
if (!z.email().safeParse(email).success) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.INVALID_EMAIL);
|
|
38
|
+
leadIdentifier = email.toLowerCase();
|
|
39
|
+
identifierType = "email";
|
|
40
|
+
leadEmail = leadIdentifier;
|
|
41
|
+
createData = {
|
|
42
|
+
email: leadIdentifier,
|
|
43
|
+
metadata: metadata ? JSON.stringify(metadata) : void 0
|
|
44
|
+
};
|
|
45
|
+
} else {
|
|
46
|
+
const session = await getSessionFromCtx(ctx);
|
|
47
|
+
if (!session) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.EMAIL_OR_SESSION_REQUIRED);
|
|
48
|
+
leadIdentifier = session.user.id;
|
|
49
|
+
identifierType = "user";
|
|
50
|
+
leadEmail = session.user.email;
|
|
51
|
+
createData = {
|
|
52
|
+
userId: leadIdentifier,
|
|
53
|
+
metadata: metadata ? JSON.stringify(metadata) : void 0
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const whereField = identifierType === "email" ? "email" : "userId";
|
|
29
57
|
let lead = await ctx.context.adapter.findOne({
|
|
30
58
|
model: "lead",
|
|
31
59
|
where: [{
|
|
32
|
-
field:
|
|
33
|
-
value:
|
|
60
|
+
field: whereField,
|
|
61
|
+
value: leadIdentifier
|
|
34
62
|
}]
|
|
35
63
|
});
|
|
36
64
|
if (!lead) try {
|
|
37
65
|
lead = await ctx.context.adapter.create({
|
|
38
66
|
model: "lead",
|
|
39
|
-
data:
|
|
40
|
-
email: normalizedEmail,
|
|
41
|
-
metadata: metadata ? JSON.stringify(metadata) : void 0
|
|
42
|
-
}
|
|
67
|
+
data: createData
|
|
43
68
|
});
|
|
44
69
|
} catch (e) {
|
|
45
70
|
ctx.context.logger.info("Error creating lead");
|
|
46
71
|
lead = await ctx.context.adapter.findOne({
|
|
47
72
|
model: "lead",
|
|
48
73
|
where: [{
|
|
49
|
-
field:
|
|
50
|
-
value:
|
|
74
|
+
field: whereField,
|
|
75
|
+
value: leadIdentifier
|
|
51
76
|
}]
|
|
52
77
|
});
|
|
53
78
|
}
|
|
54
|
-
if (options.
|
|
55
|
-
const token = await
|
|
79
|
+
if (options.sendConfirmationEmail && lead && !lead.confirmed) {
|
|
80
|
+
const token = await createConfirmationToken(ctx.context.secret, {
|
|
81
|
+
identifier: leadIdentifier,
|
|
82
|
+
type: identifierType
|
|
83
|
+
}, options.expiresIn ?? 3600);
|
|
56
84
|
const url = `${ctx.context.baseURL}/lead/verify?token=${token}`;
|
|
57
85
|
const unsubscribeToken = await createUnsubscribeToken(ctx.context.secret, lead.id, options.unsubscribeExpiresIn);
|
|
58
86
|
const unsubscribeUrl = `${ctx.context.baseURL}/lead/unsubscribe?token=${unsubscribeToken}`;
|
|
59
|
-
if (await options.
|
|
87
|
+
if (await options.sendConfirmationEmail({
|
|
60
88
|
lead,
|
|
89
|
+
email: leadEmail,
|
|
61
90
|
url,
|
|
62
91
|
token,
|
|
63
92
|
unsubscribeUrl
|
|
64
93
|
}, ctx.request)) await ctx.context.adapter.update({
|
|
65
94
|
model: "lead",
|
|
66
95
|
where: [{
|
|
67
|
-
field:
|
|
68
|
-
value:
|
|
96
|
+
field: whereField,
|
|
97
|
+
value: leadIdentifier
|
|
69
98
|
}],
|
|
70
|
-
update: {
|
|
99
|
+
update: { confirmationSentAt: /* @__PURE__ */ new Date() }
|
|
71
100
|
});
|
|
72
101
|
}
|
|
73
102
|
return ctx.json({ status: true });
|
|
@@ -85,26 +114,30 @@ const verify = (options) => createAuthEndpoint("/lead/verify", {
|
|
|
85
114
|
if (e instanceof JWTExpired) throw APIError.from("UNAUTHORIZED", LEAD_ERROR_CODES.TOKEN_EXPIRED);
|
|
86
115
|
throw APIError.from("UNAUTHORIZED", LEAD_ERROR_CODES.INVALID_TOKEN);
|
|
87
116
|
}
|
|
88
|
-
const parsed =
|
|
117
|
+
const parsed = z.object({
|
|
118
|
+
identifier: z.string(),
|
|
119
|
+
type: z.enum(["email", "user"])
|
|
120
|
+
}).parse(jwt.payload);
|
|
121
|
+
const whereField = parsed.type === "user" ? "userId" : "email";
|
|
89
122
|
let lead = await ctx.context.adapter.findOne({
|
|
90
123
|
model: "lead",
|
|
91
124
|
where: [{
|
|
92
|
-
field:
|
|
93
|
-
value: parsed.
|
|
125
|
+
field: whereField,
|
|
126
|
+
value: parsed.identifier
|
|
94
127
|
}]
|
|
95
128
|
});
|
|
96
129
|
if (!lead) return ctx.json({ status: true });
|
|
97
|
-
if (lead.
|
|
130
|
+
if (lead.confirmed) return ctx.json({ status: true });
|
|
98
131
|
lead = await ctx.context.adapter.update({
|
|
99
132
|
model: "lead",
|
|
100
133
|
where: [{
|
|
101
|
-
field:
|
|
102
|
-
value: parsed.
|
|
134
|
+
field: whereField,
|
|
135
|
+
value: parsed.identifier
|
|
103
136
|
}],
|
|
104
|
-
update: {
|
|
137
|
+
update: { confirmed: true }
|
|
105
138
|
});
|
|
106
139
|
if (!lead) return ctx.json({ status: true });
|
|
107
|
-
if (options.
|
|
140
|
+
if (options.onConfirmed) await ctx.context.runInBackgroundOrAwait(options.onConfirmed({ lead }, ctx.request));
|
|
108
141
|
return ctx.json({ status: true });
|
|
109
142
|
});
|
|
110
143
|
const unsubscribeQuerySchema = z.object({ token: z.string().meta({ description: "Signed unsubscribe token" }) });
|
|
@@ -137,71 +170,209 @@ const unsubscribe = (options) => createAuthEndpoint("/lead/unsubscribe", {
|
|
|
137
170
|
});
|
|
138
171
|
return ctx.json({ status: true });
|
|
139
172
|
});
|
|
140
|
-
const
|
|
173
|
+
const unsubscribeSession = (_options) => createAuthEndpoint("/lead/unsubscribe-session", {
|
|
174
|
+
method: "POST",
|
|
175
|
+
use: [sessionMiddleware]
|
|
176
|
+
}, async (ctx) => {
|
|
177
|
+
const userId = ctx.context.session.user.id;
|
|
178
|
+
if (!await ctx.context.adapter.findOne({
|
|
179
|
+
model: "lead",
|
|
180
|
+
where: [{
|
|
181
|
+
field: "userId",
|
|
182
|
+
value: userId
|
|
183
|
+
}]
|
|
184
|
+
})) return ctx.json({ status: true });
|
|
185
|
+
await ctx.context.adapter.delete({
|
|
186
|
+
model: "lead",
|
|
187
|
+
where: [{
|
|
188
|
+
field: "userId",
|
|
189
|
+
value: userId
|
|
190
|
+
}]
|
|
191
|
+
});
|
|
192
|
+
return ctx.json({ status: true });
|
|
193
|
+
});
|
|
194
|
+
const resendSchema = z.object({ email: z.string().optional().meta({ description: "Email address to resend the verification email to" }) });
|
|
141
195
|
const resend = (options) => createAuthEndpoint("/lead/resend", {
|
|
142
196
|
method: "POST",
|
|
143
197
|
body: resendSchema
|
|
144
198
|
}, async (ctx) => {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
199
|
+
const email = ctx.body.email;
|
|
200
|
+
let identifierType;
|
|
201
|
+
let leadIdentifier;
|
|
202
|
+
let leadEmail;
|
|
203
|
+
if (email) {
|
|
204
|
+
if (!z.email().safeParse(email).success) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.INVALID_EMAIL);
|
|
205
|
+
leadIdentifier = email.toLowerCase();
|
|
206
|
+
identifierType = "email";
|
|
207
|
+
leadEmail = leadIdentifier;
|
|
208
|
+
} else {
|
|
209
|
+
const session = await getSessionFromCtx(ctx);
|
|
210
|
+
if (!session) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.EMAIL_OR_SESSION_REQUIRED);
|
|
211
|
+
leadIdentifier = session.user.id;
|
|
212
|
+
identifierType = "user";
|
|
213
|
+
leadEmail = session.user.email;
|
|
214
|
+
}
|
|
215
|
+
const whereField = identifierType === "email" ? "email" : "userId";
|
|
148
216
|
const lead = await ctx.context.adapter.findOne({
|
|
149
217
|
model: "lead",
|
|
150
218
|
where: [{
|
|
151
|
-
field:
|
|
152
|
-
value:
|
|
219
|
+
field: whereField,
|
|
220
|
+
value: leadIdentifier
|
|
153
221
|
}]
|
|
154
222
|
});
|
|
155
223
|
if (!lead) return ctx.json({ status: true });
|
|
156
|
-
if (options.
|
|
157
|
-
const token = await
|
|
224
|
+
if (options.sendConfirmationEmail && !lead.confirmed) {
|
|
225
|
+
const token = await createConfirmationToken(ctx.context.secret, {
|
|
226
|
+
identifier: leadIdentifier,
|
|
227
|
+
type: identifierType
|
|
228
|
+
}, options.expiresIn ?? 3600);
|
|
158
229
|
const url = `${ctx.context.baseURL}/lead/verify?token=${token}`;
|
|
159
230
|
const unsubscribeToken = await createUnsubscribeToken(ctx.context.secret, lead.id, options.unsubscribeExpiresIn);
|
|
160
231
|
const unsubscribeUrl = `${ctx.context.baseURL}/lead/unsubscribe?token=${unsubscribeToken}`;
|
|
161
|
-
if (await options.
|
|
232
|
+
if (await options.sendConfirmationEmail({
|
|
162
233
|
lead,
|
|
234
|
+
email: leadEmail,
|
|
163
235
|
url,
|
|
164
236
|
token,
|
|
165
237
|
unsubscribeUrl
|
|
166
238
|
}, ctx.request)) await ctx.context.adapter.update({
|
|
167
239
|
model: "lead",
|
|
168
240
|
where: [{
|
|
169
|
-
field:
|
|
170
|
-
value:
|
|
241
|
+
field: whereField,
|
|
242
|
+
value: leadIdentifier
|
|
171
243
|
}],
|
|
172
|
-
update: {
|
|
244
|
+
update: { confirmationSentAt: /* @__PURE__ */ new Date() }
|
|
173
245
|
});
|
|
174
246
|
}
|
|
175
247
|
return ctx.json({ status: true });
|
|
176
248
|
});
|
|
177
|
-
const updateSchema = z.object({
|
|
178
|
-
id: z.string().meta({ description: "The id of the lead to update" }),
|
|
179
|
-
metadata: z.record(z.string(), z.any()).optional().meta({ description: "Additional metadata to store with the lead" })
|
|
180
|
-
});
|
|
249
|
+
const updateSchema = z.object({ metadata: z.record(z.string(), z.any()).optional().meta({ description: "Additional metadata to store with the lead" }) });
|
|
181
250
|
const update = (options) => createAuthEndpoint("/lead/update", {
|
|
182
251
|
method: "POST",
|
|
183
252
|
body: updateSchema,
|
|
253
|
+
use: [sessionMiddleware],
|
|
184
254
|
metadata: { $Infer: { body: {} } }
|
|
185
255
|
}, async (ctx) => {
|
|
186
|
-
const
|
|
256
|
+
const userId = ctx.context.session.user.id;
|
|
187
257
|
if (!await ctx.context.adapter.findOne({
|
|
188
258
|
model: "lead",
|
|
189
259
|
where: [{
|
|
190
|
-
field: "
|
|
191
|
-
value:
|
|
260
|
+
field: "userId",
|
|
261
|
+
value: userId
|
|
192
262
|
}]
|
|
193
263
|
})) return ctx.json({ status: true });
|
|
194
264
|
const metadata = validateMetadata(options, ctx.body.metadata, ctx.context.logger);
|
|
195
265
|
await ctx.context.adapter.update({
|
|
196
266
|
model: "lead",
|
|
197
267
|
where: [{
|
|
198
|
-
field: "
|
|
199
|
-
value:
|
|
268
|
+
field: "userId",
|
|
269
|
+
value: userId
|
|
200
270
|
}],
|
|
201
271
|
update: { metadata: metadata ? JSON.stringify(metadata) : void 0 }
|
|
202
272
|
});
|
|
203
273
|
return ctx.json({ status: true });
|
|
204
274
|
});
|
|
275
|
+
const listQuerySchema = z.object({
|
|
276
|
+
searchValue: z.string().optional().meta({ description: "The value to search for. Eg: \"some name\"" }),
|
|
277
|
+
searchField: z.enum(["email"]).meta({ description: "The field to search in, defaults to email." }).optional(),
|
|
278
|
+
searchOperator: z.enum([
|
|
279
|
+
"contains",
|
|
280
|
+
"starts_with",
|
|
281
|
+
"ends_with"
|
|
282
|
+
]).meta({ description: "The operator to use for the search. Can be `contains`, `starts_with` or `ends_with`. Eg: \"contains\"" }).optional(),
|
|
283
|
+
limit: z.coerce.number().meta({ description: "The number of lead to return" }).optional(),
|
|
284
|
+
offset: z.coerce.number().meta({ description: "The offset to start from" }).optional(),
|
|
285
|
+
sortBy: z.string().meta({ description: "The field to sort by" }).optional(),
|
|
286
|
+
sortDirection: z.enum(["asc", "desc"]).meta({ description: "The direction to sort by" }).optional(),
|
|
287
|
+
filterField: z.string().meta({ description: "The field to filter by" }).optional(),
|
|
288
|
+
filterValue: z.string().meta({ description: "The value to filter by" }).or(z.number()).or(z.boolean()).or(z.array(z.string())).or(z.array(z.number())).optional(),
|
|
289
|
+
filterOperator: z.enum(whereOperators).meta({ description: "The operator to use for the filter" }).optional()
|
|
290
|
+
});
|
|
291
|
+
const listLeads = (options) => createAuthEndpoint("/lead/list-leads", {
|
|
292
|
+
method: "GET",
|
|
293
|
+
query: listQuerySchema,
|
|
294
|
+
use: [sessionMiddleware]
|
|
295
|
+
}, async (ctx) => {
|
|
296
|
+
if (!ctx.context.hasPlugin("admin")) throw APIError.from("NOT_FOUND", LEAD_ERROR_CODES.ADMIN_PLUGIN_REQUIRED);
|
|
297
|
+
const allowedRoles = options.admin?.roles ?? ["admin"];
|
|
298
|
+
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);
|
|
299
|
+
const where = [];
|
|
300
|
+
if (ctx.query?.searchValue) where.push({
|
|
301
|
+
field: ctx.query.searchField || "email",
|
|
302
|
+
operator: ctx.query.searchOperator || "contains",
|
|
303
|
+
value: ctx.query.searchValue
|
|
304
|
+
});
|
|
305
|
+
if (ctx.query?.filterValue !== void 0) where.push({
|
|
306
|
+
field: ctx.query.filterField || "email",
|
|
307
|
+
operator: ctx.query.filterOperator || "eq",
|
|
308
|
+
value: ctx.query.filterValue
|
|
309
|
+
});
|
|
310
|
+
const limit = ctx.query.limit ?? 100;
|
|
311
|
+
const offset = ctx.query.offset ?? 0;
|
|
312
|
+
const [leads, total] = await Promise.all([ctx.context.adapter.findMany({
|
|
313
|
+
model: "lead",
|
|
314
|
+
limit,
|
|
315
|
+
offset,
|
|
316
|
+
...ctx.query.sortBy && { sortBy: {
|
|
317
|
+
field: ctx.query.sortBy,
|
|
318
|
+
direction: ctx.query.sortDirection || "asc"
|
|
319
|
+
} },
|
|
320
|
+
where: where.length ? where : void 0
|
|
321
|
+
}), ctx.context.adapter.count({ model: "lead" })]);
|
|
322
|
+
return ctx.json({
|
|
323
|
+
leads,
|
|
324
|
+
total,
|
|
325
|
+
limit,
|
|
326
|
+
offset
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
const getLeadQuerySchema = z.object({ id: z.string().meta({ description: "The id of the Lead" }) });
|
|
330
|
+
const getLead = (options) => createAuthEndpoint("lead/get-lead", {
|
|
331
|
+
method: "POST",
|
|
332
|
+
query: getLeadQuerySchema,
|
|
333
|
+
use: [sessionMiddleware]
|
|
334
|
+
}, async (ctx) => {
|
|
335
|
+
if (!ctx.context.hasPlugin("admin")) throw APIError.from("NOT_FOUND", LEAD_ERROR_CODES.ADMIN_PLUGIN_REQUIRED);
|
|
336
|
+
const allowedRoles = options.admin?.roles ?? ["admin"];
|
|
337
|
+
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);
|
|
338
|
+
const lead = await ctx.context.adapter.findOne({
|
|
339
|
+
model: "lead",
|
|
340
|
+
where: [{
|
|
341
|
+
field: "id",
|
|
342
|
+
value: ctx.body.leadId
|
|
343
|
+
}]
|
|
344
|
+
});
|
|
345
|
+
if (!lead) throw APIError.from("NOT_FOUND", LEAD_ERROR_CODES.LEAD_NOT_FOUND);
|
|
346
|
+
return lead;
|
|
347
|
+
});
|
|
348
|
+
const removeLeadBodySchema = z.object({ leadId: z.coerce.string().meta({ description: "The lead id" }) });
|
|
349
|
+
const removeLead = (options) => createAuthEndpoint("lead/remove-lead", {
|
|
350
|
+
method: "POST",
|
|
351
|
+
body: removeLeadBodySchema,
|
|
352
|
+
use: [sessionMiddleware]
|
|
353
|
+
}, async (ctx) => {
|
|
354
|
+
if (!ctx.context.hasPlugin("admin")) throw APIError.from("NOT_FOUND", LEAD_ERROR_CODES.ADMIN_PLUGIN_REQUIRED);
|
|
355
|
+
const allowedRoles = options.admin?.roles ?? ["admin"];
|
|
356
|
+
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);
|
|
357
|
+
if (!await ctx.context.adapter.findOne({
|
|
358
|
+
model: "lead",
|
|
359
|
+
where: [{
|
|
360
|
+
field: "id",
|
|
361
|
+
value: ctx.body.leadId
|
|
362
|
+
}]
|
|
363
|
+
})) throw APIError.from("NOT_FOUND", LEAD_ERROR_CODES.LEAD_NOT_FOUND);
|
|
364
|
+
await ctx.context.adapter.delete({
|
|
365
|
+
model: "lead",
|
|
366
|
+
where: [{
|
|
367
|
+
field: "id",
|
|
368
|
+
value: ctx.body.leadId
|
|
369
|
+
}]
|
|
370
|
+
});
|
|
371
|
+
return ctx.json({ success: true });
|
|
372
|
+
});
|
|
373
|
+
async function createConfirmationToken(secret, payload, expiresIn) {
|
|
374
|
+
return new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(Math.floor(Date.now() / 1e3) + expiresIn).sign(new TextEncoder().encode(secret));
|
|
375
|
+
}
|
|
205
376
|
async function createUnsubscribeToken(secret, leadId, expiresIn) {
|
|
206
377
|
const jwt = new SignJWT({ id: leadId }).setProtectedHeader({ alg: "HS256" }).setIssuedAt();
|
|
207
378
|
if (expiresIn !== void 0) jwt.setExpirationTime(Math.floor(Date.now() / 1e3) + expiresIn);
|
|
@@ -235,16 +406,25 @@ const lead$1 = { lead: { fields: {
|
|
|
235
406
|
},
|
|
236
407
|
email: {
|
|
237
408
|
type: "string",
|
|
238
|
-
required:
|
|
409
|
+
required: false,
|
|
239
410
|
unique: true
|
|
240
411
|
},
|
|
241
|
-
|
|
412
|
+
userId: {
|
|
413
|
+
type: "string",
|
|
414
|
+
required: false,
|
|
415
|
+
unique: true,
|
|
416
|
+
references: {
|
|
417
|
+
model: "user",
|
|
418
|
+
field: "id"
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
confirmed: {
|
|
242
422
|
type: "boolean",
|
|
243
423
|
defaultValue: false,
|
|
244
424
|
required: true,
|
|
245
425
|
input: false
|
|
246
426
|
},
|
|
247
|
-
|
|
427
|
+
confirmationSentAt: {
|
|
248
428
|
type: "date",
|
|
249
429
|
required: false,
|
|
250
430
|
input: false
|
|
@@ -260,16 +440,23 @@ const getSchema = (options) => {
|
|
|
260
440
|
//#endregion
|
|
261
441
|
//#region src/index.ts
|
|
262
442
|
const lead = (options = {}) => {
|
|
443
|
+
const endpoints = {
|
|
444
|
+
subscribe: subscribe(options),
|
|
445
|
+
verify: verify(options),
|
|
446
|
+
unsubscribe: unsubscribe(options),
|
|
447
|
+
unsubscribeSession: unsubscribeSession(options),
|
|
448
|
+
resend: resend(options),
|
|
449
|
+
update: update(options),
|
|
450
|
+
...options.admin?.enabled ? {
|
|
451
|
+
listLeads: listLeads(options),
|
|
452
|
+
getLead: getLead(options),
|
|
453
|
+
removeLead: removeLead(options)
|
|
454
|
+
} : {}
|
|
455
|
+
};
|
|
263
456
|
return {
|
|
264
457
|
id: "lead",
|
|
265
458
|
schema: getSchema(options),
|
|
266
|
-
endpoints
|
|
267
|
-
subscribe: subscribe(options),
|
|
268
|
-
verify: verify(options),
|
|
269
|
-
unsubscribe: unsubscribe(options),
|
|
270
|
-
resend: resend(options),
|
|
271
|
-
update: update(options)
|
|
272
|
-
},
|
|
459
|
+
endpoints,
|
|
273
460
|
options,
|
|
274
461
|
rateLimit: [{
|
|
275
462
|
pathMatcher: (path) => ["/lead/subscribe", "/lead/resend"].includes(path),
|
package/dist/index.mjs.map
CHANGED
|
@@ -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 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\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 LEAD_NOT_FOUND: 'Lead not found',\n});\n","import {\n BASE_ERROR_CODES,\n type InternalLogger,\n type StandardSchemaV1,\n type Where,\n} from 'better-auth';\nimport { whereOperators } from 'better-auth/adapters';\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 searchValue: z.string().optional().meta({\n description: 'The value to search for. Eg: \"some name\"',\n }),\n searchField: z\n .enum(['email'])\n .meta({\n description: 'The field to search in, defaults to email.',\n })\n .optional(),\n searchOperator: z\n .enum(['contains', 'starts_with', 'ends_with'])\n .meta({\n description:\n 'The operator to use for the search. Can be `contains`, `starts_with` or `ends_with`. Eg: \"contains\"',\n })\n .optional(),\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 sortBy: z\n .string()\n .meta({\n description: 'The field to sort by',\n })\n .optional(),\n sortDirection: z\n .enum(['asc', 'desc'])\n .meta({\n description: 'The direction to sort by',\n })\n .optional(),\n filterField: z\n .string()\n .meta({\n description: 'The field to filter by',\n })\n .optional(),\n filterValue: z\n .string()\n .meta({\n description: 'The value to filter by',\n })\n .or(z.number())\n .or(z.boolean())\n .or(z.array(z.string()))\n .or(z.array(z.number()))\n .optional(),\n filterOperator: z\n .enum(whereOperators)\n .meta({\n description: 'The operator to use for the filter',\n })\n .optional(),\n});\n\nexport const listLeads = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/list-leads',\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 where: Where[] = [];\n\n if (ctx.query?.searchValue) {\n where.push({\n field: ctx.query.searchField || 'email',\n operator: ctx.query.searchOperator || 'contains',\n value: ctx.query.searchValue,\n });\n }\n\n if (ctx.query?.filterValue !== undefined) {\n where.push({\n field: ctx.query.filterField || 'email',\n operator: ctx.query.filterOperator || 'eq',\n value: ctx.query.filterValue,\n });\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 ...(ctx.query.sortBy && {\n sortBy: {\n field: ctx.query.sortBy,\n direction: ctx.query.sortDirection || 'asc',\n },\n }),\n where: where.length ? where : undefined,\n }),\n ctx.context.adapter.count({ model: 'lead' }),\n ]);\n\n return ctx.json({ leads, total, limit, offset });\n },\n );\n\nconst getLeadQuerySchema = z.object({\n id: z.string().meta({\n description: 'The id of the Lead',\n }),\n});\n\nexport const getLead = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n 'lead/get-lead',\n {\n method: 'POST',\n query: getLeadQuerySchema,\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 lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [{ field: 'id', value: ctx.body.leadId }],\n });\n\n if (!lead) {\n throw APIError.from('NOT_FOUND', LEAD_ERROR_CODES.LEAD_NOT_FOUND);\n }\n\n return lead;\n },\n );\n\nconst removeLeadBodySchema = z.object({\n leadId: z.coerce.string().meta({\n description: 'The lead id',\n }),\n});\n\nexport const removeLead = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n 'lead/remove-lead',\n {\n method: 'POST',\n body: removeLeadBodySchema,\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 lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [{ field: 'id', value: ctx.body.leadId }],\n });\n\n if (!lead) {\n throw APIError.from('NOT_FOUND', LEAD_ERROR_CODES.LEAD_NOT_FOUND);\n }\n\n await ctx.context.adapter.delete({\n model: 'lead',\n where: [{ field: 'id', value: ctx.body.leadId }],\n });\n\n return ctx.json({ success: true });\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 {\n getLead,\n listLeads,\n removeLead,\n resend,\n subscribe,\n unsubscribe,\n unsubscribeSession,\n update,\n verify,\n} 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\n ? {\n listLeads: listLeads(options),\n getLead: getLead(options),\n removeLead: removeLead(options),\n }\n : {}),\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.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Better Auth Lead plugin",
|
|
5
5
|
"homepage": "https://github.com/marcjulian/better-auth-plugins#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"zod": "^4.4.3"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@types/node": "^25.
|
|
33
|
-
"bumpp": "^
|
|
32
|
+
"@types/node": "^25.6.2",
|
|
33
|
+
"bumpp": "^11.1.0",
|
|
34
34
|
"tsdown": "^0.22.0",
|
|
35
|
-
"typescript": "^
|
|
35
|
+
"typescript": "^6.0.3",
|
|
36
36
|
"vitest": "^4.1.7"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|