Skip to content

Commit

Permalink
feat(UserBundle): Add FrontendLoginLinkHandler and updated README (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
eliot488995568 authored Sep 24, 2024
1 parent 6475423 commit 9045642
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 2 deletions.
19 changes: 17 additions & 2 deletions lib/RoadizUserBundle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,23 @@ services:

### Override login link URL

Decorate `Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface` service if you need to generate a login-link
with a different **base-uri**, for example if you are using a different domain for your frontend application.
Roadiz User Bundle provides a custom `Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface` service to generate a login-link with a different **base-uri**,
all you need is to register `RZ\Roadiz\UserBundle\Security\Security\FrontendLoginLinkHandler` service in your project with its mandatory arguments:

```yaml
# config/services.yaml
services:
RZ\Roadiz\UserBundle\Security\Security\FrontendLoginLinkHandler:
$decorated: '@RZ\Roadiz\UserBundle\Security\Security\FrontendLoginLinkHandler.inner'
arguments:
$decorated: '@App\Security\LoginLinkHandler.inner'
$frontendLoginCheckRoute: '%frontend_login_check_route%'
$frontendLoginLinkRequestRoutes:
- 'frontend_login_link_request_route'
- 'another_login_link_request_route'
$signatureHasher: '@security.authenticator.login_link_signature_hasher.api_login_link'
```
Now for each `$frontendLoginLinkRequestRoutes` login_link will be generated using `$frontendLoginCheckRoute` base URL

## Public users roles

Expand Down
1 change: 1 addition & 0 deletions lib/RoadizUserBundle/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ services:
- '../src/Entity/'
- '../src/Tests/'
- '../src/Event/'
- '../src/Security/'

RZ\Roadiz\UserBundle\State\UserSignupProcessor:
tags: [ 'api_platform.state_processor' ]
Expand Down
79 changes: 79 additions & 0 deletions lib/RoadizUserBundle/src/Security/FrontendLoginLinkHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

namespace RZ\Roadiz\UserBundle\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Signature\SignatureHasher;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\LoginLink\LoginLinkDetails;
use Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface;

final readonly class FrontendLoginLinkHandler implements LoginLinkHandlerInterface
{
/**
* @var array|int[]
*/
private array $options;

public function __construct(
private LoginLinkHandlerInterface $decorated,
private string $frontendLoginCheckRoute,
private SignatureHasher $signatureHasher,
private array $frontendLoginLinkRequestRoutes,
array $options = []
) {
$this->options = array_merge([
'lifetime' => 600,
], $options);
}

/**
* @inheritDoc
*/
public function createLoginLink(
UserInterface $user,
?Request $request = null,
int $lifetime = null
): LoginLinkDetails {
if (null === $request) {
throw new \InvalidArgumentException('Request cannot be null.');
}
/*
* If user does not request a login link from `$frontendLoginLinkRequestRoutes`, we fallback to the decorated handler
*/
if (!\in_array($request->attributes->get('_route'), $this->frontendLoginLinkRequestRoutes, true)) {
return $this->decorated->createLoginLink($user, $request);
}

$expires = time() + ($lifetime ?: $this->options['lifetime']);
$expiresAt = new \DateTimeImmutable('@' . $expires);

$parameters = [
'user' => $user->getUserIdentifier(),
'expires' => $expires,
'hash' => $this->signatureHasher->computeSignatureHash($user, $expires),
];
$redirect = $request->getPayload()->get('redirect');
if (\is_string($redirect)) {
$parameters['redirect'] = $redirect;
}

$url = $this->frontendLoginCheckRoute . '?' . http_build_query($parameters);

if (filter_var($url, FILTER_VALIDATE_URL) === false) {
throw new \InvalidArgumentException(sprintf('The URL "%s" is not valid.', $url));
}

return new LoginLinkDetails($url, $expiresAt);
}

/**
* @inheritDoc
*/
public function consumeLoginLink(Request $request): UserInterface
{
return $this->decorated->consumeLoginLink($request);
}
}

0 comments on commit 9045642

Please sign in to comment.