Private
Public Access
1
0

chg: usr: add modern Webauthn authentication #4

This commit is contained in:
2026-04-12 15:19:03 +02:00
parent acbe9c7f63
commit 0144a3953c
23 changed files with 2845 additions and 13 deletions

View 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;
}
}