@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.cjs +66 -28
- 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 +66 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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:
|
|
3173
|
+
emailVerified: invitedVerified,
|
|
3154
3174
|
activatedAt: /* @__PURE__ */ new Date()
|
|
3155
3175
|
}).returning();
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
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
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
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", {
|
|
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: {
|