diff --git a/docker-compose.yml b/docker-compose.yml index f60e923..2557b34 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,6 @@ services: restart: always env_file: - .env - ports: - - "3000:3000" depends_on: - minio networks: @@ -20,12 +18,31 @@ services: restart: always env_file: - .env + ports: + - "9003:9001" volumes: - ./minio-data:/data command: server /data --console-address ":9001" networks: - app-network + nginx: + image: nginx:latest + container_name: ledmatrix-proxy + restart: always + ports: + - "8080:80" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - backend + - minio + networks: + - app-network + networks: app-network: - driver: bridge \ No newline at end of file + driver: bridge + +volumes: + minio-data: \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..122d9c3 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,27 @@ +events {} + +http { + server { + listen 80; + server_name localhost; + + location /api/ { + proxy_pass http://ledmatrix-backend:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + location /ledmatrix/ { + proxy_pass http://ledmatrix-storage:9000; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 67e0c83..b33c963 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { Server } from "./server"; import { config as baseConfig } from "./config/config"; -import { S3Service } from "./services/s3Service"; +import { S3ClientConfig, S3Service } from "./services/s3Service"; import { UserService } from "./services/db/UserService"; import { SpotifyTokenService } from "./services/spotifyTokenService"; import { connectToDatabase } from "./services/db/database.service"; @@ -21,6 +21,7 @@ async function bootstrap() { MINIO_ROOT_PASSWORD, DB_NAME, DB_CONN_STRING, + MINIO_SERVER_URL, } = process.env; if (!SECRET_KEY || SECRET_KEY.length < 32) { @@ -45,11 +46,16 @@ async function bootstrap() { throw new Error("MINIO_BUCKET_NAME environment variable is not set."); } + if (!MINIO_SERVER_URL) { + throw new Error("MINIO_SERVER_URL environment variable is not set."); + } + if (!DB_NAME || !DB_CONN_STRING) { throw new Error("DB_NAME and/or DB_CONN_STRING environment variable is not set."); } - const s3ClientConfig = { + const s3ClientConfig: S3ClientConfig = { + publicUrl: MINIO_SERVER_URL, endpoint: MINIO_ENDPOINT, port: parseInt(MINIO_PORT), accessKey: MINIO_ROOT_USER, diff --git a/src/rest/restStorage.ts b/src/rest/restStorage.ts index 2fae66e..a01819e 100644 --- a/src/rest/restStorage.ts +++ b/src/rest/restStorage.ts @@ -46,12 +46,10 @@ export class RestStorage { const userId = req.payload.uuid; const objectKey = req.params[0]; - console.log(userId); - console.log(objectKey); - if (!objectKey || !objectKey.startsWith(`user-${userId}`)) { return forbidden(res); } + try { const expiresInSeconds = 60; const downloadUrl = await this.s3Service.getSignedDownloadUrl(objectKey, expiresInSeconds); diff --git a/src/services/s3Service.ts b/src/services/s3Service.ts index 385c1d1..fe649b1 100644 --- a/src/services/s3Service.ts +++ b/src/services/s3Service.ts @@ -16,6 +16,7 @@ export interface S3ClientConfig { secretAccessKey: string; bucket: string; region?: string; + publicUrl: string; } export class S3Service { @@ -23,6 +24,7 @@ export class S3Service { private readonly client: S3Client; private readonly bucketName: string; + private readonly publicUrl: string; private constructor(clientConfig: S3ClientConfig) { this.client = new S3Client({ @@ -36,6 +38,7 @@ export class S3Service { }); this.bucketName = clientConfig.bucket; + this.publicUrl = clientConfig.publicUrl; } public static getInstance(config?: S3ClientConfig): S3Service { @@ -103,11 +106,19 @@ export class S3Service { } async getSignedDownloadUrl(objectKey: string, expiresIn: number = 60): Promise { + // temporary client for public url + const signingClient = new S3Client({ + endpoint: this.publicUrl, + forcePathStyle: true, + region: this.client.config.region, + credentials: this.client.config.credentials, + }); + const command = new GetObjectCommand({ Bucket: this.bucketName, Key: objectKey, }); - return await getSignedUrl(this.client, command, { expiresIn }); + return await getSignedUrl(signingClient, command, { expiresIn }); } } diff --git a/tests/services/s3Service.test.ts b/tests/services/s3Service.test.ts index 7654b50..1ddfc30 100644 --- a/tests/services/s3Service.test.ts +++ b/tests/services/s3Service.test.ts @@ -27,6 +27,7 @@ const testConfig: S3ClientConfig = { accessKey: "test-key", secretAccessKey: "test-secret", bucket: "test-bucket", + publicUrl: "http://test-publicUrl", }; const MockS3Client = vi.mocked(S3Client); @@ -46,6 +47,9 @@ describe("S3Service", () => { }) as never ); + // @ts-ignore + S3Service.instance = undefined; + s3Service = S3Service.getInstance(testConfig); }); @@ -174,10 +178,11 @@ describe("S3Service", () => { }); }); + // ignore test for now. describe("getSignedDownloadUrl", () => { it("should generate a signed URL for a given object key", async () => { const objectKey = "user-123/image.png"; - const fakeSignedUrl = "http://test-minio:9000/test-bucket/user-123/image.png?signed=true"; + const fakeSignedUrl = "http://test-publicUrl/test-bucket/user-123/image.png?signed=true"; mockGetSignedUrl.mockResolvedValue(fakeSignedUrl);