Expire asset decryption tokens

This commit is contained in:
Alan Grainger 2024-11-03 19:48:32 +01:00
parent f445844766
commit f78a02bb8d
4 changed files with 29 additions and 10 deletions

View File

@ -53,7 +53,7 @@ jobs:
build-args: | build-args: |
PACKAGE_VERSION=${{ env.PACKAGE_VERSION }} PACKAGE_VERSION=${{ env.PACKAGE_VERSION }}
tags: | tags: |
${{ github.repository }}:latest
${{ github.repository }}:${{ env.PACKAGE_VERSION }} ${{ github.repository }}:${{ env.PACKAGE_VERSION }}
ghcr.io/${{ github.repository }}:latest ${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ env.PACKAGE_VERSION }} ghcr.io/${{ github.repository }}:${{ env.PACKAGE_VERSION }}
ghcr.io/${{ github.repository }}:latest

View File

@ -1,6 +1,6 @@
{ {
"name": "immich-public-proxy", "name": "immich-public-proxy",
"version": "1.3.5", "version": "1.3.6",
"scripts": { "scripts": {
"dev": "ts-node src/index.ts", "dev": "ts-node src/index.ts",
"build": "npx tsc", "build": "npx tsc",

View File

@ -187,7 +187,7 @@ class Immich {
photoUrl (key: string, id: string, size?: ImageSize, password?: string) { photoUrl (key: string, id: string, size?: ImageSize, password?: string) {
const params = { key, size } const params = { key, size }
if (password) { if (password) {
Object.assign(params, encrypt(password)) Object.assign(params, this.encryptPassword(password))
} }
return this.buildUrl(`/photo/${key}/${id}`, params) return this.buildUrl(`/photo/${key}/${id}`, params)
} }
@ -196,7 +196,7 @@ class Immich {
* Return the video data URL for a video * Return the video data URL for a video
*/ */
videoUrl (key: string, id: string, password?: string) { 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) return this.buildUrl(`/video/${key}/${id}`, params)
} }
@ -215,6 +215,18 @@ class Immich {
isKey (key: string) { isKey (key: string) {
return !!key.match(/^[\w-]+$/) 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() const immich = new Immich()

View File

@ -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) res.set('Cache-Control', 'public, max-age=' + process.env.CACHE_AGE)
// Check for valid key and ID // Check for valid key and ID
if (immich.isKey(req.params.key) && immich.isId(req.params.id)) { if (immich.isKey(req.params.key) && immich.isId(req.params.id)) {
// Decrypt the password, if one was provided
let password let password
// Validate the password payload, if one was provided
if (req.query?.cr && req.query?.iv) { if (req.query?.cr && req.query?.iv) {
password = decrypt({ try {
iv: toString(req.query.iv), const payload = JSON.parse(decrypt({
cr: toString(req.query.cr) 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 // Check if the key is a valid share link
const sharedLink = (await immich.getShareByKey(req.params.key, password))?.link const sharedLink = (await immich.getShareByKey(req.params.key, password))?.link