diff --git a/src/Dto/AccessTokenLoginDto.php b/src/Dto/UserTokenLoginDto.php similarity index 91% rename from src/Dto/AccessTokenLoginDto.php rename to src/Dto/UserTokenLoginDto.php index ce3dcff..44fb82e 100644 --- a/src/Dto/AccessTokenLoginDto.php +++ b/src/Dto/UserTokenLoginDto.php @@ -4,7 +4,10 @@ namespace App\Dto; use Symfony\Component\Validator\Constraints as Assert; -final class AccessTokenLoginDto +/** + * What the fuck? + */ +final class UserTokenLoginDto { /** * The username of the User to be authenticated. diff --git a/src/Entity/User.php b/src/Entity/User.php index efa94f0..952a141 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -78,11 +78,11 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface private ?Accounting $accounting = null; /** - * The AccessTokens owned by this user. Owner only property. + * The UserTokens owned by this user. Owner only property. */ #[API\ApiProperty(writable: false, readableLink: true, security: 'is_granted("AUTH_OWNER", object)')] - #[ORM\OneToMany(mappedBy: 'ownedBy', targetEntity: AccessToken::class, orphanRemoval: true)] - private Collection $accessTokens; + #[ORM\OneToMany(mappedBy: 'ownedBy', targetEntity: UserToken::class, orphanRemoval: true)] + private Collection $tokens; #[ORM\Column] private ?bool $active = null; @@ -101,7 +101,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface $this->accounting = new Accounting(); $this->accounting->setOwnerClass(User::class); - $this->accessTokens = new ArrayCollection(); + $this->tokens = new ArrayCollection(); } public function getId(): ?int @@ -227,29 +227,29 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface } /** - * @return Collection + * @return Collection */ - public function getAccessTokens(): Collection + public function getTokens(): Collection { - return $this->accessTokens; + return $this->tokens; } - public function addAccessToken(AccessToken $accessToken): static + public function addToken(UserToken $token): static { - if (!$this->accessTokens->contains($accessToken)) { - $this->accessTokens->add($accessToken); - $accessToken->setOwnedBy($this); + if (!$this->tokens->contains($token)) { + $this->tokens->add($token); + $token->setOwnedBy($this); } return $this; } - public function removeAccessToken(AccessToken $accessToken): static + public function removeToken(UserToken $token): static { - if ($this->accessTokens->removeElement($accessToken)) { + if ($this->tokens->removeElement($token)) { // set the owning side to null (unless already changed) - if ($accessToken->getOwnedBy() === $this) { - $accessToken->setOwnedBy(null); + if ($token->getOwnedBy() === $this) { + $token->setOwnedBy(null); } } diff --git a/src/Entity/AccessToken.php b/src/Entity/UserToken.php similarity index 69% rename from src/Entity/AccessToken.php rename to src/Entity/UserToken.php index b684457..9a5e203 100644 --- a/src/Entity/AccessToken.php +++ b/src/Entity/UserToken.php @@ -3,24 +3,24 @@ namespace App\Entity; use ApiPlatform\Metadata as API; -use App\Dto\AccessTokenLoginDto; -use App\Repository\AccessTokenRepository; -use App\State\AccessTokenLoginProcessor; +use App\Dto\UserTokenLoginDto; +use App\Repository\UserTokenRepository; +use App\State\UserTokenLoginProcessor; use Doctrine\ORM\Mapping as ORM; /** - * AccessTokens authenticate requests on behalf of the User who owns them.\ + * UserTokens authenticates requests on behalf of the User who owns them.\ * \ - * When an AccessToken is created the system generates a SHA-256 hash that is unique for the User it represents. + * When a UserToken is created v4 generates a SHA-256 hash that is unique for that Token and the User it represents. * The value of a token comes preceded by a 4-digit-length prefix based on the type of token it is.\ * \ * `oat_` means the token was created via an OAuth flow.\ * `pat_` means the token was created via a login flow. */ -#[API\Post(input: AccessTokenLoginDto::class, processor: AccessTokenLoginProcessor::class)] +#[API\Post(input: UserTokenLoginDto::class, processor: UserTokenLoginProcessor::class)] #[API\Delete(security: 'is_granted("AUTH_OWNER")')] -#[ORM\Entity(repositoryClass: AccessTokenRepository::class)] -class AccessToken +#[ORM\Entity(repositoryClass: UserTokenRepository::class)] +class UserToken { #[ORM\Id] #[ORM\GeneratedValue] @@ -34,9 +34,9 @@ class AccessToken private ?string $token = null; /** - * The User on behalf of which this Token authenticates. + * The User on behalf of which this UserToken authenticates. */ - #[ORM\ManyToOne(inversedBy: 'accessTokens')] + #[ORM\ManyToOne(inversedBy: 'tokens')] #[ORM\JoinColumn(nullable: false)] private ?User $ownedBy = null; diff --git a/src/OpenApi/OpenApiDescription.md b/src/OpenApi/OpenApiDescription.md index d2cd5df..65b6f33 100644 --- a/src/OpenApi/OpenApiDescription.md +++ b/src/OpenApi/OpenApiDescription.md @@ -12,15 +12,17 @@ This API is still in early development and is not set to have backward compatibi # Authentication -The v4 API uses AccessTokens to authenticate requests. AccessTokens exist under an User's scope, when you obtain a token this will only grant you as much permissions as the User under which it was created. - -To obtain an AccessToken you must send a POST request to [/v4/access_tokens](/v4/access_tokens) with the User login credentials (username and password) in the payload. If the credentials are correct an AccessToken will be created, the `token` property value of which you must include in future requests Authorization header using the [Bearer](https://swagger.io/docs/specification/authentication/bearer-authentication/) strategy. +The v4 API uses Access Tokens to authenticate requests. To use a Token, include it in the Authorization header using the [Bearer](https://swagger.io/docs/specification/authentication/bearer-authentication/) strategy. ```shell curl -X 'GET' \ 'https://api.goteo.org/v4/projects?page=1' \ -H 'accept: application/json' \ - -H 'Authorization: Bearer ' + -H 'Authorization: Bearer ' ``` -Users can delete AccessTokens owned by them at any moment, revoking your application's access to the v4 API on their behalf. When an AccessToken fails to authenticate a request you will receive a 401 Unauthorized response from the API, at which point your application must look to get a new AccessToken from the User. +UserTokens exist under an User's scope, when you obtain a token this will only grant you as much permissions as the User under which it was created. + +To obtain a UserToken you must send a POST request to [/v4/user_tokens](/v4/user_tokens) with the User login credentials (username and password) in the payload. If the credentials are correct a UserToken will be created, the `token` property value of which you must include in future requests. + +Users can delete UserTokens owned by them at any moment, revoking your application's access to the v4 API on their behalf. When a UserToken fails to authenticate a request you will receive a 401 Unauthorized response from the API, at which point your application must look to get a new UserToken from the User. diff --git a/src/Repository/AccessTokenRepository.php b/src/Repository/UserTokenRepository.php similarity index 59% rename from src/Repository/AccessTokenRepository.php rename to src/Repository/UserTokenRepository.php index e947f46..4588053 100644 --- a/src/Repository/AccessTokenRepository.php +++ b/src/Repository/UserTokenRepository.php @@ -2,27 +2,27 @@ namespace App\Repository; -use App\Entity\AccessToken; +use App\Entity\UserToken; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; /** - * @extends ServiceEntityRepository + * @extends ServiceEntityRepository * - * @method AccessToken|null find($id, $lockMode = null, $lockVersion = null) - * @method AccessToken|null findOneBy(array $criteria, array $orderBy = null) - * @method AccessToken[] findAll() - * @method AccessToken[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + * @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 AccessTokenRepository extends ServiceEntityRepository +class UserTokenRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) { - parent::__construct($registry, AccessToken::class); + parent::__construct($registry, UserToken::class); } // /** - // * @return AccessToken[] Returns an array of AccessToken objects + // * @return UserToken[] Returns an array of UserToken objects // */ // public function findByExampleField($value): array // { @@ -36,7 +36,7 @@ class AccessTokenRepository extends ServiceEntityRepository // ; // } - // public function findOneBySomeField($value): ?AccessToken + // public function findOneBySomeField($value): ?UserToken // { // return $this->createQueryBuilder('a') // ->andWhere('a.exampleField = :val') diff --git a/src/Security/AccessTokenHandler.php b/src/Security/AccessTokenHandler.php index 1d7c766..c2467de 100644 --- a/src/Security/AccessTokenHandler.php +++ b/src/Security/AccessTokenHandler.php @@ -2,20 +2,20 @@ namespace App\Security; -use App\Repository\AccessTokenRepository; +use App\Repository\UserTokenRepository; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; class AccessTokenHandler implements AccessTokenHandlerInterface { - public function __construct(private AccessTokenRepository $accessTokenRepository) + public function __construct(private UserTokenRepository $userTokenRepository) { } public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge { - $token = $this->accessTokenRepository->findOneBy(['token' => $accessToken]); + $token = $this->userTokenRepository->findOneBy(['token' => $accessToken]); if (!$token) { throw new BadCredentialsException(); diff --git a/src/Service/Auth/AccessTokenType.php b/src/Service/Auth/AccessTokenType.php deleted file mode 100644 index a287ea6..0000000 --- a/src/Service/Auth/AccessTokenType.php +++ /dev/null @@ -1,18 +0,0 @@ -config; } - public function generateAccessToken(User $user, AccessTokenType $type): AccessToken + public function generateUserToken(User $user, AuthTokenType $type): UserToken { - $token = new AccessToken; + $token = new UserToken; $token->setOwnedBy($user); $token->setToken(sprintf('%s_%s', $type->value, hash( diff --git a/src/Service/Auth/AuthTokenType.php b/src/Service/Auth/AuthTokenType.php new file mode 100644 index 0000000..066f25d --- /dev/null +++ b/src/Service/Auth/AuthTokenType.php @@ -0,0 +1,18 @@ +userRepository->findOneBy(['username' => $data->username]); if (!$user) { @@ -39,7 +39,7 @@ class AccessTokenLoginProcessor implements ProcessorInterface throw new BadCredentialsException(); } - $token = $this->authService->generateAccessToken($user, AccessTokenType::Personal); + $token = $this->authService->generateUserToken($user, AuthTokenType::Personal); $this->entityManager->persist($token); $this->entityManager->flush();