Compare commits

...

7 commits

Author SHA1 Message Date
Jan Böhmer
57a0dfdbdb Fixed phpstan issue
Some checks failed
Build assets artifact / Build assets artifact (push) Has been cancelled
Docker Image Build / build (linux/amd64, amd64, ubuntu-latest) (push) Has been cancelled
Docker Image Build / build (linux/arm/v7, armv7, ubuntu-24.04-arm) (push) Has been cancelled
Docker Image Build / build (linux/arm64, arm64, ubuntu-24.04-arm) (push) Has been cancelled
Docker Image Build (FrankenPHP) / build (linux/amd64, amd64, ubuntu-latest) (push) Has been cancelled
Docker Image Build (FrankenPHP) / build (linux/arm/v7, armv7, ubuntu-24.04-arm) (push) Has been cancelled
Docker Image Build (FrankenPHP) / build (linux/arm64, arm64, ubuntu-24.04-arm) (push) Has been cancelled
Static analysis / Static analysis (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.5, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.5, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.5, sqlite) (push) Has been cancelled
Docker Image Build / merge (push) Has been cancelled
Docker Image Build (FrankenPHP) / merge (push) Has been cancelled
2026-06-07 22:49:41 +02:00
Jan Böhmer
61dcc99597
New Crowdin updates (#1383)
* New translations messages.en.xlf (German)

[ci skip]

* New translations messages.en.xlf (German)

[ci skip]

* New translations messages.en.xlf (German)

[ci skip]

* New translations messages.en.xlf (English)

[ci skip]

* New translations messages.en.xlf (German)

[ci skip]

* New translations messages.en.xlf (English)

[ci skip]
2026-06-07 22:47:22 +02:00
Jan Böhmer
cf8826a906 Bumped version to 2.12.1 2026-06-07 22:43:33 +02:00
Jan Böhmer
4b00541dd6 Improved APP_SECRET warning message 2026-06-07 22:43:06 +02:00
Jan Böhmer
93ab410857 Added documentation about changing the APP_SECRET env on installation 2026-06-07 22:37:47 +02:00
Jan Böhmer
cb28afcdf5 Moved APP_SECRET value to the top of .env 2026-06-07 22:28:29 +02:00
Jan Böhmer
f888e10827 Show a warning if using the default APP_SECRET value 2026-06-07 22:26:45 +02:00
11 changed files with 184 additions and 20 deletions

5
.env
View file

@ -1,6 +1,10 @@
#### Part-DB Configuration #### Part-DB Configuration
# See https://docs.part-db.de/configuration.html for documentation of available options # See https://docs.part-db.de/configuration.html for documentation of available options
# Change this to a random value to secure your installation! You can generate a random string with "openssl rand -hex 16"
# Share that value with nobody and keep it secret
APP_SECRET=a03498528f5a5fc089273ec9ae5b2849
################################################################################### ###################################################################################
# Database settings # Database settings
################################################################################### ###################################################################################
@ -158,7 +162,6 @@ CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
###> symfony/framework-bundle ### ###> symfony/framework-bundle ###
APP_ENV=prod APP_ENV=prod
APP_SECRET=a03498528f5a5fc089273ec9ae5b2849
APP_SHARE_DIR=var/share APP_SHARE_DIR=var/share
###< symfony/framework-bundle ### ###< symfony/framework-bundle ###

View file

@ -1 +1 @@
2.12.0 2.12.1

View file

@ -114,10 +114,21 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
* `datastructure_create`: Creation of a new data structure (e.g. category, manufacturer, ...) * `datastructure_create`: Creation of a new data structure (e.g. category, manufacturer, ...)
* `CHECK_FOR_UPDATES` (default `1`): Set this to 0 if you do not want Part-DB to connect to GitHub to check for new * `CHECK_FOR_UPDATES` (default `1`): Set this to 0 if you do not want Part-DB to connect to GitHub to check for new
versions, or if your server cannot connect to the internet. versions, or if your server cannot connect to the internet.
* `APP_SECRET` (env only): This variable is a configuration parameter used for various security-related purposes, * `APP_SECRET` (env only): A secret key used by Symfony for cryptographic operations — signing cookies, generating
particularly for securing and protecting various aspects of your application. It's a secret key that is used for CSRF tokens, and other security-sensitive tasks. **You must change this from the default value before exposing
cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this Part-DB to any network.** The default value shipped with Part-DB is publicly known; leaving it in place would allow
value should be handled as confidential data and not shared publicly. an attacker to forge signed cookies and bypass CSRF protection.
Generate a secure value and add it to `.env.local`:
```bash
echo "APP_SECRET=$(openssl rand -hex 32)" >> .env.local
```
For Docker, pass it in the `environment` section of your `docker-compose.yaml`:
```yaml
environment:
- APP_SECRET=<output of: openssl rand -hex 32>
```
Part-DB displays a warning on the homepage (visible to administrators only) as long as the default value is in use.
* `SHOW_PART_IMAGE_OVERLAY`: Set to 0 to disable the part image overlay, which appears if you hover over an image in the * `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 part image gallery
* `IPN_SUGGEST_REGEX`: A global regular expression, that part IPNs have to fulfill. Enforce your own format for your users. * `IPN_SUGGEST_REGEX`: A global regular expression, that part IPNs have to fulfill. Enforce your own format for your users.

View file

@ -47,7 +47,10 @@ services:
- DATABASE_URL=sqlite:///%kernel.project_dir%/var/db/app.db - DATABASE_URL=sqlite:///%kernel.project_dir%/var/db/app.db
# In docker env logs will be redirected to stderr # In docker env logs will be redirected to stderr
- APP_ENV=docker - APP_ENV=docker
# Secret key used to sign cookies and CSRF tokens. MUST be changed to a unique random value before going live!
# Generate one with: openssl rand -hex 32
- APP_SECRET=CHANGE_ME
# Uncomment this, if you want to use the automatic database migration feature. With this you have you do not have to # Uncomment this, if you want to use the automatic database migration feature. With this you have you do not have to
# run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/ # run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/
# folder (under .automigration-backup), so you can restore it, if the migration fails. # folder (under .automigration-backup), so you can restore it, if the migration fails.
@ -89,7 +92,11 @@ services:
4. Customize the settings by changing the environment variables (or adding new ones). See [Configuration]({% link 4. Customize the settings by changing the environment variables (or adding new ones). See [Configuration]({% link
configuration.md %}) for more information. configuration.md %}) for more information.
5. Inside the folder, run 5. Make sure to change the `APP_SECRET` variable to a unique random value (32 characters). You can generate one with the following command:
```bash
openssl rand -hex 32
```
6. Inside the folder, run
```bash ```bash
docker-compose up -d docker-compose up -d
@ -100,7 +107,7 @@ services:
> Otherwise Part-DB console might use the wrong configuration to execute commands. > Otherwise Part-DB console might use the wrong configuration to execute commands.
6. Create the initial database with 7. Create the initial database with
```bash ```bash
docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate
@ -108,7 +115,7 @@ docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate
and watch for the password output and watch for the password output
6. Part-DB is available under `http://localhost:8080` and you can log in with the username `admin` and the password shown 8. Part-DB is available under `http://localhost:8080` and you can log in with the username `admin` and the password shown
before before
The docker image uses a SQLite database and all data (database, uploads, and other media) is put into folders relative to The docker image uses a SQLite database and all data (database, uploads, and other media) is put into folders relative to
@ -121,6 +128,7 @@ If you want to use MySQL as a database, you can use the following docker-compose
{: .warning } {: .warning }
> You have to replace the values for MYSQL_ROOT_PASSWORD and MYSQL_PASSWORD with your own passwords!! > You have to replace the values for MYSQL_ROOT_PASSWORD and MYSQL_PASSWORD with your own passwords!!
> You have to change MYSQL_PASSWORD in the database section and for the DATABASE_URL in the partdb section. > You have to change MYSQL_PASSWORD in the database section and for the DATABASE_URL in the partdb section.
> Generate a random string for APP_SECRET.
```yaml ```yaml
version: '3.3' version: '3.3'
@ -142,14 +150,19 @@ services:
environment: environment:
# Replace SECRET_USER_PASSWORD with the value of MYSQL_PASSWORD from below # Replace SECRET_USER_PASSWORD with the value of MYSQL_PASSWORD from below
- DATABASE_URL=mysql://partdb:SECRET_USER_PASSWORD@database:3306/partdb - DATABASE_URL=mysql://partdb:SECRET_USER_PASSWORD@database:3306/partdb
# Secret key used to sign cookies. MUST be changed to a unique random value before going live!
# Generate one with: openssl rand -hex 32
- APP_SECRET=CHANGE_ME
# In docker env logs will be redirected to stderr # In docker env logs will be redirected to stderr
- APP_ENV=docker - APP_ENV=docker
# Uncomment this, if you want to use the automatic database migration feature. With this you do not have to # Uncomment this, if you want to use the automatic database migration feature. With this you do not have to
# run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/ # run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/
# folder (under .automigration-backup), so you can restore it, if the migration fails. # folder (under .automigration-backup), so you can restore it, if the migration fails.
# This feature is currently experimental, so use it at your own risk! # This feature is currently experimental, so use it at your own risk!
# - DB_AUTOMIGRATE=true # - DB_AUTOMIGRATE=true
# You can configure Part-DB using the webUI or environment variables # You can configure Part-DB using the webUI or environment variables
# However you can add any other environment configuration you want here # However you can add any other environment configuration you want here

View file

@ -136,6 +136,15 @@ cp .env .env.local
In your `.env.local` you can configure Part-DB according to your wishes and overwrite web interface settings. In your `.env.local` you can configure Part-DB according to your wishes and overwrite web interface settings.
A full list of configuration options can be found [here](../configuration.md). A full list of configuration options can be found [here](../configuration.md).
{: .important }
> **Change `APP_SECRET` before going live.** The default value shipped with Part-DB is publicly known and must not be
> used in production — it would allow an attacker to forge signed cookies and bypass CSRF protection.
> Generate a new value and add it to your `.env.local`:
> ```bash
> echo "APP_SECRET=$(openssl rand -hex 32)" >> .env.local
> ```
> or edit the file with a text editor and add a new value for `APP_SECRET` (you can generate a random value with `openssl rand -hex 32`).
Please check that the configured base currency matches your mainly used currency, as Please check that the configured base currency matches your mainly used currency, as
this can not be changed after creating price information. this can not be changed after creating price information.

View file

@ -22,6 +22,7 @@ declare(strict_types=1);
*/ */
namespace App\Command; namespace App\Command;
use App\Services\System\AppSecretChecker;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
@ -33,7 +34,9 @@ use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
#[AsCommand('partdb:check-requirements', 'Checks if the requirements Part-DB needs or recommends are fulfilled.')] #[AsCommand('partdb:check-requirements', 'Checks if the requirements Part-DB needs or recommends are fulfilled.')]
class CheckRequirementsCommand extends Command class CheckRequirementsCommand extends Command
{ {
public function __construct(protected ContainerBagInterface $params) public function __construct(protected ContainerBagInterface $params,
private readonly AppSecretChecker $appSecretChecker
)
{ {
parent::__construct(); parent::__construct();
} }
@ -121,6 +124,16 @@ class CheckRequirementsCommand extends Command
$io->success('Debug mode disabled.'); $io->success('Debug mode disabled.');
} }
//Check if APP_SECRET has been changed from the default
if ($io->isVerbose()) {
$io->comment('Checking APP_SECRET...');
}
if ($this->appSecretChecker->isInsecureSecret()) {
$io->warning('APP_SECRET is set to the default value shipped with Part-DB. This is a security risk! Generate a new secret (e.g. using "openssl rand -hex 32") and set it as APP_SECRET in your .env.local file.');
} elseif (!$only_issues) {
$io->success('APP_SECRET has been changed from the default value.');
}
} }
protected function checkPHPExtensions(SymfonyStyle $io, bool $only_issues = false): void protected function checkPHPExtensions(SymfonyStyle $io, bool $only_issues = false): void

View file

@ -24,6 +24,7 @@ namespace App\Controller;
use App\DataTables\LogDataTable; use App\DataTables\LogDataTable;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use App\Services\System\AppSecretChecker;
use App\Services\System\BannerHelper; use App\Services\System\BannerHelper;
use App\Services\System\GitVersionInfoProvider; use App\Services\System\GitVersionInfoProvider;
use App\Services\System\UpdateAvailableFacade; use App\Services\System\UpdateAvailableFacade;
@ -36,8 +37,11 @@ use Symfony\Component\Routing\Attribute\Route;
class HomepageController extends AbstractController class HomepageController extends AbstractController
{ {
public function __construct(private readonly DataTableFactory $dataTable, private readonly BannerHelper $bannerHelper) public function __construct(
{ private readonly DataTableFactory $dataTable,
private readonly BannerHelper $bannerHelper,
private readonly AppSecretChecker $appSecretChecker,
) {
} }
@ -84,6 +88,8 @@ class HomepageController extends AbstractController
'new_version_available' => $updateAvailableManager->isUpdateAvailable(), 'new_version_available' => $updateAvailableManager->isUpdateAvailable(),
'new_version' => $updateAvailableManager->getLatestVersionString(), 'new_version' => $updateAvailableManager->getLatestVersionString(),
'new_version_url' => $updateAvailableManager->getLatestVersionUrl(), 'new_version_url' => $updateAvailableManager->getLatestVersionUrl(),
'insecure_app_secret' => $this->appSecretChecker->isInsecureSecret(),
'suggested_app_secret' => $this->appSecretChecker->isInsecureSecret() ? $this->appSecretChecker->generateSecret() : null,
]); ]);
} }
} }

View file

@ -0,0 +1,63 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Services\System;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
/**
* Checks whether APP_SECRET has been changed from the default value shipped with Part-DB.
*/
final readonly class AppSecretChecker
{
/** Known default/example secrets that must not be used in production. */
public const INSECURE_SECRETS = [
'a03498528f5a5fc089273ec9ae5b2849', // default in .env
'318b5d659e07a0b3f96d9b3a83b254ca', // default in .env.dev
'CHANGE_ME' //example secret used in documentation and error messages
];
public function __construct(
#[Autowire('%kernel.secret%')]
private string $appSecret,
) {
}
/**
* @return bool True if the app secret is one of the known insecure default secrets, false otherwise.
*/
public function isInsecureSecret(): bool
{
return in_array($this->appSecret, self::INSECURE_SECRETS, true);
}
/**
* Generates a new random app secret that can be used to replace the default insecure one.
* @return string
* @throws \Random\RandomException
*/
public function generateSecret(): string
{
//Symfony docs recommend 32 characters for the app secret, which are 16 random bytes when hex-encoded.
return bin2hex(random_bytes(16));
}
}

View file

@ -85,6 +85,16 @@
{% block content %} {% block content %}
{% if insecure_app_secret and is_granted('@system.server_infos') %}
<div class="alert alert-warning" role="alert">
<h5><i class="fa-solid fa-triangle-exclamation fa-fw"></i> {% trans %}system.app_secret.insecure.title{% endtrans %}</h5>
<p class="mb-1">{% trans %}system.app_secret.insecure.message{% endtrans %}</p>
<p class="mb-0">{% trans %}system.app_secret.insecure.suggestion{% endtrans %}
<br><code>APP_SECRET={{ suggested_app_secret }}</code></p>
<small>{% trans %}update_manager.new_version_available.only_administrators_can_see{% endtrans %}</small>
</div>
{% endif %}
{% if is_granted('@system.show_updates') %} {% if is_granted('@system.show_updates') %}
{{ nv.new_version_alert(new_version_available, new_version, new_version_url) }} {{ nv.new_version_alert(new_version_available, new_version, new_version_url) }}
{% endif %} {% endif %}

View file

@ -64,7 +64,7 @@
<unit id="KSFhj_3" name="currency.iso_code.caption"> <unit id="KSFhj_3" name="currency.iso_code.caption">
<segment state="translated"> <segment state="translated">
<source>currency.iso_code.caption</source> <source>currency.iso_code.caption</source>
<target>ISO Code</target> <target>ISO-Code</target>
</segment> </segment>
</unit> </unit>
<unit id="UqMuNZQ" name="currency.symbol.caption"> <unit id="UqMuNZQ" name="currency.symbol.caption">
@ -13647,5 +13647,23 @@ Buerklin-API-Authentication-Server:
<target>Aktiviert das Browser-Plugin für diese Instanz.</target> <target>Aktiviert das Browser-Plugin für diese Instanz.</target>
</segment> </segment>
</unit> </unit>
<unit id="JqcvhX_" name="system.app_secret.insecure.title">
<segment state="translated">
<source>system.app_secret.insecure.title</source>
<target>Unsicheres APP_SECRET</target>
</segment>
</unit>
<unit id="XSSYWTJ" name="system.app_secret.insecure.message">
<segment state="translated">
<source>system.app_secret.insecure.message</source>
<target>Die Umgebungsvariable &lt;code&gt;APP_SECRET&lt;/code&gt; ist auf den Standardwert gesetzt, der mit Part-DB ausgeliefert wird. Dies stellt ein Sicherheitsrisiko dar! Setzen Sie sie in Ihrer Datei &lt;code&gt;.env.local&lt;/code&gt; oder &lt;code&gt;docker-compose.yaml&lt;/code&gt; auf einen neuen Zufallswert.</target>
</segment>
</unit>
<unit id="e7.f3m0" name="system.app_secret.insecure.suggestion">
<segment state="translated">
<source>system.app_secret.insecure.suggestion</source>
<target>Sie können diesen zufällig generierten Wert verwenden (geben Sie ihn niemandem weiter):</target>
</segment>
</unit>
</file> </file>
</xliff> </xliff>

View file

@ -13649,5 +13649,23 @@ Buerklin-API Authentication server:
<target>When enabled users with the info provider permission can submit pages to Part-DB and retrieve them later.</target> <target>When enabled users with the info provider permission can submit pages to Part-DB and retrieve them later.</target>
</segment> </segment>
</unit> </unit>
<unit id="JqcvhX_" name="system.app_secret.insecure.title">
<segment state="translated">
<source>system.app_secret.insecure.title</source>
<target>Insecure APP_SECRET</target>
</segment>
</unit>
<unit id="XSSYWTJ" name="system.app_secret.insecure.message">
<segment state="translated">
<source>system.app_secret.insecure.message</source>
<target>The &lt;code&gt;APP_SECRET&lt;/code&gt; environment variable is set to the default value shipped with Part-DB. This is a security risk! Set it to a new random value in your &lt;code&gt;.env.local&lt;/code&gt; or &lt;code&gt;docker-compose.yaml&lt;/code&gt; file.</target>
</segment>
</unit>
<unit id="e7.f3m0" name="system.app_secret.insecure.suggestion">
<segment state="translated">
<source>system.app_secret.insecure.suggestion</source>
<target>You can use this randomly generated value (share it with nobody):</target>
</segment>
</unit>
</file> </file>
</xliff> </xliff>