diff --git a/cp/app/Controllers/ProfileController.php b/cp/app/Controllers/ProfileController.php index acff656..fd5a2e1 100644 --- a/cp/app/Controllers/ProfileController.php +++ b/cp/app/Controllers/ProfileController.php @@ -26,6 +26,10 @@ class ProfileController extends Controller $username = $_SESSION['auth_username']; $email = $_SESSION['auth_email']; $status = $_SESSION['auth_status']; + $tfa = new \RobThree\Auth\TwoFactorAuth('Namingo'); + $secret = $tfa->createSecret(); + $qrcodeDataUri = $tfa->getQRCodeImageAsDataUri($email, $secret); + if ($status == 0) { $status = "Confirmed"; } else { @@ -36,53 +40,79 @@ class ProfileController extends Controller $role = "Admin"; } else { $role = "Unknown"; - } + } + + global $container; + $csrfName = $container->get('csrf')->getTokenName(); + $csrfValue = $container->get('csrf')->getTokenValue(); - return view($response,'admin/profile/profile.twig',['email' => $email, 'username' => $username, 'status' => $status, 'role' => $role]); + return view($response,'admin/profile/profile.twig',['email' => $email, 'username' => $username, 'status' => $status, 'role' => $role, 'qrcodeDataUri' => $qrcodeDataUri, 'secret' => $secret, 'csrf_name' => $csrfName, 'csrf_value' => $csrfValue]); } public function getRegistrationChallenge(Request $request, Response $response) { - $username = $_SESSION['auth_username']; + $userName = $_SESSION['auth_username']; $userEmail = $_SESSION['auth_email']; + $userId = $_SESSION['auth_user_id']; + + // Convert the user ID to a hexadecimal string + $userIdHex = dechex($userId); + // Pad with a leading zero if the length is odd + if (strlen($userIdHex) % 2 !== 0) { + $userIdHex = '0' . $userIdHex; + } - $challenge = $this->webAuthn->prepareChallengeForRegistration($username, $userEmail); - $_SESSION['webauthn_challenge'] = $challenge; // Store the challenge in the session + // Convert the padded hexadecimal string to binary, then encode in Base64 + $userIdBin = hex2bin($userIdHex); + $userIdBase64 = base64_encode($userIdBin); + + // Generate the create arguments using the WebAuthn library + $createArgs = $this->webAuthn->getCreateArgs($userIdBase64, $userName, $userEmail, 60 * 4, 0, 'required', null); - $response->getBody()->write(json_encode($challenge)); + // Encode the challenge in Base64 + $base64Challenge = base64_encode($this->webAuthn->getChallenge()); + + // Set the challenge and user ID in the createArgs object + $createArgs->publicKey->challenge = $base64Challenge; + $createArgs->publicKey->user->id = $userIdBase64; + + // Store the challenge in the session + $_SESSION['webauthn_challenge'] = $base64Challenge; + + // Send the modified $createArgs to the client + $response->getBody()->write(json_encode($createArgs)); return $response->withHeader('Content-Type', 'application/json'); } public function verifyRegistration(Request $request, Response $response) { - $data = json_decode($request->getBody()->getContents(), true); + $data = json_decode($request->getBody()->getContents()); try { - $credential = $this->webAuthn->processCreate($data, $_SESSION['webauthn_challenge']); - unset($_SESSION['webauthn_challenge']); - - $db = $this->container->get('db'); - - try { - $db->insert( - 'users_webauthn', - [ - 'user_id' => $_SESSION['auth_user_id'], - 'credential_id' => $credential->getCredentialId(), // Binary data - 'public_key' => $credential->getPublicKey(), // Text data - 'attestation_object' => $credential->getAttestationObject(), // Binary data - 'sign_count' => $credential->getSignCount() // Integer - ] - ); - } catch (IntegrityConstraintViolationException $e) { - // Handle the case where the insert operation violates a constraint - // For example, a duplicate credential_id - throw new \Exception('Could not store WebAuthn credentials: ' . $e->getMessage()); - } catch (Error $e) { - // Handle other database errors - throw new \Exception('Database error: ' . $e->getMessage()); - } + // Decode the incoming data + $clientDataJSON = base64_decode($data->response->clientDataJSON); + $attestationObject = base64_decode($data->response->attestationObject); + // Retrieve the challenge from the session + $challenge = $_SESSION['webauthn_challenge']; + + // Process the WebAuthn response + $credential = $this->webAuthn->processCreate($clientDataJSON, $attestationObject, $challenge, true, true, false); + + // Store the credential data in the database + $db = $this->container->get('db'); + $db->insert( + 'users_webauthn', + [ + 'user_id' => $_SESSION['auth_user_id'], + 'credential_id' => base64_encode($credential->credentialId), // Binary data encoded in Base64 + 'public_key' => $credential->publicKey, // Text data + 'attestation_object' => base64_encode($credential->attestationObject), // Binary data encoded in Base64 + 'sign_count' => $credential->signCount // Integer + ] + ); + + // Send success response $response->getBody()->write(json_encode(['success' => true])); return $response->withHeader('Content-Type', 'application/json'); } catch (\Exception $e) { diff --git a/cp/composer.json b/cp/composer.json index 929e752..b91604f 100644 --- a/cp/composer.json +++ b/cp/composer.json @@ -37,7 +37,8 @@ "league/iso3166": "^4.3", "stripe/stripe-php": "^13.3", "robthree/twofactorauth": "^2.1", - "lbuchs/webauthn": "^2.1" + "lbuchs/webauthn": "^2.1", + "bacon/bacon-qr-code": "^2.0" }, "autoload": { "psr-4": { diff --git a/cp/resources/views/admin/profile/profile.twig b/cp/resources/views/admin/profile/profile.twig index 56fae37..8c1de6d 100644 --- a/cp/resources/views/admin/profile/profile.twig +++ b/cp/resources/views/admin/profile/profile.twig @@ -101,12 +101,23 @@
Set up 2FA for additional security. Scan the QR code with your authentication app and enter the provided code below to verify.
Manual Entry Secret
+{{ secret|split(4)|join(' ') }}
+
+ If you're unable to scan the QR code, enter this secret manually into your authentication app. The secret is case-sensitive and should be entered exactly as shown.
+