Summary
With STORAGE_PROVIDER=local and NOT_SECURED=true, uploading media from the Media library always fails. Uppy reports:
[Uppy] This looks like a network error, the endpoint might be blocked by an internet provider or a firewall.
[Uppy] TypeError: Cannot read properties of undefined (reading 'error')
The message points at networking, which is misleading — the backend is reachable and healthy. The actual cause is an auth/CORS mismatch, and nothing in the error hints at that.
Cause
NOT_SECURED=true switches the app from cookie auth to header auth. Two things follow from it:
apps/backend/src/main.ts drops credentials: true from the CORS config:
cors: {
...(!process.env.NOT_SECURED ? { credentials: true } : {}),
- Auth moves to the
auth header, which is added to allowedHeaders/exposedHeaders in the same block, and the auth cookie is set client-side (not httpOnly).
But the local uploader still asks for cookie credentials — libraries/react-shared-libraries/src/helpers/uppy.upload.ts:
case 'local':
return {
plugin: XHRUpload,
options: {
endpoint: `${backendUrl}/media/upload-server`,
withCredentials: true,
},
};
So in header-auth mode the upload sends a cookie the server is not using, against a CORS policy that forbids credentials — the browser blocks the response, and Uppy surfaces it as a generic network error.
Worth noting getUppyUploadPlugin already receives the authenticated fetch as its second parameter, and the cloudflare branch uses it in five places via fetchUploadApiEndpoint. The local branch is the only uploader that ignores it.
Reproduce
.env: STORAGE_PROVIDER="local", NOT_SECURED="true", frontend and backend on different origins (the default 4200 / 3000 split).
- Start the app, sign in, open Media.
- Upload any valid file.
Expected: the file uploads. Actual: Uppy reports a network error and the library stays empty.
Confirming it is CORS rather than networking:
$ curl -si -X OPTIONS http://localhost:3000/media/upload-server \
-H 'Origin: http://localhost:4200' -H 'Access-Control-Request-Method: POST' \
| grep -i access-control-allow-credentials
# (no output with NOT_SECURED=true; present without it)
Suggested fix
The two modes can be told apart without new configuration: in cookie-auth mode the auth cookie is set httpOnly: true (users.controller.ts) and is therefore unreadable from JS, while in NOT_SECURED mode the frontend sets it client-side and it is readable. So:
const readAuthCookie = () =>
typeof document === 'undefined'
? undefined
: document.cookie.match(/(?:^|;\s*)auth=([^;]+)/)?.[1];
case 'local': {
// httpOnly in cookie-auth mode, so a readable `auth` cookie means the app is
// running in NOT_SECURED (header auth) mode, where CORS forbids credentials.
const usesHeaderAuth = Boolean(readAuthCookie());
return {
plugin: XHRUpload,
options: {
endpoint: `${backendUrl}/media/upload-server`,
withCredentials: !usesHeaderAuth,
headers: () => {
const token = readAuthCookie();
return token ? { auth: decodeURIComponent(token) } : {};
},
},
};
}
In the default cookie-auth mode this is byte-for-byte equivalent to today's behaviour (withCredentials: true, no extra headers), so there is no regression risk for the common path. In NOT_SECURED mode it sends the same auth header every other authenticated request already sends.
Happy to open a PR — I have the change working locally.
Environment
- postiz
81af6c9
STORAGE_PROVIDER=local, NOT_SECURED=true
- Node 24, pnpm 10.6.1, macOS
Found while using postiz as an independent conformance client against a local API emulator, so the whole stack was on localhost with the frontend and backend on separate ports.
Summary
With
STORAGE_PROVIDER=localandNOT_SECURED=true, uploading media from the Media library always fails. Uppy reports:The message points at networking, which is misleading — the backend is reachable and healthy. The actual cause is an auth/CORS mismatch, and nothing in the error hints at that.
Cause
NOT_SECURED=trueswitches the app from cookie auth to header auth. Two things follow from it:apps/backend/src/main.tsdropscredentials: truefrom the CORS config:authheader, which is added toallowedHeaders/exposedHeadersin the same block, and theauthcookie is set client-side (nothttpOnly).But the local uploader still asks for cookie credentials —
libraries/react-shared-libraries/src/helpers/uppy.upload.ts:So in header-auth mode the upload sends a cookie the server is not using, against a CORS policy that forbids credentials — the browser blocks the response, and Uppy surfaces it as a generic network error.
Worth noting
getUppyUploadPluginalready receives the authenticatedfetchas its second parameter, and thecloudflarebranch uses it in five places viafetchUploadApiEndpoint. Thelocalbranch is the only uploader that ignores it.Reproduce
.env:STORAGE_PROVIDER="local",NOT_SECURED="true", frontend and backend on different origins (the default4200/3000split).Expected: the file uploads. Actual: Uppy reports a network error and the library stays empty.
Confirming it is CORS rather than networking:
Suggested fix
The two modes can be told apart without new configuration: in cookie-auth mode the
authcookie is sethttpOnly: true(users.controller.ts) and is therefore unreadable from JS, while inNOT_SECUREDmode the frontend sets it client-side and it is readable. So:In the default cookie-auth mode this is byte-for-byte equivalent to today's behaviour (
withCredentials: true, no extra headers), so there is no regression risk for the common path. InNOT_SECUREDmode it sends the sameauthheader every other authenticated request already sends.Happy to open a PR — I have the change working locally.
Environment
81af6c9STORAGE_PROVIDER=local,NOT_SECURED=trueFound while using postiz as an independent conformance client against a local API emulator, so the whole stack was on
localhostwith the frontend and backend on separate ports.