progress
This commit is contained in:
parent
1311917325
commit
1f9209e215
4
.env
4
.env
|
@ -27,3 +27,7 @@ APP_SECRET=17902f251c557579ee832f20ed66776b
|
|||
DATABASE_URL="mysql://root:136431@127.0.0.1:3306/hsx?serverVersion=8&charset=utf8mb4"
|
||||
# DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=15&charset=utf8"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> nelmio/cors-bundle ###
|
||||
CORS_ALLOW_ORIGIN='*'
|
||||
###< nelmio/cors-bundle ###
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="App\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="App\Tests\" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/var" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/maker-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/inflector" />
|
||||
|
@ -31,6 +34,8 @@
|
|||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/stopwatch" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/laminas/laminas-code" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/friendsofphp/proxy-manager-lts" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfonycasts/verify-email-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/nelmio/cors-bundle" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
|
|
|
@ -61,9 +61,16 @@
|
|||
<path value="$PROJECT_DIR$/vendor/symfony/stopwatch" />
|
||||
<path value="$PROJECT_DIR$/vendor/laminas/laminas-code" />
|
||||
<path value="$PROJECT_DIR$/vendor/friendsofphp/proxy-manager-lts" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfonycasts/verify-email-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/nelmio/cors-bundle" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="8.1">
|
||||
<option name="suggestChangeDefaultLanguageLevel" value="false" />
|
||||
</component>
|
||||
<component name="PhpUnit">
|
||||
<phpunit_settings>
|
||||
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" />
|
||||
</phpunit_settings>
|
||||
</component>
|
||||
</project>
|
|
@ -10,13 +10,15 @@
|
|||
"doctrine/doctrine-bundle": "^2.8",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.2",
|
||||
"doctrine/orm": "^2.14",
|
||||
"nelmio/cors-bundle": "^2.2",
|
||||
"symfony/console": "6.2.*",
|
||||
"symfony/dotenv": "6.2.*",
|
||||
"symfony/flex": "^2",
|
||||
"symfony/framework-bundle": "6.2.*",
|
||||
"symfony/runtime": "6.2.*",
|
||||
"symfony/security-bundle": "6.2.*",
|
||||
"symfony/yaml": "6.2.*"
|
||||
"symfony/yaml": "6.2.*",
|
||||
"symfonycasts/verify-email-bundle": "^1.13"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
|
@ -61,7 +63,7 @@
|
|||
},
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"allow-contrib": true,
|
||||
"require": "6.2.*",
|
||||
"docker": true
|
||||
}
|
||||
|
|
112
composer.lock
generated
112
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "954f3c9bd813741915237122a725f96e",
|
||||
"content-hash": "b769efde9eff86f833f47b14e5c80c52",
|
||||
"packages": [
|
||||
{
|
||||
"name": "doctrine/cache",
|
||||
|
@ -1458,6 +1458,67 @@
|
|||
],
|
||||
"time": "2022-12-08T02:08:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nelmio/cors-bundle",
|
||||
"version": "2.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nelmio/NelmioCorsBundle.git",
|
||||
"reference": "0ee5ee30b0ee08ea122d431ae6e0ddeb87f035c0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/0ee5ee30b0ee08ea122d431ae6e0ddeb87f035c0",
|
||||
"reference": "0ee5ee30b0ee08ea122d431ae6e0ddeb87f035c0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"symfony/framework-bundle": "^4.3 || ^5.0 || ^6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.2",
|
||||
"symfony/phpunit-bridge": "^4.3 || ^5.0 || ^6.0"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nelmio\\CorsBundle\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nelmio",
|
||||
"homepage": "http://nelm.io"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application",
|
||||
"keywords": [
|
||||
"api",
|
||||
"cors",
|
||||
"crossdomain"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nelmio/NelmioCorsBundle/issues",
|
||||
"source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.2.0"
|
||||
},
|
||||
"time": "2021-12-01T09:34:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/cache",
|
||||
"version": "3.0.0",
|
||||
|
@ -4584,6 +4645,53 @@
|
|||
}
|
||||
],
|
||||
"time": "2022-12-14T16:11:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfonycasts/verify-email-bundle",
|
||||
"version": "v1.13.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/SymfonyCasts/verify-email-bundle.git",
|
||||
"reference": "eb7bc997f36ad872a0d56bf209fe37fed148b0a7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/SymfonyCasts/verify-email-bundle/zipball/eb7bc997f36ad872a0d56bf209fe37fed148b0a7",
|
||||
"reference": "eb7bc997f36ad872a0d56bf209fe37fed148b0a7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": ">=7.2.5",
|
||||
"symfony/config": "^5.4 | ^6.0",
|
||||
"symfony/dependency-injection": "^5.4 | ^6.0",
|
||||
"symfony/deprecation-contracts": "^2.2 | ^3.0",
|
||||
"symfony/http-kernel": "^5.4 | ^6.0",
|
||||
"symfony/routing": "^5.4 | ^6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/orm": "^2.7",
|
||||
"doctrine/persistence": "^2.0",
|
||||
"symfony/framework-bundle": "^5.4 | ^6.0",
|
||||
"symfony/phpunit-bridge": "^5.4 | ^6.0",
|
||||
"vimeo/psalm": "^4.3"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SymfonyCasts\\Bundle\\VerifyEmail\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Simple, stylish Email Verification for Symfony",
|
||||
"support": {
|
||||
"issues": "https://github.com/SymfonyCasts/verify-email-bundle/issues",
|
||||
"source": "https://github.com/SymfonyCasts/verify-email-bundle/tree/v1.13.0"
|
||||
},
|
||||
"time": "2023-01-04T12:46:15+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
|
@ -4748,5 +4856,5 @@
|
|||
"ext-iconv": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.2.0"
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
|
|
|
@ -6,4 +6,6 @@ return [
|
|||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle::class => ['all' => true],
|
||||
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
|
||||
];
|
||||
|
|
10
config/packages/nelmio_cors.yaml
Normal file
10
config/packages/nelmio_cors.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
nelmio_cors:
|
||||
defaults:
|
||||
origin_regex: true
|
||||
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
|
||||
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
|
||||
allow_headers: ['Content-Type', 'Authorization']
|
||||
expose_headers: ['Link']
|
||||
max_age: 3600
|
||||
paths:
|
||||
'^/': null
|
|
@ -16,7 +16,14 @@ security:
|
|||
main:
|
||||
lazy: true
|
||||
provider: app_user_provider
|
||||
json_login:
|
||||
# api_login is a route we will create below
|
||||
check_path: api_login
|
||||
username_path: email
|
||||
password_path: password
|
||||
|
||||
custom_authenticators:
|
||||
- App\Security\ApiKeyAuthenticator
|
||||
# activate different ways to authenticate
|
||||
# https://symfony.com/doc/current/security.html#the-firewall
|
||||
|
||||
|
@ -27,7 +34,7 @@ security:
|
|||
# Note: Only the *first* access control that matches will be used
|
||||
access_control:
|
||||
# - { path: ^/admin, roles: ROLE_ADMIN }
|
||||
# - { path: ^/profile, roles: ROLE_USER }
|
||||
- { path: ^/api/acc/*, roles: ROLE_USER }
|
||||
|
||||
when@test:
|
||||
security:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Kernel;
|
||||
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||
|
||||
return function (array $context) {
|
||||
|
|
|
@ -11,7 +11,13 @@ class GeneralController extends AbstractController
|
|||
#[Route('/', name: 'general_home')]
|
||||
public function general_home(): JsonResponse
|
||||
{
|
||||
phpinfo();
|
||||
return $this->json([
|
||||
'message' => 'Welcome to hesabix API.',
|
||||
]);
|
||||
}
|
||||
#[Route('api/acc/dd', name: 'acc_dd')]
|
||||
public function acc_dd(): JsonResponse
|
||||
{
|
||||
return $this->json([
|
||||
'message' => 'Welcome to hesabix API.',
|
||||
]);
|
||||
|
|
135
src/Controller/UserController.php
Normal file
135
src/Controller/UserController.php
Normal file
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use App\Entity\UserToken;
|
||||
use Exception;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
||||
use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||
use App\Entity\User;
|
||||
use App\Security\EmailVerifier;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher,
|
||||
Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken,
|
||||
Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
|
||||
|
||||
class UserController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* function to generate random strings
|
||||
* @param int $length number of characters in the generated string
|
||||
* @return string a new string is created with random characters of the desired length
|
||||
*/
|
||||
private function RandomString(int $length = 32): string
|
||||
{
|
||||
return substr(str_shuffle(str_repeat($x='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ceil($length/strlen($x)) )),1,$length);
|
||||
}
|
||||
|
||||
#[Route('/api/user/login', name: 'api_login')]
|
||||
public function api_login(#[CurrentUser] ?User $user,EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
if (null === $user) {
|
||||
return $this->json([
|
||||
'message' => 'missing credentials',
|
||||
], Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$tokenString = $this->RandomString(254); // somehow create an API token for $user
|
||||
$token = new UserToken();
|
||||
$token->setUser($user);
|
||||
$token->setToken($tokenString);
|
||||
$entityManager->persist($token);
|
||||
$entityManager->flush();
|
||||
return $this->json([
|
||||
'user' => $user->getUserIdentifier(),
|
||||
'token' => $tokenString,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/api/user/check/login', name: 'api_user_check_login')]
|
||||
public function api_user_check_login(#[CurrentUser] ?User $user,EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
if (null === $user) {
|
||||
return $this->json([
|
||||
'message' => 'missing credentials',
|
||||
], Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
return $this->json(
|
||||
['result'=>true]
|
||||
);
|
||||
}
|
||||
|
||||
#[Route('/api/user/current/info', name: 'api_user_current_info')]
|
||||
public function api_user_current_info(#[CurrentUser] ?User $user,EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
return $this->json([
|
||||
'email'=>$user->getEmail(),
|
||||
'fullname'=>$user->getFullName(),
|
||||
'businessCount'=>count($user->getBusinesses())
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
#[Route('/api/user/logout', name: 'api_user_logout')]
|
||||
public function api_user_logout(Security $security,EntityManagerInterface $entityManager,Request $request): Response
|
||||
{
|
||||
// logout the user in on the current firewall
|
||||
$security->logout(false);
|
||||
$apiToken = $request->headers->get('X-AUTH-TOKEN');
|
||||
|
||||
if (null == $apiToken) {
|
||||
// The token header was empty, authentication fails with HTTP Status
|
||||
// Code 401 "Unauthorized"
|
||||
throw new CustomUserMessageAuthenticationException('No API token provided');
|
||||
}
|
||||
$tk = $entityManager->getRepository(UserToken::class)->findByApiToken($apiToken);
|
||||
if (! $tk) {
|
||||
throw new UserNotFoundException();
|
||||
}
|
||||
$entityManager->getRepository(UserToken::class)->remove($tk,true);
|
||||
return $this->json(['result'=>true]);
|
||||
}
|
||||
|
||||
|
||||
#[Route('/api/user/update/info', name: 'api_user_update_info')]
|
||||
public function api_user_update_info(#[CurrentUser] ?User $user,EntityManagerInterface $entityManager,Request $request): Response
|
||||
{
|
||||
$pameters = [];
|
||||
if ($content = $request->getContent()) {
|
||||
$pameters = json_decode($content, true);
|
||||
}
|
||||
$user->setFullName($pameters['fullname']);
|
||||
$entityManager->persist($user);
|
||||
$entityManager->flush();
|
||||
return $this->json(['result'=>true]);
|
||||
}
|
||||
|
||||
|
||||
#[Route('/api/user/register', name: 'api_user_register')]
|
||||
public function api_user_register(Request $request, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
$user = new User();
|
||||
$user->setEmail('alizadeh.babak@gmail.com');
|
||||
$user->setRoles([]);
|
||||
$user->setPassword(
|
||||
$userPasswordHasher->hashPassword(
|
||||
$user,
|
||||
'123456'
|
||||
)
|
||||
);
|
||||
$entityManager->persist($user);
|
||||
$entityManager->flush();
|
||||
|
||||
return $this->json(['ok']);
|
||||
}
|
||||
}
|
36
src/Entity/Business.php
Normal file
36
src/Entity/Business.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\BusinessRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: BusinessRepository::class)]
|
||||
class Business
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'businesses')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?User $owner = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getOwner(): ?User
|
||||
{
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
public function setOwner(?User $owner): self
|
||||
{
|
||||
$this->owner = $owner;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
namespace App\Entity;
|
||||
|
||||
use App\Repository\UserRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
@ -27,6 +29,24 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||
#[ORM\Column]
|
||||
private ?string $password = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'user', targetEntity: UserToken::class, orphanRemoval: true)]
|
||||
private Collection $userTokens;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $fullName = null;
|
||||
|
||||
#[ORM\Column(length: 50)]
|
||||
private ?string $dateRegister = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'owner', targetEntity: Business::class, orphanRemoval: true)]
|
||||
private Collection $businesses;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->userTokens = new ArrayCollection();
|
||||
$this->businesses = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
|
@ -96,4 +116,88 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||
// If you store any temporary, sensitive data on the user, clear it here
|
||||
// $this->plainPassword = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, UserToken>
|
||||
*/
|
||||
public function getUserTokens(): Collection
|
||||
{
|
||||
return $this->userTokens;
|
||||
}
|
||||
|
||||
public function addUserToken(UserToken $userToken): self
|
||||
{
|
||||
if (!$this->userTokens->contains($userToken)) {
|
||||
$this->userTokens->add($userToken);
|
||||
$userToken->setUser($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeUserToken(UserToken $userToken): self
|
||||
{
|
||||
if ($this->userTokens->removeElement($userToken)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($userToken->getUser() === $this) {
|
||||
$userToken->setUser(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFullName(): ?string
|
||||
{
|
||||
return $this->fullName;
|
||||
}
|
||||
|
||||
public function setFullName(string $fullName): self
|
||||
{
|
||||
$this->fullName = $fullName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDateRegister(): ?string
|
||||
{
|
||||
return $this->dateRegister;
|
||||
}
|
||||
|
||||
public function setDateRegister(string $dateRegister): self
|
||||
{
|
||||
$this->dateRegister = $dateRegister;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Business>
|
||||
*/
|
||||
public function getBusinesses(): Collection
|
||||
{
|
||||
return $this->businesses;
|
||||
}
|
||||
|
||||
public function addBusiness(Business $business): self
|
||||
{
|
||||
if (!$this->businesses->contains($business)) {
|
||||
$this->businesses->add($business);
|
||||
$business->setOwner($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeBusiness(Business $business): self
|
||||
{
|
||||
if ($this->businesses->removeElement($business)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($business->getOwner() === $this) {
|
||||
$business->setOwner(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
51
src/Entity/UserToken.php
Normal file
51
src/Entity/UserToken.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\UserTokenRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: UserTokenRepository::class)]
|
||||
class UserToken
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $token = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'userTokens')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?User $user = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getToken(): ?string
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
public function setToken(string $token): self
|
||||
{
|
||||
$this->token = $token;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(?User $user): self
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
66
src/Repository/BusinessRepository.php
Normal file
66
src/Repository/BusinessRepository.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Business;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Business>
|
||||
*
|
||||
* @method Business|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method Business|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method Business[] findAll()
|
||||
* @method Business[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class BusinessRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Business::class);
|
||||
}
|
||||
|
||||
public function save(Business $entity, bool $flush = false): void
|
||||
{
|
||||
$this->getEntityManager()->persist($entity);
|
||||
|
||||
if ($flush) {
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function remove(Business $entity, bool $flush = false): void
|
||||
{
|
||||
$this->getEntityManager()->remove($entity);
|
||||
|
||||
if ($flush) {
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Business[] Returns an array of Business objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('b')
|
||||
// ->andWhere('b.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('b.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?Business
|
||||
// {
|
||||
// return $this->createQueryBuilder('b')
|
||||
// ->andWhere('b.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
66
src/Repository/UserTokenRepository.php
Normal file
66
src/Repository/UserTokenRepository.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\UserToken;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<UserToken>
|
||||
*
|
||||
* @method UserToken|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method UserToken|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method UserToken[] findAll()
|
||||
* @method UserToken[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class UserTokenRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, UserToken::class);
|
||||
}
|
||||
|
||||
public function save(UserToken $entity, bool $flush = false): void
|
||||
{
|
||||
$this->getEntityManager()->persist($entity);
|
||||
|
||||
if ($flush) {
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function remove(UserToken $entity, bool $flush = false): void
|
||||
{
|
||||
$this->getEntityManager()->remove($entity);
|
||||
|
||||
if ($flush) {
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Doctrine\ORM\NonUniqueResultException
|
||||
*/
|
||||
public function findByApiToken($value): UserToken | null
|
||||
{
|
||||
return $this->createQueryBuilder('u')
|
||||
->andWhere('u.token = :val')
|
||||
->setParameter('val', $value)
|
||||
->orderBy('u.id', 'ASC')
|
||||
->setMaxResults(10)
|
||||
->getQuery()
|
||||
->getOneOrNullResult()
|
||||
;
|
||||
}
|
||||
|
||||
// public function findOneBySomeField($value): ?UserToken
|
||||
// {
|
||||
// return $this->createQueryBuilder('u')
|
||||
// ->andWhere('u.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
24
src/Security/AccessDeniedHandler.php
Normal file
24
src/Security/AccessDeniedHandler.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
|
||||
|
||||
class AccessDeniedHandler implements AccessDeniedHandlerInterface
|
||||
{
|
||||
public function handle(Request $request, AccessDeniedException $accessDeniedException): ?Response
|
||||
{
|
||||
return new JsonResponse([
|
||||
'error' => [
|
||||
'code' => $accessDeniedException->getCode(),
|
||||
'message' => $accessDeniedException->getMessage(),
|
||||
],
|
||||
], $accessDeniedException->getCode());
|
||||
}
|
||||
}
|
81
src/Security/ApiKeyAuthenticator.php
Normal file
81
src/Security/ApiKeyAuthenticator.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
use App\Repository\UserTokenRepository;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
|
||||
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
||||
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
|
||||
class ApiKeyAuthenticator extends AbstractAuthenticator
|
||||
{
|
||||
/**
|
||||
* @var UserTokenRepository
|
||||
*/
|
||||
private UserTokenRepository $userTokenRepository;
|
||||
|
||||
public function __construct(UserTokenRepository $userRepository)
|
||||
{
|
||||
$this->userTokenRepository = $userRepository;
|
||||
}
|
||||
/**
|
||||
* Called on every request to decide if this authenticator should be
|
||||
* used for the request. Returning `false` will cause this authenticator
|
||||
* to be skipped.
|
||||
*/
|
||||
public function supports(Request $request): ?bool
|
||||
{
|
||||
return $request->headers->has('X-AUTH-TOKEN');
|
||||
}
|
||||
|
||||
public function authenticate(Request $request): Passport
|
||||
{
|
||||
$apiToken = $request->headers->get('X-AUTH-TOKEN');
|
||||
|
||||
if (null == $apiToken) {
|
||||
// The token header was empty, authentication fails with HTTP Status
|
||||
// Code 401 "Unauthorized"
|
||||
//throw new CustomUserMessageAuthenticationException('No API token provided');
|
||||
}
|
||||
|
||||
|
||||
return new SelfValidatingPassport(
|
||||
new UserBadge($apiToken, function($apiToken) {
|
||||
$tk = $this->userTokenRepository->findByApiToken($apiToken);
|
||||
if (! $tk) {
|
||||
throw new UserNotFoundException();
|
||||
}
|
||||
return $tk->getUser();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
|
||||
{
|
||||
// on success, let the request continue
|
||||
return null;
|
||||
}
|
||||
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
||||
{
|
||||
$data = [
|
||||
// you may want to customize or obfuscate the message first
|
||||
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
|
||||
|
||||
// or to translate this message
|
||||
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
|
||||
];
|
||||
|
||||
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
}
|
24
src/Security/AuthenticationEntryPoint.php
Normal file
24
src/Security/AuthenticationEntryPoint.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
|
||||
|
||||
class AuthenticationEntryPoint implements AuthenticationEntryPointInterface
|
||||
{
|
||||
public function start(Request $request, AuthenticationException $authException = null): JsonResponse
|
||||
{
|
||||
return new JsonResponse([
|
||||
'error' => [
|
||||
'code' => Response::HTTP_UNAUTHORIZED,
|
||||
'message' => $authException?->getMessage() ?? Response::$statusTexts[Response::HTTP_UNAUTHORIZED],
|
||||
],
|
||||
], Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
}
|
15
symfony.lock
15
symfony.lock
|
@ -26,6 +26,18 @@
|
|||
"migrations/.gitignore"
|
||||
]
|
||||
},
|
||||
"nelmio/cors-bundle": {
|
||||
"version": "2.2",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.5",
|
||||
"ref": "6bea22e6c564fba3a1391615cada1437d0bde39c"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/nelmio_cors.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/console": {
|
||||
"version": "6.2",
|
||||
"recipe": {
|
||||
|
@ -102,5 +114,8 @@
|
|||
"files": [
|
||||
"config/packages/security.yaml"
|
||||
]
|
||||
},
|
||||
"symfonycasts/verify-email-bundle": {
|
||||
"version": "v1.13.0"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue