Private
Public Access
1
0

new: usr: implement the 2FA authentication (TOTP and backup codes) #4

This commit is contained in:
2026-04-12 17:55:57 +02:00
parent 0144a3953c
commit fb8a54f687
23 changed files with 1603 additions and 266 deletions

View File

@@ -18,6 +18,10 @@ use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\Table;
use Scheb\TwoFactorBundle\Model\BackupCodeInterface;
use Scheb\TwoFactorBundle\Model\Totp\TotpConfiguration;
use Scheb\TwoFactorBundle\Model\Totp\TotpConfigurationInterface;
use Scheb\TwoFactorBundle\Model\Totp\TwoFactorInterface as TotpTwoFactorInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
@@ -36,7 +40,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
#[Entity(repositoryClass: UserRepository::class)]
#[UniqueEntity(fields: ['username'], message: 'This username is already taken.')]
#[UniqueEntity(fields: ['email'], message: 'This email address is already registered.')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
class User implements UserInterface, PasswordAuthenticatedUserInterface, TotpTwoFactorInterface, BackupCodeInterface
{
#[Id, GeneratedValue, Column]
private ?int $id = null;
@@ -65,6 +69,12 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?DateTime $resetTokenExpiresAt = null;
#[Column(length: 255, nullable: true)]
private ?string $totpSecret = null;
#[Column(type: Types::JSON, nullable: true)]
private ?array $backupCodes = [];
public function getId(): ?int
{
@@ -169,4 +179,58 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
$this->resetTokenExpiresAt = $resetTokenExpiresAt;
return $this;
}
// --- TotpTwoFactorInterface ---
public function isTotpAuthenticationEnabled(): bool
{
return null !== $this->totpSecret;
}
public function getTotpAuthenticationUsername(): string
{
return $this->getUserIdentifier();
}
public function getTotpAuthenticationConfiguration(): ?TotpConfigurationInterface
{
if (null === $this->totpSecret) {
return null;
}
return new TotpConfiguration($this->totpSecret, TotpConfiguration::ALGORITHM_SHA1, 30, 6);
}
public function getTotpSecret(): ?string
{
return $this->totpSecret;
}
public function setTotpSecret(?string $totpSecret): self
{
$this->totpSecret = $totpSecret;
return $this;
}
// --- BackupCodeInterface ---
public function isBackupCode(string $code): bool
{
return \in_array($code, $this->backupCodes ?? [], true);
}
public function invalidateBackupCode(string $code): void
{
$this->backupCodes = array_values(array_filter($this->backupCodes ?? [], fn($c) => $c !== $code));
}
public function getBackupCodes(): array
{
return $this->backupCodes ?? [];
}
public function setBackupCodes(array $backupCodes): self
{
$this->backupCodes = $backupCodes;
return $this;
}
}