Add two-factor authentication support for users and subusers

- Extend `users_o` and `subusers_o` with `two_factor_enabled` and `two_factor_secret` properties.
- Implement methods for managing 2FA (`isTwoFactorEnabled`, `setTwoFactorSecret`, `verify_2fa_code`) in authentication logic.
- Add 2FA handling in login flows for both users and subusers, including token generation and validation.
- Introduce `totp` class for TOTP-based authentication, including QR code generation and code verification.
- Add test cases for 2FA functionality (`TwoFactorAuthTest.php`) and coverage for login scenarios with 2FA.
- Update OpenAPI specifications to include 2FA flows (`auth/2fa/setup`, `auth/2fa/enable`, `auth/2fa/verify`, `auth/2fa/disable`).
This commit is contained in:
Jeppe Bundgaard
2026-02-23 17:00:18 +01:00
parent 827fafd46b
commit f6b526f4ef
9 changed files with 899 additions and 21 deletions
+40
View File
@@ -42,6 +42,8 @@ class users_o extends db
public object_property $email_notifications_enabled;
public object_property $wash_certificate_email; // Optional
protected array $wash_subscription_transactions;
public object_property $two_factor_secret;
public object_property $two_factor_enabled;
public function structure(): void
@@ -102,6 +104,8 @@ class users_o extends db
$this->sms_notifications_enabled = new object_property($this->table, $this->id, 'sms_notifications_enabled', 'bool', false);
$this->email_notifications_enabled = new object_property($this->table, $this->id, 'email_notifications_enabled', 'bool', false);
$this->wash_certificate_email = new object_property($this->table, $this->id, 'wash_certificate_email', 'string', false);
$this->two_factor_secret = new object_property($this->table, $this->id, 'two_factor_secret', 'string', false);
$this->two_factor_enabled = new object_property($this->table, $this->id, 'two_factor_enabled', 'bool', false);
$this->renderLanguagePack();
}
@@ -198,6 +202,42 @@ class users_o extends db
return $this->wash_certificate_email->value();
}
/**
* @throws Exception
*/
public function isTwoFactorEnabled(): bool
{
self::requireSelected();
return (bool)$this->two_factor_enabled->value();
}
/**
* @throws Exception
*/
public function getTwoFactorSecret(): string|null
{
self::requireSelected();
return $this->two_factor_secret->value();
}
/**
* @throws Exception
*/
public function setTwoFactorSecret(string|null $secret): void
{
self::requireSelected();
$this->two_factor_secret->set($secret);
}
/**
* @throws Exception
*/
public function setTwoFactorEnabled(bool $enabled): void
{
self::requireSelected();
$this->two_factor_enabled->set($enabled);
}
public function add(string $customer_number, mixed $password, int $role = 0): void
{