From c49880385996b94de0c6e84d7778242c104ba9cf Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Sun, 7 Sep 2025 08:12:25 -0600 Subject: [PATCH 1/5] Add INITIAL_ADMIN_API_KEY environment variable support - Add configuration parameter for initial admin API key - Implement getInitialAdminApiToken() method in AbstractMultiPlatformMigration - Create migration to automatically generate admin API token on initial setup - Add CLAUDE.md to .gitignore for local development documentation --- .gitignore | 3 + config/parameters.yaml | 6 ++ migrations/Version20250907000000.php | 71 +++++++++++++++++++ .../AbstractMultiPlatformMigration.php | 25 +++++++ 4 files changed, 105 insertions(+) create mode 100644 migrations/Version20250907000000.php diff --git a/.gitignore b/.gitignore index 76655919c..e57afb074 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ yarn-error.log ###> phpstan/phpstan ### phpstan.neon ###< phpstan/phpstan ### + +# Claude Code project documentation (local development only) +CLAUDE.md diff --git a/config/parameters.yaml b/config/parameters.yaml index 154fbd8a5..a72262eee 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -43,6 +43,10 @@ parameters: ###################################################################################################################### partdb.saml.enabled: '%env(bool:SAML_ENABLED)%' # If this is set to true, SAML authentication is enabled + ###################################################################################################################### + # API Configuration + ###################################################################################################################### + partdb.api.initial_admin_key: '%env(trim:string:INITIAL_ADMIN_API_KEY)%' # Initial admin API key for automated access (env only) ###################################################################################################################### # Miscellaneous @@ -104,3 +108,5 @@ parameters: env(SAML_ROLE_MAPPING): '{}' env(DATABASE_EMULATE_NATURAL_SORT): 0 + + env(INITIAL_ADMIN_API_KEY): '' diff --git a/migrations/Version20250907000000.php b/migrations/Version20250907000000.php new file mode 100644 index 000000000..1a2a33bdc --- /dev/null +++ b/migrations/Version20250907000000.php @@ -0,0 +1,71 @@ +getInitialAdminApiToken(); + if (empty($apiToken)) { + return; + } + + // Create a proper API token with the 'tcp_' prefix and the provided key + $fullToken = 'tcp_' . $apiToken; + + // Set expiration to 1 year from now + $validUntil = date('Y-m-d H:i:s', strtotime('+1 year')); + $currentDateTime = date('Y-m-d H:i:s'); + + // Insert the API token for the admin user (user_id = 2) + // Level 4 = FULL access (can do everything the user can do) + $sql = "INSERT INTO api_tokens (user_id, name, token, level, valid_until, datetime_added, last_modified) + VALUES (2, 'Initial Admin Token', ?, 4, ?, ?, ?)"; + + $this->addSql($sql, [$fullToken, $validUntil, $currentDateTime, $currentDateTime]); + } + + public function mySQLUp(Schema $schema): void + { + $this->createInitialAdminApiToken(); + } + + public function mySQLDown(Schema $schema): void + { + // Remove the initial admin token if it exists + $this->addSql("DELETE FROM api_tokens WHERE name = 'Initial Admin Token' AND user_id = 2"); + } + + public function sqLiteUp(Schema $schema): void + { + $this->createInitialAdminApiToken(); + } + + public function sqLiteDown(Schema $schema): void + { + // Remove the initial admin token if it exists + $this->addSql("DELETE FROM api_tokens WHERE name = 'Initial Admin Token' AND user_id = 2"); + } + + public function postgreSQLUp(Schema $schema): void + { + $this->createInitialAdminApiToken(); + } + + public function postgreSQLDown(Schema $schema): void + { + // Remove the initial admin token if it exists + $this->addSql("DELETE FROM api_tokens WHERE name = 'Initial Admin Token' AND user_id = 2"); + } +} \ No newline at end of file diff --git a/src/Migration/AbstractMultiPlatformMigration.php b/src/Migration/AbstractMultiPlatformMigration.php index bc2b3f191..1bf42e1d6 100644 --- a/src/Migration/AbstractMultiPlatformMigration.php +++ b/src/Migration/AbstractMultiPlatformMigration.php @@ -35,6 +35,7 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration { final public const ADMIN_PW_LENGTH = 10; protected string $admin_pw = ''; + protected string $admin_api_token = ''; /** @noinspection SenselessProxyMethodInspection * This method is required to redefine the logger type hint to protected @@ -108,6 +109,23 @@ public function getInitalAdminPW(): string return password_hash((string) $this->admin_pw, PASSWORD_DEFAULT); } + /** + * Returns the initial admin API token if configured via environment variable. + * If not configured, returns empty string (no token will be created). + */ + public function getInitialAdminApiToken(): string + { + if ($this->admin_api_token === '') { + $apiKey = getenv('INITIAL_ADMIN_API_KEY'); + if (!empty($apiKey)) { + // Use the provided API key directly (should be generated with openssl rand -hex 32) + $this->admin_api_token = $apiKey; + } + } + + return $this->admin_api_token; + } + public function postUp(Schema $schema): void { parent::postUp($schema); @@ -117,6 +135,13 @@ public function postUp(Schema $schema): void $this->logger->warning('The initial password for the "admin" user is: '.$this->admin_pw.''); $this->logger->warning(''); } + + if ($this->admin_api_token !== '') { + $this->logger->warning(''); + $this->logger->warning('Initial admin API token has been created with the provided key'); + $this->logger->warning('Use this token in Authorization header: Bearer tcp_'.$this->admin_api_token.''); + $this->logger->warning(''); + } } /** From 99cd18096bb67c4ee5cc10b9a1cf9fd86ceb067a Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Sun, 7 Sep 2025 08:42:17 -0600 Subject: [PATCH 2/5] Add INITIAL_ADMIN_API_KEY documentation - Document environment variable in configuration.md - Add section to API authentication documentation - Include examples in Docker installation guide for both SQLite and MySQL setups - Provide usage instructions for CI/CD and automated deployments --- docs/api/authentication.md | 8 ++++++++ docs/configuration.md | 5 +++++ docs/installation/installation_docker.md | 10 ++++++++++ 3 files changed, 23 insertions(+) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index b386c0cdb..f30e4276e 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -32,6 +32,14 @@ tokens as you want and also delete them again. When deleting a token, it is immediately invalidated and can not be used anymore, which means that the application can not access the API anymore with this token. +### Initial Admin API Token + +For automated deployments and CI/CD pipelines, Part-DB supports automatically creating an initial admin API token +during database setup. Set the `INITIAL_ADMIN_API_KEY` environment variable to a 64-character random string +(generate with `openssl rand -hex 32`) before running database migrations. Part-DB will create an API token named +"Initial Admin Token" with FULL scope that expires after 1 year. The token can be used immediately with the format +`Bearer tcp_` in the Authorization header. + ### Token permissions and scopes API tokens are ultimately limited by the permissions of the user, which belongs to the token. That means that the token diff --git a/docs/configuration.md b/docs/configuration.md index d4b217816..04653ec3b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -114,6 +114,11 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept particularly for securing and protecting various aspects of your application. It's a secret key that is used for cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this value should be handled as confidential data and not shared publicly. +* `INITIAL_ADMIN_API_KEY` (env only): When set to a 64-character random string (generate with `openssl rand -hex 32`), + Part-DB will automatically create an API token named "Initial Admin Token" for the admin user during database + migrations. This token will have FULL scope and expire after 1 year. This is useful for automated deployments, + CI/CD pipelines, and Docker setups where you need immediate API access without manual token creation. The token + can be used with the format `Bearer tcp_` in the Authorization header. * `SHOW_PART_IMAGE_OVERLAY`: Set to 0 to disable the part image overlay, which appears if you hover over an image in the part image gallery diff --git a/docs/installation/installation_docker.md b/docs/installation/installation_docker.md index 232633abb..cf0039f7c 100644 --- a/docs/installation/installation_docker.md +++ b/docs/installation/installation_docker.md @@ -75,6 +75,11 @@ services: # Use gravatars for user avatars, when user has no own avatar defined - USE_GRAVATAR=0 + # Automatically create an admin API token during database setup (useful for CI/CD pipelines) + # Generate a 64-character random string with: openssl rand -hex 32 + # The token will be available as: Bearer tcp_ + #- INITIAL_ADMIN_API_KEY=your_64_character_random_string_here + # Override value if you want to show a given text on homepage. # When this is empty the content of config/banner.md is used as banner #- BANNER=This is a test banner
with a line break @@ -146,6 +151,11 @@ services: # However you can add add any other environment configuration you want here # See .env file for all available options or https://docs.part-db.de/configuration.html + # Automatically create an admin API token during database setup (useful for CI/CD pipelines) + # Generate a 64-character random string with: openssl rand -hex 32 + # The token will be available as: Bearer tcp_ + #- INITIAL_ADMIN_API_KEY=your_64_character_random_string_here + # Override value if you want to show to show a given text on homepage. # When this is outcommented the webUI can be used to configure the banner #- BANNER=This is a test banner
with a line break From 99b12aa409a8c0267495f59984e6a4505748fd27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 23 Sep 2025 20:14:45 +0200 Subject: [PATCH 3/5] Removed unnessecary parameter --- config/parameters.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/config/parameters.yaml b/config/parameters.yaml index a72262eee..6ecd4e9cc 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -43,11 +43,6 @@ parameters: ###################################################################################################################### partdb.saml.enabled: '%env(bool:SAML_ENABLED)%' # If this is set to true, SAML authentication is enabled - ###################################################################################################################### - # API Configuration - ###################################################################################################################### - partdb.api.initial_admin_key: '%env(trim:string:INITIAL_ADMIN_API_KEY)%' # Initial admin API key for automated access (env only) - ###################################################################################################################### # Miscellaneous ###################################################################################################################### From 410208680341ab1925ddc4cb1cc5a009f5dbe560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 23 Sep 2025 20:21:56 +0200 Subject: [PATCH 4/5] Do not put the initial admin key env into the docker compose templates This is an feature for advanced users only and will risk secret leaking --- docs/installation/installation_docker.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/installation/installation_docker.md b/docs/installation/installation_docker.md index cf0039f7c..232633abb 100644 --- a/docs/installation/installation_docker.md +++ b/docs/installation/installation_docker.md @@ -75,11 +75,6 @@ services: # Use gravatars for user avatars, when user has no own avatar defined - USE_GRAVATAR=0 - # Automatically create an admin API token during database setup (useful for CI/CD pipelines) - # Generate a 64-character random string with: openssl rand -hex 32 - # The token will be available as: Bearer tcp_ - #- INITIAL_ADMIN_API_KEY=your_64_character_random_string_here - # Override value if you want to show a given text on homepage. # When this is empty the content of config/banner.md is used as banner #- BANNER=This is a test banner
with a line break @@ -151,11 +146,6 @@ services: # However you can add add any other environment configuration you want here # See .env file for all available options or https://docs.part-db.de/configuration.html - # Automatically create an admin API token during database setup (useful for CI/CD pipelines) - # Generate a 64-character random string with: openssl rand -hex 32 - # The token will be available as: Bearer tcp_ - #- INITIAL_ADMIN_API_KEY=your_64_character_random_string_here - # Override value if you want to show to show a given text on homepage. # When this is outcommented the webUI can be used to configure the banner #- BANNER=This is a test banner
with a line break From d314f15509d2f97442502fe05be37bd011059b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 23 Sep 2025 20:32:58 +0200 Subject: [PATCH 5/5] Ensure the initial API key is long enough --- src/Migration/AbstractMultiPlatformMigration.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Migration/AbstractMultiPlatformMigration.php b/src/Migration/AbstractMultiPlatformMigration.php index 1bf42e1d6..5cd2c3381 100644 --- a/src/Migration/AbstractMultiPlatformMigration.php +++ b/src/Migration/AbstractMultiPlatformMigration.php @@ -34,8 +34,8 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration { final public const ADMIN_PW_LENGTH = 10; - protected string $admin_pw = ''; - protected string $admin_api_token = ''; + protected ?string $admin_pw = null; + protected ?string $admin_api_token = null; /** @noinspection SenselessProxyMethodInspection * This method is required to redefine the logger type hint to protected @@ -97,7 +97,7 @@ public function getOldDBVersion(): int */ public function getInitalAdminPW(): string { - if ($this->admin_pw === '') { + if ($this->admin_pw === null) { if (!empty($_ENV['INITIAL_ADMIN_PW'])) { $this->admin_pw = $_ENV['INITIAL_ADMIN_PW']; } else { @@ -115,9 +115,14 @@ public function getInitalAdminPW(): string */ public function getInitialAdminApiToken(): string { - if ($this->admin_api_token === '') { - $apiKey = getenv('INITIAL_ADMIN_API_KEY'); + if ($this->admin_api_token === null) { + $apiKey = $_ENV('INITIAL_ADMIN_API_KEY'); if (!empty($apiKey)) { + //Ensure the length of the API key is correct + if (strlen($apiKey) < 64) { + $this->abortIf(true, 'The provided INITIAL_ADMIN_API_KEY is too short! It must be at least 64 characters long! You can generate a valid key with "openssl rand -hex 32"'); + } + // Use the provided API key directly (should be generated with openssl rand -hex 32) $this->admin_api_token = $apiKey; }