fix(auth): persist signup verification sessions and supply delivery URLs
This commit is contained in:
1 parent
84e16ebc41
commit
f7fe3bb17b
12 files changed
+237
-8
No files matched your search
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user