feat(felis): add felis nano — Yggdrasil hasJoined multiplexer without a control plane

`felis nano` serves the vanilla sessionserver protocol
(GET /session/minecraft/hasJoined) as a federating multiplexer over
Mojang plus any number of third-party Yggdrasil roots, with no k3s,
Postgres, or panel — a MultiLogin-style auth front-end delivered as a
subcommand of the single felis binary rather than a separate build.

- config.LoadNano reads only [[auth_source]] blocks; it skips the
  database.url / root_domain / archive requirements the full server
  needs. Zero sources is valid (Mojang-only).
- Mojang is prepended in code (Identity:true), never from config, so it
  is always the sole identity root. Third-party profiles are rewritten
  to canonical = UUIDv3(felisAuthNS, tag+":"+nativeID).
- validateAuthSources rejects unknown keys, duplicate tags, and
  scheme-less URLs — a malformed nano config fails loud at load.
- Reuses api.HasJoinedHandler with a stub Repo (no blacklist backend);
  a rejected login is a 204, matching the vanilla sessionserver.
- nano.go binds the -listen flag and ignores [server] listen in config.

Verified on WSL (go1.26.4): go build/vet/test ./... green; a runtime
smoke against the template config returns 204 on a miss and logs
"Mojang + 0 third-party source(s)"; a duplicate-tag config exits
non-zero citing "unique".
This commit is contained in:
flyemoji committed 2026-07-13 02:39:41 +09:00
1 parent cc0c945811
commit d177428fb0
6 files changed
+188 -16

No files matched your search

+13
View File
@@ -59,6 +59,19 @@ type sessionProfile struct {
Properties []json.RawMessage `json:"properties,omitempty"`
}
// HasJoinedHandler returns an http.Handler serving only the Felis-nano hasJoined
// multiplexer route (GET /session/minecraft/hasJoined), for a standalone host that
// federates logins without standing up the full felis-api. sources is the priority list
// (put the Mojang identity source first for 正版优先); repo backs the reclaim blacklist
// gate — a stub that never bars is fine for a host without the reclaim DB. The full
// felis-api mounts the same handler through its internal-face route table instead.
func HasJoinedHandler(sources []AuthSource, repo Repo) http.Handler {
a := &API{AuthSources: sources, Repo: repo}
mux := http.NewServeMux()
mux.HandleFunc("GET /session/minecraft/hasJoined", a.handleHasJoined)
return mux
}
// handleHasJoined is the multi-source session verifier (Felis-nano). It is a Public
// internal-face route: authlib speaks the vanilla sessionserver protocol and sends no
// service token. A rejected login is 204 No Content — exactly what Mojang returns for an