diff --git a/.env.example b/.env.example index 3fd9072117..253a9764b7 100644 --- a/.env.example +++ b/.env.example @@ -53,5 +53,6 @@ VITE_TUS_ENDPOINT=https://tusd.tusdemo.net/files/ VITE_XHR_ENDPOINT=https://xhr-server.herokuapp.com/upload VITE_TRANSLOADIT_KEY=*** +VITE_TRANSLOADIT_SECRET=*** VITE_TRANSLOADIT_TEMPLATE=*** VITE_TRANSLOADIT_SERVICE_URL=https://api2.transloadit.com diff --git a/private/dev/Dashboard.js b/private/dev/Dashboard.js index ff23603e3c..3e882be118 100644 --- a/private/dev/Dashboard.js +++ b/private/dev/Dashboard.js @@ -26,6 +26,8 @@ import Audio from '@uppy/audio' import Compressor from '@uppy/compressor' /* eslint-enable import/no-extraneous-dependencies */ +import generateSignatureIfSecret from './generateSignatureIfSecret.js' + // DEV CONFIG: create a .env file in the project root directory to customize those values. const { VITE_UPLOADER : UPLOADER, @@ -33,17 +35,30 @@ const { VITE_TUS_ENDPOINT : TUS_ENDPOINT, VITE_XHR_ENDPOINT : XHR_ENDPOINT, VITE_TRANSLOADIT_KEY : TRANSLOADIT_KEY, + VITE_TRANSLOADIT_SECRET : TRANSLOADIT_SECRET, VITE_TRANSLOADIT_TEMPLATE : TRANSLOADIT_TEMPLATE, VITE_TRANSLOADIT_SERVICE_URL : TRANSLOADIT_SERVICE_URL, } = import.meta.env -import.meta.env.VITE_TRANSLOADIT_KEY = '***' // to avoid leaking secrets in screenshots. +import.meta.env.VITE_TRANSLOADIT_KEY &&= '***' // to avoid leaking secrets in screenshots. +import.meta.env.VITE_TRANSLOADIT_SECRET &&= '***' // to avoid leaking secrets in screenshots. console.log(import.meta.env) // DEV CONFIG: enable or disable Golden Retriever const RESTORE = false +async function getAssemblyOptions () { + return generateSignatureIfSecret(TRANSLOADIT_SECRET, { + auth: { + key: TRANSLOADIT_KEY, + }, + // It's more secure to use a template_id and enable + // Signature Authentication + template_id: TRANSLOADIT_TEMPLATE, + }) +} + // Rest is implementation! Obviously edit as necessary... export default () => { @@ -111,10 +126,7 @@ export default () => { uppyDashboard.use(Transloadit, { service: TRANSLOADIT_SERVICE_URL, waitForEncoding: true, - params: { - auth: { key: TRANSLOADIT_KEY }, - template_id: TRANSLOADIT_TEMPLATE, - }, + getAssemblyOptions, }) break case 'transloadit-s3': @@ -122,10 +134,7 @@ export default () => { uppyDashboard.use(Transloadit, { waitForEncoding: true, importFromUploadURLs: true, - params: { - auth: { key: TRANSLOADIT_KEY }, - template_id: TRANSLOADIT_TEMPLATE, - }, + getAssemblyOptions, }) break case 'transloadit-xhr': diff --git a/private/dev/DragDrop.js b/private/dev/DragDrop.js index 3f3cf1b1de..658f224a9a 100644 --- a/private/dev/DragDrop.js +++ b/private/dev/DragDrop.js @@ -11,7 +11,8 @@ const { VITE_TUS_ENDPOINT : TUS_ENDPOINT, } = import.meta.env -import.meta.env.VITE_TRANSLOADIT_KEY = '***' // to avoid leaking secrets in screenshots. +import.meta.env.VITE_TRANSLOADIT_KEY &&= '***' // to avoid leaking secrets in screenshots. +import.meta.env.VITE_TRANSLOADIT_SECRET &&= '***' // to avoid leaking secrets in screenshots. console.log(import.meta.env) export default () => { diff --git a/private/dev/generateSignatureIfSecret.js b/private/dev/generateSignatureIfSecret.js new file mode 100644 index 0000000000..2f40ce20e5 --- /dev/null +++ b/private/dev/generateSignatureIfSecret.js @@ -0,0 +1,34 @@ +const enc = new TextEncoder('utf-8') +async function sign (secret, body) { + const algorithm = { name: 'HMAC', hash: 'SHA-384' } + + const key = await crypto.subtle.importKey('raw', enc.encode(secret), algorithm, false, ['sign', 'verify']) + const signature = await crypto.subtle.sign(algorithm.name, key, enc.encode(body)) + return `sha384:${Array.from(new Uint8Array(signature), x => x.toString(16).padStart(2, '0')).join('')}` +} +function getExpiration (future) { + return new Date(Date.now() + future) + .toISOString() + .replace('T', ' ') + .replace(/\.\d+Z$/, '+00:00') +} +/** + * Adds an expiration date and signs the params object if a secret is passed to + * it. If no secret is given, it returns the same object. + * + * @param {string | undefined} secret + * @param {object} params + * @returns {{ params: string, signature?: string }} + */ +export default async function generateSignatureIfSecret (secret, params) { + let signature + if (secret) { + // eslint-disable-next-line no-param-reassign + params.auth.expires = getExpiration(5 * 60 * 1000) + // eslint-disable-next-line no-param-reassign + params = JSON.stringify(params) + signature = await sign(secret, params) + } + + return { params, signature } +}