fix(auth): persist signup verification sessions and supply delivery URLs

This commit is contained in:
Clintchiz committed 2026-08-26 10:55:48 +05:30
1 parent 84e16ebc41
commit f7fe3bb17b
12 files changed
+237 -8

No files matched your search

+27 -3
View File
@@ -45,6 +45,7 @@ const DEFAULT_TOKEN_TTLS: Record<AuthTokenPurpose, number> = {
"change-email": 30 * 60_000,
"change-phone": 15 * 60_000,
"login-mfa": 5 * 60_000,
"verification-session": 30 * 60_000,
};
export interface AuthEngine {
@@ -52,6 +53,9 @@ export interface AuthEngine {
readonly onSignedIn?: AuthSignedInHandler;
readonly onSignedOut?: AuthSignedOutHandler;
readonly onSuccessfulSignUp?: AuthSuccessfulSignUpHandler;
readonly onSignUp?: AuthSuccessfulSignUpHandler;
createVerificationSession(userId: string): Promise<string>;
validateVerificationSession(token: string): Promise<ReturnType<typeof publicUser> | undefined>;
register(input: RegisterInput): Promise<AuthResult>;
login(input: LoginInput): Promise<AuthResult>;
logout(sessionId: string, reason?: string): Promise<void>;
@@ -264,6 +268,9 @@ function passkeyChallengeTtl(timeout: number): number {
}
export function createAuthEngine(options: AuthEngineOptions): AuthEngine {
if (options.onSignUp && options.onSuccessfulSignUp) {
throw new TypeError("Configure either onSignUp or onSuccessfulSignUp, not both");
}
if (options.secret.length < MIN_SECRET_LENGTH) {
throw new TypeError(`auth secret must be at least ${MIN_SECRET_LENGTH} characters`);
}
@@ -847,7 +854,24 @@ export function createAuthEngine(options: AuthEngineOptions): AuthEngine {
store,
onSignedIn: options.onSignedIn,
onSignedOut: options.onSignedOut,
onSuccessfulSignUp: options.onSuccessfulSignUp,
onSuccessfulSignUp: options.onSignUp ?? options.onSuccessfulSignUp,
onSignUp: options.onSignUp ?? options.onSuccessfulSignUp,
async createVerificationSession(userId) {
const user = await store.findUserById(userId);
if (!user || !["pending", "active"].includes(user.status) || user.emailVerified) {
throw new Error("Email verification session is unavailable");
}
return issueToken(user.id, "verification-session");
},
async validateVerificationSession(raw) {
if (!raw || raw.length > 512) return undefined;
const token = await inspectToken(raw, "verification-session");
if (!token) return undefined;
const user = await store.findUserById(token.userId);
if (!user || !["pending", "active"].includes(user.status) || user.emailVerified)
return undefined;
return publicUser(user);
},
async register(input) {
try {
@@ -897,8 +921,8 @@ export function createAuthEngine(options: AuthEngineOptions): AuthEngine {
const identities = await store.listIdentities(user.id);
const email = identities.find((item) => item.type === "email");
const phone = identities.find((item) => item.type === "phone");
if (email) await sendToken(user, "verify-email", email.value);
if (phone) await sendToken(user, "verify-phone", phone.value);
if (email) await sendToken(user, "verify-email", email.value, input.baseUrl);
if (phone) await sendToken(user, "verify-phone", phone.value, input.baseUrl);
await audit({ userId: user.id, type: "account.registered", severity: "info" });
return {
ok: true,