# 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$...,... ``` 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 ` 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 ` → 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 |