fix(allowlist): 唤醒名单可查看、可取消或恢复唤醒权限,换主人时清空

This commit is contained in:
Lemon-miaow committed 2026-09-27 02:55:30 +08:00
1 parent d807fd5887
commit 4a26bf4ca0
23 files changed
+1190 -32

No files matched your search

+96
View File
@@ -0,0 +1,96 @@
package api
import (
"net/http"
"github.com/google/uuid"
"felis.lolicon.best/internal/naming"
)
// The wake allowlist (server_allowlist) decides who may wake a sleeping server
// whose autostartPolicy is allowlist. A player lands on it by joining the server
// once (RecordJoin); these two routes let the owner, or an admin, see who is on it
// and take a player's wake right away or give it back. It is Felis's own record in
// Postgres, so it answers whether the server is running or not, unlike the
// Minecraft whitelist under /access, which is the game server's and needs RCON.
// allowlistWakeRequest is the PUT /servers/{name}/allowlist/{uuid} body.
type allowlistWakeRequest struct {
CanWake *bool `json:"can_wake"`
}
// allowlistServer resolves {name} for the allowlist routes and applies their
// gate: 400 for a malformed name, 404 for a server that does not exist, 403 for a
// caller who neither owns it nor is an admin.
func (a *API) allowlistServer(w http.ResponseWriter, r *http.Request) (string, bool) {
name := r.PathValue("name")
if err := naming.ValidateServerName(name); err != nil {
writeError(w, r, newError(http.StatusBadRequest, "bad_name", "invalid server name: %v", err))
return "", false
}
rec, err := a.Repo.ServerByName(r.Context(), name)
if err != nil {
a.writeLookupError(w, r, err)
return "", false
}
if !a.isOwnerOrAdmin(principalFromContext(r.Context()), rec) {
writeError(w, r, errForbidden)
return "", false
}
return name, true
}
// handleAllowlistList returns the server's wake allowlist, newest first,
// including the players whose wake right was taken away.
func (a *API) handleAllowlistList(w http.ResponseWriter, r *http.Request) {
name, ok := a.allowlistServer(w, r)
if !ok {
return
}
entries, err := a.Repo.ServerAllowlist(r.Context(), name)
if err != nil {
writeError(w, r, err)
return
}
writeJSON(w, http.StatusOK, map[string]any{"server": name, "entries": entries})
}
// handleAllowlistSetWake takes a player's wake right away (can_wake false) or
// gives it back (true). The entry stays on the list either way, so a revoked
// player's next join does not quietly undo the owner's choice.
func (a *API) handleAllowlistSetWake(w http.ResponseWriter, r *http.Request) {
name, ok := a.allowlistServer(w, r)
if !ok {
return
}
id, err := uuid.Parse(r.PathValue("uuid"))
if err != nil {
writeError(w, r, newError(http.StatusBadRequest, "bad_request", "invalid Minecraft UUID"))
return
}
var req allowlistWakeRequest
if err := decodeJSON(w, r, &req); err != nil {
writeError(w, r, err)
return
}
if req.CanWake == nil {
writeError(w, r, newError(http.StatusBadRequest, "bad_request", "can_wake is required"))
return
}
if err := a.Repo.SetAllowlistWake(r.Context(), name, id.String(), *req.CanWake); err != nil {
a.writeLookupError(w, r, err)
return
}
p := principalFromContext(r.Context())
e := AuditEntry{Actor: auditActor(p), Action: "allowlist.revoke", ServerName: name}
if *req.CanWake {
e.Action = "allowlist.restore"
}
if p != nil {
e.ActorUserID = p.UserID
}
e.Payload = auditPayload(map[string]any{"mc_uuid": id.String()})
a.auditEntry(r, e)
w.WriteHeader(http.StatusNoContent)
}