Initial commit
This commit is contained in:
204
lib/Controllers/EnrollmentController.php
Normal file
204
lib/Controllers/EnrollmentController.php
Normal file
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KTXM\AuthenticationProviderTotp\Controllers;
|
||||
|
||||
use KTXC\Http\Response\JsonResponse;
|
||||
use KTXC\SessionIdentity;
|
||||
use KTXF\Controller\ControllerAbstract;
|
||||
use KTXF\Routing\Attributes\AuthenticatedRoute;
|
||||
use KTXM\AuthenticationProviderTotp\Services\EnrollmentService;
|
||||
|
||||
/**
|
||||
* TOTP Enrollment Controller
|
||||
*
|
||||
* Handles TOTP device enrollment for users:
|
||||
* - Get enrollment status
|
||||
* - Begin enrollment (generate QR code)
|
||||
* - Complete enrollment (verify code)
|
||||
* - Remove enrollment
|
||||
* - Regenerate recovery codes
|
||||
*/
|
||||
class EnrollmentController extends ControllerAbstract
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SessionIdentity $userIdentity,
|
||||
private readonly EnrollmentService $enrollmentService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get TOTP enrollment status for the current user
|
||||
*/
|
||||
#[AuthenticatedRoute('/totp/status', name: 'totp.status', methods: ['GET'])]
|
||||
public function status(): JsonResponse
|
||||
{
|
||||
$userId = $this->userIdentity->identifier();
|
||||
if (!$userId) {
|
||||
return new JsonResponse(
|
||||
['error' => 'User not authenticated', 'error_code' => 'unauthorized'],
|
||||
JsonResponse::HTTP_UNAUTHORIZED
|
||||
);
|
||||
}
|
||||
|
||||
$isEnrolled = $this->enrollmentService->isEnrolled($userId);
|
||||
|
||||
return new JsonResponse([
|
||||
'enrolled' => $isEnrolled,
|
||||
'provider' => 'totp',
|
||||
'label' => 'TOTP Code',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin TOTP enrollment - generates secret and QR code
|
||||
*/
|
||||
#[AuthenticatedRoute('/totp/enroll', name: 'totp.enroll.begin', methods: ['POST'])]
|
||||
public function beginEnrollment(string $accountName = ''): JsonResponse
|
||||
{
|
||||
$userId = $this->userIdentity->identifier();
|
||||
if (!$userId) {
|
||||
return new JsonResponse(
|
||||
['error' => 'User not authenticated', 'error_code' => 'unauthorized'],
|
||||
JsonResponse::HTTP_UNAUTHORIZED
|
||||
);
|
||||
}
|
||||
|
||||
$config = [];
|
||||
if (!empty($accountName)) {
|
||||
$config['account_name'] = $accountName;
|
||||
}
|
||||
|
||||
$result = $this->enrollmentService->beginEnrollment($userId, $config);
|
||||
|
||||
if (!$result['success']) {
|
||||
return new JsonResponse(
|
||||
['error' => $result['error'], 'error_code' => $result['error_code']],
|
||||
JsonResponse::HTTP_BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
$enrollment = $result['enrollment'] ?? [];
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'pending',
|
||||
'secret' => $enrollment['secret'] ?? '',
|
||||
'provisioning_uri' => $enrollment['provisioning_uri'] ?? '',
|
||||
'qr_code' => $enrollment['qr_code'] ?? '',
|
||||
'recovery_codes' => $enrollment['recovery_codes'] ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete TOTP enrollment - verify the initial code
|
||||
*/
|
||||
#[AuthenticatedRoute('/totp/enroll/verify', name: 'totp.enroll.verify', methods: ['POST'])]
|
||||
public function completeEnrollment(string $code): JsonResponse
|
||||
{
|
||||
$userId = $this->userIdentity->identifier();
|
||||
if (!$userId) {
|
||||
return new JsonResponse(
|
||||
['error' => 'User not authenticated', 'error_code' => 'unauthorized'],
|
||||
JsonResponse::HTTP_UNAUTHORIZED
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($code)) {
|
||||
return new JsonResponse(
|
||||
['error' => 'Verification code is required', 'error_code' => 'invalid_request'],
|
||||
JsonResponse::HTTP_BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
$result = $this->enrollmentService->completeEnrollment($userId, $code);
|
||||
|
||||
if (!$result['success']) {
|
||||
return new JsonResponse(
|
||||
['error' => $result['error'], 'error_code' => $result['error_code']],
|
||||
JsonResponse::HTTP_BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'enrolled',
|
||||
'message' => 'TOTP successfully enabled for your account',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove TOTP enrollment (disable 2FA)
|
||||
*/
|
||||
#[AuthenticatedRoute('/totp/enroll', name: 'totp.enroll.remove', methods: ['DELETE'])]
|
||||
public function removeEnrollment(string $code): JsonResponse
|
||||
{
|
||||
$userId = $this->userIdentity->identifier();
|
||||
if (!$userId) {
|
||||
return new JsonResponse(
|
||||
['error' => 'User not authenticated', 'error_code' => 'unauthorized'],
|
||||
JsonResponse::HTTP_UNAUTHORIZED
|
||||
);
|
||||
}
|
||||
|
||||
// Verify current code before removing
|
||||
$verifyResult = $this->enrollmentService->verifyCode($userId, $code);
|
||||
if (!$verifyResult['success']) {
|
||||
return new JsonResponse(
|
||||
['error' => 'Invalid verification code', 'error_code' => 'invalid_code'],
|
||||
JsonResponse::HTTP_BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
$result = $this->enrollmentService->removeEnrollment($userId);
|
||||
|
||||
if (!$result['success']) {
|
||||
return new JsonResponse(
|
||||
['error' => $result['error'], 'error_code' => $result['error_code']],
|
||||
JsonResponse::HTTP_BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'removed',
|
||||
'message' => 'TOTP has been disabled for your account',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin endpoint: Get TOTP enrollment status for any user
|
||||
*/
|
||||
#[AuthenticatedRoute('/admin/status/{uid}', name: 'totp.admin.status', methods: ['GET'])]
|
||||
public function adminStatus(string $uid): JsonResponse
|
||||
{
|
||||
// TODO: Add permission check for admin operations
|
||||
|
||||
$isEnrolled = $this->enrollmentService->isEnrolled($uid);
|
||||
|
||||
return new JsonResponse([
|
||||
'enrolled' => $isEnrolled,
|
||||
'provider' => 'totp',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin endpoint: Remove TOTP enrollment for any user (no code verification required)
|
||||
*/
|
||||
#[AuthenticatedRoute('/admin/enrollment/{uid}', name: 'totp.admin.remove', methods: ['DELETE'])]
|
||||
public function adminRemoveEnrollment(string $uid): JsonResponse
|
||||
{
|
||||
// TODO: Add permission check for admin operations
|
||||
|
||||
$result = $this->enrollmentService->removeEnrollment($uid);
|
||||
|
||||
if (!$result['success']) {
|
||||
return new JsonResponse(
|
||||
['error' => $result['error'], 'error_code' => $result['error_code']],
|
||||
JsonResponse::HTTP_BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'removed',
|
||||
'message' => 'TOTP enrollment removed successfully',
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user