From 2178fa9816e76320b35f14f36c88196ed8228815 Mon Sep 17 00:00:00 2001 From: Ryan T Date: Mon, 29 Mar 2021 18:48:21 -0400 Subject: [PATCH] Error if any ENVs are missing (#8) --- package.json | 4 +- src/hooks/use-s3-upload.tsx | 113 +++++++++++++++++++----------------- src/pages/api/s3-upload.ts | 91 +++++++++++++++++------------ yarn.lock | 16 ++--- 4 files changed, 123 insertions(+), 101 deletions(-) diff --git a/package.json b/package.json index 4f8aade..06ef338 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,8 @@ "react-dom": "^17.0.1", "size-limit": "^4.7.0", "tsdx": "^0.14.1", - "tslib": "^2.0.3", - "typescript": "^4.0.5" + "tslib": "^2.1.0", + "typescript": "^4.2.3" }, "dependencies": { "aws-sdk": "^2.791.0", diff --git a/src/hooks/use-s3-upload.tsx b/src/hooks/use-s3-upload.tsx index 0214e0e..ba50798 100644 --- a/src/hooks/use-s3-upload.tsx +++ b/src/hooks/use-s3-upload.tsx @@ -63,60 +63,65 @@ export const useS3Upload = () => { let res = await fetch(`/api/s3-upload?filename=${filename}`); let data = await res.json(); - let s3 = new S3({ - accessKeyId: data.token.Credentials.AccessKeyId, - secretAccessKey: data.token.Credentials.SecretAccessKey, - sessionToken: data.token.Credentials.SessionToken, - }); - - let blob = await getFileContents(file); - - let params = { - ACL: 'public-read', - Bucket: data.bucket, - Key: data.key, - Body: blob, - CacheControl: 'max-age=630720000, public', - ContentType: file.type, - }; - - // at some point make this configurable - // let uploadOptions = { - // partSize: 100 * 1024 * 1024, - // queueSize: 1, - // }; - - let s3Upload = s3.upload(params); - - setFiles(files => [ - ...files, - { file, progress: 0, uploaded: 0, size: file.size }, - ]); - - s3Upload.on('httpUploadProgress', event => { - if (event.total) { - setFiles(files => - files.map(trackedFile => - trackedFile.file === file - ? { - file, - uploaded: event.loaded, - size: event.total, - progress: (event.loaded / event.total) * 100, - } - : trackedFile - ) - ); - } - }); - - let uploadResult = await s3Upload.promise(); - - return { - url: uploadResult.Location, - bucket: uploadResult.Bucket, - key: uploadResult.Key, - }; + if (data.error) { + console.error(data.error); + throw data.error; + } else { + let s3 = new S3({ + accessKeyId: data.token.Credentials.AccessKeyId, + secretAccessKey: data.token.Credentials.SecretAccessKey, + sessionToken: data.token.Credentials.SessionToken, + }); + + let blob = await getFileContents(file); + + let params = { + ACL: 'public-read', + Bucket: data.bucket, + Key: data.key, + Body: blob, + CacheControl: 'max-age=630720000, public', + ContentType: file.type, + }; + + // at some point make this configurable + // let uploadOptions = { + // partSize: 100 * 1024 * 1024, + // queueSize: 1, + // }; + + let s3Upload = s3.upload(params); + + setFiles(files => [ + ...files, + { file, progress: 0, uploaded: 0, size: file.size }, + ]); + + s3Upload.on('httpUploadProgress', event => { + if (event.total) { + setFiles(files => + files.map(trackedFile => + trackedFile.file === file + ? { + file, + uploaded: event.loaded, + size: event.total, + progress: (event.loaded / event.total) * 100, + } + : trackedFile + ) + ); + } + }); + + let uploadResult = await s3Upload.promise(); + + return { + url: uploadResult.Location, + bucket: uploadResult.Bucket, + key: uploadResult.Key, + }; + } }; return { diff --git a/src/pages/api/s3-upload.ts b/src/pages/api/s3-upload.ts index 156e815..2fe030c 100644 --- a/src/pages/api/s3-upload.ts +++ b/src/pages/api/s3-upload.ts @@ -16,43 +16,50 @@ type Options = { let makeRouteHandler = (options: Options = {}): Handler => { let route: NextRouteHandler = async function(req, res) { - let config = { - accessKeyId: process.env.S3_UPLOAD_KEY, - secretAccessKey: process.env.S3_UPLOAD_SECRET, - region: process.env.S3_UPLOAD_REGION, - }; - - let bucket = process.env.S3_UPLOAD_BUCKET; - - let filename = req.query.filename as string; - let key = options.key - ? options.key(req, filename) - : `next-s3-uploads/${uuidv4()}/${filename.replace(/\s/g, '-')}`; - - let policy = { - Statement: [ - { - Sid: 'Stmt1S3UploadAssets', - Effect: 'Allow', - Action: ['s3:PutObject', 's3:PutObjectAcl'], - Resource: [`arn:aws:s3:::${bucket}/${key}`], - }, - ], - }; - - let sts = new aws.STS(config); - - let token = await sts - .getFederationToken({ - Name: 'S3UploadWebToken', - Policy: JSON.stringify(policy), - DurationSeconds: 60 * 60, // 1 hour - }) - .promise(); - - res.statusCode = 200; - - res.status(200).json({ token, key, bucket }); + let missing = missingEnvs(); + if (missing.length > 0) { + res + .status(500) + .json({ error: `Next S3 Upload: Missing ENVs ${missing.join(', ')}` }); + } else { + let config = { + accessKeyId: process.env.S3_UPLOAD_KEY, + secretAccessKey: process.env.S3_UPLOAD_SECRET, + region: process.env.S3_UPLOAD_REGION, + }; + + let bucket = process.env.S3_UPLOAD_BUCKET; + + let filename = req.query.filename as string; + let key = options.key + ? options.key(req, filename) + : `next-s3-uploads/${uuidv4()}/${filename.replace(/\s/g, '-')}`; + + let policy = { + Statement: [ + { + Sid: 'Stmt1S3UploadAssets', + Effect: 'Allow', + Action: ['s3:PutObject', 's3:PutObjectAcl'], + Resource: [`arn:aws:s3:::${bucket}/${key}`], + }, + ], + }; + + let sts = new aws.STS(config); + + let token = await sts + .getFederationToken({ + Name: 'S3UploadWebToken', + Policy: JSON.stringify(policy), + DurationSeconds: 60 * 60, // 1 hour + }) + .promise(); + + res.statusCode = 200; + + res.status(200).json({ token, key, bucket }); + } }; let configure = (options: Options) => makeRouteHandler(options); @@ -60,6 +67,16 @@ let makeRouteHandler = (options: Options = {}): Handler => { return Object.assign(route, { configure }); }; +let missingEnvs = (): string[] => { + let keys = [ + 'S3_UPLOAD_KEY', + 'S3_UPLOAD_SECRET', + 'S3_UPLOAD_REGION', + 'S3_UPLOAD_BUCKET', + ]; + return keys.filter(key => !process.env[key]); +}; + let APIRoute = makeRouteHandler(); export { APIRoute }; diff --git a/yarn.lock b/yarn.lock index da78dfa..0ff502b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10094,10 +10094,10 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" - integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== +tslib@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== tsutils@^3.17.1: version "3.17.1" @@ -10190,10 +10190,10 @@ typescript@^3.7.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== -typescript@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.5.tgz#ae9dddfd1069f1cb5beb3ef3b2170dd7c1332389" - integrity sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ== +typescript@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3" + integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw== unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4"