@syncello/auth 3.4.1 → 3.6.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 +112 -75
- 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 +112 -75
- 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: {
|
|
@@ -5738,9 +5776,10 @@ var PROVIDERS = {
|
|
|
5738
5776
|
authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
5739
5777
|
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
5740
5778
|
userinfoUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
5741
|
-
scopes: ["email"],
|
|
5779
|
+
scopes: ["email", "profile"],
|
|
5742
5780
|
emailExtractor: (data) => data.email || null,
|
|
5743
|
-
userIdExtractor: (data) => data.id || null
|
|
5781
|
+
userIdExtractor: (data) => data.id || null,
|
|
5782
|
+
nameExtractor: (data) => data.name || null
|
|
5744
5783
|
},
|
|
5745
5784
|
microsoft: {
|
|
5746
5785
|
authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
|
@@ -5748,7 +5787,8 @@ var PROVIDERS = {
|
|
|
5748
5787
|
userinfoUrl: "https://graph.microsoft.com/v1.0/me",
|
|
5749
5788
|
scopes: ["openid", "email", "User.Read"],
|
|
5750
5789
|
emailExtractor: (data) => data.mail || data.userPrincipalName || null,
|
|
5751
|
-
userIdExtractor: (data) => data.id || null
|
|
5790
|
+
userIdExtractor: (data) => data.id || null,
|
|
5791
|
+
nameExtractor: (data) => data.displayName || null
|
|
5752
5792
|
}
|
|
5753
5793
|
};
|
|
5754
5794
|
function getProviderCredentials(provider, env) {
|
|
@@ -5985,6 +6025,7 @@ var callbackHandler = async (c) => {
|
|
|
5985
6025
|
const userInfo = await userinfoResponse.json();
|
|
5986
6026
|
const email = providerConfig.emailExtractor(userInfo)?.toLowerCase();
|
|
5987
6027
|
const providerUserId = providerConfig.userIdExtractor(userInfo);
|
|
6028
|
+
const name = providerConfig.nameExtractor(userInfo);
|
|
5988
6029
|
if (!email) {
|
|
5989
6030
|
logger_default.warn("No email returned from OAuth provider", { provider });
|
|
5990
6031
|
return c.redirect(`${appUrl}/login?error=oauth_no_email`);
|
|
@@ -5995,53 +6036,49 @@ var callbackHandler = async (c) => {
|
|
|
5995
6036
|
}
|
|
5996
6037
|
const { db, schema } = getAuthContext(c);
|
|
5997
6038
|
const [existingUser] = await db.select({ id: schema.users.id, email: schema.users.email }).from(schema.users).where((0, import_drizzle_orm32.eq)(schema.users.email, email)).limit(1);
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
await
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
userId: existingUser.id,
|
|
6014
|
-
email: existingUser.email,
|
|
6039
|
+
let authUser = existingUser;
|
|
6040
|
+
if (!authUser) {
|
|
6041
|
+
const [createdUser] = await db.insert(schema.users).values({
|
|
6042
|
+
email,
|
|
6043
|
+
name: name || null,
|
|
6044
|
+
hashedPassword: null,
|
|
6045
|
+
pepperKid: null,
|
|
6046
|
+
emailVerified: true,
|
|
6047
|
+
// Provider verified the email
|
|
6048
|
+
activatedAt: /* @__PURE__ */ new Date()
|
|
6049
|
+
}).returning({ id: schema.users.id, email: schema.users.email });
|
|
6050
|
+
authUser = createdUser;
|
|
6051
|
+
logSecurityEvent("user_signup_oauth", "low", {
|
|
6052
|
+
userId: createdUser.id,
|
|
6053
|
+
email: createdUser.email,
|
|
6015
6054
|
provider
|
|
6016
6055
|
});
|
|
6017
|
-
if (mobileRedirectUri && isDeepLinkUri(mobileRedirectUri)) {
|
|
6018
|
-
const mobileUrl = new URL(mobileRedirectUri);
|
|
6019
|
-
mobileUrl.searchParams.set("code", loginCode);
|
|
6020
|
-
mobileUrl.searchParams.set("action", "login");
|
|
6021
|
-
return c.redirect(mobileUrl.toString());
|
|
6022
|
-
}
|
|
6023
|
-
return c.redirect(`${appUrl}/auth/callback?code=${loginCode}&action=login`);
|
|
6024
|
-
} else {
|
|
6025
|
-
const signupToken = crypto.randomUUID();
|
|
6026
|
-
await c.env.OAUTH_STATES.put(
|
|
6027
|
-
`oauth:signup:${signupToken}`,
|
|
6028
|
-
JSON.stringify({ email, provider, providerUserId, invitationToken }),
|
|
6029
|
-
{ expirationTtl: 600 }
|
|
6030
|
-
);
|
|
6031
|
-
logger_default.info("OAuth signup initiated", { provider, email });
|
|
6032
|
-
if (mobileRedirectUri && isDeepLinkUri(mobileRedirectUri)) {
|
|
6033
|
-
const mobileUrl = new URL(mobileRedirectUri);
|
|
6034
|
-
mobileUrl.searchParams.set("oauth_token", signupToken);
|
|
6035
|
-
mobileUrl.searchParams.set("email", email);
|
|
6036
|
-
if (invitationToken) {
|
|
6037
|
-
mobileUrl.searchParams.set("invitation", invitationToken);
|
|
6038
|
-
}
|
|
6039
|
-
return c.redirect(mobileUrl.toString());
|
|
6040
|
-
}
|
|
6041
|
-
let signupUrl = `${appUrl}/signup?oauth_token=${signupToken}&email=${encodeURIComponent(email)}`;
|
|
6042
|
-
if (invitationToken) signupUrl += `&invitation=${invitationToken}`;
|
|
6043
|
-
return c.redirect(signupUrl);
|
|
6044
6056
|
}
|
|
6057
|
+
const loginCode = crypto.randomUUID();
|
|
6058
|
+
await c.env.OAUTH_STATES.put(
|
|
6059
|
+
`oauth:login:${loginCode}`,
|
|
6060
|
+
JSON.stringify({
|
|
6061
|
+
userId: authUser.id,
|
|
6062
|
+
email: authUser.email,
|
|
6063
|
+
provider,
|
|
6064
|
+
providerUserId,
|
|
6065
|
+
redirect: loginRedirect,
|
|
6066
|
+
invitationToken
|
|
6067
|
+
}),
|
|
6068
|
+
{ expirationTtl: 60 }
|
|
6069
|
+
);
|
|
6070
|
+
logSecurityEvent("oauth_login_initiated", "low", {
|
|
6071
|
+
userId: authUser.id,
|
|
6072
|
+
email: authUser.email,
|
|
6073
|
+
provider
|
|
6074
|
+
});
|
|
6075
|
+
if (mobileRedirectUri && isDeepLinkUri(mobileRedirectUri)) {
|
|
6076
|
+
const mobileUrl = new URL(mobileRedirectUri);
|
|
6077
|
+
mobileUrl.searchParams.set("code", loginCode);
|
|
6078
|
+
mobileUrl.searchParams.set("action", "login");
|
|
6079
|
+
return c.redirect(mobileUrl.toString());
|
|
6080
|
+
}
|
|
6081
|
+
return c.redirect(`${appUrl}/auth/callback?code=${loginCode}&action=login`);
|
|
6045
6082
|
} catch (err) {
|
|
6046
6083
|
logError(err, { context: "oauth_callback", provider });
|
|
6047
6084
|
return c.redirect(`${appUrl}/login?error=oauth_failed`);
|