@syncello/auth 3.5.0 → 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.d.cts CHANGED
@@ -318,6 +318,46 @@ interface AuthDatabase {
318
318
  delete: (...args: any[]) => any;
319
319
  query: Record<string, any>;
320
320
  }
321
+ /** A newly-created user, passed to invitation hooks. */
322
+ interface InvitationHookUser {
323
+ id: string;
324
+ email: string;
325
+ name: string | null;
326
+ }
327
+ /**
328
+ * App-owned invitation hooks. The library stays agnostic of any specific
329
+ * invitation model — the consuming app validates the token and links the user.
330
+ *
331
+ * When `/signup` (or OAuth signup) is called with an `invitationToken`, the
332
+ * library calls `verify`. If it returns a matching email, the account is created
333
+ * with `emailVerified: true` and NO verification email is sent — the token, which
334
+ * was delivered to that address, is itself the proof of ownership (the same trust
335
+ * model as OAuth-provider signup). `onAccepted` then runs so the app can link the
336
+ * user (e.g. create a team membership) atomically with signup.
337
+ */
338
+ interface InvitationHooks<TEnv = Record<string, unknown>> {
339
+ /**
340
+ * Validate that `token` is a live invitation for `email`. Return the invited
341
+ * email (normalized) to auto-verify the new account, or `null` to reject the
342
+ * token. MUST confirm the token matches this email — returning a non-matching
343
+ * email is treated as a rejection by the caller.
344
+ */
345
+ verify(args: {
346
+ token: string;
347
+ email: string;
348
+ db: AuthDatabase;
349
+ env: TEnv;
350
+ }): Promise<{
351
+ email: string;
352
+ } | null>;
353
+ /** Link the just-created, verified user (create membership, mark the invite accepted). */
354
+ onAccepted?(args: {
355
+ token: string;
356
+ user: InvitationHookUser;
357
+ db: AuthDatabase;
358
+ env: TEnv;
359
+ }): Promise<void>;
360
+ }
321
361
  /**
322
362
  * Configuration for createAuth
323
363
  */
@@ -330,6 +370,8 @@ interface AuthConfig<TEnv = Record<string, unknown>> {
330
370
  getEnv: (c: Context) => TEnv;
331
371
  /** Optional email branding tokens */
332
372
  emailBranding?: EmailBranding;
373
+ /** Optional app-owned invitation hooks for token-verified signup. */
374
+ invitations?: InvitationHooks<TEnv>;
333
375
  }
334
376
  /**
335
377
  * Auth context available in all auth operations.
@@ -340,6 +382,7 @@ interface AuthContext<TEnv = Record<string, unknown>> {
340
382
  schema: AuthSchema;
341
383
  env: TEnv;
342
384
  emailBranding?: EmailBranding;
385
+ invitations?: InvitationHooks<TEnv>;
343
386
  }
344
387
  /**
345
388
  * Creates an auth instance with injected database and schema.
@@ -1662,6 +1705,7 @@ declare const authRoutes: OpenAPIHono<{
1662
1705
  name?: string | undefined;
1663
1706
  turnstileToken?: string | undefined;
1664
1707
  oauthToken?: string | undefined;
1708
+ invitationToken?: string | undefined;
1665
1709
  };
1666
1710
  };
1667
1711
  output: {
@@ -1683,6 +1727,7 @@ declare const authRoutes: OpenAPIHono<{
1683
1727
  name?: string | undefined;
1684
1728
  turnstileToken?: string | undefined;
1685
1729
  oauthToken?: string | undefined;
1730
+ invitationToken?: string | undefined;
1686
1731
  };
1687
1732
  };
1688
1733
  output: {
@@ -1704,6 +1749,7 @@ declare const authRoutes: OpenAPIHono<{
1704
1749
  name?: string | undefined;
1705
1750
  turnstileToken?: string | undefined;
1706
1751
  oauthToken?: string | undefined;
1752
+ invitationToken?: string | undefined;
1707
1753
  };
1708
1754
  };
1709
1755
  output: {
@@ -2700,4 +2746,4 @@ declare function calculateBounceRate(db: PostgresJsDatabase<Record<string, unkno
2700
2746
  */
2701
2747
  declare function calculateComplaintRate(db: PostgresJsDatabase<Record<string, unknown>>, emailEventsTable: EmailEventsTable, hoursAgo?: number): Promise<number>;
2702
2748
 
2703
- export { AUTH_DEFAULTS, type AccountLockoutTables, type Auth, type AuthConfig, type AuthContext, type AuthDatabase, type AuthSchema, CHALLENGE_TTL_MS, type ChallengePayload, CloudflareEmailAdapter, type DeviceType, type EmailAdapter, type EmailBranding, type EmailChangeConfirmationData, type EmailChangeNotificationData, type EmailChangeTables, type EmailSendOptions, type EmailSendResult, EmailService, type EmailTemplate, type EmailType, type Env, type InferNewSession, type InferNewUser, type InferSession, type InferUser, MAX_CHALLENGE_ATTEMPTS, type PasswordResetEmailData, type ProblemDetails, ProblemTypes, ResendAdapter, type ResendEventType, type ResendWebhookPayload, type SendEmailBinding, type SendEmailResult, type SessionData, type SessionTables, TRUSTED_DEVICE_COOKIE_BASE, TRUSTED_DEVICE_TTL_MS, type TotpVerifyResult, type TwoFactorCodeEmailData, type TwoFactorDisabledEmailData, type TwoFactorEnabledEmailData, type ValidationResult, type Variables, type VerificationEmailData, authRoutes, calculateBounceRate, calculateComplaintRate, cancelEmailChange, checkAccountLocked, checkBreachedPassword, clearAccountLockout, clearChallengeCookie, clearSessionCookie, clearTrustedDeviceCookie, confirmEmailChange, createAuth, createAuthMiddleware, createChallengeToken, createDeviceToken, createEmailAdapter, createProblemDetails, createSession, csrf, decryptTotpSecret, deleteAllUserSessions, deleteChallengeToken, deleteSession, encryptTotpSecret, extractAppUrl, formatBackupCode, generate2faCodeEmail, generate2faDisabledEmail, generate2faEnabledEmail, generateApiKey, generateBackupCodes, generateCodeChallenge, generateCodeVerifier, generateEmailChangeConfirmation, generateEmailChangeNotification, generateFingerprint, generatePasswordResetEmail, generateQrCodeUri, generateSecureToken, generateTotpSecret, generateTraceId, generateVerificationEmail, getAllowedOrigins, getAuthContext, getChallengeCookieName, getClientIp, getKeyPrefix, getMinutesUntilUnlock, getSessionCookieName, getTotpCounter, getTrustedDeviceCookieName, handleWebhookEvent, hashApiKey, hashBackupCode, hashPassword, hashToken, incrementFailedAttempts, invalidateAllUserSessions, isCapacitorApp, isCloudflarePreviewUrl, isDeepLinkUri, isValidApiKeyFormat, logError, logSecurityEvent, logger, normalizeBackupCode, optionalAuth, parseDeviceName, parseDeviceType, problemJson, problems, rateLimit, refreshSession, requestEmailChange, requireAuth, requireVerifiedEmail, retrieveChallengeToken, setChallengeCookie, setSessionCookie, setTrustedDeviceCookie, storeChallengeToken, updateChallengeAttempts, validateChallengePayload, validateOrigin, validatePassword, validatePasswordWithBreachCheck, validateSession, validateTrustedDevice, verifyBackupCode, verifyCodeChallenge, verifyPassword, verifyPasswordWithRotation, verifyTotpCode, verifyTurnstileToken, verifyWebhookSignature };
2749
+ export { AUTH_DEFAULTS, type AccountLockoutTables, type Auth, type AuthConfig, type AuthContext, type AuthDatabase, type AuthSchema, CHALLENGE_TTL_MS, type ChallengePayload, CloudflareEmailAdapter, type DeviceType, type EmailAdapter, type EmailBranding, type EmailChangeConfirmationData, type EmailChangeNotificationData, type EmailChangeTables, type EmailSendOptions, type EmailSendResult, EmailService, type EmailTemplate, type EmailType, type Env, type InferNewSession, type InferNewUser, type InferSession, type InferUser, type InvitationHookUser, type InvitationHooks, MAX_CHALLENGE_ATTEMPTS, type PasswordResetEmailData, type ProblemDetails, ProblemTypes, ResendAdapter, type ResendEventType, type ResendWebhookPayload, type SendEmailBinding, type SendEmailResult, type SessionData, type SessionTables, TRUSTED_DEVICE_COOKIE_BASE, TRUSTED_DEVICE_TTL_MS, type TotpVerifyResult, type TwoFactorCodeEmailData, type TwoFactorDisabledEmailData, type TwoFactorEnabledEmailData, type ValidationResult, type Variables, type VerificationEmailData, authRoutes, calculateBounceRate, calculateComplaintRate, cancelEmailChange, checkAccountLocked, checkBreachedPassword, clearAccountLockout, clearChallengeCookie, clearSessionCookie, clearTrustedDeviceCookie, confirmEmailChange, createAuth, createAuthMiddleware, createChallengeToken, createDeviceToken, createEmailAdapter, createProblemDetails, createSession, csrf, decryptTotpSecret, deleteAllUserSessions, deleteChallengeToken, deleteSession, encryptTotpSecret, extractAppUrl, formatBackupCode, generate2faCodeEmail, generate2faDisabledEmail, generate2faEnabledEmail, generateApiKey, generateBackupCodes, generateCodeChallenge, generateCodeVerifier, generateEmailChangeConfirmation, generateEmailChangeNotification, generateFingerprint, generatePasswordResetEmail, generateQrCodeUri, generateSecureToken, generateTotpSecret, generateTraceId, generateVerificationEmail, getAllowedOrigins, getAuthContext, getChallengeCookieName, getClientIp, getKeyPrefix, getMinutesUntilUnlock, getSessionCookieName, getTotpCounter, getTrustedDeviceCookieName, handleWebhookEvent, hashApiKey, hashBackupCode, hashPassword, hashToken, incrementFailedAttempts, invalidateAllUserSessions, isCapacitorApp, isCloudflarePreviewUrl, isDeepLinkUri, isValidApiKeyFormat, logError, logSecurityEvent, logger, normalizeBackupCode, optionalAuth, parseDeviceName, parseDeviceType, problemJson, problems, rateLimit, refreshSession, requestEmailChange, requireAuth, requireVerifiedEmail, retrieveChallengeToken, setChallengeCookie, setSessionCookie, setTrustedDeviceCookie, storeChallengeToken, updateChallengeAttempts, validateChallengePayload, validateOrigin, validatePassword, validatePasswordWithBreachCheck, validateSession, validateTrustedDevice, verifyBackupCode, verifyCodeChallenge, verifyPassword, verifyPasswordWithRotation, verifyTotpCode, verifyTurnstileToken, verifyWebhookSignature };
package/dist/index.d.ts CHANGED
@@ -318,6 +318,46 @@ interface AuthDatabase {
318
318
  delete: (...args: any[]) => any;
319
319
  query: Record<string, any>;
320
320
  }
321
+ /** A newly-created user, passed to invitation hooks. */
322
+ interface InvitationHookUser {
323
+ id: string;
324
+ email: string;
325
+ name: string | null;
326
+ }
327
+ /**
328
+ * App-owned invitation hooks. The library stays agnostic of any specific
329
+ * invitation model — the consuming app validates the token and links the user.
330
+ *
331
+ * When `/signup` (or OAuth signup) is called with an `invitationToken`, the
332
+ * library calls `verify`. If it returns a matching email, the account is created
333
+ * with `emailVerified: true` and NO verification email is sent — the token, which
334
+ * was delivered to that address, is itself the proof of ownership (the same trust
335
+ * model as OAuth-provider signup). `onAccepted` then runs so the app can link the
336
+ * user (e.g. create a team membership) atomically with signup.
337
+ */
338
+ interface InvitationHooks<TEnv = Record<string, unknown>> {
339
+ /**
340
+ * Validate that `token` is a live invitation for `email`. Return the invited
341
+ * email (normalized) to auto-verify the new account, or `null` to reject the
342
+ * token. MUST confirm the token matches this email — returning a non-matching
343
+ * email is treated as a rejection by the caller.
344
+ */
345
+ verify(args: {
346
+ token: string;
347
+ email: string;
348
+ db: AuthDatabase;
349
+ env: TEnv;
350
+ }): Promise<{
351
+ email: string;
352
+ } | null>;
353
+ /** Link the just-created, verified user (create membership, mark the invite accepted). */
354
+ onAccepted?(args: {
355
+ token: string;
356
+ user: InvitationHookUser;
357
+ db: AuthDatabase;
358
+ env: TEnv;
359
+ }): Promise<void>;
360
+ }
321
361
  /**
322
362
  * Configuration for createAuth
323
363
  */
@@ -330,6 +370,8 @@ interface AuthConfig<TEnv = Record<string, unknown>> {
330
370
  getEnv: (c: Context) => TEnv;
331
371
  /** Optional email branding tokens */
332
372
  emailBranding?: EmailBranding;
373
+ /** Optional app-owned invitation hooks for token-verified signup. */
374
+ invitations?: InvitationHooks<TEnv>;
333
375
  }
334
376
  /**
335
377
  * Auth context available in all auth operations.
@@ -340,6 +382,7 @@ interface AuthContext<TEnv = Record<string, unknown>> {
340
382
  schema: AuthSchema;
341
383
  env: TEnv;
342
384
  emailBranding?: EmailBranding;
385
+ invitations?: InvitationHooks<TEnv>;
343
386
  }
344
387
  /**
345
388
  * Creates an auth instance with injected database and schema.
@@ -1662,6 +1705,7 @@ declare const authRoutes: OpenAPIHono<{
1662
1705
  name?: string | undefined;
1663
1706
  turnstileToken?: string | undefined;
1664
1707
  oauthToken?: string | undefined;
1708
+ invitationToken?: string | undefined;
1665
1709
  };
1666
1710
  };
1667
1711
  output: {
@@ -1683,6 +1727,7 @@ declare const authRoutes: OpenAPIHono<{
1683
1727
  name?: string | undefined;
1684
1728
  turnstileToken?: string | undefined;
1685
1729
  oauthToken?: string | undefined;
1730
+ invitationToken?: string | undefined;
1686
1731
  };
1687
1732
  };
1688
1733
  output: {
@@ -1704,6 +1749,7 @@ declare const authRoutes: OpenAPIHono<{
1704
1749
  name?: string | undefined;
1705
1750
  turnstileToken?: string | undefined;
1706
1751
  oauthToken?: string | undefined;
1752
+ invitationToken?: string | undefined;
1707
1753
  };
1708
1754
  };
1709
1755
  output: {
@@ -2700,4 +2746,4 @@ declare function calculateBounceRate(db: PostgresJsDatabase<Record<string, unkno
2700
2746
  */
2701
2747
  declare function calculateComplaintRate(db: PostgresJsDatabase<Record<string, unknown>>, emailEventsTable: EmailEventsTable, hoursAgo?: number): Promise<number>;
2702
2748
 
2703
- export { AUTH_DEFAULTS, type AccountLockoutTables, type Auth, type AuthConfig, type AuthContext, type AuthDatabase, type AuthSchema, CHALLENGE_TTL_MS, type ChallengePayload, CloudflareEmailAdapter, type DeviceType, type EmailAdapter, type EmailBranding, type EmailChangeConfirmationData, type EmailChangeNotificationData, type EmailChangeTables, type EmailSendOptions, type EmailSendResult, EmailService, type EmailTemplate, type EmailType, type Env, type InferNewSession, type InferNewUser, type InferSession, type InferUser, MAX_CHALLENGE_ATTEMPTS, type PasswordResetEmailData, type ProblemDetails, ProblemTypes, ResendAdapter, type ResendEventType, type ResendWebhookPayload, type SendEmailBinding, type SendEmailResult, type SessionData, type SessionTables, TRUSTED_DEVICE_COOKIE_BASE, TRUSTED_DEVICE_TTL_MS, type TotpVerifyResult, type TwoFactorCodeEmailData, type TwoFactorDisabledEmailData, type TwoFactorEnabledEmailData, type ValidationResult, type Variables, type VerificationEmailData, authRoutes, calculateBounceRate, calculateComplaintRate, cancelEmailChange, checkAccountLocked, checkBreachedPassword, clearAccountLockout, clearChallengeCookie, clearSessionCookie, clearTrustedDeviceCookie, confirmEmailChange, createAuth, createAuthMiddleware, createChallengeToken, createDeviceToken, createEmailAdapter, createProblemDetails, createSession, csrf, decryptTotpSecret, deleteAllUserSessions, deleteChallengeToken, deleteSession, encryptTotpSecret, extractAppUrl, formatBackupCode, generate2faCodeEmail, generate2faDisabledEmail, generate2faEnabledEmail, generateApiKey, generateBackupCodes, generateCodeChallenge, generateCodeVerifier, generateEmailChangeConfirmation, generateEmailChangeNotification, generateFingerprint, generatePasswordResetEmail, generateQrCodeUri, generateSecureToken, generateTotpSecret, generateTraceId, generateVerificationEmail, getAllowedOrigins, getAuthContext, getChallengeCookieName, getClientIp, getKeyPrefix, getMinutesUntilUnlock, getSessionCookieName, getTotpCounter, getTrustedDeviceCookieName, handleWebhookEvent, hashApiKey, hashBackupCode, hashPassword, hashToken, incrementFailedAttempts, invalidateAllUserSessions, isCapacitorApp, isCloudflarePreviewUrl, isDeepLinkUri, isValidApiKeyFormat, logError, logSecurityEvent, logger, normalizeBackupCode, optionalAuth, parseDeviceName, parseDeviceType, problemJson, problems, rateLimit, refreshSession, requestEmailChange, requireAuth, requireVerifiedEmail, retrieveChallengeToken, setChallengeCookie, setSessionCookie, setTrustedDeviceCookie, storeChallengeToken, updateChallengeAttempts, validateChallengePayload, validateOrigin, validatePassword, validatePasswordWithBreachCheck, validateSession, validateTrustedDevice, verifyBackupCode, verifyCodeChallenge, verifyPassword, verifyPasswordWithRotation, verifyTotpCode, verifyTurnstileToken, verifyWebhookSignature };
2749
+ export { AUTH_DEFAULTS, type AccountLockoutTables, type Auth, type AuthConfig, type AuthContext, type AuthDatabase, type AuthSchema, CHALLENGE_TTL_MS, type ChallengePayload, CloudflareEmailAdapter, type DeviceType, type EmailAdapter, type EmailBranding, type EmailChangeConfirmationData, type EmailChangeNotificationData, type EmailChangeTables, type EmailSendOptions, type EmailSendResult, EmailService, type EmailTemplate, type EmailType, type Env, type InferNewSession, type InferNewUser, type InferSession, type InferUser, type InvitationHookUser, type InvitationHooks, MAX_CHALLENGE_ATTEMPTS, type PasswordResetEmailData, type ProblemDetails, ProblemTypes, ResendAdapter, type ResendEventType, type ResendWebhookPayload, type SendEmailBinding, type SendEmailResult, type SessionData, type SessionTables, TRUSTED_DEVICE_COOKIE_BASE, TRUSTED_DEVICE_TTL_MS, type TotpVerifyResult, type TwoFactorCodeEmailData, type TwoFactorDisabledEmailData, type TwoFactorEnabledEmailData, type ValidationResult, type Variables, type VerificationEmailData, authRoutes, calculateBounceRate, calculateComplaintRate, cancelEmailChange, checkAccountLocked, checkBreachedPassword, clearAccountLockout, clearChallengeCookie, clearSessionCookie, clearTrustedDeviceCookie, confirmEmailChange, createAuth, createAuthMiddleware, createChallengeToken, createDeviceToken, createEmailAdapter, createProblemDetails, createSession, csrf, decryptTotpSecret, deleteAllUserSessions, deleteChallengeToken, deleteSession, encryptTotpSecret, extractAppUrl, formatBackupCode, generate2faCodeEmail, generate2faDisabledEmail, generate2faEnabledEmail, generateApiKey, generateBackupCodes, generateCodeChallenge, generateCodeVerifier, generateEmailChangeConfirmation, generateEmailChangeNotification, generateFingerprint, generatePasswordResetEmail, generateQrCodeUri, generateSecureToken, generateTotpSecret, generateTraceId, generateVerificationEmail, getAllowedOrigins, getAuthContext, getChallengeCookieName, getClientIp, getKeyPrefix, getMinutesUntilUnlock, getSessionCookieName, getTotpCounter, getTrustedDeviceCookieName, handleWebhookEvent, hashApiKey, hashBackupCode, hashPassword, hashToken, incrementFailedAttempts, invalidateAllUserSessions, isCapacitorApp, isCloudflarePreviewUrl, isDeepLinkUri, isValidApiKeyFormat, logError, logSecurityEvent, logger, normalizeBackupCode, optionalAuth, parseDeviceName, parseDeviceType, problemJson, problems, rateLimit, refreshSession, requestEmailChange, requireAuth, requireVerifiedEmail, retrieveChallengeToken, setChallengeCookie, setSessionCookie, setTrustedDeviceCookie, storeChallengeToken, updateChallengeAttempts, validateChallengePayload, validateOrigin, validatePassword, validatePasswordWithBreachCheck, validateSession, validateTrustedDevice, verifyBackupCode, verifyCodeChallenge, verifyPassword, verifyPasswordWithRotation, verifyTotpCode, verifyTurnstileToken, verifyWebhookSignature };
package/dist/index.js CHANGED
@@ -949,12 +949,13 @@ import { eq as eq4, and as and2, gt as gt2 } from "drizzle-orm";
949
949
 
950
950
  // src/factory.ts
951
951
  function createAuth(config) {
952
- const { db, schema, getEnv, emailBranding } = config;
952
+ const { db, schema, getEnv, emailBranding, invitations } = config;
953
953
  const getContext = (c) => ({
954
954
  db,
955
955
  schema,
956
956
  env: getEnv(c),
957
- emailBranding
957
+ emailBranding,
958
+ invitations
958
959
  });
959
960
  return {
960
961
  db,
@@ -2932,7 +2933,10 @@ var signupRequestSchema = z.object({
2932
2933
  name: z.string().min(1).optional().openapi({ example: "John Doe" }),
2933
2934
  turnstileToken: z.string().optional().openapi({ example: "XXXX.DUMMY.TOKEN.XXXX" }),
2934
2935
  // OAuth signup completion token (from /signup?oauth_token=...)
2935
- oauthToken: z.string().optional().openapi({ example: "a1b2c3d4-..." })
2936
+ oauthToken: z.string().optional().openapi({ example: "a1b2c3d4-..." }),
2937
+ // App invitation token (from /signup?token=...). When it validates for this
2938
+ // email, the account is created already-verified with no verification email.
2939
+ invitationToken: z.string().optional().openapi({ example: "iIB8tDJJ..." })
2936
2940
  }).openapi("SignupRequest");
2937
2941
  var loginRequestSchema = z.object({
2938
2942
  email: z.string().email().openapi({ example: "user@example.com" }),
@@ -3062,8 +3066,8 @@ var signupRoute = createRoute({
3062
3066
  }
3063
3067
  });
3064
3068
  var signupHandler = async (c) => {
3065
- const { email, password, name, turnstileToken, oauthToken } = c.req.valid("json");
3066
- const { db: database, schema, emailBranding } = getAuthContext(c);
3069
+ const { email, password, name, turnstileToken, oauthToken, invitationToken } = c.req.valid("json");
3070
+ const { db: database, schema, emailBranding, invitations } = getAuthContext(c);
3067
3071
  const env = c.env;
3068
3072
  if (oauthToken) {
3069
3073
  const stored = await env.OAUTH_STATES.get(`oauth:signup:${oauthToken}`);
@@ -3145,40 +3149,74 @@ var signupHandler = async (c) => {
3145
3149
  return problems.badRequest(c, passwordValidation.error || "Invalid password");
3146
3150
  }
3147
3151
  const hashedPassword = await hashPassword(password, env.PASSWORD_PEPPER_V1);
3152
+ let invitedVerified = false;
3153
+ if (invitationToken && invitations) {
3154
+ const match = await invitations.verify({
3155
+ token: invitationToken,
3156
+ email: email.toLowerCase(),
3157
+ db: database,
3158
+ env
3159
+ });
3160
+ if (!match || match.email.toLowerCase() !== email.toLowerCase()) {
3161
+ return problems.badRequest(
3162
+ c,
3163
+ "This invitation is invalid, expired, or for a different email address"
3164
+ );
3165
+ }
3166
+ invitedVerified = true;
3167
+ }
3148
3168
  const [newUser] = await database.insert(schema.users).values({
3149
3169
  email: email.toLowerCase(),
3150
3170
  name: name || null,
3151
3171
  hashedPassword,
3152
3172
  pepperKid: "v1",
3153
- emailVerified: false,
3173
+ emailVerified: invitedVerified,
3154
3174
  activatedAt: /* @__PURE__ */ new Date()
3155
3175
  }).returning();
3156
- const emailToken = generateSecureToken(32);
3157
- const tokenHash = await hashToken(emailToken);
3158
- const expiresAt = Date.now() + 24 * 60 * 60 * 1e3;
3159
- await database.insert(schema.emailVerificationTokens).values({
3160
- tokenHash,
3161
- userId: newUser.id,
3162
- email: newUser.email,
3163
- expiresAt,
3164
- createdAt: Date.now()
3165
- });
3166
- const emailService = new EmailService(env, void 0, emailBranding);
3167
- const verificationUrl = `${env.APP_URL}/verify-email?token=${emailToken}`;
3168
- await emailService.sendVerificationEmail(
3169
- {
3176
+ if (!invitedVerified) {
3177
+ const emailToken = generateSecureToken(32);
3178
+ const tokenHash = await hashToken(emailToken);
3179
+ const expiresAt = Date.now() + 24 * 60 * 60 * 1e3;
3180
+ await database.insert(schema.emailVerificationTokens).values({
3181
+ tokenHash,
3182
+ userId: newUser.id,
3170
3183
  email: newUser.email,
3171
- token: emailToken,
3172
- verificationUrl,
3173
- firstName: newUser.name || void 0
3174
- },
3175
- database,
3176
- schema.users
3177
- );
3184
+ expiresAt,
3185
+ createdAt: Date.now()
3186
+ });
3187
+ const emailService = new EmailService(env, void 0, emailBranding);
3188
+ const verificationUrl = `${env.APP_URL}/verify-email?token=${emailToken}`;
3189
+ await emailService.sendVerificationEmail(
3190
+ {
3191
+ email: newUser.email,
3192
+ token: emailToken,
3193
+ verificationUrl,
3194
+ firstName: newUser.name || void 0
3195
+ },
3196
+ database,
3197
+ schema.users
3198
+ );
3199
+ }
3200
+ if (invitedVerified && invitationToken && invitations?.onAccepted) {
3201
+ try {
3202
+ await invitations.onAccepted({
3203
+ token: invitationToken,
3204
+ user: { id: newUser.id, email: newUser.email, name: newUser.name },
3205
+ db: database,
3206
+ env
3207
+ });
3208
+ } catch (err) {
3209
+ logError(err, { context: "signup_invitation_onAccepted_failed", userId: newUser.id });
3210
+ }
3211
+ }
3178
3212
  const fingerprint = await generateFingerprint(c.req.raw);
3179
3213
  const ipAddress = getClientIp(c.req.raw);
3180
3214
  const sessionId = await createSession(database, { sessions: schema.sessions }, newUser.id, fingerprint, ipAddress);
3181
- logSecurityEvent("user_signup", "low", { userId: newUser.id, email: newUser.email });
3215
+ logSecurityEvent("user_signup", "low", {
3216
+ userId: newUser.id,
3217
+ email: newUser.email,
3218
+ invited: invitedVerified
3219
+ });
3182
3220
  c.header("Set-Cookie", setSessionCookie(sessionId, c.env), { append: true });
3183
3221
  return c.json({
3184
3222
  user: {