mercury-web/src/features/auth/actions.ts

75 lines
2 KiB
TypeScript

import { authStrings } from "./strings";
export interface AuthActionResult {
ok: boolean;
twoFactor?: boolean;
challenge?: string;
error?: string;
}
// Mirrors AuthError.userMessage in ios/Mercury/Features/Auth/AuthRepository.swift,
// keyed by the `{error:{code,message}}` envelope the Go backend writes
// (internal/transport/api/respond.go, auth_handlers.go, twofa_handlers.go).
const ERROR_MESSAGE_BY_CODE: Record<string, string> = {
invalid_credentials: authStrings.errors.invalidCredentials,
email_taken: authStrings.errors.emailTaken,
invalid_code: authStrings.errors.invalidCode,
invalid_challenge: authStrings.errors.invalidCode,
};
function messageForCode(code: string | undefined): string {
if (code && ERROR_MESSAGE_BY_CODE[code]) return ERROR_MESSAGE_BY_CODE[code];
return authStrings.errors.unknown;
}
async function postAuth(
path: "login" | "register" | "forgot" | "2fa",
body: unknown,
): Promise<AuthActionResult> {
let res: Response;
try {
res = await fetch(`/api/auth/${path}`, {
method: "POST",
credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
} catch {
return { ok: false, error: authStrings.errors.network };
}
const data = await res.json().catch(() => ({}));
if (!res.ok) {
return { ok: false, error: messageForCode(data?.error?.code) };
}
if (data?.twoFactor) {
return { ok: false, twoFactor: true, challenge: data.challenge };
}
return { ok: true };
}
export function submitLogin(
email: string,
password: string,
): Promise<AuthActionResult> {
return postAuth("login", { email, password });
}
export function submitRegister(
email: string,
password: string,
): Promise<AuthActionResult> {
return postAuth("register", { email, password });
}
export function submit2FA(
challenge: string,
code: string,
): Promise<AuthActionResult> {
return postAuth("2fa", { challenge, code });
}
export function submitForgot(email: string): Promise<AuthActionResult> {
return postAuth("forgot", { email });
}