@syncello/auth 3.1.0 → 3.2.1

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