add public icon route + forwarded proxy trust; SPEC/AGENTS docs

This commit is contained in:
proitlab committed 2026-08-16 20:58:38 +07:00
1 parent e737cd0b63
commit bc8021825a
4 files changed
+23 -6

No files matched your search

+2 -1
View File
@@ -3,7 +3,7 @@
Project conventions and operational gotchas for agents working in this repo.
## Layout
- `app/main.py` — FastAPI app: public endpoints `GET /api/getTokenInfo`, `GET /api/version/android`, `GET /img/token/<filename>`; mounts `/static`; startup calls `db.init_db()`.
- `app/main.py` — FastAPI app: public endpoints `GET /api/getTokenInfo`, `GET /api/version/android`, `GET /img/token/<filename>` + `GET /public/img/token/<filename>` (alias, seed iconUrl path); mounts `/static`; startup calls `db.init_db()`.
- `app/admin.py` — sqladmin panel: `TokenAdmin` (custom WTForms form, `on_model_change`), `VersionAdmin`, `create_admin(app)`. Upload icons saved under `app/static/icons/token/` with `iconUrl = {IDRS_PUBLIC_URL}/img/token/<filename>`.
- `app/models.py` — SQLAlchemy `Token`/`Version`, engine, `SessionLocal`, `ICON_DIR`, `DB_PATH` (env `IDRS_DB_PATH`).
- `app/db.py` — seed from `tokenList.json`/`versionAndroid.json` (once, empty DB only).
@@ -26,6 +26,7 @@ Project conventions and operational gotchas for agents working in this repo.
- Deploy (Swarm): `docker stack deploy -c docker-compose.yml idrs` — service `idrs-api`, traefik labels (Host `idrs.databisnis.id`, `entrypoints=websecure`, `certresolver=letsencrypt`), constraint `node.hostname != server2U`, external overlay `traefik-net` (must pre-exist on manager), `env_file: .env`.
- NFS bind mounts: `/mnt/nfs/server5.saltis.id/data/idrs/data` → `/app/data` (DB), `/mnt/nfs/server5.saltis.id/data/idrs/static` → `/app/app/static` (icons live at `<static>/icons/`; `icons/` subdir auto-created on first upload).
- MUST use a single uvicorn worker (in-memory starlette sessions → `--workers >1` breaks login).
- Behind traefik, uvicorn must trust the proxy scheme or admin assets come out `http://` and the panel is unstyled (mixed content): stack env sets `FORWARDED_ALLOW_IPS=*`.
- DB + uploaded icons persist in the volumes across container recreate; code changes need an image rebuild + re-push.
## Verification
+4 -1
View File
@@ -72,8 +72,11 @@ T20|x|Dockerfile (python:3.12-slim, uvicorn single worker, non-root) + .dockerig
T21|x|`tokenList.json`: replace seed iconUrl domain `idrs.kriptoteknologi.io` → `idrs.databisnis.id` (14 rows; existing DBs need manual update)|V5
T22|x|icon upload → `token/` subdir + `/img/token/<filename>` route (flat route removed); migrate `ayam-logo.png` into `token/`, backfill token1 iconUrl|V15
T23|x|docker-compose.yml: swarm stack — service `idrs-api` (registry image `git.proit.id/proitlab/idrs-api`), traefik labels (Host `idrs.databisnis.id`, websecure, letsencrypt), constraint `node.hostname != server2U`, external `traefik-net`, NFS bind mounts (`data` + `static`)|§C
T24|x|`/public/img/token/<filename>` alias route (seed iconUrl path) serving `icons/token/`; `FORWARDED_ALLOW_IPS=*` in stack env so uvicorn trusts traefik scheme (admin assets load over https)|V14,V15
## §B — Bug log
id|date|cause|fix
B1|2026-08-16|seed `tokenList.json`: token CHIP `"filename":null` → `t.get("filename","")` returns None (key exists) → NOT NULL constraint failed|seed null-coalesces `t.get("filename") or ""`, `t.get("iconUrl") or ""` (V5)
B2|2026-08-16|icon upload checks only client `content-type` header, saves any filename, `/img` serves mime via `guess_type` → uploaded `x.html` served as `text/html` on API origin (stored XSS)|V14: magic-bytes PNG check + `.png` extension + size cap + forced `image/png` content-type on `/img`
B2|2026-08-16|icon upload checks only client `content-type` header, saves any filename, `/img` serves mime via `guess_type` → uploaded `x.html` served as `text/html` on API origin (stored XSS)|V14: magic-bytes PNG check + `.png` extension + size cap + forced `image/png` content-type on `/img`
B3|2026-08-16|behind traefik, uvicorn ignores `X-Forwarded-Proto` from non-loopback proxy (default `FORWARDED_ALLOW_IPS=127.0.0.1`) → sqladmin statics URLs absolute `http://` → browser blocks as mixed content → admin unstyled|`FORWARDED_ALLOW_IPS=*` in stack env so uvicorn honors forwarded scheme
B4|2026-08-16|seed `iconUrl`s use `/public/img/token/<file>` (original API path shape) but app only served `/img/token/<file>` → seeded icons 404 on mirror host|`/public/img/token/<filename>` alias route serves `icons/token/`
+11 -2
View File
@@ -48,8 +48,7 @@ def get_version_android():
}
@app.get("/img/token/{filename}")
def get_icon(filename: str):
def _serve_icon(filename: str):
token_dir = ICON_DIR / "token"
path = (token_dir / filename).resolve()
if not path.is_file() or token_dir.resolve() not in path.parents:
@@ -57,6 +56,16 @@ def get_icon(filename: str):
return FileResponse(path, media_type="image/png")
@app.get("/img/token/{filename}")
def get_icon(filename: str):
return _serve_icon(filename)
@app.get("/public/img/token/{filename}")
def get_public_icon(filename: str):
return _serve_icon(filename)
@app.on_event("startup")
def startup():
db.init_db()
+6 -2
View File
@@ -1,8 +1,11 @@
services:
idrs-api:
image: git.proit.id/proitlab/idrs-api:latest
env_file:
- .env
environment:
ADMIN_USERNAME: admin
ADMIN_PASSWORD: Pro4dm1n
IDRS_PUBLIC_URL: https://idrs.databisnis.id
FORWARDED_ALLOW_IPS: "*"
volumes:
- /mnt/nfs/server5.saltis.id/data/idrs/data:/app/data
- /mnt/nfs/server5.saltis.id/data/idrs/static:/app/app/static
@@ -20,6 +23,7 @@ services:
- traefik.http.routers.idrs-api.entrypoints=websecure
- traefik.http.routers.idrs-api.tls.certresolver=letsencrypt
- traefik.http.services.idrs-api.loadbalancer.server.port=8000
- id.proit.routing=mvnet
networks:
traefik-net: