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