@@ -45,12 +45,56 @@ docker run --rm -p 3000:3000 tbd
|
||||
| `GET` | `/api/add?x=2&y=3` | Adds two numbers, returns EDN: `{:result 5}` (400 if either is missing or not a number) |
|
||||
| `GET` | `/api-docs` | Swagger UI |
|
||||
| `GET` | `/swagger.json` | OpenAPI/Swagger spec |
|
||||
| `GET` | `/auth/login` | Starts the Gitea OAuth login (redirects to Gitea) |
|
||||
| `GET` | `/auth/callback` | OAuth redirect target: completes the login, sets the session cookie, redirects to `/` |
|
||||
| `POST` | `/auth/logout` | Ends the session |
|
||||
| `GET` | `/auth/me` | The logged-in user, `{:login "alice" :id 7 :admin? true}` (401 if not logged in) |
|
||||
| `POST` | `/nrepl` | **Admin only.** Body `{"enabled": true}` (JSON or EDN) starts the nREPL debug server, `false` stops it; returns `{:enabled true :bind "127.0.0.1" :port 7888}` |
|
||||
|
||||
```bash
|
||||
curl localhost:3000/api/ping # pong
|
||||
curl 'localhost:3000/api/add?x=2&y=3' # {:result 5}
|
||||
```
|
||||
|
||||
## Authentication and the admin API
|
||||
|
||||
Login is OAuth2 (authorization-code flow) against a [Gitea](https://about.gitea.com/) instance.
|
||||
Anyone with a Gitea account can log in, but routes marked `:admin? true` (currently
|
||||
`POST /nrepl`) also require the user to be on the `ADMIN_USERS` allowlist: **401** without a
|
||||
login, **403** for a logged-in non-admin. With no allowlist, nobody is an admin.
|
||||
|
||||
1. In Gitea (Settings > Applications) create an OAuth2 application, a confidential client, with
|
||||
the redirect URI `<BASE_URL>/auth/callback`.
|
||||
2. `cp .env-template .env` and fill it in. `script/server` loads `.env`; for Docker use
|
||||
`docker run --env-file .env`. `.env` is git-ignored and kept out of the image.
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `GITEA_URL`, `GITEA_CLIENT_ID`, `GITEA_CLIENT_SECRET` | The Gitea OAuth2 application. Without all three (and `BASE_URL`), `/auth/login` answers 503 |
|
||||
| `BASE_URL` | Public URL of this app; an `https://` one also marks the session cookie `Secure` |
|
||||
| `ADMIN_USERS` | Comma-separated Gitea usernames (case-insensitive) allowed to use the admin API |
|
||||
| `SESSION_SECRET` | Signs and encrypts session cookies. Unset = a random key per start, so logins don't survive a restart |
|
||||
| `NREPL_PORT`, `NREPL_BIND` | Where the debug nREPL listens (default `7888` on `127.0.0.1`) |
|
||||
|
||||
Sessions live in an encrypted, signed, `HttpOnly`, `SameSite=Lax` cookie and expire after 8 hours
|
||||
(enforced server-side). The cookie is stateless, so `POST /auth/logout` clears it in the browser but
|
||||
can't revoke a copy someone already captured; it just expires on its own.
|
||||
|
||||
### The nREPL endpoint
|
||||
|
||||
```bash
|
||||
# after logging in in a browser, copy the `tbd-session` cookie value:
|
||||
curl -XPOST localhost:3000/nrepl -H 'Cookie: tbd-session=...' \
|
||||
-H 'Content-Type: application/json' -d '{"enabled": true}'
|
||||
clj -Sdeps '{:deps {nrepl/nrepl {:mvn/version "1.2.0"}}}' -M -m nrepl.cmdline --connect --host 127.0.0.1 --port 7888
|
||||
```
|
||||
|
||||
nREPL has no authentication and evaluates arbitrary code, so enabling it is effectively remote code
|
||||
execution for whoever can reach the port. It therefore binds to loopback by default; reach it through
|
||||
an SSH tunnel (or `docker exec`) rather than setting `NREPL_BIND=0.0.0.0`. Each enable/disable is
|
||||
logged with the admin's Gitea username. It stays on until an admin disables it (or the server
|
||||
restarts), so switch it off when you're done.
|
||||
|
||||
## UI
|
||||
|
||||
`ui/` is a [re-frame](https://github.com/day8/re-frame) app (ClojureScript, React 19 via
|
||||
@@ -109,7 +153,10 @@ src/tbd/
|
||||
core.clj Router, Ring app, and server startup
|
||||
<group>/routes.clj Each route group (health, math, ...) owns its route subtree
|
||||
<group>/core.clj Business logic for the group, with no HTTP concerns
|
||||
auth/ Gitea OAuth (core), session + admin middleware (middleware), /auth routes
|
||||
nrepl/ On-demand nREPL server (core) and the admin-only POST /nrepl route
|
||||
ui/ ClojureScript re-frame app (shadow-cljs project); ui/src/tbd/ui/{db,events,subs,views,core}.cljs
|
||||
.env-template Settings the server reads from the environment; copy to .env (git-ignored)
|
||||
resources/public/ index.html (the shell served at /) and js/ (compiled UI output, git-ignored)
|
||||
test/ Unit tests
|
||||
integration-test/ Dockerized integration tests and coverage harness
|
||||
@@ -117,4 +164,5 @@ script/ Bash entry points (server, ui-build, test, coverage, repl,
|
||||
```
|
||||
|
||||
To add a new group, create `tbd.<group>.routes` with a `routes` fn that returns a reitit route
|
||||
vector, then add it to the router in `src/tbd/core.clj`.
|
||||
vector, then add it to the router in `src/tbd/core.clj`. To make a route admin-only, put
|
||||
`:admin? true` in its route data.
|
||||
|
||||
Reference in New Issue
Block a user