feat(upload): increase document upload size limits to 50MB across the application

This commit is contained in:
marshalyordanos
2026-08-11 09:26:23 +03:00
parent 577390ca6b
commit ea6eccbf09
9 changed files with 190 additions and 18 deletions

86
docs/uploads.md Normal file
View File

@@ -0,0 +1,86 @@
# Upload size limits
A document upload passes through four independent ceilings. The **smallest one
wins**, so raising a limit in application code changes nothing while a lower
limit sits in front of it.
| # | Layer | Limit | Where it lives |
| - | ----- | ----- | -------------- |
| 1 | Reverse proxy (`client_max_body_size`) | **50m** | nginx config on the API host — **not in this repo** for deployed environments |
| 2 | Express JSON/urlencoded body | `JSON_BODY_LIMIT` = 80mb | `apps/edr-freight-api/src/main.ts` |
| 3 | Multer multipart (`fileSize`) | `DOCUMENT_UPLOAD_MAX_BYTES` = 50MB | `apps/edr-freight-api/src/common/document-upload.options.ts` |
| 4 | Per-field portal gate (`max_size_mb`) | 50 | `freight.file_upload_fields`, seeded by `file-upload-settings.seeder.ts` |
Layer 4 is the only one the customer sees before uploading — `SmartFileInput`
blocks the file client-side with "File size exceeds the limit of NMB". Layers
13 produce a failed request after the fact.
## The nginx layer is the one that bites
nginx defaults `client_max_body_size` to **1m** and answers anything larger with
its own HTML error page:
```
HTTP/1.1 413 Request Entity Too Large
Server: nginx
Content-Type: text/html
```
Two tells that a 413 came from the proxy rather than the API:
- the body is nginx's HTML page, not the API's JSON envelope, and
- `curl -w '%{size_upload}'` reports **0** — nginx rejects on the `Content-Length`
header, so the body is never transmitted.
This is also why the failure looks like a CORS error in the browser: nginx's
error response carries no `Access-Control-Allow-Origin` header.
### Applying it
Add to the `server` (or `location`) block fronting the API and reload:
```nginx
server {
server_name edrfreightapi-staging.edrsc.com;
client_max_body_size 50m;
location / {
proxy_pass http://freight-api:3001;
# Large uploads stream for a while; the default 60s read timeout can
# cut off a slow client mid-body.
proxy_read_timeout 300s;
proxy_request_buffering off;
}
}
```
```bash
nginx -t && nginx -s reload # -t first: a bad config that reloads takes the site down
```
On Kubernetes ingress-nginx this is an annotation on the Ingress instead:
```yaml
nginx.ingress.kubernetes.io/proxy-body-size: 50m
```
Note that `client_max_body_size 0` disables the check entirely — do not use it.
An unbounded body is a denial-of-service vector, and layer 3 buffers uploads in
memory.
## Verifying end to end
512KB should pass the proxy and reach the API; 50MB should too. A `401` here is
a *success* for this purpose — it means the request got past nginx to the API's
auth layer.
```bash
head -c 52428800 /dev/urandom > big.bin
curl -s -o /dev/null -w 'HTTP %{http_code} uploaded %{size_upload}\n' \
-X POST "https://edrfreightapi-staging.edrsc.com/api/companies/<id>/documents" \
-F "test=@big.bin"
```
- `413` with `uploaded 0` → the proxy is still capped; layer 1 was not applied.
- `401`/`200` with the full byte count → the body made it through.