* feat(security): encrypt server-owned credentials at rest Introduce AES-256-GCM at-rest encryption (HKDF-derived from a required SECRET_KEY) for server-owned credentials, with row-bound AAD, a versioned enc:v1: envelope, and an idempotent startup backfill. - internal/secret: cipher + RowAAD/SettingsAAD + the startup backfill engine. - SECRET_KEY required at bootstrap; cipher threaded as an explicit dependency. - server_settings: EncryptedSettingsRepo decorator over the audited SensitiveSettingKeys (also drives admin redaction); the config watcher and watch-sync settings reads decrypt too. - Arr keys inline-encrypted; the ambiguous SecretResolver indirection removed from requests/autoscan. - Per-table columns encrypted: subtitles, watch-sync, webhook-sync (not webhook_secret), history-import, and the jellycompat session's bridged Silo access/refresh tokens. - Startup backfill (resolve-then-encrypt for arr refs) is best-effort and primary-node gated. Equality-looked-up secrets and plugin_runtime_configs.config_value are out of scope (need hashing / cross-repo design) — see docs/architecture/secret-encryption.md. Refs #45 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(compose): require SECRET_KEY in docker-compose The server now fatals without SECRET_KEY, so the integrated service (and the commented distributed proxy/transcode examples) pass it through with a fail-fast guard matching the existing MEDIA_ROOT pattern. Distributed worker nodes must use the SAME key as the primary to decrypt shared data. Generate with: openssl rand -base64 48. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(security): encrypt history import session credentials --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
63 lines
2.0 KiB
Go
63 lines
2.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/catalog"
|
|
)
|
|
|
|
// SectionsAllowProfileCustomSettingKey is the server_settings key controlling
|
|
// whether non-admin profiles may add user-added sections of admin-only recipes.
|
|
const SectionsAllowProfileCustomSettingKey = "sections.allow_profile_custom_sections"
|
|
|
|
// SectionSettingsHandler exposes GET/PUT for the sections-related server setting.
|
|
type SectionSettingsHandler struct {
|
|
Settings catalog.SettingsStore
|
|
}
|
|
|
|
type sectionsSettingResponse struct {
|
|
AllowProfileCustomSections bool `json:"allow_profile_custom_sections"`
|
|
}
|
|
|
|
// HandleGet handles GET /api/admin/settings/sections.
|
|
func (h *SectionSettingsHandler) HandleGet(w http.ResponseWriter, r *http.Request) {
|
|
var resp sectionsSettingResponse
|
|
if h.Settings != nil {
|
|
v, _ := h.Settings.Get(r.Context(), SectionsAllowProfileCustomSettingKey)
|
|
resp.AllowProfileCustomSections = v == "true"
|
|
}
|
|
writeJSON(w, http.StatusOK, resp)
|
|
}
|
|
|
|
// HandleGetProfileFlag exposes the allow_profile_custom_sections setting to any
|
|
// authenticated profile (no admin required). Read-only.
|
|
func (h *SectionSettingsHandler) HandleGetProfileFlag(w http.ResponseWriter, r *http.Request) {
|
|
var resp sectionsSettingResponse
|
|
if h.Settings != nil {
|
|
v, _ := h.Settings.Get(r.Context(), SectionsAllowProfileCustomSettingKey)
|
|
resp.AllowProfileCustomSections = v == "true"
|
|
}
|
|
writeJSON(w, http.StatusOK, resp)
|
|
}
|
|
|
|
// HandlePut handles PUT /api/admin/settings/sections.
|
|
func (h *SectionSettingsHandler) HandlePut(w http.ResponseWriter, r *http.Request) {
|
|
var req sectionsSettingResponse
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "bad_json", err.Error())
|
|
return
|
|
}
|
|
if h.Settings != nil {
|
|
value := "false"
|
|
if req.AllowProfileCustomSections {
|
|
value = "true"
|
|
}
|
|
if err := h.Settings.Set(r.Context(), SectionsAllowProfileCustomSettingKey, value); err != nil {
|
|
writeError(w, http.StatusInternalServerError, "save_failed", err.Error())
|
|
return
|
|
}
|
|
}
|
|
writeJSON(w, http.StatusOK, req)
|
|
}
|