diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 906bf00..c1df357 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -10,6 +10,7 @@ security: security: false main: lazy: true + stateless: false provider: user_dto_provider custom_authenticators: - App\Security\NostrAuthenticator diff --git a/config/routes/web_profiler.yaml b/config/routes/web_profiler.yaml index c82beff..82fe7ea 100644 --- a/config/routes/web_profiler.yaml +++ b/config/routes/web_profiler.yaml @@ -1,7 +1,8 @@ -web_profiler_wdt: - resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' - prefix: /_wdt +when@local: + web_profiler_wdt: + resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' + prefix: /_wdt -web_profiler_profiler: - resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' - prefix: /_profiler + web_profiler_profiler: + resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' + prefix: /_profiler diff --git a/src/Controller/LoginController.php b/src/Controller/LoginController.php index 9f47227..5c2e7de 100644 --- a/src/Controller/LoginController.php +++ b/src/Controller/LoginController.php @@ -17,9 +17,13 @@ class LoginController extends AbstractController public function index(#[CurrentUser] ?User $user): Response { if (null !== $user) { - return new JsonResponse('Authentication Successful', 200); + return new JsonResponse([ + 'message' => 'Authentication Successful', + ], 200); } - return new JsonResponse('Unauthenticated', 401); + return new JsonResponse([ + 'message' => 'Unauthenticated', + ], 401); } } diff --git a/src/Entity/User.php b/src/Entity/User.php index 1f24b7d..64bbc8b 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -5,6 +5,7 @@ namespace App\Entity; use App\Repository\UserEntityRepository; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Security\Core\User\EquatableInterface; use Symfony\Component\Security\Core\User\UserInterface; /** @@ -12,7 +13,7 @@ use Symfony\Component\Security\Core\User\UserInterface; */ #[ORM\Entity(repositoryClass: UserEntityRepository::class)] #[ORM\Table(name: "app_user")] -class User implements UserInterface +class User implements UserInterface, EquatableInterface { #[ORM\Id] #[ORM\GeneratedValue] @@ -112,4 +113,9 @@ class User implements UserInterface { return $this->relays; } + + public function isEqualTo(UserInterface $user): bool + { + return $this->getUserIdentifier() === $user->getUserIdentifier(); + } } diff --git a/src/Security/NostrAuthenticator.php b/src/Security/NostrAuthenticator.php index b472228..550a488 100644 --- a/src/Security/NostrAuthenticator.php +++ b/src/Security/NostrAuthenticator.php @@ -5,6 +5,8 @@ namespace App\Security; use App\Entity\Event; use Mdanter\Ecc\Crypto\Signature\SchnorrSignature; use swentel\nostr\Key\Key; +use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -20,6 +22,9 @@ use Symfony\Component\Serializer\Serializer; class NostrAuthenticator extends AbstractAuthenticator implements InteractiveAuthenticatorInterface { + public function __construct( + private readonly Security $security + ) {} public function supports(Request $request): ?bool { @@ -59,7 +64,7 @@ class NostrAuthenticator extends AbstractAuthenticator implements InteractiveAut public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { - return null; + return new Response('Authentication Successful', 200); } public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response diff --git a/src/Security/UserDTOProvider.php b/src/Security/UserDTOProvider.php index 6b51855..b6357b1 100644 --- a/src/Security/UserDTOProvider.php +++ b/src/Security/UserDTOProvider.php @@ -6,6 +6,7 @@ use App\Entity\User; use App\Enum\KindsEnum; use App\Service\NostrClient; use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; use swentel\nostr\Key\Key; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; @@ -14,7 +15,8 @@ readonly class UserDTOProvider implements UserProviderInterface { public function __construct( private EntityManagerInterface $entityManager, - private NostrClient $nostrClient + private NostrClient $nostrClient, + private LoggerInterface $logger ) {} /** @@ -42,42 +44,54 @@ readonly class UserDTOProvider implements UserProviderInterface */ public function loadUserByIdentifier(string $identifier): UserInterface { - $user = $this->entityManager->getRepository(User::class)->findOneBy(['npub' => $identifier]); - $metadata = $relays = null; - - if (!$user) { - // user - $user = new User(); - $user->setNpub($identifier); - $this->entityManager->persist($user); - $this->entityManager->flush(); - } - try { $key = new Key(); $pubkey = $key->convertToHex($identifier); $data = $this->nostrClient->getLoginData($pubkey); + + $this->logger->info('Load user by identifier.', ['data' => $data]); + + $metadata = null; + $relays = null; + foreach ($data as $d) { $ev = $d->event; - if ($ev->kind === KindsEnum::METADATA) { + $this->logger->info('Load user by identifier event.', ['event' => $ev]); + if ($ev->kind === KindsEnum::METADATA->value) { $metadata = json_decode($ev->content); + $this->logger->info('Load user by identifier event.', ['metadata' => $metadata]); } - if ($ev->kind === KindsEnum::RELAY_LIST) { + if ($ev->kind === KindsEnum::RELAY_LIST->value) { $relays = $ev->tags; } } } catch (\Exception $e) { - // nothing to do here right now + $this->logger->error('Error getting user data.', ['exception' => $e]); + $metadata = null; + $relays = null; } + // Fallback metadata if none fetched if (is_null($metadata)) { - // if no metadata event, use what you have $metadata = new \stdClass(); - $metadata->name = substr($identifier, 0, 5) . ':' . substr($identifier, -5); + $metadata->name = substr($identifier, 0, 8) . '…' . substr($identifier, -4); } + + // Get or create user + $user = $this->entityManager->getRepository(User::class)->findOneBy(['npub' => $identifier]); + + if (!$user) { + $user = new User(); + $user->setNpub($identifier); + $this->entityManager->persist($user); + } + + // Update with fresh metadata/relays $user->setMetadata($metadata); $user->setRelays($relays); + $this->entityManager->flush(); + return $user; }