From 4df4d8c9b507272651992cdde527319da9c18779 Mon Sep 17 00:00:00 2001 From: anand Date: Tue, 4 Aug 2026 03:34:52 -0500 Subject: [PATCH] object storage related updates --- src/main.ts | 9 +++++-- src/upload/upload.service.ts | 47 ++++++++++++++++++++++++++++-------- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/main.ts b/src/main.ts index de7a95a..25fde4b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,8 +21,13 @@ async function bootstrap() { const appEnv = configService.get('app.env') || 'development'; const corsOrigins = configService.get('cors.origins') || []; - // Security - app.use(helmet()); + // Security — allow cross-origin API reads from frontend/admin domains + app.use( + helmet({ + crossOriginResourcePolicy: { policy: 'cross-origin' }, + crossOriginOpenerPolicy: { policy: 'same-origin-allow-popups' }, + }), + ); app.use(cookieParser()); // CORS diff --git a/src/upload/upload.service.ts b/src/upload/upload.service.ts index 3af17c7..18e264d 100644 --- a/src/upload/upload.service.ts +++ b/src/upload/upload.service.ts @@ -16,12 +16,31 @@ export class UploadService { private bucket: string; private region: string; private folderPrefix: string; + /** Endpoint used for SDK signing/calls (no /s3 path prefix). */ private endpoint: string; + /** Public base shown in browser URLs (may include /s3 path prefix). */ + private publicEndpoint: string; constructor(private configService: ConfigService) { this.region = this.configService.get('AWS_REGION') || 'us-east-1'; this.bucket = this.configService.get('AWS_S3_BUCKET') || ''; - this.endpoint = this.configService.get('S3_ENDPOINT') || ''; + const rawEndpoint = (this.configService.get('S3_ENDPOINT') || '').replace( + /\/$/, + '', + ); + // Path-prefix public URL (e.g. https://re-quest.com/s3): sign against the host + // root so SigV4 path matches what MinIO sees after nginx strips /s3. + if (rawEndpoint.endsWith('/s3')) { + this.publicEndpoint = rawEndpoint; + this.endpoint = rawEndpoint.slice(0, -3); + } else { + this.endpoint = rawEndpoint; + this.publicEndpoint = + (this.configService.get('S3_PUBLIC_ENDPOINT') || rawEndpoint).replace( + /\/$/, + '', + ); + } // Use S3_FOLDER_PREFIX from env, defaults to 'development' for dev environment this.folderPrefix = this.configService.get('S3_FOLDER_PREFIX') || 'development'; @@ -52,6 +71,14 @@ export class UploadService { this.s3Client = new S3Client(s3Config); } + /** Rewrite SDK URLs to the public /s3 path when configured. */ + private toPublicUrl(url: string): string { + if (!this.endpoint || !this.publicEndpoint || this.endpoint === this.publicEndpoint) { + return url; + } + return url.replace(this.endpoint, this.publicEndpoint); + } + /** * Get the full S3 key with environment prefix */ @@ -139,11 +166,9 @@ export class UploadService { expiresIn: 3600, // 1 hour }); - const publicUrl = `https://${this.bucket}.s3.${this.region}.amazonaws.com/${key}`; - return { - uploadUrl, - publicUrl, + uploadUrl: this.toPublicUrl(uploadUrl), + publicUrl: this.getPublicUrl(key), key, }; } @@ -163,15 +188,17 @@ export class UploadService { Key: key, }); - return getSignedUrl(this.s3Client, command, { + const url = await getSignedUrl(this.s3Client, command, { expiresIn: 3600, // 1 hour }); + return this.toPublicUrl(url); } getPublicUrl(key: string): string { + const base = this.publicEndpoint || this.endpoint; // For custom S3-compatible endpoints (Contabo, MinIO, etc.) - if (this.endpoint) { - return `${this.endpoint}/${this.bucket}/${key}`; + if (base) { + return `${base}/${this.bucket}/${key}`; } // For AWS S3 return `https://${this.bucket}.s3.${this.region}.amazonaws.com/${key}`; @@ -242,7 +269,7 @@ export class UploadService { const publicUrl = this.getPublicUrl(key); return { - uploadUrl, + uploadUrl: this.toPublicUrl(uploadUrl), publicUrl, key, }; @@ -279,7 +306,7 @@ export class UploadService { const publicUrl = this.getPublicUrl(key); return { - uploadUrl, + uploadUrl: this.toPublicUrl(uploadUrl), publicUrl, key, };