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>
79 lines
3.9 KiB
Markdown
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
|