docs icon indicating copy to clipboard operation
docs copied to clipboard

REST API create empty content when "Content-Type" header is not defined

Open pomeh opened this issue 1 year ago • 4 comments

Describe the Bug

Creating a user via curl and REST API with a few fields defined creates an empty user.

To Reproduce

Create the user via curl:

$ curl -X POST -H 'Authorization: Bearer admin-token' "http://127.0.0.1:8055/users" -d '{ "email": "[email protected]", "first_name":"hello" }'
{"data":{"id":"3c8d7d56-ba17-49cb-a09e-2adf64c42587","first_name":null,"last_name":null,"email":null,"password":null,"location":null,"title":null,"description":null,"tags":null,"avatar":null,"language":null,"tfa_secret":null,"status":"active","role":null,"token":null,"last_access":null,"last_page":null,"provider":"default","external_identifier":null,"auth_data":null,"email_notifications":true,"appearance":null,"theme_dark":null,"theme_light":null,"theme_light_overrides":null,"theme_dark_overrides":null,"policies":[]}}

The token used belongs to an Admin user

-> the user is created, but both email and first_name fields are ignored during creation (the response includes "first_name":null,"email":null,, which is incorrect.) -> using Directus app/admin access, we can see the user has been created, but all fields are empty too (this is coherent with the POST JSON response)

Note: I'm running a docker image derived from Directus v11.0.2, containing curl binary. All curl calls are done from inside this custom docker container (no nginx or other RP in between).

If I perform the same curl call again, I have exactly the same behavior, and another empty user is created.

Same behavior again with an "empty json body": curl -X POST -H 'Authorization: Bearer admin-token' "http://127.0.0.1:8055/users" -d '{}'.

After further investigations, I found the cause: my curl cause does not have Content-Type: application/json defined. Using curl -v, I can see curl defaults to Content-Type: application/x-www-form-urlencoded, which I guess Directus does not understand, then it reads the request body as "empty string" no matter what I send.

As soon as I add -H 'Content-Type: application/json' curl's option, the user is created with the given fields correctly set:

$ curl -H 'Content-Type: application/json' -X POST -H 'Authorization: Bearer admin-token' "http://127.0.0.1:8055/users" -d '{ "email": "[email protected]", "first_name":"hello" }'
{"data":{"id":"e0bf25b6-f6b9-4619-a4eb-4ecf8592f993","first_name":"hello","last_name":null,"email":"[email protected]","password":null,"location":null,"title":null,"description":null,"tags":null,"avatar":null,"language":null,"tfa_secret":null,"status":"active","role":null,"token":null,"last_access":null,"last_page":null,"provider":"default","external_identifier":null,"auth_data":null,"email_notifications":true,"appearance":null,"theme_dark":null,"theme_light":null,"theme_light_overrides":null,"theme_dark_overrides":null,"policies":[]}}

If I retry, I now get an expected error:

$ curl -H 'Content-Type: application/json' -X POST -H 'Authorization: Bearer admin-token' "http://127.0.0.1:8055/users" -d '{ "email": "[email protected]", "first_name":"hello" }'
{"errors":[{"message":"Value for field \"email\" in collection \"directus_users\" has to be unique.","extensions":{"collection":"directus_users","field":"email","code":"RECORD_NOT_UNIQUE"}}]}

Still, I can call multiple times with empty JSON body:

$ curl -H 'Content-Type: application/json' -X POST -H 'Authorization: Bearer admin-token' "http://127.0.0.1:8055/users" -d '{}'
{"data":{"id":"4a190eca-aab5-4581-989e-43cd8b971ef5","first_name":null,"last_name":null,"email":null,"password":null,"location":null,"title":null,"description":null,"tags":null,"avatar":null,"language":null,"tfa_secret":null,"status":"active","role":null,"token":null,"last_access":null,"last_page":null,"provider":"default","external_identifier":null,"auth_data":null,"email_notifications":true,"appearance":null,"theme_dark":null,"theme_light":null,"theme_light_overrides":null,"theme_dark_overrides":null,"policies":[]}}

$ curl -H 'Content-Type: application/json' -X POST -H 'Authorization: Bearer admin-token' "http://127.0.0.1:8055/users" -d '{}'
{"data":{"id":"5637b9a7-9a55-4a6e-af7d-964122788e82","first_name":null,"last_name":null,"email":null,"password":null,"location":null,"title":null,"description":null,"tags":null,"avatar":null,"language":null,"tfa_secret":null,"status":"active","role":null,"token":null,"last_access":null,"last_page":null,"provider":"default","external_identifier":null,"auth_data":null,"email_notifications":true,"appearance":null,"theme_dark":null,"theme_light":null,"theme_light_overrides":null,"theme_dark_overrides":null,"policies":[]}}/

To recap, in my comprehension, there are 2 issues:

  1. Directus REST API should reject requests that does not have a valid Content-Type header (valid = a value Directus can handle correctly). Because according to the docs, The API uses JSON for input and output, and application/x-www-form-urlencoded is not JSON.

I also tried with 'Content-Type: application/whatever' and with no Content-Type header at all, in both case an empty user is created

  1. empty body should not be allowed in the POST payload. Even if all users fields are optionnals, it should detect empty payload and rejects with an error like "no payload found".

I didn't test other REST API endpoints, but my guess is the behavior would be the same for endpoints where all fields are optionals (some POST, probably all PATCH). This may be highly mistaken because:

  • you can create empty items in some collections
  • you may receive a 200 OK after a PATCH, you think the update has been done, but no update has been performed. So one would have to check the HTTP Code 200 + the JSON response to ensure all required update has been done

Last but not least: This also may explain the behavior reported in issue https://github.com/directus/directus/issues/22826

Directus Version

v11.0.2

Hosting Strategy

Self-Hosted (Docker Image)

Database

sqlite

pomeh avatar Aug 28 '24 16:08 pomeh

I don't know if I fully agree with statement 2 that empty post bodies should be prevented generally speaking, but 1 is a good suggestion!

rijkvanzanten avatar Sep 02 '24 20:09 rijkvanzanten

This is more of a Documentation issue than a bug.

The following prevent us from doing so:

  • Extensions can define their own content type or none at all so we cannot simply reject requests missing content-types
  • Extensions can add their own content type parser
  • Our import endpoint (e.g. /import/:collection) accepts an optional multipart content type
  • Empty body for POST can be a valid request

Due to the above this is not as trivial as enforcing a content type or rejecting empty body POST requests

ComfortablyCoding avatar Mar 11 '25 14:03 ComfortablyCoding

restricting the content type to always be enforced

This is not what I'm saying or suggesting :)

As you pointed out, the "correct" Content-Type highly depend on each endpoint you're talking to. For most Directus REST API endpoints, application/json seems the standard. Using another CT or none for these endpoint should be considered an error.

Some endpoint, like this "import endpoint", could accept multiple CT values and handle each accordingly.

For the extensions, the use might declare what Content-Type(s) its endpoint support, all other CT value being rejected.

So yes, I guess this is not trivial as this can not be a "one change to fix them all", but rather require a low-level change for each endpoint.

pomeh avatar Mar 11 '25 15:03 pomeh