docs: add sprint planning — dependency diagram + specs for all 8 stories

Analyzes all Taiga stories, maps hard/soft dependencies, and writes
full specs (Goal, Data Model, Components, Verification, Open Questions)
for every story before any implementation begins.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 11:53:24 +02:00
co-authored by Claude Sonnet 4.6
commit 1070ba3ac1
10 changed files with 1239 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
# Spec: 08 · Authorize User
**Taiga Story:** #8 · `08-authorize-user`
**Status:** Ready
**Branch:** `feature/08-authorize-user`
**Depends on:** `01-Bootstrap` (needs full feature set to protect; implement last)
---
## 1. Goal
Protect all data-modifying API endpoints behind login. Hide CRUD controls and admin nav links for unauthenticated visitors. Simple `.env`-based user/password map; password hashed with username as salt.
---
## 2. Background / Scope / Context / Constraints
- No OAuth, LDAP, or DB-stored users — `.env` file maps `username → hashed_password`.
- Password hashed with bcrypt where the username is used as the pepper/salt input alongside the password (e.g. hash `username:password` together or prepend username to password before hashing).
- Helper script `scripts/create_user.sh` (or `scripts/create_user.py`) generates the bcrypt entry.
- Session: JWT token (short-lived, e.g. 1 h) stored in `localStorage` or `httpOnly` cookie.
- Frontend reads auth state from Pinia and conditionally renders CRUD buttons + admin link.
- Read-only endpoints (`GET *`) remain public.
- Write endpoints (`POST`, `PUT`, `DELETE`) require valid JWT.
- Admin backfill endpoint from story #7 must also be protected.
---
## 3. Necessary Refactorings
- Echo middleware: JWT auth middleware applied to all non-GET routes.
- All existing CRUD handlers continue to work unchanged — middleware handles auth, not handlers.
- `FruitList.vue`, `FruitDetail.vue`, `PublicationList.vue`, `PublicationDetail.vue`: conditionally show/hide edit, delete, upload, create buttons.
- Nav/header: hide "Administration" link if not logged in.
---
## 4. Related Data Model
No DB changes. Auth is stateless (JWT) backed by `.env`.
`.env` addition:
```
USERS_MAP=admin:$2a$12$...<bcrypt_hash>,...
```
Or a separate file `users.env`:
```
# username:bcrypt_hash
admin:$2a$12$abc...
julia:$2a$12$xyz...
```
---
## 5. Affected Places / Related Components
### Backend
| File | Purpose |
|------|---------|
| `backend/internal/auth/auth.go` | `LoadUsers(path)`, `VerifyPassword(user, pass)`, `GenerateJWT()`, `ValidateJWT()` |
| `backend/internal/middleware/jwt_auth.go` | Echo middleware — checks `Authorization: Bearer <token>` header |
| `backend/cmd/server/router.go` | Apply middleware to write-route groups |
| `backend/internal/handler/auth_handler.go` | `POST /api/v1/auth/login` → returns JWT |
| `scripts/create_user.sh` (or `.py`) | `./create_user.sh <username> <password>` → prints `.env` line with bcrypt hash |
| `.env.example` | Add `USERS_FILE=./users.env` |
### API Endpoints
| Method | Path | Auth required |
|--------|------|:---:|
| POST | `/api/v1/auth/login` | No |
| GET | `/api/v1/fruits` | No |
| GET | `/api/v1/fruits/:id` | No |
| POST/PUT/DELETE | `/api/v1/fruits/*` | **Yes** |
| POST/PUT/DELETE | `/api/v1/publications/*` | **Yes** |
| POST | `/api/v1/admin/*` | **Yes** |
### Frontend
| File | Change |
|------|--------|
| `frontend/src/stores/authStore.ts` | Pinia: `isLoggedIn`, `token`, `login()`, `logout()` |
| `frontend/src/api/auth.ts` | `POST /api/v1/auth/login` |
| `frontend/src/views/LoginView.vue` | Login form |
| `frontend/src/router/index.ts` | Admin route guard (redirect if not logged in) |
| All CRUD views | Wrap buttons with `v-if="authStore.isLoggedIn"` |
| `frontend/src/components/NavBar.vue` | Conditionally show admin link + login/logout button |
---
## 6. Out of Scope
- Role-based access control (admin vs. read-only)
- Password reset / forgot password flow
- Session revocation / token blacklist
- Multiple auth backends (OAuth, SSO)
- User management UI
---
## 7. Verification
| Check | Expected |
|-------|---------|
| `POST /api/v1/fruits` without token | 401 Unauthorized |
| `POST /api/v1/auth/login` with valid creds | 200 + JWT in response |
| `POST /api/v1/fruits` with valid JWT | 201 Created |
| Expired JWT | 401 |
| `GET /api/v1/fruits` without token | 200 (public) |
| Frontend: not logged in → no CRUD buttons visible | Pass |
| Frontend: logged in → CRUD buttons visible | Pass |
| Frontend: not logged in → admin nav link hidden | Pass |
| `create_user.sh admin secret` → entry verifiable with bcrypt | Pass |
| `go test ./...` auth middleware tests | Green |
---
## 8. Open Questions
| # | Question | Impact |
|---|----------|--------|
| 1 | JWT stored in `localStorage` (simpler) or `httpOnly` cookie (more secure)? | XSS risk vs complexity |
| 2 | Token expiry: 1h with no refresh, or sliding expiry? | UX |
| 3 | Username used as bcrypt cost salt or prepended to password string? | Auth security |
| 4 | Should login endpoint be rate-limited? | Brute-force protection |
| 5 | Is `users.env` committed to git or only `.env.example`? | Secrets management |