diff --git a/api/app/Http/Controllers/Api/V1/Auth/MfaSetupController.php b/api/app/Http/Controllers/Api/V1/Auth/MfaSetupController.php index 6fe0b3f1..6e700161 100644 --- a/api/app/Http/Controllers/Api/V1/Auth/MfaSetupController.php +++ b/api/app/Http/Controllers/Api/V1/Auth/MfaSetupController.php @@ -135,7 +135,7 @@ final class MfaSetupController extends Controller $user = $request->user(); return $this->success([ - 'mfa_enabled' => $user->mfa_enabled, + 'enabled' => $user->mfa_enabled, 'method' => $user->mfa_method, 'confirmed_at' => $user->mfa_confirmed_at?->toIso8601String(), 'backup_codes_remaining' => $this->mfaService->getRemainingBackupCodeCount($user), diff --git a/api/tests/Feature/Auth/MfaSetupControllerTest.php b/api/tests/Feature/Auth/MfaSetupControllerTest.php index d9aafeb2..37a2f237 100644 --- a/api/tests/Feature/Auth/MfaSetupControllerTest.php +++ b/api/tests/Feature/Auth/MfaSetupControllerTest.php @@ -147,7 +147,7 @@ class MfaSetupControllerTest extends TestCase $response->assertOk() ->assertJson([ 'data' => [ - 'mfa_enabled' => false, + 'enabled' => false, 'method' => null, 'confirmed_at' => null, 'backup_codes_remaining' => 0, @@ -164,7 +164,7 @@ class MfaSetupControllerTest extends TestCase $response->assertOk() ->assertJson([ 'data' => [ - 'mfa_enabled' => true, + 'enabled' => true, 'method' => 'totp', 'backup_codes_remaining' => 10, ], diff --git a/apps/app/src/components/account-settings/SecurityTab.vue b/apps/app/src/components/account-settings/SecurityTab.vue index d14abccf..0e6041e1 100644 --- a/apps/app/src/components/account-settings/SecurityTab.vue +++ b/apps/app/src/components/account-settings/SecurityTab.vue @@ -80,10 +80,13 @@ const backupCodesColor = computed(() => { }) function onSetupCompleted() { + // Refetch MFA status so the method cards update (enabled, method, backup codes) refetchMfaStatus() - // Immediately clear the MFA enforcement state so the banner disappears - // and the router guard no longer redirects + // Immediately clear the enforcement flag so the router guard unblocks navigation authStore.mfaSetupRequired = false + // Refresh the full /auth/me response into the store so the flag stays + // correct on subsequent navigations (without needing a full page reload) + authStore.refreshUser() } function onDisabled() { diff --git a/apps/app/src/stores/useAuthStore.ts b/apps/app/src/stores/useAuthStore.ts index e838b3d7..1db98c15 100644 --- a/apps/app/src/stores/useAuthStore.ts +++ b/apps/app/src/stores/useAuthStore.ts @@ -88,6 +88,20 @@ export const useAuthStore = defineStore('auth', () => { } } + /** + * Re-fetch /auth/me and update the store. Safe to call at any time + * after initialization (e.g. after MFA setup or profile update). + */ + async function refreshUser(): Promise { + try { + const { data } = await apiClient.get<{ success: boolean; data: MeResponse }>('/auth/me') + setUser(data.data) + } + catch { + // Silently ignore — the existing state stays + } + } + /** * Called once on app startup. Validates the httpOnly cookie by calling * GET /auth/me. On 401, clears everything. @@ -132,5 +146,6 @@ export const useAuthStore = defineStore('auth', () => { logout, handleUnauthorized, initialize, + refreshUser, } })