---
phase: 02-users-authentication
plan: "06"
subsystem: frontend-auth-ux
tags: [gap-closure, auth-layout, admin-guard, qr-code, settings-ux]
dependency_graph:
  requires: ["02-05"]
  provides: ["auth-layout-switching", "admin-role-guard", "account-settings-tab", "totp-qr-image"]
  affects: ["frontend/src/App.vue", "frontend/src/router/index.js", "frontend/src/views/SettingsView.vue"]
tech_stack:
  added: ["qrcode@1.5.4"]
  patterns: ["layout-switching via route.meta.layout", "role guard in beforeEach", "AccountView content extracted to SettingsAccountTab"]
key_files:
  created:
    - frontend/src/components/settings/SettingsAccountTab.vue
  modified:
    - frontend/src/App.vue
    - frontend/src/router/index.js
    - frontend/src/views/SettingsView.vue
    - frontend/src/components/auth/TotpEnrollment.vue
    - frontend/package.json
decisions:
  - "AuthLayout rendered unconditionally by App.vue via v-if on route.meta.layout — AuthLayout owns its own router-view"
  - "requiresAdmin guard appended to existing beforeEach after silent refresh — non-admin redirected to /"
  - "SettingsAccountTab created as standalone component (not inline in SettingsView) to keep SettingsView manageable"
  - "/account route redirects to /settings; AccountView.vue kept on disk but unreachable from router"
metrics:
  duration: "~25 minutes"
  completed: "2026-05-31T18:40:52Z"
  tasks_completed: 3
  tasks_total: 3
  files_created: 1
  files_modified: 5
---

# Phase 02 Plan 06: UAT Gap Closure — Auth Layout + Admin Guard + Account Tab + QR Code

One-liner: Five UAT gaps closed — layout-aware App.vue, admin route guard, Account settings tab extracted from AccountView, and TOTP QR image via qrcode library.

## Tasks Completed

| Task | Description | Commit | Files |
|------|-------------|--------|-------|
| 1 | Verify backend fix + regression test for admin create_user (GAP 1) | (verify-only, no code change) | backend/api/admin.py (confirmed), backend/tests/test_admin_api.py (confirmed) |
| 2 | Auth route layout switching + admin role guard (GAPs 2, 3, 4) | aa957d6 | frontend/src/App.vue, frontend/src/router/index.js |
| 3 | AccountView merged into SettingsView as Account tab + QR code in TotpEnrollment (GAPs 3 and 5) | c08ea42 | frontend/package.json, frontend/src/components/auth/TotpEnrollment.vue, frontend/src/views/SettingsView.vue, frontend/src/components/settings/SettingsAccountTab.vue |

## What Was Built

### Task 1: Backend verification (GAP 1 — admin create_user HTTP 500)

Confirmed `await session.flush()` at admin.py:247 (before `write_audit_log()`) and `test_create_user_writes_audit_log` test in test_admin_api.py. Both were already present from plan 02-04. Test passed on first run.

No code changes made — verification only.

### Task 2: Auth layout switching + admin role guard (GAPs 2, 3, 4)

**App.vue** refactored to layout-aware root component:
- `v-if="route.meta.layout === 'auth'"` renders `<AuthLayout />` which owns its own `<router-view />`
- `v-else` renders the full app shell (AppSidebar + router-view)
- Added `useRoute` import; `AuthLayout` import from `./layouts/AuthLayout.vue`

**router/index.js** three changes:
- All four auth routes (`/login`, `/register`, `/password-reset`, `/password-reset/confirm`) updated with `meta: { public: true, layout: 'auth' }`
- `/admin` route updated with `meta: { requiresAdmin: true }`
- `beforeEach` extended with role check: `if (to.meta.requiresAdmin && authStore.user?.role !== 'admin') return { path: '/' }`
- `/account` route changed to `{ path: '/account', redirect: '/settings' }` — AccountView now embedded in SettingsView

### Task 3: AccountView merged into SettingsView + QR code (GAPs 3 and 5)

**qrcode@1.5.4** installed as runtime dependency (verified 20M+ weekly downloads, canonical npm package).

**TotpEnrollment.vue** updated:
- Added `import QRCode from 'qrcode'`
- Added `qrDataUrl` ref
- In `startSetup()`: `qrDataUrl.value = await QRCode.toDataURL(qrUri.value, { width: 200, margin: 1 })`
- Replaced `<a :href="qrUri">` link block with `<img v-if="qrDataUrl" :src="qrDataUrl" alt="TOTP QR code" ...>`
- Manual secret display (`<code>` block) kept as fallback

**SettingsAccountTab.vue** created at `frontend/src/components/settings/`:
- Full AccountView content without the outer page wrapper (`<div class="p-8 max-w-2xl mx-auto">` and `<h2>` heading removed)
- All four sections: Account information, Two-factor authentication (TotpEnrollment), Change password (PasswordStrengthBar), Sessions (sign-out-all)
- All script setup logic ported: changePassword, disableTotp, onTotpEnrolled, signOutAll, all refs
- Import paths adjusted for new location (`../../stores/auth.js`, `../auth/...`, `../ui/...`)

**SettingsView.vue** updated:
- Added `{ id: 'account', label: 'Account' }` to tabs array
- Added `<SettingsAccountTab v-if="activeTab === 'account'" />` panel
- Added `import SettingsAccountTab from '../components/settings/SettingsAccountTab.vue'`

## Verification Results

| Check | Result |
|-------|--------|
| `pytest tests/test_admin_api.py::test_create_user_writes_audit_log -v` | PASSED |
| `npm run build` | Exit 0, 156 modules transformed |
| `npm test` | 107/107 passed (11 test files) |
| `pytest -v` (full backend) | 343 passed, 1 pre-existing failure (test_extract_docx — missing docx module, unrelated) |
| 4 auth routes have `meta.layout:'auth'` | Confirmed (grep count = 4) |
| `/admin` has `meta.requiresAdmin` | Confirmed |
| `requiresAdmin` role check in `beforeEach` | Confirmed |
| `qrcode` in package.json dependencies | Confirmed (`"qrcode": "^1.5.4"`) |
| `QRCode.toDataURL` + `img` tag in TotpEnrollment | Confirmed |
| `SettingsAccountTab` imported and rendered in SettingsView | Confirmed |

## Deviations from Plan

None — plan executed exactly as written. Task 1 was verify-only; the fix was already present from plan 02-04 execution.

## Known Stubs

None. All functional paths are wired.

## Threat Flags

No new threat surface introduced. Changes are frontend-only layout/UX routing (Task 1 is backend verify-only with no code changes). The requiresAdmin guard closes T-02-GAP-01 (elevation of privilege). The auth layout conditional closes T-02-GAP-02 (information disclosure via sidebar on public routes).

## Self-Check: PASSED

Files confirmed present:
- frontend/src/App.vue (modified)
- frontend/src/router/index.js (modified)
- frontend/src/components/settings/SettingsAccountTab.vue (created)
- frontend/src/views/SettingsView.vue (modified)
- frontend/src/components/auth/TotpEnrollment.vue (modified)
- frontend/package.json (modified)

Commits confirmed:
- aa957d6 feat(02-06): auth layout switching + admin role guard (GAPs 2, 3, 4)
- c08ea42 feat(02-06): Account tab in SettingsView + QR code in TotpEnrollment (GAPs 3, 5)
