package webapp import ( "net/http" "time" ) // handleSettings renders the worker fields (worker id, region, // capabilities, version, last heartbeat ack, last token rotation). // The token is masked; rotation is a POST to // /settings/rotate-token (see handleRotateToken). func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) { writeNoStore(w) sess, _ := sessionFromContext(r.Context()) token := "" if s.deps.Runner != nil { token = s.deps.Runner.Token() } rotatedAt := time.Time{} if s.deps.Runner != nil { rotatedAt = s.deps.Runner.TokenRotatedAt() } data := settingsPageData{ basePageData: s.newBasePage(r, "Settings", sess), WorkerID: workerIDOrDash(s.deps.Runner), RegionCode: regionOrDash(s.deps.Runner), WorkerVersion: workerVersionOrDash(s.deps.Runner), Capabilities: capabilitiesOrEmpty(s.deps.Runner), LastAckAt: lastAckOrZero(s.deps.Runner), TokenMasked: MaskToken(token), TokenRotatedAt: rotatedAt, } if err := s.templates.Execute(w, "settings.html", data); err != nil { s.deps.Logger.Printf("render settings: %v", err) http.Error(w, "template error", http.StatusInternalServerError) } } func workerVersionOrDash(v WorkerView) string { if v == nil { return "—" } return v.WorkerVersion() } func capabilitiesOrEmpty(v WorkerView) []string { if v == nil { return nil } return v.WorkerCapabilities() } type settingsPageData struct { basePageData WorkerID string RegionCode string WorkerVersion string Capabilities []string LastAckAt time.Time TokenMasked string TokenRotatedAt time.Time } // handleRotateToken calls the worker-defined rotator (if any) to // issue a fresh token via the main app's API and update the in- // memory runner config. On failure, return 502 and keep the old // token (the doc's Phase 1 contract). func (s *Server) handleRotateToken(w http.ResponseWriter, r *http.Request) { writeNoStore(w) sess, ok := sessionFromContext(r.Context()) if !ok { redirectTo(w, r, "/web/login") return } if !s.requireCSRF(sess, r) { http.Error(w, "csrf token required", http.StatusForbidden) return } if s.deps.TokenRotator == nil { http.Error(w, "token rotation is not configured", http.StatusNotImplemented) return } newToken, err := s.deps.TokenRotator(r.Context()) if err != nil { s.deps.Logger.Printf("token rotation: %v", err) http.Error(w, "rotation failed: "+err.Error(), http.StatusBadGateway) return } _ = s.store.WriteAudit(r.Context(), &AuditEntry{ Actor: auditActorLocal, Role: auditRoleAdmin, AuthMode: auditAuthModeLocal, IP: clientIP(r), UA: r.UserAgent(), Action: "token_rotation", Target: auditTargetSelf, }) _ = newToken // the rotator updates the runner; the page just confirms success. redirectTo(w, r, "/settings") }