Further preparation for 2FA and WebAuthn

This commit is contained in:
Pinga 2023-11-18 12:24:49 +02:00
parent 95e47cd9a6
commit e7ddc2e997
4 changed files with 87 additions and 2 deletions

View file

@ -27,5 +27,38 @@ class ProfileController extends Controller
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]);
} }
public function getRegistrationChallenge(Request $request, Response $response)
{
$user = $request->getAttribute('user'); // Assuming you have the user info
$username = $user->getUsername(); // Replace with your method to get the username
$userEmail = $user->getEmail(); // Replace with your method to get the user's email
$challenge = $this->webAuthn->prepareChallengeForRegistration($username, $userEmail);
$_SESSION['webauthn_challenge'] = $challenge; // Store the challenge in the session
$response->getBody()->write(json_encode($challenge));
return $response->withHeader('Content-Type', 'application/json');
}
public function verifyRegistration(Request $request, Response $response)
{
$data = json_decode($request->getBody()->getContents(), true);
try {
$credential = $this->webAuthn->processCreate($data, $_SESSION['webauthn_challenge']);
unset($_SESSION['webauthn_challenge']);
// Store the credential data in the database
// $user->addWebAuthnCredential($credential);
$response->getBody()->write(json_encode(['success' => true]));
return $response->withHeader('Content-Type', 'application/json');
} catch (\Exception $e) {
// Handle error, return an appropriate response
$response->getBody()->write(json_encode(['error' => $e->getMessage()]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
}
} }

View file

@ -35,7 +35,9 @@
"gettext/gettext": "^5.7", "gettext/gettext": "^5.7",
"punic/punic": "^3.8", "punic/punic": "^3.8",
"league/iso3166": "^4.3", "league/iso3166": "^4.3",
"stripe/stripe-php": "^13.3" "stripe/stripe-php": "^13.3",
"robthree/twofactorauth": "^2.1",
"lbuchs/webauthn": "^2.1"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View file

@ -119,7 +119,7 @@
<div class="card-body"> <div class="card-body">
<p>Secure your account with WebAuthn. Click the button below to register your device for passwordless sign-in.</p> <p>Secure your account with WebAuthn. Click the button below to register your device for passwordless sign-in.</p>
<!-- Connect WebAuthn Button --> <!-- Connect WebAuthn Button -->
<button type="button" class="btn btn-success">Connect WebAuthn Device</button> <button type="button" class="btn btn-success" id="connectWebAuthnButton">Connect WebAuthn Device</button>
<!-- WebAuthn Devices Table --> <!-- WebAuthn Devices Table -->
<div class="table-responsive mt-4"> <div class="table-responsive mt-4">
<table class="table table-striped"> <table class="table table-striped">
@ -156,4 +156,51 @@
</div> </div>
</footer> </footer>
</div> </div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const connectButton = document.getElementById('connectWebAuthnButton');
connectButton.addEventListener('click', function() {
// Step 1: Get the registration challenge from the server
fetch('/webauthn/register/challenge')
.then(response => response.json())
.then(data => {
// Step 2: Call WebAuthn API to create credentials
return navigator.credentials.create({
publicKey: data
});
})
.then(credentials => {
// Step 3: Send the credentials back to the server for verification
const publicKeyCredential = {
id: credentials.id,
rawId: btoa(String.fromCharCode.apply(null, new Uint8Array(credentials.rawId))),
type: credentials.type,
response: {
attestationObject: btoa(String.fromCharCode.apply(null, new Uint8Array(credentials.response.attestationObject))),
clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(credentials.response.clientDataJSON)))
}
};
return fetch('/webauthn/register/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(publicKeyCredential)
});
})
.then(response => {
if(response.ok) {
// Handle successful registration, e.g., update the UI
} else {
// Handle registration error
}
})
.catch(error => {
console.error('Error during WebAuthn registration:', error);
});
});
});
</script>
{% endblock %} {% endblock %}

View file

@ -83,6 +83,9 @@ $app->group('', function ($route) {
$route->get('/support/media', SupportController::class .':mediakit')->setName('mediakit'); $route->get('/support/media', SupportController::class .':mediakit')->setName('mediakit');
$route->get('/profile', ProfileController::class .':profile')->setName('profile'); $route->get('/profile', ProfileController::class .':profile')->setName('profile');
$route->get('/webauthn/register/challenge', ProfileController::class . ':getRegistrationChallenge')->setName('webauthn.register.challenge');
$route->post('/webauthn/register/verify', ProfileController::class . ':verifyRegistration')->setName('webauthn.register.verify');
$route->get('/mode', HomeController::class .':mode')->setName('mode'); $route->get('/mode', HomeController::class .':mode')->setName('mode');
$route->get('/lang', HomeController::class .':lang')->setName('lang'); $route->get('/lang', HomeController::class .':lang')->setName('lang');
$route->get('/avatar', HomeController::class .':avatar')->setName('avatar'); $route->get('/avatar', HomeController::class .':avatar')->setName('avatar');