From f78a02bb8d9b7c5c7e97f498324f402b9001c792 Mon Sep 17 00:00:00 2001 From: Alan Grainger Date: Sun, 3 Nov 2024 19:48:32 +0100 Subject: [PATCH] Expire asset decryption tokens --- .github/workflows/ci.yaml | 4 ++-- app/package.json | 2 +- app/src/immich.ts | 16 ++++++++++++++-- app/src/index.ts | 17 ++++++++++++----- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fc9e8e2..ebd62e7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -53,7 +53,7 @@ jobs: build-args: | PACKAGE_VERSION=${{ env.PACKAGE_VERSION }} tags: | - ${{ github.repository }}:latest ${{ github.repository }}:${{ env.PACKAGE_VERSION }} - ghcr.io/${{ github.repository }}:latest + ${{ github.repository }}:latest ghcr.io/${{ github.repository }}:${{ env.PACKAGE_VERSION }} + ghcr.io/${{ github.repository }}:latest diff --git a/app/package.json b/app/package.json index d651891..516d102 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "immich-public-proxy", - "version": "1.3.5", + "version": "1.3.6", "scripts": { "dev": "ts-node src/index.ts", "build": "npx tsc", diff --git a/app/src/immich.ts b/app/src/immich.ts index 75f794b..ab0fa94 100644 --- a/app/src/immich.ts +++ b/app/src/immich.ts @@ -187,7 +187,7 @@ class Immich { photoUrl (key: string, id: string, size?: ImageSize, password?: string) { const params = { key, size } if (password) { - Object.assign(params, encrypt(password)) + Object.assign(params, this.encryptPassword(password)) } return this.buildUrl(`/photo/${key}/${id}`, params) } @@ -196,7 +196,7 @@ class Immich { * Return the video data URL for a video */ videoUrl (key: string, id: string, password?: string) { - const params = password ? encrypt(password) : {} + const params = password ? this.encryptPassword(password) : {} return this.buildUrl(`/video/${key}/${id}`, params) } @@ -215,6 +215,18 @@ class Immich { isKey (key: string) { return !!key.match(/^[\w-]+$/) } + + /** + * When loading assets from a password-protected link, make the decryption key valid for a + * short time. If the visitor loads the share link again, it will renew that expiry time. + * This prevents people from sharing the image links and bypassing password protection. + */ + encryptPassword (password: string) { + return encrypt(JSON.stringify({ + password, + expires: dayjs().add(1, 'hour').format() + })) + } } const immich = new Immich() diff --git a/app/src/index.ts b/app/src/index.ts index 2ee37b8..63aab74 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -37,13 +37,20 @@ app.get('/:type(photo|video)/:key/:id', async (req, res) => { res.set('Cache-Control', 'public, max-age=' + process.env.CACHE_AGE) // Check for valid key and ID if (immich.isKey(req.params.key) && immich.isId(req.params.id)) { - // Decrypt the password, if one was provided let password + // Validate the password payload, if one was provided if (req.query?.cr && req.query?.iv) { - password = decrypt({ - iv: toString(req.query.iv), - cr: toString(req.query.cr) - }) + try { + const payload = JSON.parse(decrypt({ + iv: toString(req.query.iv), + cr: toString(req.query.cr) + })) + if (payload?.expires && dayjs(payload.expires) > dayjs()) { + password = payload.password + } else { + log(`Attempted to load assets from ${req.params.key} with an expired decryption token`) + } + } catch (e) { } } // Check if the key is a valid share link const sharedLink = (await immich.getShareByKey(req.params.key, password))?.link