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