From 4f5521a6cf3b8604675c525d42b9f3b76bf22e5c Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 30 Apr 2026 18:07:12 +0000 Subject: [PATCH] refactor(auth): replace 2FA string sentinel with typed error Use a dedicated TwoFactorRequiredError and shared type guard to keep login flow checks type-safe and less brittle than matching magic strings. Made-with: Cursor --- heicode/web/default/src/features/auth/api.ts | 11 ++++++----- heicode/web/default/src/features/auth/errors.ts | 12 ++++++++++++ heicode/web/default/src/features/auth/index.ts | 1 + .../auth/sign-in/components/user-auth-form.tsx | 6 ++---- 4 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 heicode/web/default/src/features/auth/errors.ts diff --git a/heicode/web/default/src/features/auth/api.ts b/heicode/web/default/src/features/auth/api.ts index c659afc..769e9ae 100644 --- a/heicode/web/default/src/features/auth/api.ts +++ b/heicode/web/default/src/features/auth/api.ts @@ -1,6 +1,10 @@ import { api } from '@/lib/api' import { resetHeicodeAuthenticatedSession } from '@/features/auth/heicode-authenticated-session' import { saveUserId } from '@/features/auth/lib/storage' +import { + isTwoFactorRequiredError, + TwoFactorRequiredError, +} from '@/features/auth/errors' import type { LoginPayload, LoginResponse, @@ -72,7 +76,7 @@ async function establishManagerCookieSession(payload: LoginPayload) { throw new Error(body?.message || 'Unable to establish Manager session') } if (body.data?.require_2fa) { - throw new Error('TWO_FACTOR_REQUIRED') + throw new TwoFactorRequiredError() } if (body.data?.id != null) { saveUserId(body.data.id) @@ -151,10 +155,7 @@ export async function login(payload: LoginPayload) { try { await establishManagerCookieSession(payload) } catch (syncErr) { - if ( - syncErr instanceof Error && - syncErr.message === 'TWO_FACTOR_REQUIRED' - ) { + if (isTwoFactorRequiredError(syncErr)) { throw syncErr } clearHeicodeTokens() diff --git a/heicode/web/default/src/features/auth/errors.ts b/heicode/web/default/src/features/auth/errors.ts new file mode 100644 index 0000000..0379ccf --- /dev/null +++ b/heicode/web/default/src/features/auth/errors.ts @@ -0,0 +1,12 @@ +export class TwoFactorRequiredError extends Error { + constructor() { + super('Two-factor authentication is required') + this.name = 'TwoFactorRequiredError' + } +} + +export function isTwoFactorRequiredError( + error: unknown +): error is TwoFactorRequiredError { + return error instanceof TwoFactorRequiredError +} diff --git a/heicode/web/default/src/features/auth/index.ts b/heicode/web/default/src/features/auth/index.ts index 224e405..d5f74eb 100644 --- a/heicode/web/default/src/features/auth/index.ts +++ b/heicode/web/default/src/features/auth/index.ts @@ -71,6 +71,7 @@ export { getAffiliateCode, saveAffiliateCode, } from './lib/storage' +export { TwoFactorRequiredError, isTwoFactorRequiredError } from './errors' export { isValidOTP, diff --git a/heicode/web/default/src/features/auth/sign-in/components/user-auth-form.tsx b/heicode/web/default/src/features/auth/sign-in/components/user-auth-form.tsx index a12a20c..3422aaa 100644 --- a/heicode/web/default/src/features/auth/sign-in/components/user-auth-form.tsx +++ b/heicode/web/default/src/features/auth/sign-in/components/user-auth-form.tsx @@ -22,6 +22,7 @@ import { loginFormSchema } from '@/features/auth/constants' import { useAuthRedirect } from '@/features/auth/hooks/use-auth-redirect' import { useTurnstile } from '@/features/auth/hooks/use-turnstile' import { Turnstile } from '@/components/turnstile' +import { isTwoFactorRequiredError } from '@/features/auth/errors' import type { AuthFormProps } from '@/features/auth/types' export function UserAuthForm({ @@ -78,10 +79,7 @@ export function UserAuthForm({ toast.error(res.message) } } catch (error) { - if ( - error instanceof Error && - error.message === 'TWO_FACTOR_REQUIRED' - ) { + if (isTwoFactorRequiredError(error)) { redirectTo2FA() return }