Add simple auth

This commit is contained in:
Adrien
2026-04-06 14:29:53 +02:00
parent e5d53b4e80
commit 0cf318f0a7
21 changed files with 1083 additions and 31 deletions
+172
View File
@@ -0,0 +1,172 @@
# Tasks: Basic Login Protection
**Input**: Design documents from `/specs/003-basic-login/`
**Prerequisites**: plan.md ✅ spec.md ✅ research.md ✅ data-model.md ✅ contracts/ ✅ quickstart.md ✅
**Tests**: Not requested — no test tasks included.
**Organization**: Tasks grouped by user story to enable independent implementation and testing.
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies on incomplete tasks)
- **[Story]**: Which user story this task belongs to (US1, US2, US3)
---
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: No new project initialization needed — existing backend and frontend projects are in place. Phase 1 confirms the entry points for changes.
- [x] T001 Verify `spring-boot-starter-security` is present in `backend/pom.xml` (already included — confirm, no change needed)
- [x] T002 Verify Pinia is listed in `frontend/package.json` dependencies (already included — confirm, no change needed)
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: Backend credential endpoint and frontend auth store — required by all three user stories.
**⚠️ CRITICAL**: No user story work can begin until this phase is complete.
- [x] T003 Add `app.auth.username` property to `backend/src/main/resources/application.yaml` with value `${APP_AUTH_USERNAME:neurosurgeon}` alongside the existing `app.auth.password` entry
- [x] T004 Update `backend/src/main/java/com/aiteacher/config/SecurityConfig.java` to inject `@Value("${app.auth.username}")` and pass it to `User.builder().username(username)` instead of the hardcoded string `"neurosurgeon"`
- [x] T005 Create `backend/src/main/java/com/aiteacher/auth/AuthController.java``@RestController` at `/api/v1/auth`, with a single `GET /check` method that accepts a `Principal` argument and returns `ResponseEntity.ok(Map.of("username", principal.getName()))`
- [x] T006 Create `frontend/src/stores/authStore.ts` — Pinia store with `username` and `password` refs (initially `null`), `isAuthenticated` computed, `setCredentials(u, p)` action, and `clearCredentials()` action (sessionStorage persistence added in Phase 5 / US3)
**Checkpoint**: Backend exposes `GET /api/v1/auth/check`; `authStore` is callable from any Vue component.
---
## Phase 3: User Story 1 - Authenticate to Access the App (Priority: P1) 🎯 MVP
**Goal**: Unauthenticated users are redirected to `/login`; successful credential entry grants full app access.
**Independent Test**: Open the app in incognito → redirected to `/login`. Enter wrong credentials → error shown. Enter correct credentials → land on Library (`/`). Refresh the page → stays on Library (credentials held in-memory for now; persistence comes in US3).
### Implementation for User Story 1
- [x] T007 [US1] Create `frontend/src/views/LoginView.vue` — centered card with username input, password input, submit button, and an error message area; on submit, call `authStore.setCredentials(u, p)`, then call `GET /api/v1/auth/check` via the `api` service; on 200 navigate to `/`; on failure call `authStore.clearCredentials()` and display the error
- [x] T008 [US1] Update `frontend/src/services/api.ts` — remove the hardcoded `auth: { username, password }` field from the axios instance; add a **request interceptor** that reads `authStore.username` and `authStore.password` and sets `config.auth` dynamically; add a **response interceptor** that on `401` calls `authStore.clearCredentials()` and redirects to `/login` (replace the existing error-normalisation interceptor rather than adding a second one — keep error normalisation intact)
- [x] T009 [US1] Update `frontend/src/router/index.ts` — add a `{ path: '/login', name: 'login', component: LoginView }` route; add a `router.beforeEach` guard that redirects to `{ name: 'login' }` when `to.name !== 'login'` and `!authStore.isAuthenticated`
**Checkpoint**: US1 fully functional — incognito flow, failed login, and successful login all work independently.
---
## Phase 4: User Story 2 - Log Out of the App (Priority: P2)
**Goal**: Authenticated users can log out, terminating their session and returning to `/login`.
**Independent Test**: Log in → click "Sign out" in the navbar → redirected to `/login`; navigating back to any protected route redirects to `/login` again.
### Implementation for User Story 2
- [x] T010 [US2] Update `frontend/src/App.vue` — import `useAuthStore` and `useRouter`; add a "Sign out" button to the navbar (visible only when `authStore.isAuthenticated`); clicking it calls `authStore.clearCredentials()` then `router.push({ name: 'login' })`; hide the navbar links (`RouterLink` items) when on the login page by checking `$route.name !== 'login'`
**Checkpoint**: US2 fully functional — logout clears session and blocks re-entry without credentials.
---
## Phase 5: User Story 3 - Session Persistence Across Browser Refresh (Priority: P3)
**Goal**: Authenticated users survive a page refresh without re-logging in; expired/invalid stored credentials redirect to `/login`.
**Independent Test**: Log in → refresh the browser → remain on the same page without re-entering credentials. Manually clear `sessionStorage` and refresh → redirected to `/login`.
### Implementation for User Story 3
- [x] T011 [US3] Update `frontend/src/stores/authStore.ts` — in `setCredentials`, write `{ username, password }` to `sessionStorage` under a key (e.g. `'auth'`); in `clearCredentials`, call `sessionStorage.removeItem('auth')`; on store initialization (module load), read from `sessionStorage` and pre-populate `username` and `password` refs if present
- [x] T012 [US3] Update `frontend/src/main.ts` — after creating the app and mounting Pinia, call `authStore.restoreSession()` (or inline the check): if `authStore.isAuthenticated`, call `GET /api/v1/auth/check`; if the response is `401`, call `authStore.clearCredentials()` so stale stored credentials are evicted before the router guard fires
**Checkpoint**: US3 fully functional — refresh persists login; stale or invalidated credentials are detected on load.
---
## Phase 6: Polish & Cross-Cutting Concerns
**Purpose**: Documentation and cleanup that spans all user stories.
- [x] T013 [P] Update `frontend/.env.example` — remove `VITE_APP_PASSWORD` (the frontend no longer reads a password from env; add a comment explaining credentials are now entered via the login form)
- [x] T014 [P] Update `README.md` — add or update the Mermaid architecture diagram to show the login flow: browser → login form → `/api/v1/auth/check` → app; this satisfies Constitution Principle IV (diagram must be updated in the same PR as any architectural change)
**Checkpoint**: Feature complete — all three user stories functional, documentation current, obsolete env var removed.
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies — confirm immediately
- **Foundational (Phase 2)**: Depends on Phase 1 — **BLOCKS all user stories**
- **US1 (Phase 3)**: Depends on Phase 2 completion
- **US2 (Phase 4)**: Depends on Phase 2 completion; integrates with authStore from Phase 2 and router from US1
- **US3 (Phase 5)**: Depends on Phase 2 completion; extends authStore from Phase 2
- **Polish (Phase 6)**: Depends on all desired stories complete
### User Story Dependencies
- **US1 (P1)**: Depends only on Foundational — the primary blocker for all UI work
- **US2 (P2)**: Depends on Foundational and US1 (logout button lives in App.vue which needs the router guard from US1)
- **US3 (P3)**: Depends on Foundational only — authStore persistence is independent of the login form; can be developed in parallel with US1 if desired
### Within Each User Story
- Foundational tasks (T003T006) must all complete before US1 starts
- T007 (LoginView) and T008 (api.ts) can be developed in parallel within US1; T009 (router guard) depends on T007 existing
- US2 is a single task (T010) with no internal ordering complexity
- T012 (main.ts restore check) depends on T011 (authStore persistence) within US3
---
## Parallel Example: Foundational Phase
```
Parallelizable within Phase 2:
T003 — application.yaml update
T004 — SecurityConfig.java update
T005 — AuthController.java (new file)
T006 — authStore.ts (new file)
All four touch different files with no shared dependencies.
```
## Parallel Example: User Story 1
```
Parallelizable within Phase 3:
T007 — LoginView.vue (new file)
T008 — api.ts update (different file)
Then sequentially:
T009 — router/index.ts (depends on LoginView existing)
```
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete Phase 1: Confirm existing deps
2. Complete Phase 2: Foundational — backend endpoint + auth store
3. Complete Phase 3: US1 — login page, axios interceptors, router guard
4. **STOP and VALIDATE**: Open incognito, verify redirect → login → success flow
5. Demo / merge if MVP is sufficient
### Incremental Delivery
1. Phase 1 + Phase 2 → Foundation ready
2. Phase 3 (US1) → Login gate works — **MVP**
3. Phase 4 (US2) → Logout works ✅
4. Phase 5 (US3) → Session survives refresh ✅
5. Phase 6 → Documentation and cleanup ✅
---
## Notes
- [P] tasks touch different files and have no dependency on an incomplete sibling task in the same phase
- No tests included (not requested in the spec)
- `VITE_APP_PASSWORD` should be removed from `.env.example` once T013 is done — do **not** remove it from any local `.env` file before the login form is working (T007T009 complete)
- The 401 response interceptor in T008 handles the edge case where stored credentials become invalid server-side — no additional handling needed
- Constitution IV requires the README Mermaid diagram to be updated in the **same PR** — T014 must not be skipped