fix(api): stop the API v1 request validator from rewriting bodies

Filling schema defaults made kin-openapi re-encode the body, so trailing data
after the JSON value of POST /auth/password was silently dropped instead of
answering 400 like the other endpoints. The handler already applies the
revokeOtherGrants default itself.

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan 2026-09-28 21:06:31 -04:00
commit 04b9e97eb2
2 changed files with 12 additions and 6 deletions

View file

@ -158,17 +158,22 @@ var _ = Describe("auth endpoints", func() {
})
DescribeTable("rejects a body with data after its JSON value, without echoing it",
func(body string) {
api.setup()
w := api.callRaw(http.MethodPost, "/api/v1/auth/login", "", body)
func(path string, needsSecret bool, body string) {
secret := ""
if gc := api.setup(); needsSecret {
secret = gc.Secret
}
w := api.callRaw(http.MethodPost, path, secret, body)
Expect(w.Code).To(Equal(http.StatusBadRequest), w.Body.String())
p := decodeProblem(w)
Expect(p.Code).To(Equal(ProblemCodeValidation))
Expect(*p.Errors).To(ConsistOf(ValidationError{Field: "", Message: "must be a single JSON value"}))
Expect(w.Body.String()).ToNot(ContainSubstring("hunter2"))
},
Entry("a trailing byte", `{"username":"a","password":"hunter2","client":"c"}x`),
Entry("a second value", `{"username":"a","password":"hunter2","client":"c"} {}`),
Entry("login with a trailing byte", "/api/v1/auth/login", false, `{"username":"a","password":"hunter2","client":"c"}x`),
Entry("login with a second value", "/api/v1/auth/login", false, `{"username":"a","password":"hunter2","client":"c"} {}`),
// This schema has a default, which the validator must not fill in by rewriting the body.
Entry("password change with a trailing byte", "/api/v1/auth/password", true, `{"currentPassword":"hunter2","newPassword":"pw2"}x`),
)
It("checks the login body even when Content-Type has a repeated parameter", func() {

View file

@ -247,7 +247,8 @@ func bearerToken(r *http.Request) (string, bool) {
return token, true
}
var validationOptions = &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc, MultiError: true}
// SkipSettingDefaults: filling defaults re-encodes the body, which hides trailing data from jsonBodyFields.
var validationOptions = &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc, MultiError: true, SkipSettingDefaults: true}
func (g *gate) validate(w http.ResponseWriter, r *http.Request, op *gateOp, rctx *chi.Context) bool {
params := make(map[string]string, len(rctx.URLParams.Keys))