Staff avatars from Steam, cached in memory

The public staff page, the staff panel's staff list and the signed-in badge show each member's
Steam avatar over their initial. The panel fetches it from the profile XML (no Web API key),
keeps it in memory for a day (failures for an hour, one shared fetch per player) and serves it
from /avatar/<steamid> with a day's browser caching, so the CSP stays img-src 'self' and
visitors never contact Steam. Only SimpleAdmin admins are served; anything else falls back to the
initial.
This commit is contained in:
Astra 2026-09-28 11:36:05 +01:00
parent 6324fc7f81
commit 861692c534
6 changed files with 196 additions and 21 deletions

View file

@ -34,15 +34,16 @@ type Config struct {
}
type Server struct {
cfg Config
site Site
st *store.Store
rc *rcon.Client
poll *live.Poller
sacfg *saconfig.File // nil when no config path is set
staff *staffCache
http *http.Client
secure bool
cfg Config
site Site
st *store.Store
rc *rcon.Client
poll *live.Poller
sacfg *saconfig.File // nil when no config path is set
staff *staffCache
avatars *avatarCache
http *http.Client
secure bool
}
func New(cfg Config, st *store.Store, rc *rcon.Client, poll *live.Poller, sacfg *saconfig.File) (*Server, error) {
@ -55,15 +56,16 @@ func New(cfg Config, st *store.Store, rc *rcon.Client, poll *live.Poller, sacfg
return nil, errors.New("base URL must be absolute, like https://bans.example.com")
}
return &Server{
cfg: cfg,
site: site,
st: st,
rc: rc,
poll: poll,
sacfg: sacfg,
staff: &staffCache{st: st, ttl: 15 * time.Second},
http: &http.Client{Timeout: 10 * time.Second},
secure: u.Scheme == "https",
cfg: cfg,
site: site,
st: st,
rc: rc,
poll: poll,
sacfg: sacfg,
staff: &staffCache{st: st, ttl: 15 * time.Second},
avatars: newAvatarCache(&http.Client{Timeout: 10 * time.Second}),
http: &http.Client{Timeout: 10 * time.Second},
secure: u.Scheme == "https",
}, nil
}
@ -88,6 +90,7 @@ func (s *Server) Handler() http.Handler {
mux.HandleFunc("GET /api/public/comms", s.publicComms)
mux.HandleFunc("GET /api/public/staff", s.publicStaff)
mux.HandleFunc("GET /api/public/players/{steamid}", s.publicPlayer)
mux.HandleFunc("GET /avatar/{steamid}", s.avatar)
admin := func(pattern string, h adminHandler) { mux.HandleFunc(pattern, s.requireStaff(h)) }
admin("GET /api/admin/overview", s.adminOverview)