- Added support for authenticating via access tokens
- update symfony to 6.4.* - some other minor stuff
This commit is contained in:
parent
4a1c6e1a72
commit
7c45f64a73
@ -8,42 +8,44 @@
|
|||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"ext-intl": "*",
|
"ext-intl": "*",
|
||||||
|
"doctrine/annotations": "^2.0",
|
||||||
"doctrine/doctrine-bundle": "^2.10",
|
"doctrine/doctrine-bundle": "^2.10",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.2",
|
"doctrine/doctrine-migrations-bundle": "^3.2",
|
||||||
"doctrine/orm": "^2.16",
|
"doctrine/orm": "^2.16",
|
||||||
"gedmo/doctrine-extensions": "^3.13",
|
"gedmo/doctrine-extensions": "^3.13",
|
||||||
"phpdocumentor/reflection-docblock": "^5.3",
|
"phpdocumentor/reflection-docblock": "^5.3",
|
||||||
"phpstan/phpdoc-parser": "^1.24",
|
"phpstan/phpdoc-parser": "^1.24",
|
||||||
"symfony/asset": "6.3.*",
|
"symfony/asset": "6.4.*",
|
||||||
"symfony/console": "6.3.*",
|
"symfony/console": "6.4.*",
|
||||||
"symfony/doctrine-messenger": "6.3.*",
|
"symfony/doctrine-messenger": "6.4.*",
|
||||||
"symfony/dotenv": "6.3.*",
|
"symfony/dotenv": "6.4.*",
|
||||||
"symfony/expression-language": "6.3.*",
|
"symfony/expression-language": "6.4.*",
|
||||||
"symfony/flex": "^2",
|
"symfony/flex": "^2",
|
||||||
"symfony/form": "6.3.*",
|
"symfony/form": "6.4.*",
|
||||||
"symfony/framework-bundle": "6.3.*",
|
"symfony/framework-bundle": "6.4.*",
|
||||||
"symfony/http-client": "6.3.*",
|
"symfony/http-client": "6.4.*",
|
||||||
"symfony/intl": "6.3.*",
|
"symfony/intl": "6.4.*",
|
||||||
"symfony/lock": "6.3.*",
|
"symfony/lock": "6.4.*",
|
||||||
"symfony/mailer": "6.3.*",
|
"symfony/mailer": "6.4.*",
|
||||||
"symfony/mime": "6.3.*",
|
"symfony/mime": "6.4.*",
|
||||||
"symfony/monolog-bundle": "^3.0",
|
"symfony/monolog-bundle": "^3.0",
|
||||||
"symfony/notifier": "6.3.*",
|
"symfony/notifier": "6.4.*",
|
||||||
"symfony/process": "6.3.*",
|
"symfony/process": "6.4.*",
|
||||||
"symfony/property-access": "6.3.*",
|
"symfony/property-access": "6.4.*",
|
||||||
"symfony/property-info": "6.3.*",
|
"symfony/property-info": "6.4.*",
|
||||||
"symfony/rate-limiter": "6.3.*",
|
"symfony/rate-limiter": "6.4.*",
|
||||||
"symfony/runtime": "6.3.*",
|
"symfony/runtime": "6.4.*",
|
||||||
"symfony/security-bundle": "6.3.*",
|
"symfony/security-bundle": "6.4.*",
|
||||||
"symfony/serializer": "6.3.*",
|
"symfony/serializer": "6.4.*",
|
||||||
"symfony/stimulus-bundle": "^2.11",
|
"symfony/stimulus-bundle": "^2.11",
|
||||||
"symfony/string": "6.3.*",
|
"symfony/string": "6.4.*",
|
||||||
"symfony/translation": "6.3.*",
|
"symfony/translation": "6.4.*",
|
||||||
"symfony/twig-bundle": "6.3.*",
|
"symfony/twig-bundle": "6.4.*",
|
||||||
"symfony/validator": "6.3.*",
|
"symfony/uid": "6.4.*",
|
||||||
"symfony/web-link": "6.3.*",
|
"symfony/validator": "6.4.*",
|
||||||
|
"symfony/web-link": "6.4.*",
|
||||||
"symfony/webpack-encore-bundle": "^2.0",
|
"symfony/webpack-encore-bundle": "^2.0",
|
||||||
"symfony/yaml": "6.3.*",
|
"symfony/yaml": "6.4.*",
|
||||||
"twig/extra-bundle": "^2.12|^3.0",
|
"twig/extra-bundle": "^2.12|^3.0",
|
||||||
"twig/twig": "^2.12|^3.0"
|
"twig/twig": "^2.12|^3.0"
|
||||||
},
|
},
|
||||||
@ -92,17 +94,17 @@
|
|||||||
"extra": {
|
"extra": {
|
||||||
"symfony": {
|
"symfony": {
|
||||||
"allow-contrib": false,
|
"allow-contrib": false,
|
||||||
"require": "6.3.*"
|
"require": "6.4.*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^9.5",
|
"phpunit/phpunit": "^9.5",
|
||||||
"symfony/browser-kit": "6.3.*",
|
"symfony/browser-kit": "6.4.*",
|
||||||
"symfony/css-selector": "6.3.*",
|
"symfony/css-selector": "6.4.*",
|
||||||
"symfony/debug-bundle": "6.3.*",
|
"symfony/debug-bundle": "6.4.*",
|
||||||
"symfony/maker-bundle": "^1.0",
|
"symfony/maker-bundle": "^1.0",
|
||||||
"symfony/phpunit-bridge": "^6.3",
|
"symfony/phpunit-bridge": "^6.3",
|
||||||
"symfony/stopwatch": "6.3.*",
|
"symfony/stopwatch": "6.4.*",
|
||||||
"symfony/web-profiler-bundle": "6.3.*"
|
"symfony/web-profiler-bundle": "6.4.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2419
composer.lock
generated
2419
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -20,6 +20,7 @@ services:
|
|||||||
$globalFactory: '@limiter.ip_login'
|
$globalFactory: '@limiter.ip_login'
|
||||||
# localFactory is the limiter for username+IP
|
# localFactory is the limiter for username+IP
|
||||||
$localFactory: '@limiter.username_ip_login'
|
$localFactory: '@limiter.username_ip_login'
|
||||||
|
$secret: '%env(APP_SECRET)%'
|
||||||
|
|
||||||
security:
|
security:
|
||||||
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||||
@ -36,6 +37,14 @@ security:
|
|||||||
dev:
|
dev:
|
||||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||||
security: false
|
security: false
|
||||||
|
api:
|
||||||
|
pattern: ^/api/
|
||||||
|
provider: app_user_provider
|
||||||
|
access_denied_handler: App\Security\AccessTokenDeniedHandler
|
||||||
|
|
||||||
|
access_token:
|
||||||
|
token_handler: App\Security\AccessTokenHandler
|
||||||
|
failure_handler: App\Security\AccessTokenDeniedHandler
|
||||||
main:
|
main:
|
||||||
lazy: true
|
lazy: true
|
||||||
provider: app_user_provider
|
provider: app_user_provider
|
||||||
|
4
config/packages/uid.yaml
Normal file
4
config/packages/uid.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
framework:
|
||||||
|
uid:
|
||||||
|
default_uuid_version: 7
|
||||||
|
time_based_uuid_version: 7
|
@ -26,4 +26,9 @@ services:
|
|||||||
tags:
|
tags:
|
||||||
- { name: doctrine.event_subscriber, connection: default }
|
- { name: doctrine.event_subscriber, connection: default }
|
||||||
calls:
|
calls:
|
||||||
- [ setAnnotationReader, [ '@annotation_reader' ] ]
|
- [ setAnnotationReader, [ '@annotation_reader' ] ]
|
||||||
|
|
||||||
|
Symfony\Component\Uid\Command\GenerateUlidCommand: ~
|
||||||
|
Symfony\Component\Uid\Command\GenerateUuidCommand: ~
|
||||||
|
Symfony\Component\Uid\Command\InspectUlidCommand: ~
|
||||||
|
Symfony\Component\Uid\Command\InspectUuidCommand: ~
|
||||||
|
@ -4,7 +4,7 @@ services:
|
|||||||
###> doctrine/doctrine-bundle ###
|
###> doctrine/doctrine-bundle ###
|
||||||
database:
|
database:
|
||||||
ports:
|
ports:
|
||||||
- "5432"
|
- "5432:5432"
|
||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
###> symfony/mailer ###
|
###> symfony/mailer ###
|
||||||
|
@ -4,6 +4,7 @@ namespace App\Command;
|
|||||||
|
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Repository\UserRepository;
|
use App\Repository\UserRepository;
|
||||||
|
use App\Utils\UserValidator;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
@ -51,7 +52,7 @@ final class AddUserCommand extends Command
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly EntityManagerInterface $entityManager,
|
private readonly EntityManagerInterface $entityManager,
|
||||||
private readonly UserPasswordHasherInterface $passwordHasher,
|
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||||
private readonly ValidatorInterface $validator,
|
private readonly UserValidator $validator,
|
||||||
private readonly UserRepository $users
|
private readonly UserRepository $users
|
||||||
) {
|
) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
0
src/Controller/.gitignore
vendored
0
src/Controller/.gitignore
vendored
21
src/Controller/DomainApiController.php
Normal file
21
src/Controller/DomainApiController.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Repository\DomainRepository;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
|
class DomainApiController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/api/domains', name: 'app_api_get_domains', methods: ['get'])]
|
||||||
|
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
||||||
|
public function index(DomainRepository $domainRepository): JsonResponse
|
||||||
|
{
|
||||||
|
return new JsonResponse([
|
||||||
|
'domains' => $domainRepository->findAll()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
33
src/Controller/DomainCommentController.php
Normal file
33
src/Controller/DomainCommentController.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Domain;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||||
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
|
|
||||||
|
class DomainCommentController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/api/domain/{id}/comment', name: 'app_api_comment_add')]
|
||||||
|
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
||||||
|
public function addCommentAPI(ValidatorInterface $validator)
|
||||||
|
{
|
||||||
|
$validator->validate([
|
||||||
|
'' => new NotBlank()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/domain/{id}/comment', name: 'app_domain_comments')]
|
||||||
|
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
||||||
|
public function index(Domain $domain): Response
|
||||||
|
{
|
||||||
|
return $this->render('domain_comment/index.html.twig', [
|
||||||
|
'domain' => $domain,
|
||||||
|
'controller_name' => 'DomainCommentController',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -14,9 +14,14 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
|||||||
use Symfony\Component\String\ByteString;
|
use Symfony\Component\String\ByteString;
|
||||||
|
|
||||||
#[Route('/domain')]
|
#[Route('/domain')]
|
||||||
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
|
||||||
class DomainController extends AbstractController
|
class DomainController extends AbstractController
|
||||||
{
|
{
|
||||||
|
#[Route('/', name: 'app_api_domain_list', methods: ['GET'])]
|
||||||
|
public function listCommentsAPI()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#[Route('/', name: 'app_domain_index', methods: ['GET'])]
|
#[Route('/', name: 'app_domain_index', methods: ['GET'])]
|
||||||
public function index(DomainRepository $domainRepository): Response
|
public function index(DomainRepository $domainRepository): Response
|
||||||
{
|
{
|
||||||
|
122
src/Entity/UserAccessToken.php
Normal file
122
src/Entity/UserAccessToken.php
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\UserAccessTokenRepository;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Bridge\Doctrine\Types\UlidType;
|
||||||
|
use Symfony\Component\Uid\Ulid;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: UserAccessTokenRepository::class)]
|
||||||
|
class UserAccessToken
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
||||||
|
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
||||||
|
#[ORM\CustomIdGenerator(class: 'doctrine.ulid_generator')]
|
||||||
|
private ?string $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255)]
|
||||||
|
private ?string $token = null;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?\DateTimeImmutable $createdAt = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
||||||
|
private ?\DateTimeInterface $updatedAt = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
||||||
|
private ?\DateTimeInterface $deletedAt = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private ?User $owner = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
||||||
|
private ?\DateTimeInterface $expiresAt = null;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setId(Ulid $id): static
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getToken(): ?string
|
||||||
|
{
|
||||||
|
return $this->token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setToken(string $token): static
|
||||||
|
{
|
||||||
|
$this->token = $token;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): ?\DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedAt(\DateTimeImmutable $createdAt): static
|
||||||
|
{
|
||||||
|
$this->createdAt = $createdAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdatedAt(): ?\DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUpdatedAt(?\DateTimeInterface $updatedAt): static
|
||||||
|
{
|
||||||
|
$this->updatedAt = $updatedAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDeletedAt(): ?\DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->deletedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDeletedAt(?\DateTimeInterface $deletedAt): static
|
||||||
|
{
|
||||||
|
$this->deletedAt = $deletedAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOwner(): ?User
|
||||||
|
{
|
||||||
|
return $this->owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOwner(?User $owner): static
|
||||||
|
{
|
||||||
|
$this->owner = $owner;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExpiresAt(): ?\DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setExpiresAt(?\DateTimeInterface $expiresAt): static
|
||||||
|
{
|
||||||
|
$this->expiresAt = $expiresAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
32
src/EventListener/ApiAuthListener.php
Normal file
32
src/EventListener/ApiAuthListener.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\EventListener;
|
||||||
|
|
||||||
|
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\CredentialsExpiredException;
|
||||||
|
|
||||||
|
final class ApiAuthListener
|
||||||
|
{
|
||||||
|
#[AsEventListener(event: KernelEvents::EXCEPTION, priority: 2)]
|
||||||
|
public function onKernelException(ExceptionEvent $event): void
|
||||||
|
{
|
||||||
|
$exception = $event->getThrowable();
|
||||||
|
if (!$exception instanceof BadCredentialsException && !$exception instanceof CredentialsExpiredException) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... perform some action (e.g. logging)
|
||||||
|
|
||||||
|
if($event->getRequest()->isXmlHttpRequest()) {
|
||||||
|
$event->setResponse(new JsonResponse(['error' => $exception->getMessage()], 401));
|
||||||
|
}
|
||||||
|
// optionally set the custom response
|
||||||
|
|
||||||
|
// or stop propagation (prevents the next exception listeners from being called)
|
||||||
|
// $event->stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
48
src/Repository/UserAccessTokenRepository.php
Normal file
48
src/Repository/UserAccessTokenRepository.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\UserAccessToken;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<UserAccessTokenRepository>
|
||||||
|
*
|
||||||
|
* @method UserAccessTokenRepository|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method UserAccessTokenRepository|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method UserAccessTokenRepository[] findAll()
|
||||||
|
* @method UserAccessTokenRepository[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class UserAccessTokenRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, UserAccessToken::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @return UserAccessToken[] Returns an array of UserAccessToken objects
|
||||||
|
// */
|
||||||
|
// public function findByExampleField($value): array
|
||||||
|
// {
|
||||||
|
// return $this->createQueryBuilder('u')
|
||||||
|
// ->andWhere('u.exampleField = :val')
|
||||||
|
// ->setParameter('val', $value)
|
||||||
|
// ->orderBy('u.id', 'ASC')
|
||||||
|
// ->setMaxResults(10)
|
||||||
|
// ->getQuery()
|
||||||
|
// ->getResult()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public function findOneBySomeField($value): ?UserAccessToken
|
||||||
|
// {
|
||||||
|
// return $this->createQueryBuilder('u')
|
||||||
|
// ->andWhere('u.exampleField = :val')
|
||||||
|
// ->setParameter('val', $value)
|
||||||
|
// ->getQuery()
|
||||||
|
// ->getOneOrNullResult()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
}
|
27
src/Security/AccessTokenDeniedHandler.php
Normal file
27
src/Security/AccessTokenDeniedHandler.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Security;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||||
|
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
|
||||||
|
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
|
||||||
|
|
||||||
|
class AccessTokenDeniedHandler implements AccessDeniedHandlerInterface, AuthenticationFailureHandlerInterface
|
||||||
|
{
|
||||||
|
public function handle(Request $request, AccessDeniedException $accessDeniedException): ?Response
|
||||||
|
{
|
||||||
|
return new Response('AccessTokenDeniedHandler', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
|
||||||
|
{
|
||||||
|
return new JsonResponse([
|
||||||
|
'result' => 'error',
|
||||||
|
'error' => $exception->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
36
src/Security/AccessTokenHandler.php
Normal file
36
src/Security/AccessTokenHandler.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Security;
|
||||||
|
|
||||||
|
use App\Entity\UserAccessToken;
|
||||||
|
use App\Repository\UserAccessTokenRepository;
|
||||||
|
use App\Repository\UserRepository;
|
||||||
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\CredentialsExpiredException;
|
||||||
|
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
|
||||||
|
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
||||||
|
|
||||||
|
class AccessTokenHandler implements AccessTokenHandlerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private UserAccessTokenRepository $repository
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserBadgeFrom(string $accessToken): UserBadge
|
||||||
|
{
|
||||||
|
/** @var $token UserAccessToken|null */
|
||||||
|
if (!$accessToken || !$token = $this->repository->findOneBy(['token' => $accessToken])) {
|
||||||
|
throw new BadCredentialsException('Invalid credentials.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if($token->getDeletedAt() || ( $token->getExpiresAt() && $token->getExpiresAt() <= new \DateTime() ) ) {
|
||||||
|
throw new CredentialsExpiredException('Token expired.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// and return a UserBadge object containing the user identifier from the found token
|
||||||
|
return new UserBadge($token->getOwner()->getId(), function() use($token){
|
||||||
|
return $token->getOwner();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
12
symfony.lock
12
symfony.lock
@ -253,6 +253,18 @@
|
|||||||
"templates/base.html.twig"
|
"templates/base.html.twig"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/uid": {
|
||||||
|
"version": "6.4",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "6.2",
|
||||||
|
"ref": "d294ad4add3e15d7eb1bae0221588ca89b38e558"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/uid.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfony/validator": {
|
"symfony/validator": {
|
||||||
"version": "6.3",
|
"version": "6.3",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html data-bs-theme="dark">
|
<html data-bs-theme="dark" lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@ -28,10 +28,6 @@
|
|||||||
<i class="fas fa-tachometer-alt fa-fw me-3"></i>
|
<i class="fas fa-tachometer-alt fa-fw me-3"></i>
|
||||||
<span>Dashboard</span>
|
<span>Dashboard</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="#" class="list-group-item list-group-item-action py-2 ripple">
|
|
||||||
<i class="fas fa-comments fa-fw me-3"></i>
|
|
||||||
<span>Comments</span>
|
|
||||||
</a>
|
|
||||||
<a href="{{ path('app_domain_index') }}" class="list-group-item list-group-item-action py-2 ripple{{ app.request.pathInfo starts with path('app_domain_index') ? ' active' : ''}}">
|
<a href="{{ path('app_domain_index') }}" class="list-group-item list-group-item-action py-2 ripple{{ app.request.pathInfo starts with path('app_domain_index') ? ' active' : ''}}">
|
||||||
<i class="fas fa-globe fa-fw me-3"></i>
|
<i class="fas fa-globe fa-fw me-3"></i>
|
||||||
<span>Domains</span>
|
<span>Domains</span>
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
<td>{{ domain.updatedAt ? domain.updatedAt|date('Y-m-d H:i:s') : '' }}</td>
|
<td>{{ domain.updatedAt ? domain.updatedAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||||
<td>{{ domain.createdAt ? domain.createdAt|date('Y-m-d H:i:s') : '' }}</td>
|
<td>{{ domain.createdAt ? domain.createdAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||||
<td>
|
<td>
|
||||||
|
<a href="{{ path('app_domain_comments', {'id': domain.id}) }}">comments</a>
|
||||||
<a href="{{ path('app_domain_show', {'id': domain.id}) }}">show</a>
|
<a href="{{ path('app_domain_show', {'id': domain.id}) }}">show</a>
|
||||||
<a href="{{ path('app_domain_edit', {'id': domain.id}) }}">edit</a>
|
<a href="{{ path('app_domain_edit', {'id': domain.id}) }}">edit</a>
|
||||||
</td>
|
</td>
|
||||||
|
44
templates/domain_comment/index.html.twig
Normal file
44
templates/domain_comment/index.html.twig
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Comment List{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>{{ domain.domain }} Comment List</h1>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Domain</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Enabled</th>
|
||||||
|
<th>DefaultSortPolicy</th>
|
||||||
|
<th>UpdatedAt</th>
|
||||||
|
<th>CreatedAt</th>
|
||||||
|
<th>actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for comment in [] %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ domain.domain }}</td>
|
||||||
|
<td>{{ domain.name }}</td>
|
||||||
|
<td>{{ domain.enabled ? 'Yes' : 'No' }}</td>
|
||||||
|
<td>{{ domain.getDefaultSortPolicyName }}</td>
|
||||||
|
<td>{{ domain.updatedAt ? domain.updatedAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||||
|
<td>{{ domain.createdAt ? domain.createdAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ path('app_domain_comments', {'id': domain.id}) }}">comments</a>
|
||||||
|
<a href="{{ path('app_domain_show', {'id': domain.id}) }}">show</a>
|
||||||
|
<a href="{{ path('app_domain_edit', {'id': domain.id}) }}">edit</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="9">no records found</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<a href="{{ path('app_domain_new') }}">Create new</a>
|
||||||
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user