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:
Dorian
2026-02-13 20:30:49 +00:00
parent abb83fe164
commit fc20c625fa
3 changed files with 55 additions and 11 deletions

View File

@@ -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,
});
}