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>
129 lines
4.6 KiB
Markdown
129 lines
4.6 KiB
Markdown
# 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 |
|