Admin endpoint: ENV/file admin key, --reset-admin-key, settings API wired into ratelimit/max-bytes/default-expiry/burn-window (#40)
CI / test (push) Successful in 22s
CI / docker (push) Skipped

This commit is contained in:
2026-09-09 00:44:02 -05:00
parent 03600b2ed5
commit a3349b4a98
10 changed files with 562 additions and 11 deletions
+29 -5
View File
@@ -419,8 +419,10 @@ func writeErr(w http.ResponseWriter, status int, msg string) {
}
type apiServer struct {
store *Store
cfg Config
store *Store
cfg Config
settings *settingsStore
adminKey string
}
func (a *apiServer) routes() http.Handler {
@@ -429,6 +431,11 @@ func (a *apiServer) routes() http.Handler {
r.Use(middleware.Timeout(30 * time.Second))
r.Use(viewerCookieMiddleware)
// admin (#40): HTML page is open (key entry via form); API is key-guarded
r.Get("/admin", a.handleAdminPage)
r.Get("/admin/api/settings", a.adminAuth(a.handleAdminGetSettings, a.adminKey))
r.Post("/admin/api/settings", a.adminAuth(a.handleAdminPostSettings, a.adminKey))
// API
r.Route("/api", func(r chi.Router) {
r.Post("/pastes", a.handleCreatePaste)
@@ -521,10 +528,15 @@ func (a *apiServer) handleCreatePaste(w http.ResponseWriter, r *http.Request) {
writeErr(w, 400, "content is required")
return
}
if int64(len(p.Content)) > a.cfg.MaxTextBytes {
writeErr(w, 413, fmt.Sprintf("content exceeds max %d bytes", a.cfg.MaxTextBytes))
if int64(len(p.Content)) > a.settings.get().MaxContentBytes { // #40: admin-tunable
writeErr(w, 413, fmt.Sprintf("content exceeds max %d bytes", a.settings.get().MaxContentBytes))
return
}
// #40: admin-configurable default expiry
if (p.ExpiresIn == nil || *p.ExpiresIn == "") && a.settings.get().DefaultExpiry != "" {
def := a.settings.get().DefaultExpiry
p.ExpiresIn = &def
}
p.ViewerID = currentViewerID(r)
created, err := a.store.CreatePaste(&p)
if err != nil {
@@ -770,6 +782,11 @@ func templateEsc(s string) string {
}
func main() {
// #40: --reset-admin-key regenerates the admin key and exits.
if len(os.Args) > 1 && (os.Args[1] == "--reset-admin-key") {
handleResetAdminKey(envOr("PALETTE_DB", "palette.db"))
return
}
cfg := Config{
Addr: envOr("PALETTE_ADDR", ":8080"),
DBPath: envOr("PALETTE_DB", "palette.db"),
@@ -782,12 +799,19 @@ func main() {
}
store.StartSweeper(time.Minute)
adminKey, err := resolveAdminKey(cfg.DBPath)
if err != nil {
log.Fatal(err)
}
ss := loadSettingsStore(cfg.DBPath, cfg)
ui, err := NewWebUI()
if err != nil {
log.Fatal(err)
}
webUIInstance = ui
srv := &apiServer{store: store, cfg: cfg}
srv := &apiServer{store: store, cfg: cfg, settings: ss, adminKey: adminKey}
globalSettingsFn = func() Settings { return ss.get() }
log.Printf("palette listening on %s", cfg.Addr)
log.Fatal(http.ListenAndServe(cfg.Addr, srv.routes()))
}