@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.cjs CHANGED
@@ -32,6 +32,7 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  AUTH_DEFAULTS: () => AUTH_DEFAULTS,
34
34
  CHALLENGE_TTL_MS: () => CHALLENGE_TTL_MS,
35
+ CloudflareEmailAdapter: () => CloudflareEmailAdapter,
35
36
  EmailService: () => EmailService,
36
37
  MAX_CHALLENGE_ATTEMPTS: () => MAX_CHALLENGE_ATTEMPTS,
37
38
  ProblemTypes: () => ProblemTypes,
@@ -2572,13 +2573,92 @@ var ResendAdapter = class {
2572
2573
  }
2573
2574
  };
2574
2575
 
2576
+ // src/lib/email/adapters/cloudflare-adapter.ts
2577
+ var CloudflareEmailAdapter = class {
2578
+ constructor(binding, options) {
2579
+ this.binding = binding;
2580
+ this.options = options;
2581
+ }
2582
+ binding;
2583
+ options;
2584
+ providerName = "cloudflare";
2585
+ async send(options) {
2586
+ if (this.options.dryRun) {
2587
+ logger_default.info("Cloudflare: Dry-run, email not sent", {
2588
+ provider: this.providerName,
2589
+ to: options.to,
2590
+ from: options.from,
2591
+ subject: options.subject
2592
+ });
2593
+ return { success: true, emailId: "dry-run" };
2594
+ }
2595
+ try {
2596
+ logger_default.info("Cloudflare: Sending email", {
2597
+ provider: this.providerName,
2598
+ to: options.to,
2599
+ subject: options.subject
2600
+ });
2601
+ const result = await this.binding.send({
2602
+ to: options.to,
2603
+ from: options.from,
2604
+ subject: options.subject,
2605
+ html: options.html,
2606
+ text: options.text,
2607
+ headers: options.tags ? Object.fromEntries(
2608
+ Object.entries(options.tags).map(([name, value]) => [`X-Tag-${name}`, value])
2609
+ ) : void 0
2610
+ });
2611
+ logger_default.info("Cloudflare: Email sent successfully", {
2612
+ provider: this.providerName,
2613
+ emailId: result.messageId,
2614
+ to: options.to
2615
+ });
2616
+ return { success: true, emailId: result.messageId };
2617
+ } catch (error) {
2618
+ const message = error instanceof Error ? error.message : "Unknown error";
2619
+ const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
2620
+ const errorText = code ? `${code}: ${message}` : message;
2621
+ if (code === "E_RECIPIENT_SUPPRESSED") {
2622
+ logger_default.warn("Cloudflare: Recipient is on the suppression list", {
2623
+ provider: this.providerName,
2624
+ to: options.to
2625
+ });
2626
+ } else {
2627
+ logger_default.error("Cloudflare send error", {
2628
+ provider: this.providerName,
2629
+ error: errorText,
2630
+ to: options.to,
2631
+ subject: options.subject
2632
+ });
2633
+ }
2634
+ return { success: false, error: errorText };
2635
+ }
2636
+ }
2637
+ };
2638
+
2575
2639
  // src/lib/email/adapters/factory.ts
2576
2640
  function createEmailAdapter(env) {
2577
- logger_default.info("Creating email adapter", { provider: "resend" });
2578
- if (!env.RESEND_API_KEY) {
2579
- throw new Error("RESEND_API_KEY is required");
2641
+ const provider = env.EMAIL_PROVIDER ?? "resend";
2642
+ logger_default.info("Creating email adapter", { provider });
2643
+ switch (provider) {
2644
+ case "resend":
2645
+ if (!env.RESEND_API_KEY) {
2646
+ throw new Error("RESEND_API_KEY is required");
2647
+ }
2648
+ return new ResendAdapter(env.RESEND_API_KEY);
2649
+ case "cloudflare": {
2650
+ if (!env.EMAIL) {
2651
+ throw new Error(
2652
+ 'EMAIL send_email binding is required when EMAIL_PROVIDER=cloudflare - add [[send_email]] name = "EMAIL" to wrangler.toml'
2653
+ );
2654
+ }
2655
+ const environment = env.ENVIRONMENT?.toLowerCase();
2656
+ const dryRun = environment !== "production" && environment !== "staging";
2657
+ return new CloudflareEmailAdapter(env.EMAIL, { dryRun });
2658
+ }
2659
+ default:
2660
+ throw new Error(`Unknown EMAIL_PROVIDER: ${String(provider)}`);
2580
2661
  }
2581
- return new ResendAdapter(env.RESEND_API_KEY);
2582
2662
  }
2583
2663
 
2584
2664
  // src/lib/email/email-service.ts
@@ -2586,6 +2666,9 @@ var EmailService = class {
2586
2666
  adapter;
2587
2667
  env;
2588
2668
  get fromAddress() {
2669
+ if (this.env.EMAIL_FROM) {
2670
+ return this.env.EMAIL_FROM;
2671
+ }
2589
2672
  const appName = this.env.APP_NAME ?? "Your App";
2590
2673
  const appUrl = this.env.APP_URL ?? "";
2591
2674
  let domain = "example.com";
@@ -3442,7 +3525,8 @@ var signupHandler = async (c) => {
3442
3525
  verificationUrl,
3443
3526
  firstName: newUser.name || void 0
3444
3527
  },
3445
- database
3528
+ database,
3529
+ schema.users
3446
3530
  );
3447
3531
  const fingerprint = await generateFingerprint(c.req.raw);
3448
3532
  const ipAddress = getClientIp(c.req.raw);
@@ -3916,7 +4000,7 @@ var forgotPasswordRoute = (0, import_zod_openapi7.createRoute)({
3916
4000
  });
3917
4001
  var forgotPasswordHandler = async (c) => {
3918
4002
  const { email, turnstileToken } = c.req.valid("json");
3919
- const database = c.get("db");
4003
+ const { db: database, schema } = getAuthContext(c);
3920
4004
  const env = c.env;
3921
4005
  const turnstileValid = await verifyTurnstileToken(
3922
4006
  turnstileToken,
@@ -3927,7 +4011,11 @@ var forgotPasswordHandler = async (c) => {
3927
4011
  if (!turnstileValid) {
3928
4012
  return problems.badRequest(c, "Invalid captcha");
3929
4013
  }
3930
- const resetResult = await createPasswordResetToken(database, email);
4014
+ const resetResult = await createPasswordResetToken(
4015
+ database,
4016
+ { users: schema.users, passwordResetTokens: schema.passwordResetTokens },
4017
+ email
4018
+ );
3931
4019
  if (!resetResult) {
3932
4020
  return c.json({ success: true });
3933
4021
  }
@@ -3939,7 +4027,8 @@ var forgotPasswordHandler = async (c) => {
3939
4027
  token: resetResult.token,
3940
4028
  resetUrl
3941
4029
  },
3942
- database
4030
+ database,
4031
+ schema.users
3943
4032
  );
3944
4033
  logSecurityEvent("password_reset_requested", "medium", { userId: resetResult.userId });
3945
4034
  return c.json({ success: true });
@@ -4081,7 +4170,7 @@ var changePasswordHandler = async (c) => {
4081
4170
  const userId = c.get("userId");
4082
4171
  if (!userId) return problems.unauthorized(c, "Not authenticated");
4083
4172
  const { currentPassword, newPassword } = c.req.valid("json");
4084
- const database = c.get("db");
4173
+ const { db: database, schema } = getAuthContext(c);
4085
4174
  const env = c.env;
4086
4175
  const passwordValidation = validatePassword(newPassword);
4087
4176
  if (!passwordValidation.valid) {
@@ -4098,7 +4187,8 @@ var changePasswordHandler = async (c) => {
4098
4187
  newPassword,
4099
4188
  currentSessionId,
4100
4189
  pepper: env.PASSWORD_PEPPER_V1,
4101
- db: database
4190
+ db: database,
4191
+ tables: { users: schema.users, sessions: schema.sessions }
4102
4192
  });
4103
4193
  if (!result.success) {
4104
4194
  return problems.badRequest(c, result.error || "Failed to change password");
@@ -4177,7 +4267,7 @@ var changeEmailHandler = async (c) => {
4177
4267
  const userId = c.get("userId");
4178
4268
  if (!userId) return problems.unauthorized(c, "Not authenticated");
4179
4269
  const { password, newEmail } = c.req.valid("json");
4180
- const database = c.get("db");
4270
+ const { db: database, schema } = getAuthContext(c);
4181
4271
  const env = c.env;
4182
4272
  const appUrl = env.APP_URL || "http://localhost:5173";
4183
4273
  const result = await requestEmailChange({
@@ -4185,7 +4275,8 @@ var changeEmailHandler = async (c) => {
4185
4275
  password,
4186
4276
  newEmail,
4187
4277
  pepper: env.PASSWORD_PEPPER_V1,
4188
- db: database
4278
+ db: database,
4279
+ tables: { users: schema.users, emailChangeTokens: schema.emailChangeTokens }
4189
4280
  });
4190
4281
  if (!result.success) {
4191
4282
  if (result.error === "Incorrect password") {
@@ -4197,7 +4288,8 @@ var changeEmailHandler = async (c) => {
4197
4288
  const confirmUrl = `${appUrl}/confirm-email-change?token=${result.confirmToken}`;
4198
4289
  const confirmResult = await emailService.sendEmailChangeConfirmation(
4199
4290
  { newEmail, confirmUrl },
4200
- database
4291
+ database,
4292
+ schema.users
4201
4293
  );
4202
4294
  if (!confirmResult.success) {
4203
4295
  return problems.badRequest(
@@ -4208,7 +4300,8 @@ var changeEmailHandler = async (c) => {
4208
4300
  const cancelUrl = `${appUrl}/cancel-email-change?token=${result.cancelToken}`;
4209
4301
  await emailService.sendEmailChangeNotification(
4210
4302
  { oldEmail: result.oldEmail, newEmail, cancelUrl },
4211
- database
4303
+ database,
4304
+ schema.users
4212
4305
  );
4213
4306
  logSecurityEvent("email_change_requested", "medium", { userId });
4214
4307
  return c.json({ success: true });
@@ -4249,8 +4342,12 @@ var confirmEmailChangeRoute = (0, import_zod_openapi13.createRoute)({
4249
4342
  });
4250
4343
  var confirmEmailChangeHandler = async (c) => {
4251
4344
  const { token } = c.req.valid("json");
4252
- const database = c.get("db");
4253
- const result = await confirmEmailChange({ token, db: database });
4345
+ const { db: database, schema } = getAuthContext(c);
4346
+ const result = await confirmEmailChange({
4347
+ token,
4348
+ db: database,
4349
+ tables: { users: schema.users, emailChangeTokens: schema.emailChangeTokens }
4350
+ });
4254
4351
  if (!result.success) {
4255
4352
  return problems.badRequest(c, result.error || "Invalid or expired token");
4256
4353
  }
@@ -4291,8 +4388,12 @@ var cancelEmailChangeRoute = (0, import_zod_openapi15.createRoute)({
4291
4388
  });
4292
4389
  var cancelEmailChangeHandler = async (c) => {
4293
4390
  const { token } = c.req.valid("json");
4294
- const database = c.get("db");
4295
- const result = await cancelEmailChange({ token, db: database });
4391
+ const { db: database, schema } = getAuthContext(c);
4392
+ const result = await cancelEmailChange({
4393
+ token,
4394
+ db: database,
4395
+ tables: { emailChangeTokens: schema.emailChangeTokens }
4396
+ });
4296
4397
  if (!result.success) {
4297
4398
  return problems.badRequest(c, result.error || "Invalid or expired token");
4298
4399
  }
@@ -4431,7 +4532,7 @@ var refreshHandler = async (c) => {
4431
4532
  if (!session) {
4432
4533
  return problems.unauthorized(c, "Invalid or expired refresh token");
4433
4534
  }
4434
- await deleteSession(db, session.id);
4535
+ await deleteSession(db, { sessions: schema.sessions }, session.id);
4435
4536
  const newSessionId = await createSession(
4436
4537
  db,
4437
4538
  { sessions: schema.sessions },
@@ -4522,7 +4623,8 @@ var resendVerificationHandler = async (c) => {
4522
4623
  verificationUrl,
4523
4624
  firstName: user.name || void 0
4524
4625
  },
4525
- db
4626
+ db,
4627
+ schema.users
4526
4628
  );
4527
4629
  logger_default.info("Verification email resent", { userId: user.id, email: user.email });
4528
4630
  return c.json({
@@ -4848,7 +4950,8 @@ var totpVerifyHandler = async (c) => {
4848
4950
  const firstName = user.name?.split(" ")[0] || null;
4849
4951
  await emailService.send2faEnabledEmail(
4850
4952
  { email: user.email, firstName, method: "totp" },
4851
- db
4953
+ db,
4954
+ schema.users
4852
4955
  );
4853
4956
  }
4854
4957
  logSecurityEvent("2fa_totp_enrolled", "high", { userId });
@@ -4905,12 +5008,12 @@ var totpDisableHandler = async (c) => {
4905
5008
  if (remaining.length === 0) {
4906
5009
  await db.delete(schema.userBackupCodes).where((0, import_drizzle_orm23.eq)(schema.userBackupCodes.userId, userId));
4907
5010
  await db.delete(schema.userTrustedDevices).where((0, import_drizzle_orm23.eq)(schema.userTrustedDevices.userId, userId));
4908
- await invalidateAllUserSessions(db, userId);
5011
+ await invalidateAllUserSessions(db, { sessions: schema.sessions, users: schema.users }, userId);
4909
5012
  const [user] = await db.select({ email: schema.users.email, name: schema.users.name }).from(schema.users).where((0, import_drizzle_orm23.eq)(schema.users.id, userId)).limit(1);
4910
5013
  if (user) {
4911
5014
  const emailService = new EmailService(env);
4912
5015
  const firstName = user.name?.split(" ")[0] || null;
4913
- await emailService.send2faDisabledEmail({ email: user.email, firstName }, db);
5016
+ await emailService.send2faDisabledEmail({ email: user.email, firstName }, db, schema.users);
4914
5017
  }
4915
5018
  logSecurityEvent("2fa_disabled", "critical", { userId, lastMethod: "totp" });
4916
5019
  return c.json({ success: true, sessionInvalidated: true });
@@ -4966,7 +5069,7 @@ var emailSetupHandler = async (c) => {
4966
5069
  await env.OAUTH_STATES.put(`email_2fa_setup:${userId}`, codeHash, { expirationTtl: 300 });
4967
5070
  const emailService = new EmailService(env);
4968
5071
  const firstName = user.name?.split(" ")[0] || null;
4969
- await emailService.send2faCodeEmail({ email: user.email, firstName, code }, db);
5072
+ await emailService.send2faCodeEmail({ email: user.email, firstName, code }, db, schema.users);
4970
5073
  logSecurityEvent("2fa_email_setup_initiated", "low", { userId });
4971
5074
  return c.json({ success: true });
4972
5075
  };
@@ -5047,7 +5150,8 @@ var emailVerifyHandler = async (c) => {
5047
5150
  const firstName = user.name?.split(" ")[0] || null;
5048
5151
  await emailService.send2faEnabledEmail(
5049
5152
  { email: user.email, firstName, method: "email" },
5050
- db
5153
+ db,
5154
+ schema.users
5051
5155
  );
5052
5156
  }
5053
5157
  logSecurityEvent("2fa_email_enrolled", "high", { userId });
@@ -5101,7 +5205,8 @@ var emailSendCodeHandler = async (c) => {
5101
5205
  const firstName = user.name?.split(" ")[0] || null;
5102
5206
  const emailResult = await emailService.send2faCodeEmail(
5103
5207
  { email: user.email, firstName, code },
5104
- db
5208
+ db,
5209
+ schema.users
5105
5210
  );
5106
5211
  if (!emailResult.success) {
5107
5212
  return problems.badRequest(
@@ -5163,12 +5268,12 @@ var emailDisableHandler = async (c) => {
5163
5268
  if (remaining.length === 0) {
5164
5269
  await db.delete(schema.userBackupCodes).where((0, import_drizzle_orm27.eq)(schema.userBackupCodes.userId, userId));
5165
5270
  await db.delete(schema.userTrustedDevices).where((0, import_drizzle_orm27.eq)(schema.userTrustedDevices.userId, userId));
5166
- await invalidateAllUserSessions(db, userId);
5271
+ await invalidateAllUserSessions(db, { sessions: schema.sessions, users: schema.users }, userId);
5167
5272
  const [user] = await db.select({ email: schema.users.email, name: schema.users.name }).from(schema.users).where((0, import_drizzle_orm27.eq)(schema.users.id, userId)).limit(1);
5168
5273
  if (user) {
5169
5274
  const emailService = new EmailService(env);
5170
5275
  const firstName = user.name?.split(" ")[0] || null;
5171
- await emailService.send2faDisabledEmail({ email: user.email, firstName }, db);
5276
+ await emailService.send2faDisabledEmail({ email: user.email, firstName }, db, schema.users);
5172
5277
  }
5173
5278
  logSecurityEvent("2fa_disabled", "critical", { userId, lastMethod: "email" });
5174
5279
  return c.json({ success: true, sessionInvalidated: true });
@@ -5489,7 +5594,7 @@ var challengeResendHandler = async (c) => {
5489
5594
  });
5490
5595
  const emailService = new EmailService(c.env);
5491
5596
  const firstName = user.name?.split(" ")[0] || null;
5492
- await emailService.send2faCodeEmail({ email: user.email, firstName, code }, db);
5597
+ await emailService.send2faCodeEmail({ email: user.email, firstName, code }, db, schema.users);
5493
5598
  logger_default.info("Email 2FA code resent", { userId: payload.userId });
5494
5599
  return c.json({ success: true });
5495
5600
  };
@@ -6013,6 +6118,7 @@ function verifyWebhookSignature(payload, signature, secret) {
6013
6118
  0 && (module.exports = {
6014
6119
  AUTH_DEFAULTS,
6015
6120
  CHALLENGE_TTL_MS,
6121
+ CloudflareEmailAdapter,
6016
6122
  EmailService,
6017
6123
  MAX_CHALLENGE_ATTEMPTS,
6018
6124
  ProblemTypes,