@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.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
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
3862
|
-
const result = await confirmEmailChange({
|
|
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
|
|
3904
|
-
const result = await cancelEmailChange({
|
|
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,
|