aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/api_files.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/admin/api_files.go')
-rw-r--r--internal/admin/api_files.go30
1 files changed, 19 insertions, 11 deletions
diff --git a/internal/admin/api_files.go b/internal/admin/api_files.go
index a19cb3b..b1b3fff 100644
--- a/internal/admin/api_files.go
+++ b/internal/admin/api_files.go
@@ -234,8 +234,13 @@ func (s *AdminServer) handleFiles(w http.ResponseWriter, r *http.Request) {
return
}
// A redacted account hash is restored from the file on disk before
- // writing, so the raw editor can never disclose or overwrite it.
- body = restoreAccountSecret(relPath, fullPath, body)
+ // writing, so the raw editor can never disclose or overwrite it. A
+ // non-mapping body is refused rather than blanking the hash.
+ body, err = restoreAccountSecret(relPath, fullPath, body)
+ if err != nil {
+ failJSON(w, err.Error())
+ return
+ }
oldContent, existed, err := snapshotBefore(fullPath)
if err != nil {
@@ -375,31 +380,34 @@ func redactAccountContent(relPath string, data []byte) []byte {
// restoreAccountSecret copies the on-disk password hash back into a submitted
// account file, so a redacted value (or any client-supplied value) can never
-// change or blank an existing hash through the raw file editor.
-func restoreAccountSecret(relPath, fullPath string, body []byte) []byte {
+// change or blank an existing hash through the raw file editor. It returns an
+// error when the existing hash cannot be preserved (for example a non-mapping
+// body), so the caller refuses the write instead of blanking the hash.
+func restoreAccountSecret(relPath, fullPath string, body []byte) ([]byte, error) {
if !isAccountFilePath(relPath) {
- return body
+ return body, nil
}
existing, err := os.ReadFile(fullPath)
if err != nil {
- return body
+ // A missing account file is a create; nothing to preserve.
+ return body, nil
}
em, err := yamlToMap(existing)
if err != nil {
- return body
+ return nil, fmt.Errorf("existing account file is not a YAML mapping: %w", err)
}
hash, ok := em["password_hash"]
if !ok {
- return body
+ return body, nil
}
bm, err := yamlToMap(body)
if err != nil {
- return body
+ return nil, fmt.Errorf("submitted account file is not a YAML mapping; refusing to blank the password hash")
}
bm["password_hash"] = hash
out, err := marshalMapYAML(bm)
if err != nil {
- return body
+ return nil, fmt.Errorf("marshal account file: %w", err)
}
- return out
+ return out, nil
}