Browse Source

Security

imwald
Nuša Pukšič 8 months ago
parent
commit
9bb3c0ddb7
  1. 1
      config/packages/security.yaml
  2. 1
      config/routes/web_profiler.yaml
  3. 8
      src/Controller/LoginController.php
  4. 8
      src/Entity/User.php
  5. 7
      src/Security/NostrAuthenticator.php
  6. 48
      src/Security/UserDTOProvider.php

1
config/packages/security.yaml

@ -10,6 +10,7 @@ security:
security: false security: false
main: main:
lazy: true lazy: true
stateless: false
provider: user_dto_provider provider: user_dto_provider
custom_authenticators: custom_authenticators:
- App\Security\NostrAuthenticator - App\Security\NostrAuthenticator

1
config/routes/web_profiler.yaml

@ -1,3 +1,4 @@
when@local:
web_profiler_wdt: web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /_wdt prefix: /_wdt

8
src/Controller/LoginController.php

@ -17,9 +17,13 @@ class LoginController extends AbstractController
public function index(#[CurrentUser] ?User $user): Response public function index(#[CurrentUser] ?User $user): Response
{ {
if (null !== $user) { 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);
} }
} }

8
src/Entity/User.php

@ -5,6 +5,7 @@ namespace App\Entity;
use App\Repository\UserEntityRepository; use App\Repository\UserEntityRepository;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
/** /**
@ -12,7 +13,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
*/ */
#[ORM\Entity(repositoryClass: UserEntityRepository::class)] #[ORM\Entity(repositoryClass: UserEntityRepository::class)]
#[ORM\Table(name: "app_user")] #[ORM\Table(name: "app_user")]
class User implements UserInterface class User implements UserInterface, EquatableInterface
{ {
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue] #[ORM\GeneratedValue]
@ -112,4 +113,9 @@ class User implements UserInterface
{ {
return $this->relays; return $this->relays;
} }
public function isEqualTo(UserInterface $user): bool
{
return $this->getUserIdentifier() === $user->getUserIdentifier();
}
} }

7
src/Security/NostrAuthenticator.php

@ -5,6 +5,8 @@ namespace App\Security;
use App\Entity\Event; use App\Entity\Event;
use Mdanter\Ecc\Crypto\Signature\SchnorrSignature; use Mdanter\Ecc\Crypto\Signature\SchnorrSignature;
use swentel\nostr\Key\Key; use swentel\nostr\Key\Key;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
@ -20,6 +22,9 @@ use Symfony\Component\Serializer\Serializer;
class NostrAuthenticator extends AbstractAuthenticator implements InteractiveAuthenticatorInterface class NostrAuthenticator extends AbstractAuthenticator implements InteractiveAuthenticatorInterface
{ {
public function __construct(
private readonly Security $security
) {}
public function supports(Request $request): ?bool 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 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 public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response

48
src/Security/UserDTOProvider.php

@ -6,6 +6,7 @@ use App\Entity\User;
use App\Enum\KindsEnum; use App\Enum\KindsEnum;
use App\Service\NostrClient; use App\Service\NostrClient;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use swentel\nostr\Key\Key; use swentel\nostr\Key\Key;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\User\UserProviderInterface;
@ -14,7 +15,8 @@ readonly class UserDTOProvider implements UserProviderInterface
{ {
public function __construct( public function __construct(
private EntityManagerInterface $entityManager, 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 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 { try {
$key = new Key(); $key = new Key();
$pubkey = $key->convertToHex($identifier); $pubkey = $key->convertToHex($identifier);
$data = $this->nostrClient->getLoginData($pubkey); $data = $this->nostrClient->getLoginData($pubkey);
$this->logger->info('Load user by identifier.', ['data' => $data]);
$metadata = null;
$relays = null;
foreach ($data as $d) { foreach ($data as $d) {
$ev = $d->event; $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); $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; $relays = $ev->tags;
} }
} }
} catch (\Exception $e) { } 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 (is_null($metadata)) {
// if no metadata event, use what you have
$metadata = new \stdClass(); $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->setMetadata($metadata);
$user->setRelays($relays); $user->setRelays($relays);
$this->entityManager->flush();
return $user; return $user;
} }

Loading…
Cancel
Save