Summary
The public download endpoint for secure sends is unreachable. Every unauthenticated request is rejected before it reaches the controller, so a share link can never be opened by a recipient.
SecurityConfig already permits the path:
"/api/public/secure-sends/**" is listed in permitAll
but JwtAuthFilter runs before the authorization rules and only skips these prefixes:
/api/auth/
/v3/api-docs
/swagger-ui
Every other path without a valid Bearer token is answered with sendError(401, "Missing Authorization header"). Because the resulting error dispatch to /error is itself not permitted, the caller sees a bare 403.
How to reproduce
Request the public endpoint without an Authorization header, directly against the running backend container:
GET http://cloud-page-backend:8090/api/public/secure-sends/<token>
Result: HTTP 403 with an empty body.
Evidence
- The backend log stays completely silent for that request. Hibernate SQL logging is active, and no query against the secure send table appears, which shows that SecureSendService.resolve is never called.
- A request to /v3/api-docs on the same instance returns 200, and a request to /api/auth/does-not-exist returns 403, which isolates the filter whitelist as the deciding factor.
- Verified against the deployed artifact, not only the source. Extracting BOOT-INF/classes/cloudpage/security/JwtAuthFilter.class from /app/app.jar shows exactly three whitelisted path constants: /api/auth/, /v3/api-docs, /swagger-ui. There is no entry for the public prefix.
Expected behaviour
An anonymous GET on /api/public/secure-sends/{token} reaches the controller, and the service decides the outcome: the file for a valid token, 401 when a password is required or wrong, and 404 or 410 for an unknown, revoked or expired token.
Suggested fix
Let JwtAuthFilter skip the public prefix, in the same way the auth and documentation paths are skipped. A more robust variant is to derive the skip list from the same source as the permitAll matchers in SecurityConfig, or to override shouldNotFilter, so that the two lists cannot drift apart again.
Please also confirm that the error dispatch is permitted, otherwise a legitimate 404 for an unknown token still surfaces as 403.
Related
The share URL that the backend hands out is wrong in a second, independent way. See the separate issue about the generated link path.
Summary
The public download endpoint for secure sends is unreachable. Every unauthenticated request is rejected before it reaches the controller, so a share link can never be opened by a recipient.
SecurityConfig already permits the path:
but JwtAuthFilter runs before the authorization rules and only skips these prefixes:
Every other path without a valid Bearer token is answered with sendError(401, "Missing Authorization header"). Because the resulting error dispatch to /error is itself not permitted, the caller sees a bare 403.
How to reproduce
Request the public endpoint without an Authorization header, directly against the running backend container:
Result: HTTP 403 with an empty body.
Evidence
Expected behaviour
An anonymous GET on /api/public/secure-sends/{token} reaches the controller, and the service decides the outcome: the file for a valid token, 401 when a password is required or wrong, and 404 or 410 for an unknown, revoked or expired token.
Suggested fix
Let JwtAuthFilter skip the public prefix, in the same way the auth and documentation paths are skipped. A more robust variant is to derive the skip list from the same source as the permitAll matchers in SecurityConfig, or to override shouldNotFilter, so that the two lists cannot drift apart again.
Please also confirm that the error dispatch is permitted, otherwise a legitimate 404 for an unknown token still surfaces as 403.
Related
The share URL that the backend hands out is wrong in a second, independent way. See the separate issue about the generated link path.