Files
ulicraft-server-v1/plan/03-drasl.md
Oier Bravo Urtasun c233b1bfbc docs(drasl): record admin bootstrap + config-key/CORS/NMSR gotchas
Capture what this session learned the hard way, in plan/03-drasl.md and CLAUDE.md:

- Correct admin key is DefaultAdmins (not DefaultAdminUsernames); wrong/unknown
  keys are silently ignored by drasl (logged as "unknown config option").
- CORSAllowOrigins must be top-level, before any [Section], or it's ignored.
- First-admin bootstrap under invite mode is a chicken-egg → temp-flip
  RequireInvite=false, register, revert.
- NMSR needs the auth. host-gateway pin + https mojank endpoints or avatars
  render the default skin (cross-ref plan/19-routes.md).
- Fix the stale example config and mark the admin-bootstrap task done.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 21:20:54 +02:00

79 lines
3.9 KiB
Markdown

# Drasl — self-hosted auth (Yggdrasil + skins)
## Summary
Self-hosted, Yggdrasil-compatible auth + skin server. Drop-in Mojang replacement
via authlib-injector. Image: `unmojang/drasl:latest`. Reached only through Caddy
at `auth.ulicraft.net`; no host port.
**OIDC/Keycloak is OUT for now** — drasl runs in **password-login** mode
(`AllowPasswordLogin = true`). Admin + guests register with a username/password on
the drasl web UI. Re-introducing Keycloak OIDC later is a future task.
Config is **TOML only** (no env var support). So `config.toml` is rendered from
`config.toml.tmpl` by `tooling/render-config.sh` to inject `BASE_DOMAIN`.
## config.toml.tmpl (key fields)
```toml
BaseURL = "https://auth.${BASE_DOMAIN}" # public scheme is HTTPS (nginx TLS)
Domain = "auth.${BASE_DOMAIN}"
ListenAddress = "0.0.0.0:25585" # internal; Caddy proxies
DefaultAdmins = ["admin"] # NOT DefaultAdminUsernames (see gotchas)
AllowPasswordLogin = true
# Top-level — NOT under any [Section] (see gotchas).
CORSAllowOrigins = ["https://${BASE_DOMAIN}"]
[ApplicationOwner]
# ...
# OIDC block intentionally omitted for now (no Keycloak).
# [[RegistrationOIDC]] ← future
```
## Critical gotchas (carry-over)
- **1.21+ secure profile**: drasl `SignPublicKeys = false` MUST pair with server
`ENFORCE_SECURE_PROFILE=FALSE`. Linked — both or neither.
- **authlib endpoint** = `BaseURL` + `/authlib-injector`
`http://auth.ulicraft.net/authlib-injector`. Must match on server and client.
Network alias makes this identical inside/outside the stack.
- **`Domain` affects skins** — if wrong, authlib clients may not see skins.
- **Config keys are validated loosely — wrong keys are silently ignored** (drasl
logs `Warning: unknown config option <key>` and moves on). Two that bit us:
- Admin list key is **`DefaultAdmins`** (top-level), NOT `DefaultAdminUsernames`
(that key never existed → no admin → admin-only routes like `GET /players`
return 403).
- **`CORSAllowOrigins` must be top-level**, before any `[Section]`. Placed after
a table header it parses as `<table>.CORSAllowOrigins`, is ignored, and the
API serves no CORS headers → landing register/account browser calls blocked.
- `RateLimit.Burst` is valid in drasl master but warns on `unmojang/drasl:latest`
(image lags) — harmless.
- **First-admin bootstrap with invite mode on is a chicken-egg.** `DefaultAdmins`
only promotes the account at startup reconciliation, and `POST /drasl/api/v2/users`
is invite-gated (admins do NOT bypass the *register* endpoint). To create the
first admin: temporarily set `RequireInvite = false` in the rendered config,
`docker compose restart drasl`, `POST /users` to register `admin`, then revert +
restart (always trap-revert). API recap: `POST /login {username,password}`
`{apiToken}`; admin-only `GET /players` with `Authorization: Bearer <token>`.
- **NMSR avatars need https + a host-gateway pin** (see `plan/19-routes.md`).
Drasl's profile embeds **https** skin URLs (BaseURL is https). NMSR reaches
those via `extra_hosts: auth.${BASE_DOMAIN}:host-gateway` (host nginx :443, real
LE cert) with https mojank endpoints. Without it the skin fetch Connect-fails to
caddy:443 and every avatar renders the DEFAULT skin.
## Tasks
- [ ] Create `drasl/config/config.toml.tmpl` (password-login, no OIDC block)
- [ ] Set `BaseURL`/`Domain` to `auth.${BASE_DOMAIN}`, `ListenAddress 0.0.0.0:25585`
- [ ] Set `SignPublicKeys = false`
- [ ] Wire `render-config.sh``drasl/config/config.toml` (gitignored)
- [ ] Remove drasl host port from compose (internal only)
- [ ] Update compose: drasl on `mcnet`, no `25585:25585` publish
- [x] Bootstrap admin account (`admin`, done 2026-06-10 via invite-flip; creds in
cochi `.env` for the mc-status roster). Create guest accounts as needed.
- [ ] Verify authlib endpoint responds through Caddy
- [ ] Future: re-add `[[RegistrationOIDC]]` Keycloak block + secret file