From 1cc5d44e7645d82d5ed79962a9bf8b2ff18fc038 Mon Sep 17 00:00:00 2001 From: jonvdveen <196496658+jonvdveen@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:26:43 -0600 Subject: [PATCH] fix: update API key permissions for Immich v3 granular permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Immich v3 split album-membership rights out of album.update into albumAsset.create / albumAsset.delete, and assigning tags to assets requires tag.asset. The hardcoded WORKFLOW_PERMISSIONS list predates this, so auto-generated workflow keys 403 at runtime on Add to Album (and Add Tag) actions: Immich API error 403: {"message":"Missing required permission: albumAsset.create"} Also, validate-api-key.ts literal-matched required permissions against the key's permission list, so a key created with the "All" checkbox (stored as ["all"]) failed validation for every permission — and the settings page refuses to save a key that fails validation, making it impossible to work around with a full-permission key. - Add albumAsset.create, albumAsset.delete, tag.asset to WORKFLOW_PERMISSIONS (matching the endpoints actionExecutor.ts actually calls: PUT/DELETE /albums/:id/assets, PUT /tags/:id/assets) - Add albumAsset.create to IMPORT_PERMISSIONS for the same reason - Treat a granted "all" permission as satisfying all requirements in validate-api-key.ts Follow-up to #294. Co-Authored-By: Claude Fable 5 --- src/config/permissions.ts | 10 +++++++--- src/pages/api/settings/validate-api-key.ts | 6 +++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/config/permissions.ts b/src/config/permissions.ts index 482f5f1..cdbdd66 100644 --- a/src/config/permissions.ts +++ b/src/config/permissions.ts @@ -8,7 +8,8 @@ export const IMPORT_PERMISSIONS: Permission[] = [ { name: "asset.upload", description: "Upload new assets" }, { name: "album.read", description: "Read album data" }, { name: "album.create", description: "Create new albums" }, - { name: "album.update", description: "Add assets to albums" }, + { name: "album.update", description: "Update album metadata" }, + { name: "albumAsset.create", description: "Add assets to albums" }, { name: "tag.create", description: "Tag imported assets" }, { name: "tag.asset", description: "Assign tags to assets" }, ]; @@ -18,8 +19,11 @@ export const WORKFLOW_PERMISSIONS: Permission[] = [ { name: "asset.update", description: "Favorite, archive, update metadata" }, { name: "album.read", description: "Read album data" }, { name: "album.create", description: "Create new albums" }, - { name: "album.update", description: "Add/remove assets from albums" }, - { name: "tag.create", description: "Create and assign tags" }, + { name: "album.update", description: "Update album metadata" }, + { name: "albumAsset.create", description: "Add assets to albums" }, + { name: "albumAsset.delete", description: "Remove assets from albums" }, + { name: "tag.create", description: "Create tags" }, + { name: "tag.asset", description: "Assign tags to assets" }, ]; export const getPermissionNames = (permissions: Permission[]): string[] => diff --git a/src/pages/api/settings/validate-api-key.ts b/src/pages/api/settings/validate-api-key.ts index 61b9491..98eea94 100644 --- a/src/pages/api/settings/validate-api-key.ts +++ b/src/pages/api/settings/validate-api-key.ts @@ -30,7 +30,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const keyData = await keyRes.json(); const granted: string[] = keyData.permissions ?? []; - const missing = REQUIRED_PERMISSIONS.filter((p) => !granted.includes(p)); + // A key created with the "All" checkbox reports its permissions as ["all"], + // which grants every permission without listing them individually. + const missing = granted.includes("all") + ? [] + : REQUIRED_PERMISSIONS.filter((p) => !granted.includes(p)); return res.status(200).json({ valid: missing.length === 0,