Implement client-side route protection with role-based access control. The auth store now extracts the role claim from JWT tokens and exposes isAdmin. Router guards enforce three levels of access: guestOnly (redirect authenticated users), requiresAuth (redirect unauthenticated to login with redirect param), and requiresAdmin (redirect non-admin users to home). Changes: - utils/jwt.ts: JWT payload parser using base64url decode (new file) - authStore: add role ref, isAdmin computed, extractRole from JWT payload - router: add route metadata (requiresAuth, requiresAdmin, guestOnly) and beforeEach guard with getActivePinia() safety for test environments - OrdersPage.vue, AdminPage.vue: placeholder pages (new files) - LoginPage.vue, RegisterPage.vue: use route.query.redirect after auth - Router.spec.ts: 14 tests covering all guard scenarios - authStore.spec.ts: tests for role extraction, isAdmin, role persistence - LoginPage.spec.ts: test for redirect query param after login - auth-guards.spec.ts: 7 Playwright E2E tests for guard behavior - login.spec.ts: fix seed user credentials (test@bilhalsning.se)
73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
|
|
test.describe('Auth guards', () => {
|
|
test('redirects unauthenticated user from /compose to /logga-in', async ({
|
|
page,
|
|
}) => {
|
|
await page.goto('/compose')
|
|
await expect(page).toHaveURL(/\/logga-in\?redirect=\/compose/)
|
|
await expect(page.getByRole('heading', { name: 'Logga in' })).toBeVisible()
|
|
})
|
|
|
|
test('redirects unauthenticated user from /orders to /logga-in', async ({
|
|
page,
|
|
}) => {
|
|
await page.goto('/orders')
|
|
await expect(page).toHaveURL(/\/logga-in\?redirect=\/orders/)
|
|
await expect(page.getByRole('heading', { name: 'Logga in' })).toBeVisible()
|
|
})
|
|
|
|
test('redirects unauthenticated user from /admin to /logga-in', async ({
|
|
page,
|
|
}) => {
|
|
await page.goto('/admin')
|
|
await expect(page).toHaveURL(/\/logga-in\?redirect=\/admin/)
|
|
await expect(page.getByRole('heading', { name: 'Logga in' })).toBeVisible()
|
|
})
|
|
|
|
test('redirects authenticated user from /logga-in to home', async ({
|
|
page,
|
|
}) => {
|
|
const jwt = makeJwt({ role: 'user' })
|
|
await page.goto('/')
|
|
await page.evaluate((token) => localStorage.setItem('auth_token', token), jwt)
|
|
await page.goto('/logga-in')
|
|
await expect(page).toHaveURL('/')
|
|
})
|
|
|
|
test('redirects authenticated user from /registrera to home', async ({
|
|
page,
|
|
}) => {
|
|
const jwt = makeJwt({ role: 'user' })
|
|
await page.goto('/')
|
|
await page.evaluate((token) => localStorage.setItem('auth_token', token), jwt)
|
|
await page.goto('/registrera')
|
|
await expect(page).toHaveURL('/')
|
|
})
|
|
|
|
test('redirects non-admin user from /admin to home', async ({ page }) => {
|
|
const jwt = makeJwt({ role: 'user' })
|
|
await page.goto('/')
|
|
await page.evaluate((token) => localStorage.setItem('auth_token', token), jwt)
|
|
await page.goto('/admin')
|
|
await expect(page).toHaveURL('/')
|
|
})
|
|
|
|
test('allows admin user to access /admin', async ({ page }) => {
|
|
const jwt = makeJwt({ role: 'admin' })
|
|
await page.goto('/')
|
|
await page.evaluate((token) => localStorage.setItem('auth_token', token), jwt)
|
|
await page.goto('/admin')
|
|
await expect(page).toHaveURL('/admin')
|
|
await expect(
|
|
page.getByRole('heading', { name: 'Administration' }),
|
|
).toBeVisible()
|
|
})
|
|
})
|
|
|
|
function makeJwt(payload: Record<string, unknown>): string {
|
|
const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
|
|
const body = btoa(JSON.stringify(payload))
|
|
const signature = 'test-sig'
|
|
return `${header}.${body}.${signature}`
|
|
}
|