chg: usr: add modern Webauthn authentication #4
This commit is contained in:
157
src/Service/WebAuthnService.php
Normal file
157
src/Service/WebAuthnService.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Entity\WebAuthnCredential;
|
||||
use App\Repository\WebAuthnCredentialRepository;
|
||||
use DateTime;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use JsonException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class WebAuthnService
|
||||
*
|
||||
* @package App\Service
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 12.
|
||||
*/
|
||||
readonly class WebAuthnService
|
||||
{
|
||||
public function __construct(
|
||||
private WebAuthnCredentialRepository $credentialRepository,
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function saveCredential(User $user, array $credentialData, string $name): WebAuthnCredential
|
||||
{
|
||||
$credential = new WebAuthnCredential();
|
||||
$credential->setUser($user);
|
||||
$credential->setCredentialData(json_encode($credentialData));
|
||||
$credential->setCredentialName($name);
|
||||
$credential->setBackupEligible($credentialData['isBackupEligible'] ?? false);
|
||||
$credential->setBackupAuthenticated($credentialData['isBackupAuthenticated'] ?? false);
|
||||
|
||||
$this->entityManager->persist($credential);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $credential;
|
||||
}
|
||||
|
||||
public function getCredentialsForUser(User $user): array
|
||||
{
|
||||
return $this->credentialRepository->findByUser($user);
|
||||
}
|
||||
|
||||
public function deleteCredential(int $id, User $user): bool
|
||||
{
|
||||
$credential = $this->credentialRepository->find($id);
|
||||
|
||||
if ($credential === null || $credential->getUser() !== $user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->entityManager->remove($credential);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function renameCredential(int $id, User $user, string $name): bool
|
||||
{
|
||||
$credential = $this->credentialRepository->find($id);
|
||||
|
||||
if ($credential === null || $credential->getUser() !== $user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$credential->setCredentialName($name);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getPublicKeyCredentialLoader(): null
|
||||
{
|
||||
/**
|
||||
* Return a simple object - the actual WebAuthn validation
|
||||
* would be done on the client side for now
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getAllCredentialSources(User $user): array
|
||||
{
|
||||
$credentials = $this->credentialRepository->findByUser($user);
|
||||
$sources = [];
|
||||
|
||||
foreach ($credentials as $credential) {
|
||||
$data = $credential->getCredentialData();
|
||||
|
||||
if ($data === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$sources[] = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to decode credential data for credential ID $credential->getId(): $e->getMessage()",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $sources;
|
||||
}
|
||||
|
||||
public function updateLastUsedAt(string $credentialId, User $user): void
|
||||
{
|
||||
$credentials = $this->credentialRepository->findByUser($user);
|
||||
|
||||
foreach ($credentials as $credential) {
|
||||
$data = json_decode($credential->getCredentialData() ?? '{}', true);
|
||||
|
||||
if (($data['id'] ?? null) !== $credentialId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$credential->setLastUsedAt(new DateTime());
|
||||
$this->entityManager->flush();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function findUserByCredentialId(string $credentialId): ?User
|
||||
{
|
||||
$allCredentials = $this->credentialRepository->findAll();
|
||||
|
||||
foreach ($allCredentials as $credential) {
|
||||
$data = json_decode($credential->getCredentialData() ?? '{}', true);
|
||||
|
||||
if (($data['id'] ?? null) !== $credentialId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $credential->getUser();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user