diff --git a/apps/login/next-env-vars.d.ts b/apps/login/next-env-vars.d.ts index bafdada19..05c4f240b 100644 --- a/apps/login/next-env-vars.d.ts +++ b/apps/login/next-env-vars.d.ts @@ -4,10 +4,12 @@ declare namespace NodeJS { [key: `${string}_AUDIENCE`]: string; // The system api url [key: `${string}_SYSTEM_USER_ID`]: string; // The service account id [key: `${string}_SYSTEM_USER_PRIVATE_KEY`]: string; // The service account private key + [key: `${string}_SYSTEM_USER_PRIVATE_KEY_FILE`]: string; // The service account private key file path AUDIENCE: string; // The fallback system api url SYSTEM_USER_ID: string; // The fallback service account id SYSTEM_USER_PRIVATE_KEY: string; // The fallback service account private key + SYSTEM_USER_PRIVATE_KEY_FILE: string; // The fallback service account private key file path /** * The Zitadel API url diff --git a/apps/login/src/lib/api.ts b/apps/login/src/lib/api.ts index 12aea4e75..7a9757906 100644 --- a/apps/login/src/lib/api.ts +++ b/apps/login/src/lib/api.ts @@ -1,7 +1,20 @@ import { newSystemToken } from "@zitadel/client/node"; -import { readFileSync } from "fs"; +import { readFile } from "fs/promises"; import { getLoginSystemUserId } from "./deployment"; +// The key token is only loaded once from disk per process. +// If the file was loaded you need to restart the process to switch the key. +let keyToken: string | undefined; + +async function getTokenFromFile(): Promise { + if (keyToken) { + return keyToken; + } + + keyToken = await readFile(process.env.SYSTEM_USER_PRIVATE_KEY_FILE, "binary"); + return keyToken; +} + /** * Creates a signed JWT token using system user credentials from environment * variables. The SYSTEM_USER_PRIVATE_KEY is expected to be base64-encoded and @@ -12,16 +25,14 @@ import { getLoginSystemUserId } from "./deployment"; * @throws If the underlying token signing fails. */ export async function systemAPIToken() { - const token = { - audience: process.env.AUDIENCE, - userID: process.env.SYSTEM_USER_ID, - token: Buffer.from(process.env.SYSTEM_USER_PRIVATE_KEY, "base64").toString("utf-8"), - }; + const key = process.env.SYSTEM_USER_PRIVATE_KEY_FILE + ? await getTokenFromFile() + : Buffer.from(process.env.SYSTEM_USER_PRIVATE_KEY, "base64").toString("utf-8"); return newSystemToken({ - audience: token.audience, - subject: token.userID, - key: token.token, + audience: process.env.AUDIENCE, + subject: process.env.SYSTEM_USER_ID, + key, }); } @@ -38,7 +49,7 @@ export async function loginServiceKeyToken() { const keyFile = process.env.ZITADEL_LOGIN_SERVICE_KEY_FILE!; try { - const key = readFileSync(keyFile, "utf-8"); + const key = await readFile(keyFile, "utf-8"); return newSystemToken({ audience: process.env.AUDIENCE || process.env.ZITADEL_API_URL, diff --git a/apps/login/src/lib/deployment.ts b/apps/login/src/lib/deployment.ts index 85c8d4953..add7e4e0e 100644 --- a/apps/login/src/lib/deployment.ts +++ b/apps/login/src/lib/deployment.ts @@ -5,13 +5,18 @@ * - AUDIENCE: The API audience for JWT authentication * - SYSTEM_USER_ID: The system user's ID * - SYSTEM_USER_PRIVATE_KEY: The private key for JWT signing + * - SYSTEM_USER_PRIVATE_KEY_FILE: The private key file path for JWT signing * * Both multi-tenant and self-hosted deployments can use system user authentication. * * @returns true if system user credentials are present, false otherwise */ export function hasSystemUserCredentials(): boolean { - return !!process.env.AUDIENCE && !!process.env.SYSTEM_USER_ID && !!process.env.SYSTEM_USER_PRIVATE_KEY; + return ( + !!process.env.AUDIENCE && + !!process.env.SYSTEM_USER_ID && + (!!process.env.SYSTEM_USER_PRIVATE_KEY || !!process.env.SYSTEM_USER_PRIVATE_KEY_FILE) + ); } /**