@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 +135 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -110
- package/dist/index.d.ts +108 -110
- package/dist/index.js +134 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
4253
|
-
const result = await confirmEmailChange({
|
|
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
|
|
4295
|
-
const result = await cancelEmailChange({
|
|
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,
|