# 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 (T003–T006) 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 (T007–T009 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