@syncello/auth 3.5.0 → 3.6.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 +82 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -1
- package/dist/index.d.ts +47 -1
- package/dist/index.js +82 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1341,12 +1341,13 @@ var import_drizzle_orm4 = require("drizzle-orm");
|
|
|
1341
1341
|
|
|
1342
1342
|
// src/factory.ts
|
|
1343
1343
|
function createAuth(config) {
|
|
1344
|
-
const { db, schema, getEnv, emailBranding } = config;
|
|
1344
|
+
const { db, schema, getEnv, emailBranding, invitations } = config;
|
|
1345
1345
|
const getContext = (c) => ({
|
|
1346
1346
|
db,
|
|
1347
1347
|
schema,
|
|
1348
1348
|
env: getEnv(c),
|
|
1349
|
-
emailBranding
|
|
1349
|
+
emailBranding,
|
|
1350
|
+
invitations
|
|
1350
1351
|
});
|
|
1351
1352
|
return {
|
|
1352
1353
|
db,
|
|
@@ -3324,7 +3325,10 @@ var signupRequestSchema = import_zod_openapi.z.object({
|
|
|
3324
3325
|
name: import_zod_openapi.z.string().min(1).optional().openapi({ example: "John Doe" }),
|
|
3325
3326
|
turnstileToken: import_zod_openapi.z.string().optional().openapi({ example: "XXXX.DUMMY.TOKEN.XXXX" }),
|
|
3326
3327
|
// OAuth signup completion token (from /signup?oauth_token=...)
|
|
3327
|
-
oauthToken: import_zod_openapi.z.string().optional().openapi({ example: "a1b2c3d4-..." })
|
|
3328
|
+
oauthToken: import_zod_openapi.z.string().optional().openapi({ example: "a1b2c3d4-..." }),
|
|
3329
|
+
// App invitation token (from /signup?token=...). When it validates for this
|
|
3330
|
+
// email, the account is created already-verified with no verification email.
|
|
3331
|
+
invitationToken: import_zod_openapi.z.string().optional().openapi({ example: "iIB8tDJJ..." })
|
|
3328
3332
|
}).openapi("SignupRequest");
|
|
3329
3333
|
var loginRequestSchema = import_zod_openapi.z.object({
|
|
3330
3334
|
email: import_zod_openapi.z.string().email().openapi({ example: "user@example.com" }),
|
|
@@ -3454,8 +3458,8 @@ var signupRoute = (0, import_zod_openapi2.createRoute)({
|
|
|
3454
3458
|
}
|
|
3455
3459
|
});
|
|
3456
3460
|
var signupHandler = async (c) => {
|
|
3457
|
-
const { email, password, name, turnstileToken, oauthToken } = c.req.valid("json");
|
|
3458
|
-
const { db: database, schema, emailBranding } = getAuthContext(c);
|
|
3461
|
+
const { email, password, name, turnstileToken, oauthToken, invitationToken } = c.req.valid("json");
|
|
3462
|
+
const { db: database, schema, emailBranding, invitations } = getAuthContext(c);
|
|
3459
3463
|
const env = c.env;
|
|
3460
3464
|
if (oauthToken) {
|
|
3461
3465
|
const stored = await env.OAUTH_STATES.get(`oauth:signup:${oauthToken}`);
|
|
@@ -3537,40 +3541,74 @@ var signupHandler = async (c) => {
|
|
|
3537
3541
|
return problems.badRequest(c, passwordValidation.error || "Invalid password");
|
|
3538
3542
|
}
|
|
3539
3543
|
const hashedPassword = await hashPassword(password, env.PASSWORD_PEPPER_V1);
|
|
3544
|
+
let invitedVerified = false;
|
|
3545
|
+
if (invitationToken && invitations) {
|
|
3546
|
+
const match = await invitations.verify({
|
|
3547
|
+
token: invitationToken,
|
|
3548
|
+
email: email.toLowerCase(),
|
|
3549
|
+
db: database,
|
|
3550
|
+
env
|
|
3551
|
+
});
|
|
3552
|
+
if (!match || match.email.toLowerCase() !== email.toLowerCase()) {
|
|
3553
|
+
return problems.badRequest(
|
|
3554
|
+
c,
|
|
3555
|
+
"This invitation is invalid, expired, or for a different email address"
|
|
3556
|
+
);
|
|
3557
|
+
}
|
|
3558
|
+
invitedVerified = true;
|
|
3559
|
+
}
|
|
3540
3560
|
const [newUser] = await database.insert(schema.users).values({
|
|
3541
3561
|
email: email.toLowerCase(),
|
|
3542
3562
|
name: name || null,
|
|
3543
3563
|
hashedPassword,
|
|
3544
3564
|
pepperKid: "v1",
|
|
3545
|
-
emailVerified:
|
|
3565
|
+
emailVerified: invitedVerified,
|
|
3546
3566
|
activatedAt: /* @__PURE__ */ new Date()
|
|
3547
3567
|
}).returning();
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
expiresAt,
|
|
3556
|
-
createdAt: Date.now()
|
|
3557
|
-
});
|
|
3558
|
-
const emailService = new EmailService(env, void 0, emailBranding);
|
|
3559
|
-
const verificationUrl = `${env.APP_URL}/verify-email?token=${emailToken}`;
|
|
3560
|
-
await emailService.sendVerificationEmail(
|
|
3561
|
-
{
|
|
3568
|
+
if (!invitedVerified) {
|
|
3569
|
+
const emailToken = generateSecureToken(32);
|
|
3570
|
+
const tokenHash = await hashToken(emailToken);
|
|
3571
|
+
const expiresAt = Date.now() + 24 * 60 * 60 * 1e3;
|
|
3572
|
+
await database.insert(schema.emailVerificationTokens).values({
|
|
3573
|
+
tokenHash,
|
|
3574
|
+
userId: newUser.id,
|
|
3562
3575
|
email: newUser.email,
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3576
|
+
expiresAt,
|
|
3577
|
+
createdAt: Date.now()
|
|
3578
|
+
});
|
|
3579
|
+
const emailService = new EmailService(env, void 0, emailBranding);
|
|
3580
|
+
const verificationUrl = `${env.APP_URL}/verify-email?token=${emailToken}`;
|
|
3581
|
+
await emailService.sendVerificationEmail(
|
|
3582
|
+
{
|
|
3583
|
+
email: newUser.email,
|
|
3584
|
+
token: emailToken,
|
|
3585
|
+
verificationUrl,
|
|
3586
|
+
firstName: newUser.name || void 0
|
|
3587
|
+
},
|
|
3588
|
+
database,
|
|
3589
|
+
schema.users
|
|
3590
|
+
);
|
|
3591
|
+
}
|
|
3592
|
+
if (invitedVerified && invitationToken && invitations?.onAccepted) {
|
|
3593
|
+
try {
|
|
3594
|
+
await invitations.onAccepted({
|
|
3595
|
+
token: invitationToken,
|
|
3596
|
+
user: { id: newUser.id, email: newUser.email, name: newUser.name },
|
|
3597
|
+
db: database,
|
|
3598
|
+
env
|
|
3599
|
+
});
|
|
3600
|
+
} catch (err) {
|
|
3601
|
+
logError(err, { context: "signup_invitation_onAccepted_failed", userId: newUser.id });
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3570
3604
|
const fingerprint = await generateFingerprint(c.req.raw);
|
|
3571
3605
|
const ipAddress = getClientIp(c.req.raw);
|
|
3572
3606
|
const sessionId = await createSession(database, { sessions: schema.sessions }, newUser.id, fingerprint, ipAddress);
|
|
3573
|
-
logSecurityEvent("user_signup", "low", {
|
|
3607
|
+
logSecurityEvent("user_signup", "low", {
|
|
3608
|
+
userId: newUser.id,
|
|
3609
|
+
email: newUser.email,
|
|
3610
|
+
invited: invitedVerified
|
|
3611
|
+
});
|
|
3574
3612
|
c.header("Set-Cookie", setSessionCookie(sessionId, c.env), { append: true });
|
|
3575
3613
|
return c.json({
|
|
3576
3614
|
user: {
|
|
@@ -3612,6 +3650,16 @@ function generateEmailOtp() {
|
|
|
3612
3650
|
const num = (bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]) >>> 0;
|
|
3613
3651
|
return String(num % 1e6).padStart(6, "0");
|
|
3614
3652
|
}
|
|
3653
|
+
var EMAIL_CODE_TTL_SECONDS = 300;
|
|
3654
|
+
async function sendEmailChallengeCode(env, database, usersTable, user, emailBranding) {
|
|
3655
|
+
const code = generateEmailOtp();
|
|
3656
|
+
await env.OAUTH_STATES.put(`email_2fa_challenge:${user.id}`, await hashToken(code), {
|
|
3657
|
+
expirationTtl: EMAIL_CODE_TTL_SECONDS
|
|
3658
|
+
});
|
|
3659
|
+
const emailService = new EmailService(env, void 0, emailBranding);
|
|
3660
|
+
const firstName = user.name?.split(" ")[0] || null;
|
|
3661
|
+
await emailService.send2faCodeEmail({ email: user.email, firstName, code }, database, usersTable);
|
|
3662
|
+
}
|
|
3615
3663
|
async function verifyAny2faCode(database, env, userId, code, method, tables2) {
|
|
3616
3664
|
const { user2faMethods: user2faMethodsTable, userBackupCodes: userBackupCodesTable } = tables2;
|
|
3617
3665
|
if (method === "totp") {
|
|
@@ -3696,7 +3744,7 @@ var loginRoute = (0, import_zod_openapi3.createRoute)({
|
|
|
3696
3744
|
});
|
|
3697
3745
|
var loginHandler = async (c) => {
|
|
3698
3746
|
const { email, password, turnstileToken } = c.req.valid("json");
|
|
3699
|
-
const { db: database, schema } = getAuthContext(c);
|
|
3747
|
+
const { db: database, schema, emailBranding } = getAuthContext(c);
|
|
3700
3748
|
const env = c.env;
|
|
3701
3749
|
const turnstileValid = await verifyTurnstileToken(
|
|
3702
3750
|
turnstileToken,
|
|
@@ -3781,6 +3829,10 @@ var loginHandler = async (c) => {
|
|
|
3781
3829
|
};
|
|
3782
3830
|
await storeChallengeToken(env.OAUTH_STATES, challengeToken, challengePayload);
|
|
3783
3831
|
setChallengeCookie(c, env, challengeToken);
|
|
3832
|
+
const emailMethod = enabled2faMethods.find((m) => m.method === "email");
|
|
3833
|
+
if (emailMethod && (emailMethod.isPrimary || enabled2faMethods.length === 1)) {
|
|
3834
|
+
await sendEmailChallengeCode(env, database, schema.users, user, emailBranding);
|
|
3835
|
+
}
|
|
3784
3836
|
return c.json({
|
|
3785
3837
|
requires2fa: true,
|
|
3786
3838
|
methods: enabled2faMethods.map((m) => m.method)
|
|
@@ -5637,14 +5689,7 @@ var challengeResendHandler = async (c) => {
|
|
|
5637
5689
|
logger_default.error("User not found during 2FA resend", { userId: payload.userId });
|
|
5638
5690
|
return problems.notFound(c, "User not found");
|
|
5639
5691
|
}
|
|
5640
|
-
|
|
5641
|
-
const codeHash = await hashToken(code);
|
|
5642
|
-
await c.env.OAUTH_STATES.put(`email_2fa_challenge:${payload.userId}`, codeHash, {
|
|
5643
|
-
expirationTtl: 300
|
|
5644
|
-
});
|
|
5645
|
-
const emailService = new EmailService(c.env, void 0, emailBranding);
|
|
5646
|
-
const firstName = user.name?.split(" ")[0] || null;
|
|
5647
|
-
await emailService.send2faCodeEmail({ email: user.email, firstName, code }, db, schema.users);
|
|
5692
|
+
await sendEmailChallengeCode(c.env, db, schema.users, { id: payload.userId, ...user }, emailBranding);
|
|
5648
5693
|
logger_default.info("Email 2FA code resent", { userId: payload.userId });
|
|
5649
5694
|
return c.json({ success: true });
|
|
5650
5695
|
};
|