diff options
Diffstat (limited to 'internal/admin/api_files.go')
| -rw-r--r-- | internal/admin/api_files.go | 30 |
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 } |
