payload-plugin-newsletter 0.6.2 → 0.8.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/CHANGELOG.md +51 -0
- package/README.md +143 -0
- package/dist/client.cjs +57 -65
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +7 -6
- package/dist/client.d.ts +7 -6
- package/dist/client.js +57 -65
- package/dist/client.js.map +1 -1
- package/dist/components.cjs +57 -65
- package/dist/components.cjs.map +1 -1
- package/dist/components.js +57 -65
- package/dist/components.js.map +1 -1
- package/dist/index.cjs +706 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -2
- package/dist/index.d.ts +67 -2
- package/dist/index.js +718 -67
- package/dist/index.js.map +1 -1
- package/package.json +11 -9
package/dist/index.cjs
CHANGED
|
@@ -31,7 +31,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
default: () => newsletterPlugin,
|
|
34
|
-
|
|
34
|
+
getServerSideAuth: () => getServerSideAuth,
|
|
35
|
+
getTokenFromRequest: () => getTokenFromRequest,
|
|
36
|
+
isAuthenticated: () => isAuthenticated,
|
|
37
|
+
newsletterPlugin: () => newsletterPlugin,
|
|
38
|
+
requireAuth: () => requireAuth,
|
|
39
|
+
verifyToken: () => verifyToken
|
|
35
40
|
});
|
|
36
41
|
module.exports = __toCommonJS(src_exports);
|
|
37
42
|
|
|
@@ -96,6 +101,238 @@ var adminOrSelf = (config) => ({ req, id }) => {
|
|
|
96
101
|
return false;
|
|
97
102
|
};
|
|
98
103
|
|
|
104
|
+
// src/emails/render.tsx
|
|
105
|
+
var import_render = require("@react-email/render");
|
|
106
|
+
|
|
107
|
+
// src/emails/MagicLink.tsx
|
|
108
|
+
var import_components = require("@react-email/components");
|
|
109
|
+
|
|
110
|
+
// src/emails/styles.ts
|
|
111
|
+
var styles = {
|
|
112
|
+
main: {
|
|
113
|
+
backgroundColor: "#f6f9fc",
|
|
114
|
+
fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif'
|
|
115
|
+
},
|
|
116
|
+
container: {
|
|
117
|
+
backgroundColor: "#ffffff",
|
|
118
|
+
border: "1px solid #f0f0f0",
|
|
119
|
+
borderRadius: "5px",
|
|
120
|
+
margin: "0 auto",
|
|
121
|
+
padding: "45px",
|
|
122
|
+
marginBottom: "64px",
|
|
123
|
+
maxWidth: "500px"
|
|
124
|
+
},
|
|
125
|
+
heading: {
|
|
126
|
+
fontSize: "24px",
|
|
127
|
+
letterSpacing: "-0.5px",
|
|
128
|
+
lineHeight: "1.3",
|
|
129
|
+
fontWeight: "600",
|
|
130
|
+
color: "#484848",
|
|
131
|
+
margin: "0 0 20px",
|
|
132
|
+
padding: "0"
|
|
133
|
+
},
|
|
134
|
+
text: {
|
|
135
|
+
fontSize: "16px",
|
|
136
|
+
lineHeight: "26px",
|
|
137
|
+
fontWeight: "400",
|
|
138
|
+
color: "#484848",
|
|
139
|
+
margin: "16px 0"
|
|
140
|
+
},
|
|
141
|
+
button: {
|
|
142
|
+
backgroundColor: "#000000",
|
|
143
|
+
borderRadius: "5px",
|
|
144
|
+
color: "#fff",
|
|
145
|
+
fontSize: "16px",
|
|
146
|
+
fontWeight: "bold",
|
|
147
|
+
textDecoration: "none",
|
|
148
|
+
textAlign: "center",
|
|
149
|
+
display: "block",
|
|
150
|
+
width: "100%",
|
|
151
|
+
padding: "14px 20px",
|
|
152
|
+
margin: "30px 0"
|
|
153
|
+
},
|
|
154
|
+
link: {
|
|
155
|
+
color: "#2754C5",
|
|
156
|
+
fontSize: "14px",
|
|
157
|
+
textDecoration: "underline",
|
|
158
|
+
wordBreak: "break-all"
|
|
159
|
+
},
|
|
160
|
+
hr: {
|
|
161
|
+
borderColor: "#e6ebf1",
|
|
162
|
+
margin: "30px 0"
|
|
163
|
+
},
|
|
164
|
+
footer: {
|
|
165
|
+
fontSize: "14px",
|
|
166
|
+
lineHeight: "24px",
|
|
167
|
+
color: "#9ca2ac",
|
|
168
|
+
textAlign: "center",
|
|
169
|
+
margin: "0"
|
|
170
|
+
},
|
|
171
|
+
code: {
|
|
172
|
+
display: "inline-block",
|
|
173
|
+
padding: "16px",
|
|
174
|
+
width: "100%",
|
|
175
|
+
backgroundColor: "#f4f4f4",
|
|
176
|
+
borderRadius: "5px",
|
|
177
|
+
border: "1px solid #eee",
|
|
178
|
+
fontSize: "14px",
|
|
179
|
+
fontFamily: "monospace",
|
|
180
|
+
textAlign: "center",
|
|
181
|
+
margin: "24px 0"
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// src/emails/MagicLink.tsx
|
|
186
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
187
|
+
var MagicLinkEmail = ({
|
|
188
|
+
magicLink,
|
|
189
|
+
email,
|
|
190
|
+
siteName = "Newsletter",
|
|
191
|
+
expiresIn = "24 hours"
|
|
192
|
+
}) => {
|
|
193
|
+
const previewText = `Sign in to ${siteName}`;
|
|
194
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Html, { children: [
|
|
195
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.Head, {}),
|
|
196
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.Preview, { children: previewText }),
|
|
197
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.Body, { style: styles.main, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Container, { style: styles.container, children: [
|
|
198
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Text, { style: styles.heading, children: [
|
|
199
|
+
"Sign in to ",
|
|
200
|
+
siteName
|
|
201
|
+
] }),
|
|
202
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Text, { style: styles.text, children: [
|
|
203
|
+
"Hi ",
|
|
204
|
+
email.split("@")[0],
|
|
205
|
+
","
|
|
206
|
+
] }),
|
|
207
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Text, { style: styles.text, children: [
|
|
208
|
+
"We received a request to sign in to your ",
|
|
209
|
+
siteName,
|
|
210
|
+
" account. Click the button below to complete your sign in:"
|
|
211
|
+
] }),
|
|
212
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Button, { href: magicLink, style: styles.button, children: [
|
|
213
|
+
"Sign in to ",
|
|
214
|
+
siteName
|
|
215
|
+
] }),
|
|
216
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.Text, { style: styles.text, children: "Or copy and paste this URL into your browser:" }),
|
|
217
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { style: styles.code, children: magicLink }),
|
|
218
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.Hr, { style: styles.hr }),
|
|
219
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Text, { style: styles.footer, children: [
|
|
220
|
+
"This link will expire in ",
|
|
221
|
+
expiresIn,
|
|
222
|
+
". If you didn't request this email, you can safely ignore it."
|
|
223
|
+
] })
|
|
224
|
+
] }) })
|
|
225
|
+
] });
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// src/emails/Welcome.tsx
|
|
229
|
+
var import_components2 = require("@react-email/components");
|
|
230
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
231
|
+
var WelcomeEmail = ({
|
|
232
|
+
email,
|
|
233
|
+
siteName = "Newsletter",
|
|
234
|
+
preferencesUrl
|
|
235
|
+
}) => {
|
|
236
|
+
const previewText = `Welcome to ${siteName}!`;
|
|
237
|
+
const firstName = email.split("@")[0];
|
|
238
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_components2.Html, { children: [
|
|
239
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Head, {}),
|
|
240
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Preview, { children: previewText }),
|
|
241
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Body, { style: styles.main, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_components2.Container, { style: styles.container, children: [
|
|
242
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_components2.Text, { style: styles.heading, children: [
|
|
243
|
+
"Welcome to ",
|
|
244
|
+
siteName,
|
|
245
|
+
"! \u{1F389}"
|
|
246
|
+
] }),
|
|
247
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_components2.Text, { style: styles.text, children: [
|
|
248
|
+
"Hi ",
|
|
249
|
+
firstName,
|
|
250
|
+
","
|
|
251
|
+
] }),
|
|
252
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_components2.Text, { style: styles.text, children: [
|
|
253
|
+
"Thanks for subscribing to ",
|
|
254
|
+
siteName,
|
|
255
|
+
"! We're excited to have you as part of our community."
|
|
256
|
+
] }),
|
|
257
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Text, { style: styles.text, children: "You'll receive our newsletter based on your preferences. Speaking of which, you can update your preferences anytime:" }),
|
|
258
|
+
preferencesUrl && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Button, { href: preferencesUrl, style: styles.button, children: "Manage Preferences" }),
|
|
259
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Text, { style: styles.text, children: "Here's what you can expect from us:" }),
|
|
260
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_components2.Text, { style: styles.text, children: [
|
|
261
|
+
"\u2022 Regular updates based on your chosen frequency",
|
|
262
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("br", {}),
|
|
263
|
+
"\u2022 Content tailored to your interests",
|
|
264
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("br", {}),
|
|
265
|
+
"\u2022 Easy unsubscribe options in every email",
|
|
266
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("br", {}),
|
|
267
|
+
"\u2022 Your privacy respected always"
|
|
268
|
+
] }),
|
|
269
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Hr, { style: styles.hr }),
|
|
270
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Text, { style: styles.footer, children: "If you have any questions, feel free to reply to this email. We're here to help!" })
|
|
271
|
+
] }) })
|
|
272
|
+
] });
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// src/emails/SignIn.tsx
|
|
276
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
277
|
+
var SignInEmail = (props) => {
|
|
278
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MagicLinkEmail, { ...props });
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// src/emails/render.tsx
|
|
282
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
283
|
+
async function renderEmail(template, data) {
|
|
284
|
+
try {
|
|
285
|
+
switch (template) {
|
|
286
|
+
case "magic-link": {
|
|
287
|
+
const magicLinkData = data;
|
|
288
|
+
return (0, import_render.render)(
|
|
289
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
290
|
+
MagicLinkEmail,
|
|
291
|
+
{
|
|
292
|
+
magicLink: magicLinkData.magicLink || magicLinkData.verificationUrl || magicLinkData.magicLinkUrl || "",
|
|
293
|
+
email: magicLinkData.email || "",
|
|
294
|
+
siteName: magicLinkData.siteName,
|
|
295
|
+
expiresIn: magicLinkData.expiresIn
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
case "signin": {
|
|
301
|
+
const signinData = data;
|
|
302
|
+
return (0, import_render.render)(
|
|
303
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
304
|
+
SignInEmail,
|
|
305
|
+
{
|
|
306
|
+
magicLink: signinData.magicLink || signinData.verificationUrl || signinData.magicLinkUrl || "",
|
|
307
|
+
email: signinData.email || "",
|
|
308
|
+
siteName: signinData.siteName,
|
|
309
|
+
expiresIn: signinData.expiresIn
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
case "welcome": {
|
|
315
|
+
const welcomeData = data;
|
|
316
|
+
return (0, import_render.render)(
|
|
317
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
318
|
+
WelcomeEmail,
|
|
319
|
+
{
|
|
320
|
+
email: welcomeData.email || "",
|
|
321
|
+
siteName: welcomeData.siteName,
|
|
322
|
+
preferencesUrl: welcomeData.preferencesUrl
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
default:
|
|
328
|
+
throw new Error(`Unknown email template: ${template}`);
|
|
329
|
+
}
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.error(`Failed to render email template ${template}:`, error);
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
99
336
|
// src/collections/Subscribers.ts
|
|
100
337
|
var createSubscribersCollection = (pluginConfig) => {
|
|
101
338
|
const slug = pluginConfig.subscribersSlug || "subscribers";
|
|
@@ -305,7 +542,24 @@ var createSubscribersCollection = (pluginConfig) => {
|
|
|
305
542
|
}
|
|
306
543
|
if (doc.subscriptionStatus === "active" && emailService) {
|
|
307
544
|
try {
|
|
308
|
-
|
|
545
|
+
const settings = await req.payload.findGlobal({
|
|
546
|
+
slug: pluginConfig.settingsSlug || "newsletter-settings"
|
|
547
|
+
});
|
|
548
|
+
const serverURL = req.payload.config.serverURL || process.env.PAYLOAD_PUBLIC_SERVER_URL || "";
|
|
549
|
+
const html = await renderEmail("welcome", {
|
|
550
|
+
email: doc.email,
|
|
551
|
+
siteName: settings?.brandSettings?.siteName || "Newsletter",
|
|
552
|
+
preferencesUrl: `${serverURL}/account/preferences`
|
|
553
|
+
// This could be customized
|
|
554
|
+
});
|
|
555
|
+
await emailService.send({
|
|
556
|
+
to: doc.email,
|
|
557
|
+
subject: settings?.brandSettings?.siteName ? `Welcome to ${settings.brandSettings.siteName}!` : "Welcome!",
|
|
558
|
+
html
|
|
559
|
+
});
|
|
560
|
+
console.log(`Welcome email sent to: ${doc.email}`);
|
|
561
|
+
} catch (error) {
|
|
562
|
+
console.error("Failed to send welcome email:", error);
|
|
309
563
|
}
|
|
310
564
|
}
|
|
311
565
|
if (pluginConfig.hooks?.afterSubscribe) {
|
|
@@ -1110,6 +1364,86 @@ function validateSubscriberData(data) {
|
|
|
1110
1364
|
};
|
|
1111
1365
|
}
|
|
1112
1366
|
|
|
1367
|
+
// src/utils/jwt.ts
|
|
1368
|
+
var import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1);
|
|
1369
|
+
function getJWTSecret() {
|
|
1370
|
+
const secret = process.env.JWT_SECRET || process.env.PAYLOAD_SECRET;
|
|
1371
|
+
if (!secret) {
|
|
1372
|
+
console.warn(
|
|
1373
|
+
"WARNING: No JWT_SECRET or PAYLOAD_SECRET found in environment variables. Magic link authentication will not work properly. Please set JWT_SECRET in your environment."
|
|
1374
|
+
);
|
|
1375
|
+
return "INSECURE_DEVELOPMENT_SECRET_PLEASE_SET_JWT_SECRET";
|
|
1376
|
+
}
|
|
1377
|
+
return secret;
|
|
1378
|
+
}
|
|
1379
|
+
function generateMagicLinkToken(subscriberId, email, config) {
|
|
1380
|
+
const payload = {
|
|
1381
|
+
subscriberId,
|
|
1382
|
+
email,
|
|
1383
|
+
type: "magic-link"
|
|
1384
|
+
};
|
|
1385
|
+
const expiresIn = config.auth?.tokenExpiration || "7d";
|
|
1386
|
+
return import_jsonwebtoken.default.sign(payload, getJWTSecret(), {
|
|
1387
|
+
expiresIn,
|
|
1388
|
+
issuer: "payload-newsletter-plugin"
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
function verifyMagicLinkToken(token) {
|
|
1392
|
+
try {
|
|
1393
|
+
const payload = import_jsonwebtoken.default.verify(token, getJWTSecret(), {
|
|
1394
|
+
issuer: "payload-newsletter-plugin"
|
|
1395
|
+
});
|
|
1396
|
+
if (payload.type !== "magic-link") {
|
|
1397
|
+
throw new Error("Invalid token type");
|
|
1398
|
+
}
|
|
1399
|
+
return payload;
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
if (error instanceof Error && error.name === "TokenExpiredError") {
|
|
1402
|
+
throw new Error("Magic link has expired. Please request a new one.");
|
|
1403
|
+
}
|
|
1404
|
+
if (error instanceof Error && error.name === "JsonWebTokenError") {
|
|
1405
|
+
throw new Error("Invalid magic link token");
|
|
1406
|
+
}
|
|
1407
|
+
throw error;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
function generateSessionToken(subscriberId, email) {
|
|
1411
|
+
const payload = {
|
|
1412
|
+
subscriberId,
|
|
1413
|
+
email,
|
|
1414
|
+
type: "session"
|
|
1415
|
+
};
|
|
1416
|
+
return import_jsonwebtoken.default.sign(payload, getJWTSecret(), {
|
|
1417
|
+
expiresIn: "30d",
|
|
1418
|
+
issuer: "payload-newsletter-plugin"
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
function verifySessionToken(token) {
|
|
1422
|
+
try {
|
|
1423
|
+
const payload = import_jsonwebtoken.default.verify(token, getJWTSecret(), {
|
|
1424
|
+
issuer: "payload-newsletter-plugin"
|
|
1425
|
+
});
|
|
1426
|
+
if (payload.type !== "session") {
|
|
1427
|
+
throw new Error("Invalid token type");
|
|
1428
|
+
}
|
|
1429
|
+
return payload;
|
|
1430
|
+
} catch (error) {
|
|
1431
|
+
if (error instanceof Error && error.name === "TokenExpiredError") {
|
|
1432
|
+
throw new Error("Session has expired. Please sign in again.");
|
|
1433
|
+
}
|
|
1434
|
+
if (error instanceof Error && error.name === "JsonWebTokenError") {
|
|
1435
|
+
throw new Error("Invalid session token");
|
|
1436
|
+
}
|
|
1437
|
+
throw error;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
function generateMagicLinkURL(token, baseURL, config) {
|
|
1441
|
+
const path = config.auth?.magicLinkPath || "/newsletter/verify";
|
|
1442
|
+
const url = new URL(path, baseURL);
|
|
1443
|
+
url.searchParams.set("token", token);
|
|
1444
|
+
return url.toString();
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1113
1447
|
// src/endpoints/subscribe.ts
|
|
1114
1448
|
var createSubscribeEndpoint = (config) => {
|
|
1115
1449
|
return {
|
|
@@ -1233,6 +1567,33 @@ var createSubscribeEndpoint = (config) => {
|
|
|
1233
1567
|
if (config.features?.surveys?.enabled && surveyResponses) {
|
|
1234
1568
|
}
|
|
1235
1569
|
if (settings?.subscriptionSettings?.requireDoubleOptIn) {
|
|
1570
|
+
try {
|
|
1571
|
+
const token = generateMagicLinkToken(
|
|
1572
|
+
String(subscriber.id),
|
|
1573
|
+
subscriber.email,
|
|
1574
|
+
config
|
|
1575
|
+
);
|
|
1576
|
+
const serverURL = req.payload.config.serverURL || process.env.PAYLOAD_PUBLIC_SERVER_URL || "";
|
|
1577
|
+
const magicLinkURL = generateMagicLinkURL(token, serverURL, config);
|
|
1578
|
+
const emailService = req.payload.newsletterEmailService;
|
|
1579
|
+
if (emailService) {
|
|
1580
|
+
const html = await renderEmail("magic-link", {
|
|
1581
|
+
magicLink: magicLinkURL,
|
|
1582
|
+
email: subscriber.email,
|
|
1583
|
+
siteName: settings?.brandSettings?.siteName || "Newsletter",
|
|
1584
|
+
expiresIn: config.auth?.tokenExpiration || "7d"
|
|
1585
|
+
});
|
|
1586
|
+
await emailService.send({
|
|
1587
|
+
to: subscriber.email,
|
|
1588
|
+
subject: settings?.brandSettings?.siteName ? `Verify your email for ${settings.brandSettings.siteName}` : "Verify your email",
|
|
1589
|
+
html
|
|
1590
|
+
});
|
|
1591
|
+
} else {
|
|
1592
|
+
console.warn("Email service not initialized, cannot send magic link");
|
|
1593
|
+
}
|
|
1594
|
+
} catch (error) {
|
|
1595
|
+
console.error("Failed to send magic link email:", error);
|
|
1596
|
+
}
|
|
1236
1597
|
}
|
|
1237
1598
|
res.json({
|
|
1238
1599
|
success: true,
|
|
@@ -1253,68 +1614,6 @@ var createSubscribeEndpoint = (config) => {
|
|
|
1253
1614
|
};
|
|
1254
1615
|
};
|
|
1255
1616
|
|
|
1256
|
-
// src/utils/jwt.ts
|
|
1257
|
-
var import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1);
|
|
1258
|
-
function getJWTSecret() {
|
|
1259
|
-
const secret = process.env.JWT_SECRET || process.env.PAYLOAD_SECRET;
|
|
1260
|
-
if (!secret) {
|
|
1261
|
-
console.warn(
|
|
1262
|
-
"WARNING: No JWT_SECRET or PAYLOAD_SECRET found in environment variables. Magic link authentication will not work properly. Please set JWT_SECRET in your environment."
|
|
1263
|
-
);
|
|
1264
|
-
return "INSECURE_DEVELOPMENT_SECRET_PLEASE_SET_JWT_SECRET";
|
|
1265
|
-
}
|
|
1266
|
-
return secret;
|
|
1267
|
-
}
|
|
1268
|
-
function verifyMagicLinkToken(token) {
|
|
1269
|
-
try {
|
|
1270
|
-
const payload = import_jsonwebtoken.default.verify(token, getJWTSecret(), {
|
|
1271
|
-
issuer: "payload-newsletter-plugin"
|
|
1272
|
-
});
|
|
1273
|
-
if (payload.type !== "magic-link") {
|
|
1274
|
-
throw new Error("Invalid token type");
|
|
1275
|
-
}
|
|
1276
|
-
return payload;
|
|
1277
|
-
} catch (error) {
|
|
1278
|
-
if (error instanceof Error && error.name === "TokenExpiredError") {
|
|
1279
|
-
throw new Error("Magic link has expired. Please request a new one.");
|
|
1280
|
-
}
|
|
1281
|
-
if (error instanceof Error && error.name === "JsonWebTokenError") {
|
|
1282
|
-
throw new Error("Invalid magic link token");
|
|
1283
|
-
}
|
|
1284
|
-
throw error;
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
function generateSessionToken(subscriberId, email) {
|
|
1288
|
-
const payload = {
|
|
1289
|
-
subscriberId,
|
|
1290
|
-
email,
|
|
1291
|
-
type: "session"
|
|
1292
|
-
};
|
|
1293
|
-
return import_jsonwebtoken.default.sign(payload, getJWTSecret(), {
|
|
1294
|
-
expiresIn: "30d",
|
|
1295
|
-
issuer: "payload-newsletter-plugin"
|
|
1296
|
-
});
|
|
1297
|
-
}
|
|
1298
|
-
function verifySessionToken(token) {
|
|
1299
|
-
try {
|
|
1300
|
-
const payload = import_jsonwebtoken.default.verify(token, getJWTSecret(), {
|
|
1301
|
-
issuer: "payload-newsletter-plugin"
|
|
1302
|
-
});
|
|
1303
|
-
if (payload.type !== "session") {
|
|
1304
|
-
throw new Error("Invalid token type");
|
|
1305
|
-
}
|
|
1306
|
-
return payload;
|
|
1307
|
-
} catch (error) {
|
|
1308
|
-
if (error instanceof Error && error.name === "TokenExpiredError") {
|
|
1309
|
-
throw new Error("Session has expired. Please sign in again.");
|
|
1310
|
-
}
|
|
1311
|
-
if (error instanceof Error && error.name === "JsonWebTokenError") {
|
|
1312
|
-
throw new Error("Invalid session token");
|
|
1313
|
-
}
|
|
1314
|
-
throw error;
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
1617
|
// src/endpoints/verify-magic-link.ts
|
|
1319
1618
|
var createVerifyMagicLinkEndpoint = (config) => {
|
|
1320
1619
|
return {
|
|
@@ -1366,6 +1665,7 @@ var createVerifyMagicLinkEndpoint = (config) => {
|
|
|
1366
1665
|
id: subscriber.id,
|
|
1367
1666
|
email: subscriber.email
|
|
1368
1667
|
};
|
|
1668
|
+
let isNewlyActivated = false;
|
|
1369
1669
|
if (subscriber.subscriptionStatus === "pending") {
|
|
1370
1670
|
await req.payload.update({
|
|
1371
1671
|
collection: config.subscribersSlug || "subscribers",
|
|
@@ -1376,6 +1676,7 @@ var createVerifyMagicLinkEndpoint = (config) => {
|
|
|
1376
1676
|
overrideAccess: false,
|
|
1377
1677
|
user: syntheticUser
|
|
1378
1678
|
});
|
|
1679
|
+
isNewlyActivated = true;
|
|
1379
1680
|
}
|
|
1380
1681
|
await req.payload.update({
|
|
1381
1682
|
collection: config.subscribersSlug || "subscribers",
|
|
@@ -1391,6 +1692,40 @@ var createVerifyMagicLinkEndpoint = (config) => {
|
|
|
1391
1692
|
String(subscriber.id),
|
|
1392
1693
|
subscriber.email
|
|
1393
1694
|
);
|
|
1695
|
+
if (isNewlyActivated) {
|
|
1696
|
+
try {
|
|
1697
|
+
const emailService = req.payload.newsletterEmailService;
|
|
1698
|
+
if (emailService) {
|
|
1699
|
+
const settings = await req.payload.findGlobal({
|
|
1700
|
+
slug: config.settingsSlug || "newsletter-settings"
|
|
1701
|
+
});
|
|
1702
|
+
const serverURL = req.payload.config.serverURL || process.env.PAYLOAD_PUBLIC_SERVER_URL || "";
|
|
1703
|
+
const html = await renderEmail("welcome", {
|
|
1704
|
+
email: subscriber.email,
|
|
1705
|
+
siteName: settings?.brandSettings?.siteName || "Newsletter",
|
|
1706
|
+
preferencesUrl: `${serverURL}/account/preferences`
|
|
1707
|
+
// This could be customized
|
|
1708
|
+
});
|
|
1709
|
+
await emailService.send({
|
|
1710
|
+
to: subscriber.email,
|
|
1711
|
+
subject: settings?.brandSettings?.siteName ? `Welcome to ${settings.brandSettings.siteName}!` : "Welcome!",
|
|
1712
|
+
html
|
|
1713
|
+
});
|
|
1714
|
+
} else {
|
|
1715
|
+
console.warn("Email service not initialized, cannot send welcome email");
|
|
1716
|
+
}
|
|
1717
|
+
} catch (error) {
|
|
1718
|
+
console.error("Failed to send welcome email:", error);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
res.cookie("newsletter-auth", sessionToken, {
|
|
1722
|
+
httpOnly: true,
|
|
1723
|
+
secure: process.env.NODE_ENV === "production",
|
|
1724
|
+
sameSite: "lax",
|
|
1725
|
+
path: "/",
|
|
1726
|
+
maxAge: 30 * 24 * 60 * 60 * 1e3
|
|
1727
|
+
// 30 days
|
|
1728
|
+
});
|
|
1394
1729
|
res.json({
|
|
1395
1730
|
success: true,
|
|
1396
1731
|
sessionToken,
|
|
@@ -1558,8 +1893,8 @@ var createUnsubscribeEndpoint = (config) => {
|
|
|
1558
1893
|
let subscriber;
|
|
1559
1894
|
if (token) {
|
|
1560
1895
|
try {
|
|
1561
|
-
const
|
|
1562
|
-
const payload =
|
|
1896
|
+
const jwt3 = await import("jsonwebtoken");
|
|
1897
|
+
const payload = jwt3.verify(
|
|
1563
1898
|
token,
|
|
1564
1899
|
process.env.JWT_SECRET || process.env.PAYLOAD_SECRET || ""
|
|
1565
1900
|
);
|
|
@@ -1640,6 +1975,224 @@ var createUnsubscribeEndpoint = (config) => {
|
|
|
1640
1975
|
};
|
|
1641
1976
|
};
|
|
1642
1977
|
|
|
1978
|
+
// src/utils/rate-limiter.ts
|
|
1979
|
+
var RateLimiter = class {
|
|
1980
|
+
constructor(options) {
|
|
1981
|
+
this.attempts = /* @__PURE__ */ new Map();
|
|
1982
|
+
this.options = options;
|
|
1983
|
+
}
|
|
1984
|
+
async checkLimit(key) {
|
|
1985
|
+
const now = Date.now();
|
|
1986
|
+
const record = this.attempts.get(key);
|
|
1987
|
+
if (!record || record.resetTime < now) {
|
|
1988
|
+
this.attempts.set(key, {
|
|
1989
|
+
count: 1,
|
|
1990
|
+
resetTime: now + this.options.windowMs
|
|
1991
|
+
});
|
|
1992
|
+
return true;
|
|
1993
|
+
}
|
|
1994
|
+
if (record.count >= this.options.maxAttempts) {
|
|
1995
|
+
return false;
|
|
1996
|
+
}
|
|
1997
|
+
record.count++;
|
|
1998
|
+
return true;
|
|
1999
|
+
}
|
|
2000
|
+
async incrementAttempt(key) {
|
|
2001
|
+
const now = Date.now();
|
|
2002
|
+
const record = this.attempts.get(key);
|
|
2003
|
+
if (!record || record.resetTime < now) {
|
|
2004
|
+
this.attempts.set(key, {
|
|
2005
|
+
count: 1,
|
|
2006
|
+
resetTime: now + this.options.windowMs
|
|
2007
|
+
});
|
|
2008
|
+
} else {
|
|
2009
|
+
record.count++;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
async reset(key) {
|
|
2013
|
+
this.attempts.delete(key);
|
|
2014
|
+
}
|
|
2015
|
+
async resetAll() {
|
|
2016
|
+
this.attempts.clear();
|
|
2017
|
+
}
|
|
2018
|
+
};
|
|
2019
|
+
|
|
2020
|
+
// src/endpoints/signin.ts
|
|
2021
|
+
var signinRateLimiter = new RateLimiter({
|
|
2022
|
+
maxAttempts: 5,
|
|
2023
|
+
windowMs: 15 * 60 * 1e3,
|
|
2024
|
+
// 15 minutes
|
|
2025
|
+
prefix: "signin"
|
|
2026
|
+
});
|
|
2027
|
+
var createSigninEndpoint = (config) => {
|
|
2028
|
+
return {
|
|
2029
|
+
path: "/newsletter/signin",
|
|
2030
|
+
method: "post",
|
|
2031
|
+
handler: async (req, res) => {
|
|
2032
|
+
try {
|
|
2033
|
+
const { email } = req.body;
|
|
2034
|
+
const validation = validateSubscriberData({ email });
|
|
2035
|
+
if (!validation.valid) {
|
|
2036
|
+
return res.status(400).json({
|
|
2037
|
+
success: false,
|
|
2038
|
+
errors: validation.errors
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
const rateLimitKey = `signin:${email.toLowerCase()}`;
|
|
2042
|
+
const allowed = await signinRateLimiter.checkLimit(rateLimitKey);
|
|
2043
|
+
if (!allowed) {
|
|
2044
|
+
return res.status(429).json({
|
|
2045
|
+
success: false,
|
|
2046
|
+
error: "Too many sign-in attempts. Please try again later."
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
const result = await req.payload.find({
|
|
2050
|
+
collection: config.subscribersSlug || "subscribers",
|
|
2051
|
+
where: {
|
|
2052
|
+
email: { equals: email.toLowerCase() },
|
|
2053
|
+
subscriptionStatus: { equals: "active" }
|
|
2054
|
+
},
|
|
2055
|
+
limit: 1,
|
|
2056
|
+
overrideAccess: true
|
|
2057
|
+
// Need to check subscriber exists
|
|
2058
|
+
});
|
|
2059
|
+
if (result.docs.length === 0) {
|
|
2060
|
+
return res.status(404).json({
|
|
2061
|
+
success: false,
|
|
2062
|
+
error: "Email not found. Please subscribe first."
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
const subscriber = result.docs[0];
|
|
2066
|
+
const token = generateMagicLinkToken(
|
|
2067
|
+
String(subscriber.id),
|
|
2068
|
+
subscriber.email,
|
|
2069
|
+
config
|
|
2070
|
+
);
|
|
2071
|
+
const serverURL = req.payload.config.serverURL || process.env.PAYLOAD_PUBLIC_SERVER_URL || "";
|
|
2072
|
+
const magicLinkURL = generateMagicLinkURL(token, serverURL, config);
|
|
2073
|
+
const emailService = req.payload.newsletterEmailService;
|
|
2074
|
+
if (emailService) {
|
|
2075
|
+
const settings = await req.payload.findGlobal({
|
|
2076
|
+
slug: config.settingsSlug || "newsletter-settings"
|
|
2077
|
+
});
|
|
2078
|
+
const html = await renderEmail("signin", {
|
|
2079
|
+
magicLink: magicLinkURL,
|
|
2080
|
+
email: subscriber.email,
|
|
2081
|
+
siteName: settings?.brandSettings?.siteName || "Newsletter",
|
|
2082
|
+
expiresIn: config.auth?.tokenExpiration || "7d"
|
|
2083
|
+
});
|
|
2084
|
+
await emailService.send({
|
|
2085
|
+
to: subscriber.email,
|
|
2086
|
+
subject: settings?.brandSettings?.siteName ? `Sign in to ${settings.brandSettings.siteName}` : "Sign in to your account",
|
|
2087
|
+
html
|
|
2088
|
+
});
|
|
2089
|
+
} else {
|
|
2090
|
+
console.warn("Email service not initialized, cannot send sign-in link");
|
|
2091
|
+
}
|
|
2092
|
+
res.json({
|
|
2093
|
+
success: true,
|
|
2094
|
+
message: "Check your email for the sign-in link"
|
|
2095
|
+
});
|
|
2096
|
+
} catch (error) {
|
|
2097
|
+
console.error("Sign-in error:", error);
|
|
2098
|
+
res.status(500).json({
|
|
2099
|
+
success: false,
|
|
2100
|
+
error: "Failed to process sign-in request"
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
2105
|
+
};
|
|
2106
|
+
|
|
2107
|
+
// src/endpoints/me.ts
|
|
2108
|
+
var createMeEndpoint = (config) => {
|
|
2109
|
+
return {
|
|
2110
|
+
path: "/newsletter/me",
|
|
2111
|
+
method: "get",
|
|
2112
|
+
handler: async (req, res) => {
|
|
2113
|
+
try {
|
|
2114
|
+
const token = req.cookies?.["newsletter-auth"];
|
|
2115
|
+
if (!token) {
|
|
2116
|
+
return res.status(401).json({
|
|
2117
|
+
success: false,
|
|
2118
|
+
error: "Not authenticated"
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
let payload;
|
|
2122
|
+
try {
|
|
2123
|
+
payload = verifySessionToken(token);
|
|
2124
|
+
} catch {
|
|
2125
|
+
return res.status(401).json({
|
|
2126
|
+
success: false,
|
|
2127
|
+
error: "Invalid or expired session"
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
const subscriber = await req.payload.findByID({
|
|
2131
|
+
collection: config.subscribersSlug || "subscribers",
|
|
2132
|
+
id: payload.subscriberId,
|
|
2133
|
+
overrideAccess: true
|
|
2134
|
+
// Need to get subscriber data
|
|
2135
|
+
});
|
|
2136
|
+
if (!subscriber || subscriber.subscriptionStatus !== "active") {
|
|
2137
|
+
return res.status(401).json({
|
|
2138
|
+
success: false,
|
|
2139
|
+
error: "Not authenticated"
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
res.json({
|
|
2143
|
+
success: true,
|
|
2144
|
+
subscriber: {
|
|
2145
|
+
id: subscriber.id,
|
|
2146
|
+
email: subscriber.email,
|
|
2147
|
+
name: subscriber.name,
|
|
2148
|
+
status: subscriber.subscriptionStatus,
|
|
2149
|
+
preferences: {
|
|
2150
|
+
frequency: subscriber.emailPreferences?.frequency,
|
|
2151
|
+
categories: subscriber.emailPreferences?.categories
|
|
2152
|
+
},
|
|
2153
|
+
createdAt: subscriber.createdAt,
|
|
2154
|
+
updatedAt: subscriber.updatedAt
|
|
2155
|
+
}
|
|
2156
|
+
});
|
|
2157
|
+
} catch (error) {
|
|
2158
|
+
console.error("Me endpoint error:", error);
|
|
2159
|
+
res.status(500).json({
|
|
2160
|
+
success: false,
|
|
2161
|
+
error: "Internal server error"
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
};
|
|
2166
|
+
};
|
|
2167
|
+
|
|
2168
|
+
// src/endpoints/signout.ts
|
|
2169
|
+
var createSignoutEndpoint = (_config) => {
|
|
2170
|
+
return {
|
|
2171
|
+
path: "/newsletter/signout",
|
|
2172
|
+
method: "post",
|
|
2173
|
+
handler: (req, res) => {
|
|
2174
|
+
try {
|
|
2175
|
+
res.clearCookie("newsletter-auth", {
|
|
2176
|
+
httpOnly: true,
|
|
2177
|
+
secure: process.env.NODE_ENV === "production",
|
|
2178
|
+
sameSite: "lax",
|
|
2179
|
+
path: "/"
|
|
2180
|
+
});
|
|
2181
|
+
res.json({
|
|
2182
|
+
success: true,
|
|
2183
|
+
message: "Signed out successfully"
|
|
2184
|
+
});
|
|
2185
|
+
} catch (error) {
|
|
2186
|
+
console.error("Signout error:", error);
|
|
2187
|
+
res.status(500).json({
|
|
2188
|
+
success: false,
|
|
2189
|
+
error: "Failed to sign out"
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
};
|
|
2194
|
+
};
|
|
2195
|
+
|
|
1643
2196
|
// src/endpoints/index.ts
|
|
1644
2197
|
function createNewsletterEndpoints(config) {
|
|
1645
2198
|
const endpoints = [
|
|
@@ -1650,7 +2203,10 @@ function createNewsletterEndpoints(config) {
|
|
|
1650
2203
|
endpoints.push(
|
|
1651
2204
|
createVerifyMagicLinkEndpoint(config),
|
|
1652
2205
|
createPreferencesEndpoint(config),
|
|
1653
|
-
createUpdatePreferencesEndpoint(config)
|
|
2206
|
+
createUpdatePreferencesEndpoint(config),
|
|
2207
|
+
createSigninEndpoint(config),
|
|
2208
|
+
createMeEndpoint(config),
|
|
2209
|
+
createSignoutEndpoint(config)
|
|
1654
2210
|
);
|
|
1655
2211
|
}
|
|
1656
2212
|
return endpoints;
|
|
@@ -1825,6 +2381,83 @@ function createMarkdownFieldInternal(config) {
|
|
|
1825
2381
|
};
|
|
1826
2382
|
}
|
|
1827
2383
|
|
|
2384
|
+
// src/utilities/session.ts
|
|
2385
|
+
var import_jsonwebtoken2 = __toESM(require("jsonwebtoken"), 1);
|
|
2386
|
+
var getTokenFromRequest = (req) => {
|
|
2387
|
+
const cookies = req.cookies || req.headers?.cookie;
|
|
2388
|
+
if (!cookies) return null;
|
|
2389
|
+
if (typeof cookies === "string") {
|
|
2390
|
+
const parsed = cookies.split(";").reduce((acc, cookie) => {
|
|
2391
|
+
const [key, value] = cookie.trim().split("=");
|
|
2392
|
+
acc[key] = value;
|
|
2393
|
+
return acc;
|
|
2394
|
+
}, {});
|
|
2395
|
+
return parsed["newsletter-auth"] || null;
|
|
2396
|
+
}
|
|
2397
|
+
return cookies["newsletter-auth"] || null;
|
|
2398
|
+
};
|
|
2399
|
+
var verifyToken = (token, secret) => {
|
|
2400
|
+
try {
|
|
2401
|
+
const decoded = import_jsonwebtoken2.default.verify(token, secret);
|
|
2402
|
+
return decoded;
|
|
2403
|
+
} catch {
|
|
2404
|
+
return null;
|
|
2405
|
+
}
|
|
2406
|
+
};
|
|
2407
|
+
var getServerSideAuth = async (context, secret) => {
|
|
2408
|
+
const token = getTokenFromRequest(context.req);
|
|
2409
|
+
if (!token) {
|
|
2410
|
+
return { subscriber: null, isAuthenticated: false };
|
|
2411
|
+
}
|
|
2412
|
+
const payloadSecret = secret || process.env.PAYLOAD_SECRET;
|
|
2413
|
+
if (!payloadSecret) {
|
|
2414
|
+
console.error("No secret provided for token verification");
|
|
2415
|
+
return { subscriber: null, isAuthenticated: false };
|
|
2416
|
+
}
|
|
2417
|
+
const decoded = verifyToken(token, payloadSecret);
|
|
2418
|
+
if (!decoded) {
|
|
2419
|
+
return { subscriber: null, isAuthenticated: false };
|
|
2420
|
+
}
|
|
2421
|
+
return {
|
|
2422
|
+
subscriber: decoded,
|
|
2423
|
+
isAuthenticated: true
|
|
2424
|
+
};
|
|
2425
|
+
};
|
|
2426
|
+
var requireAuth = (gssp) => {
|
|
2427
|
+
return async (context) => {
|
|
2428
|
+
const { isAuthenticated: isAuthenticated2, subscriber } = await getServerSideAuth(context);
|
|
2429
|
+
if (!isAuthenticated2) {
|
|
2430
|
+
return {
|
|
2431
|
+
redirect: {
|
|
2432
|
+
destination: "/auth/signin",
|
|
2433
|
+
permanent: false
|
|
2434
|
+
}
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
if (gssp) {
|
|
2438
|
+
const result = await gssp(context);
|
|
2439
|
+
return {
|
|
2440
|
+
...result,
|
|
2441
|
+
props: {
|
|
2442
|
+
...result.props,
|
|
2443
|
+
subscriber
|
|
2444
|
+
}
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
return {
|
|
2448
|
+
props: {
|
|
2449
|
+
subscriber
|
|
2450
|
+
}
|
|
2451
|
+
};
|
|
2452
|
+
};
|
|
2453
|
+
};
|
|
2454
|
+
var isAuthenticated = (req, secret) => {
|
|
2455
|
+
const token = getTokenFromRequest(req);
|
|
2456
|
+
if (!token) return false;
|
|
2457
|
+
const decoded = verifyToken(token, secret);
|
|
2458
|
+
return !!decoded;
|
|
2459
|
+
};
|
|
2460
|
+
|
|
1828
2461
|
// src/index.ts
|
|
1829
2462
|
var newsletterPlugin = (pluginConfig) => (incomingConfig) => {
|
|
1830
2463
|
const config = {
|
|
@@ -1927,6 +2560,11 @@ var newsletterPlugin = (pluginConfig) => (incomingConfig) => {
|
|
|
1927
2560
|
};
|
|
1928
2561
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1929
2562
|
0 && (module.exports = {
|
|
1930
|
-
|
|
2563
|
+
getServerSideAuth,
|
|
2564
|
+
getTokenFromRequest,
|
|
2565
|
+
isAuthenticated,
|
|
2566
|
+
newsletterPlugin,
|
|
2567
|
+
requireAuth,
|
|
2568
|
+
verifyToken
|
|
1931
2569
|
});
|
|
1932
2570
|
//# sourceMappingURL=index.cjs.map
|