Files
ai-teacher/specs/003-basic-login/tasks.md
T
2026-04-06 14:29:53 +02:00

173 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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