feat(02-02): frontend auth store, router guard, Login/Register views

- frontend/src/stores/auth.js: useAuthStore with accessToken in memory
  only (never browser storage); login() accepts options.backupCode
- frontend/src/api/client.js: extended with Bearer token injection,
  401 auto-refresh retry, all auth/admin API functions, changePassword
- frontend/src/router/index.js: auth routes added (/login, /register,
  /password-reset, /account, /admin); beforeEach guard redirects
  unauthenticated users to /login with redirect param
- frontend/src/layouts/AuthLayout.vue: centered bare layout for auth pages
- frontend/src/views/auth/LoginView.vue: three-step flow (password, TOTP,
  backup code); "Use a backup code instead" link; UI-SPEC copywriting
- frontend/src/views/auth/RegisterView.vue: registration with
  PasswordStrengthBar; HIBP error display; UI-SPEC copywriting
- frontend/src/components/auth/PasswordStrengthBar.vue: 4-segment bar
- frontend/src/components/ui/AppSpinner.vue: animate-spin SVG spinner
- Stub views: PasswordResetView, NewPasswordView, AccountView, AdminView
- .gitignore: exclude frontend/node_modules, dist, package-lock.json

npm run build exits 0. All acceptance criteria verified.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-05-22 19:45:21 +02:00
parent 1882edfff6
commit 3b7d362600
13 changed files with 1163 additions and 2 deletions
+40 -1
View File
@@ -1,18 +1,57 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '../stores/auth.js'
import HomeView from '../views/HomeView.vue'
import TopicsView from '../views/TopicsView.vue'
import DocumentView from '../views/DocumentView.vue'
import SettingsView from '../views/SettingsView.vue'
const routes = [
// Existing routes
{ path: '/', component: HomeView },
{ path: '/topics', component: TopicsView },
{ path: '/topics/:name', component: TopicsView },
{ path: '/document/:id', component: DocumentView },
{ path: '/settings', component: SettingsView },
// Phase 2 — public auth routes (no guard)
{
path: '/login',
component: () => import('../views/auth/LoginView.vue'),
meta: { public: true },
},
{
path: '/register',
component: () => import('../views/auth/RegisterView.vue'),
meta: { public: true },
},
{
path: '/password-reset',
component: () => import('../views/auth/PasswordResetView.vue'),
meta: { public: true },
},
{
path: '/password-reset/confirm',
component: () => import('../views/auth/NewPasswordView.vue'),
meta: { public: true },
},
// Phase 2 — authenticated routes
{ path: '/account', component: () => import('../views/AccountView.vue') },
{ path: '/admin', component: () => import('../views/AdminView.vue') },
]
export default createRouter({
const router = createRouter({
history: createWebHistory(),
routes,
})
// Navigation guard (D-10): redirect unauthenticated users to /login.
// Preserves the intended destination via ?redirect= query param.
router.beforeEach((to) => {
const authStore = useAuthStore()
if (!to.meta.public && !authStore.accessToken) {
return { path: '/login', query: { redirect: to.fullPath } }
}
})
export default router