From 2bf5bdd37e970e94336d607b5d968da69efe775e Mon Sep 17 00:00:00 2001 From: jj2bw <226866852+jj2bw@users.noreply.github.com> Date: Sat, 16 Aug 2025 20:40:36 +0200 Subject: [PATCH 1/7] feat: Initial 2FA implementation with Scheb\TwoFactorBundle --- composer.json | 4 ++ config/bundles.php | 1 + config/packages/scheb_2fa.yaml | 23 +++++++ config/packages/security.yaml | 6 ++ config/routes/scheb_2fa.yaml | 9 +++ src/Entity/Member.php | 91 ++++++++++++++++++++++++++- templates/security/2fa_form.html.twig | 39 ++++++++++++ 7 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 config/packages/scheb_2fa.yaml create mode 100644 config/routes/scheb_2fa.yaml create mode 100644 templates/security/2fa_form.html.twig diff --git a/composer.json b/composer.json index cf28814ca..b29e70eef 100644 --- a/composer.json +++ b/composer.json @@ -52,6 +52,10 @@ "psr/container": "2.*", "psr/log": "3.0.2 as 1.1.4", "ramsey/uuid-doctrine": "2.*", + "scheb/2fa-bundle": "^7.11.0", + "scheb/2fa-backup-code": "^7.11.0", + "scheb/2fa-google-authenticator": "^7.11.0", + "scheb/2fa-totp": "^7.11.0", "stof/doctrine-extensions-bundle": "^1.7", "symfony/apache-pack": "^1.0", "symfony/asset": "7.2.*", diff --git a/config/bundles.php b/config/bundles.php index a5459d586..aac34f2ff 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -25,4 +25,5 @@ Nelmio\SecurityBundle\NelmioSecurityBundle::class => ['all' => true], BabDev\PagerfantaBundle\BabDevPagerfantaBundle::class => ['all' => true], Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true], + Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true], ]; diff --git a/config/packages/scheb_2fa.yaml b/config/packages/scheb_2fa.yaml new file mode 100644 index 000000000..0cfb903cc --- /dev/null +++ b/config/packages/scheb_2fa.yaml @@ -0,0 +1,23 @@ +# config/packages/scheb_2fa.yaml +scheb_two_factor: + backup_codes: + enabled: true + totp: + enabled: true # If TOTP authentication should be enabled, default false + server_name: bewelcome.com # Server name used in QR code + issuer: BeWelcome # Issuer name used in QR code + leeway: 60 # Acceptable time drift in seconds, must be less or equal than the TOTP period + parameters: # Additional parameters added in the QR code + # image: 'https://my-service/img/logo.png' + image: 'https://miro.medium.com/v2/resize:fill:128:128/1*65AfOY_oNSTe2G1bFMwQ4A.jpeg' + template: security/2fa_form.html.twig + google: + enabled: true + server_name: bewelcome.com # Server name used in QR code + issuer: BeWelcome # Issuer name used in QR code + leeway: 60 # Acceptable time drift in seconds, must be less or equal than the TOTP period + template: security/2fa_form.html.twig + security_tokens: + # - Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken + # Symfony 7.2 default enable_authenticator_manager, per profiler _security_skipped_authenticators + - Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken diff --git a/config/packages/security.yaml b/config/packages/security.yaml index cc91ef5fc..881e6365c 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -66,7 +66,12 @@ security: # access_denied_handler: App\Security\AccessDeniedHandler + two_factor: + auth_form_path: 2fa_login + check_path: 2fa_login_check + access_control: + - { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS } - { path: ^/$, roles: PUBLIC_ACCESS } - { path: ^/login, roles: PUBLIC_ACCESS } - { path: ^/about, roles: PUBLIC_ACCESS } @@ -98,6 +103,7 @@ security: - { path: ^/members/avatar/, roles: PUBLIC_ACCESS } - { path: ^/deleteprofile, roles: PUBLIC_ACCESS } - { path: ^/password/check, roles: PUBLIC_ACCESS } + - { path: ^/logout, roles: PUBLIC_ACCESS } - { path: ^/admin, roles: ROLE_ADMIN } - { path: ^/api, roles: PUBLIC_ACCESS } - { path: ^/, roles: ROLE_USER } diff --git a/config/routes/scheb_2fa.yaml b/config/routes/scheb_2fa.yaml new file mode 100644 index 000000000..a8a3d5e03 --- /dev/null +++ b/config/routes/scheb_2fa.yaml @@ -0,0 +1,9 @@ +# config/routes/scheb_2fa.yaml +2fa_login: + path: /2fa + # "scheb_two_factor.form_controller" references the controller service provided by the bundle. + # You don't HAVE to use it, but - except you have very special requirements - it is recommended. + controller: "scheb_two_factor.form_controller::form" + +2fa_login_check: + path: /2fa_check diff --git a/src/Entity/Member.php b/src/Entity/Member.php index f28d426b9..c25a59606 100644 --- a/src/Entity/Member.php +++ b/src/Entity/Member.php @@ -25,6 +25,11 @@ use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; +use Scheb\TwoFactorBundle\Model\Totp\TotpConfiguration; +use Scheb\TwoFactorBundle\Model\Totp\TotpConfigurationInterface; +use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface; +use Scheb\TwoFactorBundle\Model\Totp\TwoFactorInterface; +use Symfony\Component\Security\Core\User\UserInterface; /** * @SuppressWarnings("PHPMD") @@ -37,7 +42,8 @@ class Member \Serializable, UserInterface, PasswordHasherAwareInterface, - PasswordAuthenticatedUserInterface + PasswordAuthenticatedUserInterface, + TwoFactorInterface { public const ROLE_ADMIN_ACCEPTER = 'ROLE_ADMIN_ACCEPTER'; public const ROLE_ADMIN_ADMIN = 'ROLE_ADMIN_ADMIN'; @@ -277,6 +283,21 @@ class Member private ?Language $preferredLanguage = null; + /** + * @ORM\Column(type="json") + */ + private array $backupCodes = []; + + /** + * @ORM\Column(type="string", nullable=true) + */ + private ?string $totpSecret; + + /** + * @ORM\Column(type="string", nullable=true) + */ + private ?string $googleAuthenticatorSecret; + public function __construct() { $this->addresses = new ArrayCollection(); @@ -1550,4 +1571,72 @@ public function getTranslatedFields(): Collection { return $this->translatedFields; } + + + /** + * Check if it is a valid backup code. + */ + public function isBackupCode(string $code): bool + { + return in_array($code, $this->backupCodes); + } + + /** + * Invalidate a backup code + */ + public function invalidateBackupCode(string $code): void + { + $key = array_search($code, $this->backupCodes); + if ($key !== false){ + unset($this->backupCodes[$key]); + } + } + + /** + * Add a backup code + */ + public function addBackUpCode(string $backUpCode): void + { + if (!in_array($backUpCode, $this->backupCodes)) { + $this->backupCodes[] = $backUpCode; + } + } + + public function isTotpAuthenticationEnabled(): bool + { + return $this->totpSecret ? true : false; + } + + public function getTotpAuthenticationUsername(): string + { + return $this->username; + } + + public function getTotpAuthenticationConfiguration(): ?TotpConfigurationInterface + { + // You could persist the other configuration options in the user entity to make it individual per user. + $period = 20; + $digits = 6; + return null !== $this->totpSecret ? new TotpConfiguration($this->totpSecret, TotpConfiguration::ALGORITHM_SHA1, $period, $digits) : null; + } + + public function isGoogleAuthenticatorEnabled(): bool + { + return null !== $this->googleAuthenticatorSecret; + } + + public function getGoogleAuthenticatorUsername(): string + { + return $this->username; + } + + public function getGoogleAuthenticatorSecret(): ?string + { + return $this->googleAuthenticatorSecret; + } + + public function setGoogleAuthenticatorSecret(?string $googleAuthenticatorSecret): void + { + $this->googleAuthenticatorSecret = $googleAuthenticatorSecret; + } } diff --git a/templates/security/2fa_form.html.twig b/templates/security/2fa_form.html.twig new file mode 100644 index 000000000..1bb7e54b4 --- /dev/null +++ b/templates/security/2fa_form.html.twig @@ -0,0 +1,39 @@ +{% extends "base.html.twig" %} + +{% block body %} +

Two-Factor Authentication

+ + {% if authenticationError %} +

{{ authenticationError|trans(authenticationErrorData, 'SchebTwoFactorBundle') }}

+ {% endif %} + +
+

+ + + or Cancel 2FA +

+ + {% if twoFactorProvider == "email" %} +

Hint: The current authentication code is: {{ app.user.emailAuthCode }}

+ {% endif %} + + {% if displayTrustedOption %} +

+ {% endif %} + + {% if availableTwoFactorProviders|length > 1 %} +
+

Choose authentication method: + {% for provider in availableTwoFactorProviders %} + {{ provider }} + {% if not loop.last %}, {% endif %} + {% endfor %} +

+ {% endif %} + + {% if isCsrfProtectionEnabled %} + + {% endif %} +
+{% endblock %} From 243cc6188fe6fba110546e06863bd61986ad41b0 Mon Sep 17 00:00:00 2001 From: jj2bw <226866852+jj2bw@users.noreply.github.com> Date: Sat, 16 Aug 2025 20:52:52 +0200 Subject: [PATCH 2/7] fix: composer files --- composer.json | 2 +- composer.lock | 374 +++++++++++++++++++++++++++++++++++++++++++++++++- symfony.lock | 13 ++ 3 files changed, 384 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index b29e70eef..1c502f22b 100644 --- a/composer.json +++ b/composer.json @@ -52,8 +52,8 @@ "psr/container": "2.*", "psr/log": "3.0.2 as 1.1.4", "ramsey/uuid-doctrine": "2.*", - "scheb/2fa-bundle": "^7.11.0", "scheb/2fa-backup-code": "^7.11.0", + "scheb/2fa-bundle": "^7.11.0", "scheb/2fa-google-authenticator": "^7.11.0", "scheb/2fa-totp": "^7.11.0", "stof/doctrine-extensions-bundle": "^1.7", diff --git a/composer.lock b/composer.lock index 236b37b10..3892c6115 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "71054a3266c21c57eb1c8419f904a353", + "content-hash": "6023215819a2f80e88adf72f01dd362c", "packages": [ { "name": "amphp/amp", @@ -6150,6 +6150,73 @@ ], "time": "2024-12-13T15:12:11+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512", + "shasum": "" + }, + "require": { + "php": "^8" + }, + "require-dev": { + "phpunit/phpunit": "^9", + "vimeo/psalm": "^4|^5" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2024-05-08T12:36:18+00:00" + }, { "name": "paragonie/random_compat", "version": "v9.99.100", @@ -7041,6 +7108,223 @@ ], "time": "2024-05-27T00:00:21+00:00" }, + { + "name": "scheb/2fa-backup-code", + "version": "v7.11.0", + "source": { + "type": "git", + "url": "https://github.com/scheb/2fa-backup-code.git", + "reference": "62c6099b179903db5ab03b8059068cdb28659294" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scheb/2fa-backup-code/zipball/62c6099b179903db5ab03b8059068cdb28659294", + "reference": "62c6099b179903db5ab03b8059068cdb28659294", + "shasum": "" + }, + "require": { + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "scheb/2fa-bundle": "self.version" + }, + "type": "library", + "autoload": { + "psr-4": { + "Scheb\\TwoFactorBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Scheb", + "email": "me@christianscheb.de" + } + ], + "description": "Extends scheb/2fa-bundle with backup codes support", + "homepage": "https://github.com/scheb/2fa", + "keywords": [ + "2fa", + "Authentication", + "backup-codes", + "symfony", + "two-factor", + "two-step" + ], + "support": { + "source": "https://github.com/scheb/2fa-backup-code/tree/v7.11.0" + }, + "time": "2025-04-20T08:27:40+00:00" + }, + { + "name": "scheb/2fa-bundle", + "version": "v7.11.0", + "source": { + "type": "git", + "url": "https://github.com/scheb/2fa-bundle.git", + "reference": "06a343d14dad8cdd1670157d384738f9cfba29e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scheb/2fa-bundle/zipball/06a343d14dad8cdd1670157d384738f9cfba29e5", + "reference": "06a343d14dad8cdd1670157d384738f9cfba29e5", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "symfony/config": "^6.4 || ^7.0", + "symfony/dependency-injection": "^6.4 || ^7.0", + "symfony/event-dispatcher": "^6.4 || ^7.0", + "symfony/framework-bundle": "^6.4 || ^7.0", + "symfony/http-foundation": "^6.4 || ^7.0", + "symfony/http-kernel": "^6.4 || ^7.0", + "symfony/property-access": "^6.4 || ^7.0", + "symfony/security-bundle": "^6.4 || ^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/twig-bundle": "^6.4 || ^7.0" + }, + "conflict": { + "scheb/two-factor-bundle": "*" + }, + "suggest": { + "scheb/2fa-backup-code": "Emergency codes when you have no access to other methods", + "scheb/2fa-email": "Send codes by email", + "scheb/2fa-google-authenticator": "Google Authenticator support", + "scheb/2fa-totp": "Temporary one-time password (TOTP) support (Google Authenticator compatible)", + "scheb/2fa-trusted-device": "Trusted devices support" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Scheb\\TwoFactorBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Scheb", + "email": "me@christianscheb.de" + } + ], + "description": "A generic interface to implement two-factor authentication in Symfony applications", + "homepage": "https://github.com/scheb/2fa", + "keywords": [ + "2fa", + "Authentication", + "symfony", + "two-factor", + "two-step" + ], + "support": { + "source": "https://github.com/scheb/2fa-bundle/tree/v7.11.0" + }, + "time": "2025-06-27T12:14:20+00:00" + }, + { + "name": "scheb/2fa-google-authenticator", + "version": "v7.11.0", + "source": { + "type": "git", + "url": "https://github.com/scheb/2fa-google-authenticator.git", + "reference": "01a446eb68a76c3d0528a190029afa5e6ce5c384" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scheb/2fa-google-authenticator/zipball/01a446eb68a76c3d0528a190029afa5e6ce5c384", + "reference": "01a446eb68a76c3d0528a190029afa5e6ce5c384", + "shasum": "" + }, + "require": { + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "scheb/2fa-bundle": "self.version", + "spomky-labs/otphp": "^11.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Scheb\\TwoFactorBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Scheb", + "email": "me@christianscheb.de" + } + ], + "description": "Extends scheb/2fa-bundle with two-factor authentication using Google Authenticator", + "homepage": "https://github.com/scheb/2fa", + "keywords": [ + "2fa", + "Authentication", + "google-authenticator", + "symfony", + "two-factor", + "two-step" + ], + "support": { + "source": "https://github.com/scheb/2fa-google-authenticator/tree/v7.11.0" + }, + "time": "2025-04-20T08:38:44+00:00" + }, + { + "name": "scheb/2fa-totp", + "version": "v7.11.0", + "source": { + "type": "git", + "url": "https://github.com/scheb/2fa-totp.git", + "reference": "cfc7b00eb4a068d8adac71d3f2727b8a24a4f27d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scheb/2fa-totp/zipball/cfc7b00eb4a068d8adac71d3f2727b8a24a4f27d", + "reference": "cfc7b00eb4a068d8adac71d3f2727b8a24a4f27d", + "shasum": "" + }, + "require": { + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "scheb/2fa-bundle": "self.version", + "spomky-labs/otphp": "^11.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Scheb\\TwoFactorBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Scheb", + "email": "me@christianscheb.de" + } + ], + "description": "Extends scheb/2fa-bundle with two-factor authentication using TOTP", + "homepage": "https://github.com/scheb/2fa", + "keywords": [ + "2fa", + "Authentication", + "symfony", + "totp", + "two-factor", + "two-step" + ], + "support": { + "source": "https://github.com/scheb/2fa-totp/tree/v7.11.0" + }, + "time": "2025-04-20T08:38:44+00:00" + }, { "name": "sebastian/comparator", "version": "4.0.8", @@ -7321,6 +7605,88 @@ ], "time": "2023-02-03T06:07:39+00:00" }, + { + "name": "spomky-labs/otphp", + "version": "11.3.0", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/otphp.git", + "reference": "2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33", + "reference": "2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "paragonie/constant_time_encoding": "^2.0 || ^3.0", + "php": ">=8.1", + "psr/clock": "^1.0", + "symfony/deprecation-contracts": "^3.2" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0", + "infection/infection": "^0.26|^0.27|^0.28|^0.29", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5.26|^10.0|^11.0", + "qossmic/deptrac-shim": "^1.0", + "rector/rector": "^1.0", + "symfony/phpunit-bridge": "^6.1|^7.0", + "symplify/easy-coding-standard": "^12.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "OTPHP\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/otphp/contributors" + } + ], + "description": "A PHP library for generating one time passwords according to RFC 4226 (HOTP Algorithm) and the RFC 6238 (TOTP Algorithm) and compatible with Google Authenticator", + "homepage": "https://github.com/Spomky-Labs/otphp", + "keywords": [ + "FreeOTP", + "RFC 4226", + "RFC 6238", + "google authenticator", + "hotp", + "otp", + "totp" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/otphp/issues", + "source": "https://github.com/Spomky-Labs/otphp/tree/11.3.0" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2024-06-12T11:22:32+00:00" + }, { "name": "stof/doctrine-extensions-bundle", "version": "v1.13.0", @@ -19613,7 +19979,7 @@ } ], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -19631,9 +19997,9 @@ "ext-xsl": "*", "ext-zip": "*" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "8.3.4" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/symfony.lock b/symfony.lock index 1068f752f..c4bc4ae73 100644 --- a/symfony.lock +++ b/symfony.lock @@ -456,6 +456,19 @@ "sanmai/pipeline": { "version": "v5.1.0" }, + "scheb/2fa-bundle": { + "version": "7.11", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.0", + "ref": "1e6f68089146853a790b5da9946fc5974f6fcd49" + }, + "files": [ + "config/packages/scheb_2fa.yaml", + "config/routes/scheb_2fa.yaml" + ] + }, "sebastian/cli-parser": { "version": "1.0.1" }, From a3c402c8cb7c213b7bb69cb8378d10406b2a5d58 Mon Sep 17 00:00:00 2001 From: jj2bw <226866852+jj2bw@users.noreply.github.com> Date: Sat, 16 Aug 2025 21:04:06 +0200 Subject: [PATCH 3/7] chore: autofix php-cs-fixer --- src/Entity/Member.php | 31 ++++++++++++------------------- src/Model/ProfileModel.php | 4 ++-- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/Entity/Member.php b/src/Entity/Member.php index c25a59606..2850c4b46 100644 --- a/src/Entity/Member.php +++ b/src/Entity/Member.php @@ -21,14 +21,13 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\PostLoadEventArgs; use Doctrine\ORM\Mapping as ORM; -use Exception; -use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; -use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; -use Symfony\Component\Security\Core\User\UserInterface; +use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface; use Scheb\TwoFactorBundle\Model\Totp\TotpConfiguration; use Scheb\TwoFactorBundle\Model\Totp\TotpConfigurationInterface; -use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface; use Scheb\TwoFactorBundle\Model\Totp\TwoFactorInterface; +use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface; /** @@ -37,13 +36,7 @@ #[ORM\Table(name: 'members')] #[ORM\Entity(repositoryClass: MemberRepository::class)] #[ORM\HasLifecycleCallbacks] -class Member - implements - \Serializable, - UserInterface, - PasswordHasherAwareInterface, - PasswordAuthenticatedUserInterface, - TwoFactorInterface +class Member implements \Serializable, UserInterface, PasswordHasherAwareInterface, PasswordAuthenticatedUserInterface, TwoFactorInterface { public const ROLE_ADMIN_ACCEPTER = 'ROLE_ADMIN_ACCEPTER'; public const ROLE_ADMIN_ADMIN = 'ROLE_ADMIN_ADMIN'; @@ -1572,32 +1565,31 @@ public function getTranslatedFields(): Collection return $this->translatedFields; } - /** * Check if it is a valid backup code. */ public function isBackupCode(string $code): bool { - return in_array($code, $this->backupCodes); + return \in_array($code, $this->backupCodes, true); } /** - * Invalidate a backup code + * Invalidate a backup code. */ public function invalidateBackupCode(string $code): void { - $key = array_search($code, $this->backupCodes); - if ($key !== false){ + $key = array_search($code, $this->backupCodes, true); + if (false !== $key) { unset($this->backupCodes[$key]); } } /** - * Add a backup code + * Add a backup code. */ public function addBackUpCode(string $backUpCode): void { - if (!in_array($backUpCode, $this->backupCodes)) { + if (!\in_array($backUpCode, $this->backupCodes, true)) { $this->backupCodes[] = $backUpCode; } } @@ -1617,6 +1609,7 @@ public function getTotpAuthenticationConfiguration(): ?TotpConfigurationInterfac // You could persist the other configuration options in the user entity to make it individual per user. $period = 20; $digits = 6; + return null !== $this->totpSecret ? new TotpConfiguration($this->totpSecret, TotpConfiguration::ALGORITHM_SHA1, $period, $digits) : null; } diff --git a/src/Model/ProfileModel.php b/src/Model/ProfileModel.php index 36d217e44..2ea1be240 100644 --- a/src/Model/ProfileModel.php +++ b/src/Model/ProfileModel.php @@ -21,8 +21,8 @@ class ProfileModel public function __construct( FormFactoryInterface $formFactory, EntityManagerInterface $entityManager, - Mailer $mailer) - { + Mailer $mailer, + ) { $this->formFactory = $formFactory; $this->entityManager = $entityManager; $this->mailer = $mailer; From b459ac9714b650ce7e67bec11741076d7994e27e Mon Sep 17 00:00:00 2001 From: jj2bw <226866852+jj2bw@users.noreply.github.com> Date: Sat, 16 Aug 2025 21:12:29 +0200 Subject: [PATCH 4/7] fix: Entity/Member yaml lint+doctrine schema --- src/Entity/Member.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Entity/Member.php b/src/Entity/Member.php index 2850c4b46..68e9e559d 100644 --- a/src/Entity/Member.php +++ b/src/Entity/Member.php @@ -21,10 +21,11 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\PostLoadEventArgs; use Doctrine\ORM\Mapping as ORM; -use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface; +use Scheb\TwoFactorBundle\Model\BackupCodeInterface; +use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface as GoogleTwoFactorInterface; use Scheb\TwoFactorBundle\Model\Totp\TotpConfiguration; use Scheb\TwoFactorBundle\Model\Totp\TotpConfigurationInterface; -use Scheb\TwoFactorBundle\Model\Totp\TwoFactorInterface; +use Scheb\TwoFactorBundle\Model\Totp\TwoFactorInterface as TotpTwoFactorInterface; use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -36,7 +37,7 @@ #[ORM\Table(name: 'members')] #[ORM\Entity(repositoryClass: MemberRepository::class)] #[ORM\HasLifecycleCallbacks] -class Member implements \Serializable, UserInterface, PasswordHasherAwareInterface, PasswordAuthenticatedUserInterface, TwoFactorInterface +class Member implements \Serializable, UserInterface, PasswordHasherAwareInterface, PasswordAuthenticatedUserInterface, GoogleTwoFactorInterface, TotpTwoFactorInterface, BackupCodeInterface { public const ROLE_ADMIN_ACCEPTER = 'ROLE_ADMIN_ACCEPTER'; public const ROLE_ADMIN_ADMIN = 'ROLE_ADMIN_ADMIN'; From c23cb9358e9e0c96cf1cb7f8271070338c0f099a Mon Sep 17 00:00:00 2001 From: jj2bw <226866852+jj2bw@users.noreply.github.com> Date: Sat, 16 Aug 2025 21:15:13 +0200 Subject: [PATCH 5/7] fix: duplicate line --- src/Entity/Member.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Entity/Member.php b/src/Entity/Member.php index 68e9e559d..6e703eec3 100644 --- a/src/Entity/Member.php +++ b/src/Entity/Member.php @@ -29,7 +29,6 @@ use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Core\User\UserInterface; /** * @SuppressWarnings("PHPMD") From 677bb25cdbdfae64bf683ff38691a727b36dd26c Mon Sep 17 00:00:00 2001 From: jj2bw <226866852+jj2bw@users.noreply.github.com> Date: Fri, 22 Aug 2025 21:08:49 +0200 Subject: [PATCH 6/7] chore: autofix php-cs-fixer --- src/Entity/Member.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Entity/Member.php b/src/Entity/Member.php index 710a46cdb..d0dcd5b6c 100644 --- a/src/Entity/Member.php +++ b/src/Entity/Member.php @@ -20,12 +20,12 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\PostLoadEventArgs; use Doctrine\ORM\Mapping as ORM; +use Exception; use Scheb\TwoFactorBundle\Model\BackupCodeInterface; use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface as GoogleTwoFactorInterface; use Scheb\TwoFactorBundle\Model\Totp\TotpConfiguration; use Scheb\TwoFactorBundle\Model\Totp\TotpConfigurationInterface; use Scheb\TwoFactorBundle\Model\Totp\TwoFactorInterface as TotpTwoFactorInterface; -use Exception; use Serializable; use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; @@ -39,7 +39,7 @@ #[ORM\Table(name: 'members')] #[ORM\Entity(repositoryClass: MemberRepository::class)] #[ORM\HasLifecycleCallbacks] -class Member implements \Serializable, UserInterface, PasswordHasherAwareInterface, PasswordAuthenticatedUserInterface, GoogleTwoFactorInterface, TotpTwoFactorInterface, BackupCodeInterface +class Member implements Serializable, UserInterface, PasswordHasherAwareInterface, PasswordAuthenticatedUserInterface, GoogleTwoFactorInterface, TotpTwoFactorInterface, BackupCodeInterface { public const ROLE_ADMIN_ACCEPTER = 'ROLE_ADMIN_ACCEPTER'; public const ROLE_ADMIN_ADMIN = 'ROLE_ADMIN_ADMIN'; From f7d9b85a39023a048199999dc24cfcc64917793c Mon Sep 17 00:00:00 2001 From: jj2bw <226866852+jj2bw@users.noreply.github.com> Date: Fri, 22 Aug 2025 21:11:03 +0200 Subject: [PATCH 7/7] fix: domain s/com/org/ --- config/packages/scheb_2fa.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/packages/scheb_2fa.yaml b/config/packages/scheb_2fa.yaml index 0cfb903cc..660352a15 100644 --- a/config/packages/scheb_2fa.yaml +++ b/config/packages/scheb_2fa.yaml @@ -4,7 +4,7 @@ scheb_two_factor: enabled: true totp: enabled: true # If TOTP authentication should be enabled, default false - server_name: bewelcome.com # Server name used in QR code + server_name: bewelcome.org # Server name used in QR code issuer: BeWelcome # Issuer name used in QR code leeway: 60 # Acceptable time drift in seconds, must be less or equal than the TOTP period parameters: # Additional parameters added in the QR code @@ -13,7 +13,7 @@ scheb_two_factor: template: security/2fa_form.html.twig google: enabled: true - server_name: bewelcome.com # Server name used in QR code + server_name: bewelcome.org # Server name used in QR code issuer: BeWelcome # Issuer name used in QR code leeway: 60 # Acceptable time drift in seconds, must be less or equal than the TOTP period template: security/2fa_form.html.twig