diff --git a/packages/server/fileStorage/S3FileStoreManager.ts b/packages/server/fileStorage/S3FileStoreManager.ts index 89c3a4a8957..15ae30ae469 100644 --- a/packages/server/fileStorage/S3FileStoreManager.ts +++ b/packages/server/fileStorage/S3FileStoreManager.ts @@ -32,12 +32,16 @@ export default class S3Manager extends FileStoreManager { // e.g. development, production private envSubDir: string + // e.g. action-files.parabol.co + private bucket: string + // e.g. https://action-files.parabol.co baseUrl: string private s3: S3Client constructor() { super() - const {CDN_BASE_URL, AWS_REGION, AWS_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID} = process.env + const {CDN_BASE_URL, AWS_REGION, AWS_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID, AWS_S3_BUCKET} = + process.env if (!CDN_BASE_URL || CDN_BASE_URL === 'key_CDN_BASE_URL') { throw new Error('CDN_BASE_URL ENV VAR NOT SET') } @@ -50,9 +54,15 @@ export default class S3Manager extends FileStoreManager { if (pathname.endsWith('/')) throw new Error('CDN_BASE_URL must end with the env, no trailing slash, e.g. /production') + if (!AWS_S3_BUCKET) { + throw new Error('AWS_S3_BUCKET required when using AWS S3 as filestore provider') + } + this.envSubDir = pathname.split('/').at(-1) as string this.baseUrl = baseUrl.href.slice(0, baseUrl.href.lastIndexOf(this.envSubDir)) + this.bucket = AWS_S3_BUCKET + // credentials are optional since the file store could be public & not need a key to write const credentials = AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY @@ -60,8 +70,8 @@ export default class S3Manager extends FileStoreManager { : undefined this.s3 = new S3Client({ credentials, - // The bucket is inferred from the CDN_BASE_URL - bucketEndpoint: true, + // Using true fails to work on the checkExist method, it returns a 403 + bucketEndpoint: false, region: AWS_REGION, followRegionRedirects: true, retryStrategy: new CloudflareRetry(3) @@ -75,7 +85,7 @@ export default class S3Manager extends FileStoreManager { protected async putFile(file: ArrayBufferLike, fullPath: string) { const s3Params = { Body: Buffer.from(file), - Bucket: this.baseUrl, + Bucket: this.bucket, Key: fullPath, ContentType: mime.lookup(fullPath) || 'application/octet-stream' } @@ -98,9 +108,10 @@ export default class S3Manager extends FileStoreManager { async checkExists(key: string, assetDir?: FileAssetDir) { const Key = this.prependPath(key, assetDir) try { - await this.s3.send(new HeadObjectCommand({Bucket: this.baseUrl, Key})) + await this.s3.send(new HeadObjectCommand({Bucket: this.bucket, Key})) } catch (e) { if (e instanceof Error && e.name === 'NotFound') return false + Logger.log(`Invalid error ${(e as Error).name}`) } return true } @@ -108,7 +119,7 @@ export default class S3Manager extends FileStoreManager { async presignUrl(url: string) { // Important to decodeURI so `getSignedUrl` doesn't double encode e.g. local|123/avatars/123.jpg const key = decodeURI(url.slice(this.baseUrl.length)) - const command = new GetObjectCommand({Bucket: this.baseUrl, Key: key}) + const command = new GetObjectCommand({Bucket: this.bucket, Key: key}) const encodedUri = await getSignedUrl(this.s3, command, {expiresIn: 604800}) return encodedUri } diff --git a/scripts/toolboxSrc/pushToCDN.ts b/scripts/toolboxSrc/pushToCDN.ts index 6ff207e3123..7dcc020b954 100644 --- a/scripts/toolboxSrc/pushToCDN.ts +++ b/scripts/toolboxSrc/pushToCDN.ts @@ -1,8 +1,8 @@ import fs from 'fs' import getFileStoreManager from 'parabol-server/fileStorage/getFileStoreManager' +import {Logger} from 'parabol-server/utils/Logger' import path from 'path' import getProjectRoot from '../webpack/utils/getProjectRoot' -import {Logger} from 'parabol-server/utils/Logger' const PROJECT_ROOT = getProjectRoot() @@ -66,7 +66,7 @@ const pushServerAssetsToCDN = async () => { // static assets in /dist/images are already hosted at /static/images if (process.env.FILE_STORE_PROVIDER === 'local') return const targetObject = `images/${filename}` - const exists = await fileStoreManager.checkExists(targetObject) + const exists = await fileStoreManager.checkExists(targetObject, 'build') if (exists) return false const buffer = await fs.promises.readFile(path.join(localServerAssetsDir, filename)) const url = await fileStoreManager.putBuildFile(buffer, targetObject)