From ced78961a022230ab01db5bd20878b17d8e17537 Mon Sep 17 00:00:00 2001 From: Stephan Groth <96803994+Kalvalax@users.noreply.github.com> Date: Thu, 16 Jul 2026 05:59:18 +0200 Subject: [PATCH] feat(auth): opt-in OIDC DEK unlock for API-key requests (ALLOW_APIKEY_DATA_UNLOCK) (#1064) * chore: fix release workflow to merge docs branch * fix: svg donation generator push fail * fix: svg donation generator push fail * Update termix.rb * fix: svg donation generator push fail * chore: move donation badge to badges branch to avoid ruleset conflicts * chore: remove unneeded token from donation badge workflow * chore: debug donation badge commit step * fix: escape < character in donation SVG * fix: point donation badge to badges branch * chore: remove unused donation badge svg from main * Add Rack Genius logo to README Added Rack Genius logo to the README. * chore: improve donation goal svg generator to include stablecoins * chore: donation goal generator syntax error * chore: donation goal generator incorrect docs url usage * chore: donation bar reporting wrong result * feat: add Open File Manager to tab right-click menu (#1046) * Revert "feat: add Open File Manager to tab right-click menu (#1046)" (#1050) This reverts commit 0712fdd731a9991e2d8b6cbd0de4e3d4891419f7. * Remove donation badge from README Removed donation badge from README. * Delete .github/workflows/donation-goal.yml * feat(auth): opt-in OIDC DEK unlock for API-key requests API keys authenticate but cannot touch the encrypted credential/host store ('User data not unlocked') unless the user has a live interactive session, making them unusable for headless automation. For OIDC users the DEK is server-derivable (deriveOIDCSystemKey), so handleApiKeyAuth can unlock it without a password. Gated behind ALLOW_APIKEY_DATA_UNLOCK (default off) because enabling it widens the blast radius of a leaked API key. OIDC-only; password users are untouched. Refs #1063 --------- Co-authored-by: LukeGus Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> Co-authored-by: Sankeerth Nara Co-authored-by: ZacharyZcR --- README.md | 6 ++++-- src/backend/utils/auth-manager.ts | 35 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e1ac7b30..1bff5ab4 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,6 @@ Termix is free and open source. If you find it useful, consider [donating](https://donate.termix.site/) to help cover server costs and development time. -Monthly donation goal -
Termix Banner @@ -392,6 +390,10 @@ See [Projects](https://github.com/orgs/Termix-SSH/projects/5) for all planned fe AWS +    + + AWS + diff --git a/src/backend/utils/auth-manager.ts b/src/backend/utils/auth-manager.ts index 9c73a7f8..d730379d 100644 --- a/src/backend/utils/auth-manager.ts +++ b/src/backend/utils/auth-manager.ts @@ -881,6 +881,41 @@ class AuthManager { }); }); + // Auto-unlock the per-user Data Encryption Key for OIDC-backed users so + // API-key (headless / automation) requests can access encrypted + // credentials/hosts. Without this, every encrypted-table op throws + // "User data not unlocked" because the API-key path never established a + // DEK session. OIDC DEKs are wrapped with a server-derived system key + // (deriveOIDCSystemKey), so no user password is required. Password-based + // users are deliberately NOT unlocked here (their KEK is password-derived + // and cannot be reproduced server-side). + // + // Opt-in and OFF by default: enabling this widens the blast radius of a + // leaked API key (it can then read that user's stored SSH secrets in + // clear text), so operators must turn it on explicitly. + if ( + process.env.ALLOW_APIKEY_DATA_UNLOCK === "true" && + !this.userCrypto.isUserUnlocked(matchedKey.userId) + ) { + try { + const { users } = await import("../database/db/schema.js"); + const oidcRows = await db + .select() + .from(users) + .where(eq(users.id, matchedKey.userId)) + .limit(1); + if (oidcRows[0]?.isOidc) { + await this.authenticateOIDCUser(matchedKey.userId); + } + } catch (err) { + databaseLogger.warn("API-key OIDC auto-unlock failed", { + operation: "api_key_oidc_unlock_failed", + userId: matchedKey.userId, + error: err instanceof Error ? err.message : "Unknown", + }); + } + } + req.userId = matchedKey.userId; req.apiKeyId = matchedKey.id; next();