Skip to content

Commit 7fbff84

Browse files
committed
refactor: move 2FA enrollment orchestration into RecoveryCodeService
The transaction plus enable2FA + repository->add + code generation lived in UserApiController, breaking the thin-controllers/fat-services convention and diverging from the regenerateRecoveryCodes path, which already delegates to the service. UserApiController::enableTwoFactor now only validates input and calls RecoveryCodeService::enableTwoFactorAndGenerateCodes.
1 parent b2d84d1 commit 7fbff84

3 files changed

Lines changed: 38 additions & 34 deletions

File tree

app/Http/Controllers/Api/UserApiController.php

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
use App\Http\Controllers\APICRUDController;
1616
use App\Http\Controllers\Traits\RequestProcessor;
1717
use App\Http\Controllers\UserValidationRulesFactory;
18-
use App\libs\Auth\Models\TwoFactorAuditLog;
1918
use App\ModelSerializers\SerializerRegistry;
2019
use App\Services\Auth\IRecoveryCodeService;
21-
use App\Services\Auth\ITwoFactorAuditService;
2220
use Auth\Repositories\IUserRepository;
2321
use Auth\User;
2422
use Exception;
@@ -31,8 +29,6 @@
3129
use models\exceptions\ValidationException;
3230
use OAuth2\Services\ITokenService;
3331
use OpenId\Services\IUserService;
34-
use Utils\Db\ITransactionService;
35-
use Utils\IPHelper;
3632
use Utils\Services\ILogService;
3733

3834
/**
@@ -54,42 +50,26 @@ final class UserApiController extends APICRUDController
5450
*/
5551
private $recovery_code_service;
5652

57-
/**
58-
* @var ITransactionService
59-
*/
60-
private $tx_service;
61-
62-
/**
63-
* @var ITwoFactorAuditService
64-
*/
65-
private $two_factor_audit_service;
66-
6753
/**
6854
* UserApiController constructor.
6955
* @param IUserRepository $user_repository
7056
* @param ILogService $log_service
7157
* @param IUserService $user_service
7258
* @param ITokenService $token_service
7359
* @param IRecoveryCodeService $recovery_code_service
74-
* @param ITransactionService $tx_service
75-
* @param ITwoFactorAuditService $two_factor_audit_service
7660
*/
7761
public function __construct
7862
(
7963
IUserRepository $user_repository,
8064
ILogService $log_service,
8165
IUserService $user_service,
8266
ITokenService $token_service,
83-
IRecoveryCodeService $recovery_code_service,
84-
ITransactionService $tx_service,
85-
ITwoFactorAuditService $two_factor_audit_service
67+
IRecoveryCodeService $recovery_code_service
8668
)
8769
{
8870
parent::__construct($user_repository, $user_service, $log_service);
8971
$this->token_service = $token_service;
9072
$this->recovery_code_service = $recovery_code_service;
91-
$this->tx_service = $tx_service;
92-
$this->two_factor_audit_service = $two_factor_audit_service;
9373
}
9474

9575
/**
@@ -306,19 +286,7 @@ public function enableTwoFactor()
306286
return $this->error412(['method' => ['Two-factor authentication is already enabled. Use the regenerate recovery codes endpoint to rotate your codes.']]);
307287
}
308288

309-
$codes = $this->tx_service->transaction(function () use ($user, $method) {
310-
$user->enable2FA($method);
311-
$this->repository->add($user, false);
312-
313-
return $this->recovery_code_service->generateRecoveryCodes($user);
314-
});
315-
316-
$this->two_factor_audit_service->log(
317-
$user,
318-
TwoFactorAuditLog::EventEnrollmentChanged,
319-
$method,
320-
IPHelper::getUserIp()
321-
);
289+
$codes = $this->recovery_code_service->enableTwoFactorAndGenerateCodes($user, $method);
322290

323291
return $this->ok(['recovery_codes' => $codes]);
324292
});

app/Services/Auth/IRecoveryCodeService.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ public function regenerateRecoveryCodes(User $user, string $currentPassword): ar
4444
*/
4545
public function generateRecoveryCodes(User $user): array;
4646

47+
/**
48+
* Enrolls the user into the given 2FA method and generates the first batch
49+
* of recovery codes for them, without requiring password confirmation.
50+
* Intended for enrollment via an already-authenticated session.
51+
*
52+
* @param User $user
53+
* @param string $method
54+
* @return string[] plaintext codes formatted as XXXX-XXXX
55+
* @throws ValidationException if $method is not a valid/enabled 2FA method
56+
*/
57+
public function enableTwoFactorAndGenerateCodes(User $user, string $method): array;
58+
4759
/**
4860
* @param User $user
4961
* @return int count of unused recovery codes

app/Services/Auth/RecoveryCodeService.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use App\libs\Auth\Models\TwoFactorAuditLog;
1717
use App\libs\Auth\Models\UserRecoveryCode;
1818
use Auth\Repositories\IUserRecoveryCodeRepository;
19+
use Auth\Repositories\IUserRepository;
1920
use Auth\User;
2021
use Illuminate\Support\Facades\Hash;
2122
use Laminas\Math\Rand;
@@ -33,6 +34,7 @@ final class RecoveryCodeService implements IRecoveryCodeService
3334

3435
public function __construct(
3536
private readonly IUserRecoveryCodeRepository $repository,
37+
private readonly IUserRepository $user_repository,
3638
private readonly ITransactionService $tx_service,
3739
private readonly ITwoFactorAuditService $audit_service,
3840
) {
@@ -84,6 +86,28 @@ public function generateRecoveryCodes(User $user): array
8486
return array_map(static fn(string $code) => implode('-', str_split($code, 4)), $plaintext_codes);
8587
}
8688

89+
/**
90+
* @inheritDoc
91+
*/
92+
public function enableTwoFactorAndGenerateCodes(User $user, string $method): array
93+
{
94+
$codes = $this->tx_service->transaction(function () use ($user, $method) {
95+
$user->enable2FA($method);
96+
$this->user_repository->add($user, false);
97+
98+
return $this->generateRecoveryCodes($user);
99+
});
100+
101+
$this->audit_service->log(
102+
$user,
103+
TwoFactorAuditLog::EventEnrollmentChanged,
104+
$method,
105+
IPHelper::getUserIp()
106+
);
107+
108+
return $codes;
109+
}
110+
87111
/**
88112
* @inheritDoc
89113
*/

0 commit comments

Comments
 (0)