Files
HarborForge.Backend/README.md
hzhang f03bfe9093 docs: README accuracy pass + Security section
Document the auth/RBAC/SSRF hardening in this branch: mandatory strong
SECRET_KEY (server refuses weak/default), admin-only + masked /api-keys,
admin-only /webhooks with SSRF guard, project role hierarchy, and auth
added to previously-open endpoints. Fixed stale Issues→tasks model.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:50:25 +01:00

174 lines
7.0 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.
# HarborForge Backend
The core REST API for HarborForge — an Agent/人类协同任务管理平台 (Agent/Human collaborative task-management platform).
Part of the [HarborForge](../README.md) platform.
- **Role:** core REST API — users, projects, tasks, milestones, proposals, RBAC, webhooks, worklogs, notifications, monitor telemetry.
- **Stack:** Python 3.11 · FastAPI · SQLAlchemy · MySQL
- **Port:** `8000`
The service reads its database configuration from the AbstractWizard config volume (falling back to env/defaults) and authenticates requests with JWT (HS256) signed by `SECRET_KEY`.
## Run / Build
### Docker
```bash
docker build -t harborforge-backend .
docker run -p 8000:8000 \
-e SECRET_KEY="$(openssl rand -hex 32)" \
-v /path/to/config:/config \
harborforge-backend
```
### Local (uvicorn)
```bash
pip install -r requirements.txt
export SECRET_KEY="$(openssl rand -hex 32)"
uvicorn app.main:app --host 0.0.0.0 --port 8000
```
On startup the app creates/migrates the schema, runs AbstractWizard
initialization (admin user, default project, default roles), and starts a
background monitor-polling thread.
## Configuration
Environment variables (also loadable from a `.env` file):
| Variable | Default | Description |
|----------|---------|-------------|
| `SECRET_KEY` | *(none — must be set)* | JWT signing key (HS256). The server **refuses to start** with a weak/default/short value. |
| `DATABASE_URL` | `mysql+pymysql://harborforge:harborforge_pass@mysql:3306/harborforge` | Fallback DB URL when the wizard config volume is absent. |
| `ALGORITHM` | `HS256` | JWT algorithm. |
| `ACCESS_TOKEN_EXPIRE_MINUTES` | `30` | Access-token lifetime. |
| `LOG_LEVEL` | `INFO` | Log level. |
| `CONFIG_DIR` | `/config` | AbstractWizard config volume directory. |
| `CONFIG_FILE` | `harborforge.json` | Config file name within `CONFIG_DIR`. |
Database resolution order: **wizard config volume** (`$CONFIG_DIR/$CONFIG_FILE``database` block) → `DATABASE_URL` env → built-in default.
## Security
The current code enforces the following security posture. These are
operational requirements, not optional hardening.
### Mandatory strong `SECRET_KEY`
`app/core/config.py` validates `SECRET_KEY` at import time and **raises and
refuses to start** if the value is empty, shorter than 32 characters, or a
known default/placeholder (e.g. `change-me-in-production`, `secret`,
`changeme`). Operators **must** provide a strong random key:
```bash
openssl rand -hex 32
```
A weak signing key allows JWT forgery and full authentication bypass, so this
check is intentionally fatal.
### API-key management is admin-only and masked
The `/api-keys` endpoints (`POST`, `GET`, `DELETE /api-keys/{id}`) all require
a global admin (`require_admin`). Listing **never returns the full secret**
keys are masked to a short prefix/suffix (e.g. `abc123…9f`). The full key is
only returned once, on creation.
### Webhooks router is admin-only with SSRF protection
The entire `/webhooks` router is mounted with `dependencies=[Depends(require_admin)]`,
so every webhook endpoint (create/list/get/update/delete/logs/retry) requires a
global admin. Webhook delivery (`app/services/webhook.py`) validates the target
URL before sending:
- Only `http`/`https` schemes are allowed.
- The host is DNS-resolved and **every** resolved address is rejected if it is
private, loopback, link-local, multicast, reserved, or unspecified
(SSRF protection).
- HTTP redirects are disabled (`follow_redirects=False`).
### Project role hierarchy enforcement
`check_project_role` in `app/api/rbac.py` enforces a real, ordered role
hierarchy rather than a flat membership check:
```
guest(0) < viewer(1) < member(2) < dev(3) < mgr(4) < admin(5)
```
A caller below the required rank is denied with `403`, and any unknown role on
either side is denied by default. Global admins bypass project-level checks.
### Authentication on previously open endpoints
The following endpoints now require an authenticated caller
(JWT bearer token **or** `X-API-Key`) and enforce ownership/permission:
- `DELETE /milestones/{id}` — requires milestone-edit permission.
- `POST /worklogs` — worklogs are always attributed to the caller; only admins
may log time for another user.
- `DELETE /worklogs/{id}` — caller-scoped; non-admins cannot delete another
user's worklog.
- `POST /tasks/{task_code}/assign` and `POST /tasks/batch/assign`.
- `GET /activity`.
- `GET /export/tasks`.
## Authentication
- `POST /auth/token` — OAuth2 password grant; returns a JWT bearer token.
- Authenticated requests send `Authorization: Bearer <token>` **or**
`X-API-Key: <key>` (API keys map to a user and are created by admins).
- `GET /auth/me` — current user.
- `GET /auth/me/permissions`, `GET /auth/me/apikey-permissions` — permission introspection.
## Key API Areas
| Area | Prefix / Routes | Notes |
|------|-----------------|-------|
| Auth | `/auth/*` | token, current user, permission introspection |
| Users | `/users` | registration, list/detail/update (list & mutate are admin-only) |
| Projects | `/projects` | CRUD, members (`/projects/{id}/members`), worklog summary |
| Project members | `/projects/{id}/members` | add/list/remove with role |
| Milestones | `/projects/{id}/milestones`, `/milestones/{id}` | CRUD, items, progress |
| Milestone actions | preflight / freeze / start / close | lifecycle transitions |
| Tasks | `/tasks` | CRUD, transition, take, assign, batch transition/assign, tags, search |
| Comments | `/comments`, `/tasks/{id}/comments` | CRUD |
| Proposals | `/projects/{code}/proposals` | propose / accept / reject / reopen (legacy `/proposes`) |
| Essentials | proposal essentials | feature/improvement/refactor items |
| Meetings | `/meetings` | create/list/detail/update/delete/attend |
| Roles & RBAC | `/roles` | roles, permissions, role-permission assignment |
| Webhooks | `/webhooks` | **admin-only**; CRUD, logs, retry (SSRF-guarded delivery) |
| API keys | `/api-keys` | **admin-only**; create/list (masked)/revoke |
| Worklogs | `/worklogs`, `/tasks/{id}/worklogs`, `/users/{id}/worklogs` | time tracking & summaries |
| Notifications | `/notifications` | list, unread count, mark read / read-all |
| Activity | `/activity` | activity log (authenticated) |
| Export | `/export/tasks` | CSV export (authenticated) |
| Calendar | `/calendar` | scheduling / time slots |
| Monitor | `/monitor` | public overview, admin providers/servers, heartbeat telemetry |
| Dashboard | `/dashboard/stats` | aggregate statistics |
| System | `/health`, `/version`, `/config/status` | health, version, init status |
## Task Types
| Type | 用途 |
|------|------|
| issue | 普通任务 |
| story | 用户故事 |
| test | 测试用例 |
| resolution | 决议案Agent 僵局提交)|
## CLI
The legacy Python CLI (`cli.py`) has been retired. Use the Go-based `hf` CLI instead.
See [HarborForge.Cli](../HarborForge.Cli/README.md) for installation and usage.
## Tech Stack
- Python 3.11 + FastAPI
- SQLAlchemy + MySQL (auto schema create/migrate on startup)
- JWT (python-jose, HS256) + bcrypt password hashing
- Docker