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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user