@syncello/auth 3.1.0 → 3.2.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/dist/index.js CHANGED
@@ -2181,13 +2181,92 @@ var ResendAdapter = class {
2181
2181
  }
2182
2182
  };
2183
2183
 
2184
+ // src/lib/email/adapters/cloudflare-adapter.ts
2185
+ var CloudflareEmailAdapter = class {
2186
+ constructor(binding, options) {
2187
+ this.binding = binding;
2188
+ this.options = options;
2189
+ }
2190
+ binding;
2191
+ options;
2192
+ providerName = "cloudflare";
2193
+ async send(options) {
2194
+ if (this.options.dryRun) {
2195
+ logger_default.info("Cloudflare: Dry-run, email not sent", {
2196
+ provider: this.providerName,
2197
+ to: options.to,
2198
+ from: options.from,
2199
+ subject: options.subject
2200
+ });
2201
+ return { success: true, emailId: "dry-run" };
2202
+ }
2203
+ try {
2204
+ logger_default.info("Cloudflare: Sending email", {
2205
+ provider: this.providerName,
2206
+ to: options.to,
2207
+ subject: options.subject
2208
+ });
2209
+ const result = await this.binding.send({
2210
+ to: options.to,
2211
+ from: options.from,
2212
+ subject: options.subject,
2213
+ html: options.html,
2214
+ text: options.text,
2215
+ headers: options.tags ? Object.fromEntries(
2216
+ Object.entries(options.tags).map(([name, value]) => [`X-Tag-${name}`, value])
2217
+ ) : void 0
2218
+ });
2219
+ logger_default.info("Cloudflare: Email sent successfully", {
2220
+ provider: this.providerName,
2221
+ emailId: result.messageId,
2222
+ to: options.to
2223
+ });
2224
+ return { success: true, emailId: result.messageId };
2225
+ } catch (error) {
2226
+ const message = error instanceof Error ? error.message : "Unknown error";
2227
+ const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
2228
+ const errorText = code ? `${code}: ${message}` : message;
2229
+ if (code === "E_RECIPIENT_SUPPRESSED") {
2230
+ logger_default.warn("Cloudflare: Recipient is on the suppression list", {
2231
+ provider: this.providerName,
2232
+ to: options.to
2233
+ });
2234
+ } else {
2235
+ logger_default.error("Cloudflare send error", {
2236
+ provider: this.providerName,
2237
+ error: errorText,
2238
+ to: options.to,
2239
+ subject: options.subject
2240
+ });
2241
+ }
2242
+ return { success: false, error: errorText };
2243
+ }
2244
+ }
2245
+ };
2246
+
2184
2247
  // src/lib/email/adapters/factory.ts
2185
2248
  function createEmailAdapter(env) {
2186
- logger_default.info("Creating email adapter", { provider: "resend" });
2187
- if (!env.RESEND_API_KEY) {
2188
- throw new Error("RESEND_API_KEY is required");
2249
+ const provider = env.EMAIL_PROVIDER ?? "resend";
2250
+ logger_default.info("Creating email adapter", { provider });
2251
+ switch (provider) {
2252
+ case "resend":
2253
+ if (!env.RESEND_API_KEY) {
2254
+ throw new Error("RESEND_API_KEY is required");
2255
+ }
2256
+ return new ResendAdapter(env.RESEND_API_KEY);
2257
+ case "cloudflare": {
2258
+ if (!env.EMAIL) {
2259
+ throw new Error(
2260
+ 'EMAIL send_email binding is required when EMAIL_PROVIDER=cloudflare - add [[send_email]] name = "EMAIL" to wrangler.toml'
2261
+ );
2262
+ }
2263
+ const environment = env.ENVIRONMENT?.toLowerCase();
2264
+ const dryRun = environment !== "production" && environment !== "staging";
2265
+ return new CloudflareEmailAdapter(env.EMAIL, { dryRun });
2266
+ }
2267
+ default:
2268
+ throw new Error(`Unknown EMAIL_PROVIDER: ${String(provider)}`);
2189
2269
  }
2190
- return new ResendAdapter(env.RESEND_API_KEY);
2191
2270
  }
2192
2271
 
2193
2272
  // src/lib/email/email-service.ts
@@ -2195,6 +2274,9 @@ var EmailService = class {
2195
2274
  adapter;
2196
2275
  env;
2197
2276
  get fromAddress() {
2277
+ if (this.env.EMAIL_FROM) {
2278
+ return this.env.EMAIL_FROM;
2279
+ }
2198
2280
  const appName = this.env.APP_NAME ?? "Your App";
2199
2281
  const appUrl = this.env.APP_URL ?? "";
2200
2282
  let domain = "example.com";
@@ -3051,7 +3133,8 @@ var signupHandler = async (c) => {
3051
3133
  verificationUrl,
3052
3134
  firstName: newUser.name || void 0
3053
3135
  },
3054
- database
3136
+ database,
3137
+ schema.users
3055
3138
  );
3056
3139
  const fingerprint = await generateFingerprint(c.req.raw);
3057
3140
  const ipAddress = getClientIp(c.req.raw);
@@ -3525,7 +3608,7 @@ var forgotPasswordRoute = createRoute6({
3525
3608
  });
3526
3609
  var forgotPasswordHandler = async (c) => {
3527
3610
  const { email, turnstileToken } = c.req.valid("json");
3528
- const database = c.get("db");
3611
+ const { db: database, schema } = getAuthContext(c);
3529
3612
  const env = c.env;
3530
3613
  const turnstileValid = await verifyTurnstileToken(
3531
3614
  turnstileToken,
@@ -3536,7 +3619,11 @@ var forgotPasswordHandler = async (c) => {
3536
3619
  if (!turnstileValid) {
3537
3620
  return problems.badRequest(c, "Invalid captcha");
3538
3621
  }
3539
- const resetResult = await createPasswordResetToken(database, email);
3622
+ const resetResult = await createPasswordResetToken(
3623
+ database,
3624
+ { users: schema.users, passwordResetTokens: schema.passwordResetTokens },
3625
+ email
3626
+ );
3540
3627
  if (!resetResult) {
3541
3628
  return c.json({ success: true });
3542
3629
  }
@@ -3548,7 +3635,8 @@ var forgotPasswordHandler = async (c) => {
3548
3635
  token: resetResult.token,
3549
3636
  resetUrl
3550
3637
  },
3551
- database
3638
+ database,
3639
+ schema.users
3552
3640
  );
3553
3641
  logSecurityEvent("password_reset_requested", "medium", { userId: resetResult.userId });
3554
3642
  return c.json({ success: true });
@@ -3690,7 +3778,7 @@ var changePasswordHandler = async (c) => {
3690
3778
  const userId = c.get("userId");
3691
3779
  if (!userId) return problems.unauthorized(c, "Not authenticated");
3692
3780
  const { currentPassword, newPassword } = c.req.valid("json");
3693
- const database = c.get("db");
3781
+ const { db: database, schema } = getAuthContext(c);
3694
3782
  const env = c.env;
3695
3783
  const passwordValidation = validatePassword(newPassword);
3696
3784
  if (!passwordValidation.valid) {
@@ -3707,7 +3795,8 @@ var changePasswordHandler = async (c) => {
3707
3795
  newPassword,
3708
3796
  currentSessionId,
3709
3797
  pepper: env.PASSWORD_PEPPER_V1,
3710
- db: database
3798
+ db: database,
3799
+ tables: { users: schema.users, sessions: schema.sessions }
3711
3800
  });
3712
3801
  if (!result.success) {
3713
3802
  return problems.badRequest(c, result.error || "Failed to change password");
@@ -3786,7 +3875,7 @@ var changeEmailHandler = async (c) => {
3786
3875
  const userId = c.get("userId");
3787
3876
  if (!userId) return problems.unauthorized(c, "Not authenticated");
3788
3877
  const { password, newEmail } = c.req.valid("json");
3789
- const database = c.get("db");
3878
+ const { db: database, schema } = getAuthContext(c);
3790
3879
  const env = c.env;
3791
3880
  const appUrl = env.APP_URL || "http://localhost:5173";
3792
3881
  const result = await requestEmailChange({
@@ -3794,7 +3883,8 @@ var changeEmailHandler = async (c) => {
3794
3883
  password,
3795
3884
  newEmail,
3796
3885
  pepper: env.PASSWORD_PEPPER_V1,
3797
- db: database
3886
+ db: database,
3887
+ tables: { users: schema.users, emailChangeTokens: schema.emailChangeTokens }
3798
3888
  });
3799
3889
  if (!result.success) {
3800
3890
  if (result.error === "Incorrect password") {
@@ -3806,7 +3896,8 @@ var changeEmailHandler = async (c) => {
3806
3896
  const confirmUrl = `${appUrl}/confirm-email-change?token=${result.confirmToken}`;
3807
3897
  const confirmResult = await emailService.sendEmailChangeConfirmation(
3808
3898
  { newEmail, confirmUrl },
3809
- database
3899
+ database,
3900
+ schema.users
3810
3901
  );
3811
3902
  if (!confirmResult.success) {
3812
3903
  return problems.badRequest(
@@ -3817,7 +3908,8 @@ var changeEmailHandler = async (c) => {
3817
3908
  const cancelUrl = `${appUrl}/cancel-email-change?token=${result.cancelToken}`;
3818
3909
  await emailService.sendEmailChangeNotification(
3819
3910
  { oldEmail: result.oldEmail, newEmail, cancelUrl },
3820
- database
3911
+ database,
3912
+ schema.users
3821
3913
  );
3822
3914
  logSecurityEvent("email_change_requested", "medium", { userId });
3823
3915
  return c.json({ success: true });
@@ -3858,8 +3950,12 @@ var confirmEmailChangeRoute = createRoute11({
3858
3950
  });
3859
3951
  var confirmEmailChangeHandler = async (c) => {
3860
3952
  const { token } = c.req.valid("json");
3861
- const database = c.get("db");
3862
- const result = await confirmEmailChange({ token, db: database });
3953
+ const { db: database, schema } = getAuthContext(c);
3954
+ const result = await confirmEmailChange({
3955
+ token,
3956
+ db: database,
3957
+ tables: { users: schema.users, emailChangeTokens: schema.emailChangeTokens }
3958
+ });
3863
3959
  if (!result.success) {
3864
3960
  return problems.badRequest(c, result.error || "Invalid or expired token");
3865
3961
  }
@@ -3900,8 +3996,12 @@ var cancelEmailChangeRoute = createRoute12({
3900
3996
  });
3901
3997
  var cancelEmailChangeHandler = async (c) => {
3902
3998
  const { token } = c.req.valid("json");
3903
- const database = c.get("db");
3904
- const result = await cancelEmailChange({ token, db: database });
3999
+ const { db: database, schema } = getAuthContext(c);
4000
+ const result = await cancelEmailChange({
4001
+ token,
4002
+ db: database,
4003
+ tables: { emailChangeTokens: schema.emailChangeTokens }
4004
+ });
3905
4005
  if (!result.success) {
3906
4006
  return problems.badRequest(c, result.error || "Invalid or expired token");
3907
4007
  }
@@ -4040,7 +4140,7 @@ var refreshHandler = async (c) => {
4040
4140
  if (!session) {
4041
4141
  return problems.unauthorized(c, "Invalid or expired refresh token");
4042
4142
  }
4043
- await deleteSession(db, session.id);
4143
+ await deleteSession(db, { sessions: schema.sessions }, session.id);
4044
4144
  const newSessionId = await createSession(
4045
4145
  db,
4046
4146
  { sessions: schema.sessions },
@@ -4131,7 +4231,8 @@ var resendVerificationHandler = async (c) => {
4131
4231
  verificationUrl,
4132
4232
  firstName: user.name || void 0
4133
4233
  },
4134
- db
4234
+ db,
4235
+ schema.users
4135
4236
  );
4136
4237
  logger_default.info("Verification email resent", { userId: user.id, email: user.email });
4137
4238
  return c.json({
@@ -4457,7 +4558,8 @@ var totpVerifyHandler = async (c) => {
4457
4558
  const firstName = user.name?.split(" ")[0] || null;
4458
4559
  await emailService.send2faEnabledEmail(
4459
4560
  { email: user.email, firstName, method: "totp" },
4460
- db
4561
+ db,
4562
+ schema.users
4461
4563
  );
4462
4564
  }
4463
4565
  logSecurityEvent("2fa_totp_enrolled", "high", { userId });
@@ -4514,12 +4616,12 @@ var totpDisableHandler = async (c) => {
4514
4616
  if (remaining.length === 0) {
4515
4617
  await db.delete(schema.userBackupCodes).where(eq22(schema.userBackupCodes.userId, userId));
4516
4618
  await db.delete(schema.userTrustedDevices).where(eq22(schema.userTrustedDevices.userId, userId));
4517
- await invalidateAllUserSessions(db, userId);
4619
+ await invalidateAllUserSessions(db, { sessions: schema.sessions, users: schema.users }, userId);
4518
4620
  const [user] = await db.select({ email: schema.users.email, name: schema.users.name }).from(schema.users).where(eq22(schema.users.id, userId)).limit(1);
4519
4621
  if (user) {
4520
4622
  const emailService = new EmailService(env);
4521
4623
  const firstName = user.name?.split(" ")[0] || null;
4522
- await emailService.send2faDisabledEmail({ email: user.email, firstName }, db);
4624
+ await emailService.send2faDisabledEmail({ email: user.email, firstName }, db, schema.users);
4523
4625
  }
4524
4626
  logSecurityEvent("2fa_disabled", "critical", { userId, lastMethod: "totp" });
4525
4627
  return c.json({ success: true, sessionInvalidated: true });
@@ -4575,7 +4677,7 @@ var emailSetupHandler = async (c) => {
4575
4677
  await env.OAUTH_STATES.put(`email_2fa_setup:${userId}`, codeHash, { expirationTtl: 300 });
4576
4678
  const emailService = new EmailService(env);
4577
4679
  const firstName = user.name?.split(" ")[0] || null;
4578
- await emailService.send2faCodeEmail({ email: user.email, firstName, code }, db);
4680
+ await emailService.send2faCodeEmail({ email: user.email, firstName, code }, db, schema.users);
4579
4681
  logSecurityEvent("2fa_email_setup_initiated", "low", { userId });
4580
4682
  return c.json({ success: true });
4581
4683
  };
@@ -4656,7 +4758,8 @@ var emailVerifyHandler = async (c) => {
4656
4758
  const firstName = user.name?.split(" ")[0] || null;
4657
4759
  await emailService.send2faEnabledEmail(
4658
4760
  { email: user.email, firstName, method: "email" },
4659
- db
4761
+ db,
4762
+ schema.users
4660
4763
  );
4661
4764
  }
4662
4765
  logSecurityEvent("2fa_email_enrolled", "high", { userId });
@@ -4710,7 +4813,8 @@ var emailSendCodeHandler = async (c) => {
4710
4813
  const firstName = user.name?.split(" ")[0] || null;
4711
4814
  const emailResult = await emailService.send2faCodeEmail(
4712
4815
  { email: user.email, firstName, code },
4713
- db
4816
+ db,
4817
+ schema.users
4714
4818
  );
4715
4819
  if (!emailResult.success) {
4716
4820
  return problems.badRequest(
@@ -4772,12 +4876,12 @@ var emailDisableHandler = async (c) => {
4772
4876
  if (remaining.length === 0) {
4773
4877
  await db.delete(schema.userBackupCodes).where(eq26(schema.userBackupCodes.userId, userId));
4774
4878
  await db.delete(schema.userTrustedDevices).where(eq26(schema.userTrustedDevices.userId, userId));
4775
- await invalidateAllUserSessions(db, userId);
4879
+ await invalidateAllUserSessions(db, { sessions: schema.sessions, users: schema.users }, userId);
4776
4880
  const [user] = await db.select({ email: schema.users.email, name: schema.users.name }).from(schema.users).where(eq26(schema.users.id, userId)).limit(1);
4777
4881
  if (user) {
4778
4882
  const emailService = new EmailService(env);
4779
4883
  const firstName = user.name?.split(" ")[0] || null;
4780
- await emailService.send2faDisabledEmail({ email: user.email, firstName }, db);
4884
+ await emailService.send2faDisabledEmail({ email: user.email, firstName }, db, schema.users);
4781
4885
  }
4782
4886
  logSecurityEvent("2fa_disabled", "critical", { userId, lastMethod: "email" });
4783
4887
  return c.json({ success: true, sessionInvalidated: true });
@@ -5098,7 +5202,7 @@ var challengeResendHandler = async (c) => {
5098
5202
  });
5099
5203
  const emailService = new EmailService(c.env);
5100
5204
  const firstName = user.name?.split(" ")[0] || null;
5101
- await emailService.send2faCodeEmail({ email: user.email, firstName, code }, db);
5205
+ await emailService.send2faCodeEmail({ email: user.email, firstName, code }, db, schema.users);
5102
5206
  logger_default.info("Email 2FA code resent", { userId: payload.userId });
5103
5207
  return c.json({ success: true });
5104
5208
  };
@@ -5621,6 +5725,7 @@ function verifyWebhookSignature(payload, signature, secret) {
5621
5725
  export {
5622
5726
  AUTH_DEFAULTS,
5623
5727
  CHALLENGE_TTL_MS,
5728
+ CloudflareEmailAdapter,
5624
5729
  EmailService,
5625
5730
  MAX_CHALLENGE_ATTEMPTS,
5626
5731
  ProblemTypes,