Open CreativeNative opened 3 years ago
Hello!
The relationship on the User entity would look something like this:
/**
* @ORM\OneToMany(targetEntity="CirclicalUser\Entity\UserApiToken", mappedBy="user", cascade={"all"});
*/
private $api_tokens;
You would also likely build some getters/setters on the User, like so:
public function getApiTokens(): ?Collection
{
return $this->api_tokens;
}
public function addApiToken(UserApiToken $token): void
{
$this->api_tokens->add($token);
}
public function getApiTokenWithId(string $uuid): ?UserApiToken
{
foreach ($this->api_tokens as $token) {
if ($token->getToken() === $uuid) {
return $token;
}
}
return null;
}
In the primary platform I've developed that uses tokens, the admin area has an action that allows people with necessary rights to create tokens:
public function createTokenAction(): JsonModel
{
return $this->json()->wrap(function () {
if (!$this->auth()->isAllowed($this->getPluralType(), AccessModel::ACTION_AUTHOR)) {
throw new NoAccessException();
}
/** @var User $user */
$user = $this->auth()->requireIdentity();
$token = new UserApiToken($user, ApiScopeInterface::API_SCOPE_CONTEST);
$this->userApiTokenMapper->getEntityManager()->persist($token);
$user->addApiToken($token);
$this->userMapper->update($user);
return [
'token' => $token->getUuid()->toString(),
];
});
}
Tokens are indeed fetched via logic such as this:
$tokenParameter = $this->params()->fromPost('auth_api_token');
$token = $this->userApiTokenMapper->get($tokenParameter);
Lib is being used in some prod apps that are working very well! Should be able to get it going 👍🏼
Hey @CreativeNative! Wanted to check in to see if this helped! Cheers!
Thanks a lot!
Question 1:
In my fork I had to add in the entity file UserApiToken this , inversedBy="api_tokens" to get doctrine validation passed.
/**
* @ORM\ManyToOne(targetEntity="CirclicalUser\Entity\User", inversedBy="api_tokens")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/
private UserInterface $user;
Is that right?
Question 2:
How do I validate the token? Is it enough when I get the token like so:
$token= $this->userApiTokenMapper->get($tokenParameter);
Or do I have to check if the user really has this token?
$userApiToken= $user->getApiTokenWithId($token->getUuid()->toString());
Or do I have to check if the uuids are equal like so?
$token->getUuid()->equals($userApiToken->getUuid());
I'm confused.
For explanation I create a token:
$userApiToken = new UserApiToken($user, TokenScopeInterface::REGISTRATION);
$this->entityManager->persist($userApiToken);
$user->addApiToken($userApiToken);
Than I create a link with a token:
'tokenHash' => $userApiToken->getToken(),
When the user clicks the link I want to validate the token and want to get, if possibile, also the information of the user like e-mail-address. But the following makes no sense in terms of validation:
$userApiToken = $this->userApiTokenMapper->get($tokenHash);
$user = $userApiToken->getUser();
$token = $user->getApiTokenWithId($userApiToken->getUuid()->toString());
$token->getUuid()->equals($userApiToken->getUuid());
So how do I do it the right way?
Question 3:
I can tag the token as used, but after the registration is completed, wouldn't it be better to delete the token? I can't find nothing to delete token. Don't want to fill my database with tokens that I never use again. I'm not quite sure why I can tag the token as used. Can you give me an example?
By now we have in the user entity the functions:
Wouldn't it be cool to have also a removeApiToken function?
I would like ti update the template file for the user entity to integrate the token system in a right way, but before I want to discuss everything with you.
Question 4:
When I try to get the token via $userApiTokenMapper->get($tokenHash); I get the following error.
Typed property CirclicalUser\Mapper\AbstractDoctrineMapper::$entityManager must not be accessed before initialization
File: vendor/saeven/zf3-circlical-user/src/CirclicalUser/Mapper/AbstractDoctrineMapper.php:39
So there isn't a factory where the entityManager will be injected automatically. I have to inject it by myself like so.
$userApiTokenMapper->setEntityManager($entityManager);
Isn't that a bit over engineered? Can't we use the entityManager directly and instead of the UserApiTokenMapper create a UserApiTokenRepository for the "get"-function? In general all mapper classes are looking for me more like repositories. I think this layer with AbstractDoctrineMapper is not necessary. What do you think?
Validation depends on context. We have a large application that uses this lib, and we've extended the mapper to suit a multitude of interesting use cases, but validation can be very rudimentary. At the simplest level, you can list tokens from the user relationship or search there with Criteria.
If you are coming in blind, and want to ensure that an auth token matches the request token (can be important), then you can run auth, plus use the mapper to fetch the token, and ensure that the authenticated user and owner line up.
I wouldn't be willing to change the current relationships, because they are being leveraged in important ways in a very large app that depends on things the way they are. With continued use, I think you will find that the current architecture reveals important advantages.
Regarding your mapper error, the AbstractDoctrineMapper in this library would never be used directly. It is extended by your own mappers - check out AbstractDoctrineMapperFactory's canCreate method. You will see that it looks for classes that end with 'Mapper'
A basic mapper can be as simple as an entity declaration:
class FooMapper extends AbstractDoctrineMapper
{
protected string $entityName = FooMapper::class;
}
Ohh, I see. Here comes the magic from:
'service_manager' => [
'abstract_factories' => [
AbstractDoctrineMapperFactory::class,
],
],
But than in my system the AbstractDoctrineMapperFactory isn't working, because when I use $this->userApiTokenMapper->get($tokenHash); I get the error that the EntityManager wasn't injected.
You say that you "wouldn't be willing to change the current relationships". Is that pointing on question 1? I don't want to change any relations. My problem is, that the mapping isn't valid when I add this to my user entity.
/**
* @ORM\OneToMany(targetEntity="CirclicalUser\Entity\UserApiToken", mappedBy="user", cascade={"all"});
*/
private $api_tokens;
So where it comes from that my mapping isn't valid but yours obviously is?
If you can share a rudimentary repo to reproduce the issue I’d gladly help out!
Here you go. https://github.com/CreativeNative/zf3-circlical-user/tree/UserSample
I added the updated and very basic user entity sample that I would love to push. If you check it out and run the doctrine validation you will see the error.
vendor/bin/doctrine-module orm:validate-schema
[FAIL] The entity-class CirclicalUser\Entity\User mapping is invalid:
* The field CirclicalUser\Entity\User#api_tokens is on the inverse side of a bi-directional relationship, but the specified mappedBy association on the target-entity CirclicalUser\Entity\UserApiToken#user does not contain the required 'inversedBy="api_tokens"' attribute.
I think you want here a One-To-Many, Bidirectional relationship. Like this you can ask for the user for a token and the token for a user.
Hi! To help. I would need a repo where you are using the lib within a project that's experiencing the issue. The lib itself is not meant to work "standalone", but is instead meant to be supplanted into a project that defines its own user, etc.
If you are a patient man, I can find some time in the next few days to set up a skeleton and load in this lib, and add a sample controller for you, to show one way of consuming tokens 👍🏼
That would be awesome! Also for testing or showing you new stuff. Thank you very much.
Please have a look also here. I updated the entity UserApiToken and adjusted the Bidirectional relationship.
https://github.com/CreativeNative/zf3-circlical-user/tree/UserApiToken
Here you go @CreativeNative, using existing libs in their unmodified form. I whipped this up real fast just now, and was as lazy as possible (a nice end to a good Sunday!). Everything works - I rolled in some useful libs as well! Can have some fun with Alpine and Tailwind while you're at it if you like.
I can't use your really cool skeleton by now, because I'm using PHP 7.4. It's on my list to upgrade, but first I have to resolve this. Could you verify this error in the skeleton?
[FAIL] The entity-class CirclicalUser\Entity\User mapping is invalid:
* The field CirclicalUser\Entity\User#api_tokens is on the inverse side of a bi-directional relationship, but the specified mappedBy association on the target-entity CirclicalUser\Entity\UserApiToken#user does not contain the required 'inversedBy="api_tokens"' attribute.
Totally understand. The skeleton was authored precisely to 'prove' in a sense, that no such error surfaces. I made a video and jammed it on YT for you, at this link: https://www.youtube.com/watch?v=96gI5wb4UlA
You can see that without any trouble, I can install and spawn database real quick. The user entity is precisely as it exists in the skeleton, and this library is unadulterated ;)
Hope this helps!
Feel free to hit me up on Gitter btw https://gitter.im/Circlical/zf3-circlical-user
I don't get it how to use the User API Tokens and the tests doesn't really help. To my User Entity I added
How do I get the token because like this it doesn't work.
$token = $this->userApiTokenMapper->get('d0cad39b-f269-405e-b3f9-d45b349c0587');
I think I'm missing a configuration. Do I?
I got also an error by doctrine validation.
[FAIL] The entity-class TmiUser\Entity\User mapping is invalid: