* @category Class * @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License * @link www.splendidbear.org * @since 2026. 04. 09. */ #[Table(name: 'app_user')] #[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, TotpTwoFactorInterface, BackupCodeInterface { #[Id, GeneratedValue, Column] private ?int $id = null; #[Column(length: 180, unique: true)] private ?string $username = null; #[Column] private array $roles = []; #[Column(nullable: true)] private ?string $password = null; #[Column(length: 254, unique: true, nullable: true)] private ?string $email = null; #[Column] private bool $isVerified = false; #[Column(length: 64, nullable: true)] private ?string $verificationToken = null; #[Column(length: 64, nullable: true)] private ?string $resetToken = null; #[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 { return $this->id; } public function getUsername(): ?string { return $this->username; } public function setUsername(string $username): self { $this->username = $username; return $this; } public function getUserIdentifier(): string { return (string)$this->username; } public function getRoles(): array { $roles = $this->roles; $roles[] = 'ROLE_USER'; return array_unique($roles); } public function setRoles(array $roles): self { $this->roles = $roles; return $this; } public function getPassword(): ?string { return $this->password; } public function setPassword(?string $password): self { $this->password = $password; return $this; } public function eraseCredentials(): void { } public function getEmail(): ?string { return $this->email; } public function setEmail(?string $email): self { $this->email = $email; return $this; } public function isVerified(): bool { return $this->isVerified; } public function setIsVerified(bool $isVerified): self { $this->isVerified = $isVerified; return $this; } public function getVerificationToken(): ?string { return $this->verificationToken; } public function setVerificationToken(?string $verificationToken): self { $this->verificationToken = $verificationToken; return $this; } public function getResetToken(): ?string { return $this->resetToken; } public function setResetToken(?string $resetToken): self { $this->resetToken = $resetToken; return $this; } public function getResetTokenExpiresAt(): ?DateTime { return $this->resetTokenExpiresAt; } public function setResetTokenExpiresAt(?DateTime $resetTokenExpiresAt): self { $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; } }