Fix Mixed Content on file uploads: presigned URLs now use public domain
The backend was generating presigned S3 URLs pointing to the internal MinIO endpoint (http://minio:9000), which browsers block on HTTPS pages. - Add a second S3 client in upload.service.ts configured with FRONTEND_URL for generating browser-facing presigned URLs (both upload and download) - Add nginx proxy location for /indeedhub-private/ and /indeedhub-public/ paths that forwards to MinIO without rewriting (preserves S3v4 signatures) - Keep internal S3 client for server-side operations (copy, delete, etc.) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,14 +21,20 @@ import { getPrivateS3Url } from 'src/common/helper';
|
||||
@Injectable()
|
||||
export class UploadService {
|
||||
private s3: S3Client;
|
||||
|
||||
// Separate client for generating presigned URLs that browsers can reach.
|
||||
// Uses the public-facing FRONTEND_URL instead of the internal MinIO endpoint
|
||||
// so presigned URLs are https://domain/bucket/key instead of http://minio:9000/...
|
||||
private presignS3: S3Client;
|
||||
|
||||
constructor() {
|
||||
const s3Config: any = {
|
||||
region: process.env.AWS_REGION || 'us-east-1',
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY,
|
||||
secretAccessKey: process.env.AWS_SECRET_KEY,
|
||||
},
|
||||
const credentials = {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY,
|
||||
secretAccessKey: process.env.AWS_SECRET_KEY,
|
||||
};
|
||||
const region = process.env.AWS_REGION || 'us-east-1';
|
||||
|
||||
const s3Config: any = { region, credentials };
|
||||
|
||||
// MinIO compatibility: if S3_ENDPOINT is set, override the endpoint
|
||||
// and force path-style access (MinIO requires this)
|
||||
@@ -38,6 +44,21 @@ export class UploadService {
|
||||
}
|
||||
|
||||
this.s3 = new S3Client(s3Config);
|
||||
|
||||
// For presigned URLs served to the browser, use the public domain
|
||||
// so URLs are reachable over HTTPS (avoids Mixed Content errors).
|
||||
// Nginx proxies /indeedhub-*/ paths to MinIO internally.
|
||||
const presignEndpoint = process.env.FRONTEND_URL;
|
||||
if (presignEndpoint && process.env.S3_ENDPOINT) {
|
||||
this.presignS3 = new S3Client({
|
||||
region,
|
||||
credentials,
|
||||
endpoint: presignEndpoint,
|
||||
forcePathStyle: true,
|
||||
});
|
||||
} else {
|
||||
this.presignS3 = this.s3;
|
||||
}
|
||||
}
|
||||
|
||||
async initialize({
|
||||
@@ -72,7 +93,7 @@ export class UploadService {
|
||||
PartNumber: index + 1,
|
||||
});
|
||||
promises.push(
|
||||
getSignedUrl(this.s3, command, {
|
||||
getSignedUrl(this.presignS3, command, {
|
||||
expiresIn: 60 * 60 * 24 * 7,
|
||||
}),
|
||||
);
|
||||
@@ -180,7 +201,7 @@ export class UploadService {
|
||||
Bucket: process.env.S3_PRIVATE_BUCKET_NAME,
|
||||
Key: key,
|
||||
});
|
||||
return getSignedUrl(this.s3, command, {
|
||||
return getSignedUrl(this.presignS3, command, {
|
||||
expiresIn: expires ?? 60 * 60 * 24 * 7,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ services:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
CACHEBUST: "8"
|
||||
CACHEBUST: "9"
|
||||
VITE_USE_MOCK_DATA: "false"
|
||||
VITE_CONTENT_ORIGIN: ${FRONTEND_URL}
|
||||
VITE_INDEEHUB_API_URL: /api
|
||||
@@ -47,7 +47,7 @@ services:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
CACHEBUST: "8"
|
||||
CACHEBUST: "9"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
# ── Core ─────────────────────────────────────────────
|
||||
@@ -179,7 +179,7 @@ services:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile.ffmpeg
|
||||
args:
|
||||
CACHEBUST: "8"
|
||||
CACHEBUST: "9"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ENVIRONMENT: production
|
||||
|
||||
23
nginx.conf
23
nginx.conf
@@ -84,6 +84,29 @@ server {
|
||||
add_header Cache-Control "no-store";
|
||||
}
|
||||
|
||||
# ── MinIO direct proxy (for presigned URL uploads/downloads) ──
|
||||
# The backend generates presigned URLs pointing to the public domain.
|
||||
# This location proxies those requests to MinIO WITHOUT rewriting the
|
||||
# path, so the S3v4 signature (which includes the path) stays valid.
|
||||
location ~ ^/(indeedhub-private|indeedhub-public)/ {
|
||||
resolver 127.0.0.11 valid=30s ipv6=off;
|
||||
set $minio_upstream http://minio:9000;
|
||||
|
||||
proxy_pass $minio_upstream;
|
||||
proxy_http_version 1.1;
|
||||
# Pass the original Host so MinIO's signature verification matches
|
||||
# the host the presigned URL was generated for.
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# Allow large file uploads (up to 5GB per chunk)
|
||||
client_max_body_size 5g;
|
||||
|
||||
# No caching for upload responses
|
||||
add_header Cache-Control "no-store";
|
||||
}
|
||||
|
||||
# ── WebSocket proxy to Nostr relay (Docker service) ────────
|
||||
location /relay {
|
||||
resolver 127.0.0.11 valid=30s ipv6=off;
|
||||
|
||||
Reference in New Issue
Block a user