Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Kinde auth support #167

Merged
merged 1 commit into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/src/Application/Auth/JWTTokenStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function load(string $id): ?TokenInterface
);
}

public function create(array $payload, \DateTimeInterface $expiresAt = null): TokenInterface
public function create(array|\JsonSerializable $payload, \DateTimeInterface $expiresAt = null): TokenInterface
{
$issuedAt = ($this->time)('now');
$expiresAt = $expiresAt ?? ($this->time)($this->expiresAt);
Expand Down
33 changes: 11 additions & 22 deletions app/src/Application/Bootloader/AuthBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,31 @@
use App\Application\Auth\JWTTokenStorage;
use App\Application\Auth\SuccessRedirect;
use App\Application\OAuth\ActorProvider;
use App\Application\OAuth\SessionStore;
use App\Application\OAuth\AuthProviderInterface;
use App\Application\OAuth\AuthProviderRegistryInterface;
use App\Application\OAuth\AuthProviderService;
use Psr\Http\Message\UriFactoryInterface;
use Spiral\Boot\Bootloader\Bootloader;

use Auth0\SDK\Auth0;
use Auth0\SDK\Configuration\SdkConfiguration;
use Spiral\Boot\EnvironmentInterface;
use Spiral\Bootloader\Auth\HttpAuthBootloader;
use Spiral\Core\Container\Autowire;
use Spiral\Http\ResponseWrapper;
use Spiral\Session\SessionScope;

final class AuthBootloader extends Bootloader
{
public function defineBindings(): array
public function defineSingletons(): array
{
return [
Auth0::class => static fn(SdkConfiguration $config, SessionScope $session) => new Auth0(
$config->setTransientStorage(new SessionStore($session)),
AuthProviderInterface::class => static fn(
EnvironmentInterface $env,
AuthProviderService $service,
) => $service->get(
name: $env->get('AUTH_PROVIDER', 'auth0'),
),

SdkConfiguration::class => static fn(EnvironmentInterface $env) => new SdkConfiguration(
strategy: $env->get('AUTH_STRATEGY', SdkConfiguration::STRATEGY_REGULAR),
domain: $env->get('AUTH_PROVIDER_URL'),
clientId: $env->get('AUTH_CLIENT_ID'),
redirectUri: $env->get('AUTH_CALLBACK_URL'),
clientSecret: $env->get('AUTH_CLIENT_SECRET'),
scope: \explode(',', $env->get('AUTH_SCOPES', 'openid,profile,email')),
cookieSecret: $env->get('AUTH_COOKIE_SECRET', $env->get('ENCRYPTER_KEY') ?? 'secret'),
),
];
}
AuthProviderService::class => AuthProviderService::class,
AuthProviderRegistryInterface::class => AuthProviderService::class,

public function defineSingletons(): array
{
return [
AuthSettings::class => static fn(
EnvironmentInterface $env,
UriFactoryInterface $factory,
Expand Down
4 changes: 2 additions & 2 deletions app/src/Application/Bootloader/RoutesBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
namespace App\Application\Bootloader;

use App\Application\Auth\AuthSettings;
use App\Application\HTTP\Middleware\ApiAuthMiddleware;
use App\Application\HTTP\Middleware\DetectEventTypeMiddleware;
use App\Application\HTTP\Middleware\JsonPayloadMiddleware;
use App\Interfaces\Http\EventHandlerAction;
use Spiral\Auth\Middleware\AuthMiddleware;
use Spiral\Auth\Middleware\Firewall\ExceptionFirewall;
use Spiral\Bootloader\Http\RoutesBootloader as BaseRoutesBootloader;
use Spiral\Core\Container;
Expand Down Expand Up @@ -56,7 +56,7 @@ protected function middlewareGroups(): array
{
return [
'auth' => [
AuthMiddleware::class,
ApiAuthMiddleware::class,
],
'guest' => [
'middleware:auth',
Expand Down
7 changes: 7 additions & 0 deletions app/src/Application/Exception/AuthProviderException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

declare(strict_types=1);

namespace App\Application\Exception;

class AuthProviderException extends \Exception {}
7 changes: 7 additions & 0 deletions app/src/Application/Exception/AuthProviderNotFound.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

declare(strict_types=1);

namespace App\Application\Exception;

final class AuthProviderNotFound extends AuthProviderException {}
7 changes: 7 additions & 0 deletions app/src/Application/Exception/InvalidCredentialsException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

declare(strict_types=1);

namespace App\Application\Exception;

final class InvalidCredentialsException extends AuthProviderException {}
37 changes: 37 additions & 0 deletions app/src/Application/HTTP/Middleware/ApiAuthMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace App\Application\HTTP\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Spiral\Auth\Middleware\AuthTransportWithStorageMiddleware;
use Spiral\Core\FactoryInterface;

final class ApiAuthMiddleware implements MiddlewareInterface
{
private ?MiddlewareInterface $middleware = null;

public function __construct(
private readonly FactoryInterface $factory,
) {}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if ($this->middleware === null) {
$this->initMiddleware();
}

return $this->middleware->process($request, $handler);
}

private function initMiddleware(): void
{
$this->middleware = $this->factory->make(AuthTransportWithStorageMiddleware::class, [
'transportName' => 'header',
]);
}
}
10 changes: 7 additions & 3 deletions app/src/Application/HTTP/Response/UserResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@
namespace App\Application\HTTP\Response;

use App\Application\OAuth\User;
use Psr\Http\Message\UriInterface;

final class UserResource extends JsonResource
{
public function __construct(
private readonly User $user,
private readonly ?UriInterface $logoutUrl = null,
) {
parent::__construct();
}

protected function mapData(): array
{
return [
'username' => $this->user->getUsername(),
'avatar' => $this->user->getAvatar(),
'email' => $this->user->getEmail(),
'provider' => $this->user->provider,
'username' => $this->user->username,
'avatar' => $this->user->avatar,
'email' => $this->user->email,
'logout' => $this->logoutUrl ? (string) $this->logoutUrl : null,
];
}
}
7 changes: 6 additions & 1 deletion app/src/Application/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
use App\Application\Bootloader\HttpHandlerBootloader;
use App\Application\Bootloader\MongoDBBootloader;
use App\Application\Bootloader\PersistenceBootloader;
use App\Integration\Auth0\Auth0Bootloader;
use App\Integration\Kinde\KindeBootloader;
use Modules\Events\Application\EventsBootloader;
use Modules\Inspector\Application\InspectorBootloader;
use Modules\Metrics\Application\MetricsBootloader;
Expand Down Expand Up @@ -83,6 +85,10 @@ protected function defineBootloaders(): array
SerializerBootloader::class,
BroadcastingBootloader::class,

// Auth
Auth0Bootloader::class,
KindeBootloader::class,

// Modules
HttpHandlerBootloader::class,
AppBootloader::class,
Expand All @@ -95,7 +101,6 @@ protected function defineBootloaders(): array
ProfilerBootloader::class,
MongoDBBootloader::class,
PersistenceBootloader::class,
AuthBootloader::class,
WebhooksBootloader::class,
ProjectBootloader::class,
EventsBootloader::class,
Expand Down
19 changes: 10 additions & 9 deletions app/src/Application/OAuth/ActorProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,25 @@
use Spiral\Auth\ActorProviderInterface;
use Spiral\Auth\TokenInterface;

final class ActorProvider implements ActorProviderInterface
final readonly class ActorProvider implements ActorProviderInterface
{
public function getActor(TokenInterface $token): ?User
{
$payload = $token->getPayload();
if ($payload === []) {
$payload = self::getGuestPayload();
return self::getGuestPayload();
}

return new User($payload);
return User::fromArray($payload);
}

public static function getGuestPayload(): array
public static function getGuestPayload(): User
{
return [
'nickname' => 'guest',
'email' => '',
'picture' => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 144.8 144.8" xml:space="preserve"><circle style="fill:#f5c002" cx="72.4" cy="72.4" r="72.4"/><defs><circle id="a" cx="72.4" cy="72.4" r="72.4"/></defs><clipPath id="b"><use xlink:href="#a" style="overflow:visible"/></clipPath><g style="clip-path:url(#b)"><path style="fill:#f1c9a5" d="M107 117c-5-9-35-14-35-14s-30 5-34 14l-7 28h82s-2-17-6-28z"/><path style="fill:#e4b692" d="M72 103s30 5 35 14c4 11 6 28 6 28H72v-42z"/><path style="fill:#f1c9a5" d="M64 85h17v27H64z"/><path style="fill:#e4b692" d="M72 85h9v27h-9z"/><path style="opacity:.1;fill:#ddac8c" d="M64 97c2 4 8 7 12 7l5-1V85H64v12z"/><path style="fill:#f1c9a5" d="M93 67c0-17-9-26-21-26-11 0-21 9-21 26 0 23 10 31 21 31 12 0 21-9 21-31z"/><path style="fill:#e4b692" d="M90 79c-4 0-6-4-6-9 1-5 5-8 9-8 3 1 6 5 5 9 0 5-4 9-8 8z"/><path style="fill:#f1c9a5" d="M47 71c-1-4 2-8 5-9 4 0 8 3 8 8 1 5-1 9-5 9-4 1-8-3-8-8z"/><path style="fill:#e4b692" d="M93 67c0-17-9-26-21-26v57c12 0 21-9 21-31z"/><path style="fill:#303030" d="M91 82c-1 3-3 7-6 7-5 0-8-4-13-4s-8 4-12 4c-3 0-5-4-7-7v-6 7s1 8 4 11c3 2 11 6 15 6 5 0 13-4 15-6 4-3 5-11 5-11v-7l-1 6zM62 44s4 16 26 24l-3-8 10 7c3-7 7-16-2-21-8-6-28-15-31-2z"/><path style="fill:#303030" d="M55 66s2-18 8-22c-5-2-14 5-13 10l5 12z"/><path style="fill:#fb621e" d="M107 117c-3-5-14-9-23-12a12 12 0 0 1-23 0c-9 3-21 7-23 12l-7 28h82s-2-17-6-28z"/><path style="opacity:.2;fill:#e53d0c" d="M60 108c0 6 6 11 12 11 7 0 12-5 13-11 8 2 18 6 22 10v-1c-3-5-14-9-23-12a12 12 0 0 1-23 0c-9 3-21 7-23 12l-1 1c5-4 15-8 23-10z"/><path style="fill:#e53d0c" d="M57 106a15 15 0 0 0 30 0l-3-1a12 12 0 0 1-23 0l-4 1z"/><path style="fill:#fff" d="M76 91s-1-3-4-3c-2 0-3 3-3 3h7z"/></g></svg>',
];
return new User(
provider: null,
username: 'guest',
avatar: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 144.8 144.8" xml:space="preserve"><circle style="fill:#f5c002" cx="72.4" cy="72.4" r="72.4"/><defs><circle id="a" cx="72.4" cy="72.4" r="72.4"/></defs><clipPath id="b"><use xlink:href="#a" style="overflow:visible"/></clipPath><g style="clip-path:url(#b)"><path style="fill:#f1c9a5" d="M107 117c-5-9-35-14-35-14s-30 5-34 14l-7 28h82s-2-17-6-28z"/><path style="fill:#e4b692" d="M72 103s30 5 35 14c4 11 6 28 6 28H72v-42z"/><path style="fill:#f1c9a5" d="M64 85h17v27H64z"/><path style="fill:#e4b692" d="M72 85h9v27h-9z"/><path style="opacity:.1;fill:#ddac8c" d="M64 97c2 4 8 7 12 7l5-1V85H64v12z"/><path style="fill:#f1c9a5" d="M93 67c0-17-9-26-21-26-11 0-21 9-21 26 0 23 10 31 21 31 12 0 21-9 21-31z"/><path style="fill:#e4b692" d="M90 79c-4 0-6-4-6-9 1-5 5-8 9-8 3 1 6 5 5 9 0 5-4 9-8 8z"/><path style="fill:#f1c9a5" d="M47 71c-1-4 2-8 5-9 4 0 8 3 8 8 1 5-1 9-5 9-4 1-8-3-8-8z"/><path style="fill:#e4b692" d="M93 67c0-17-9-26-21-26v57c12 0 21-9 21-31z"/><path style="fill:#303030" d="M91 82c-1 3-3 7-6 7-5 0-8-4-13-4s-8 4-12 4c-3 0-5-4-7-7v-6 7s1 8 4 11c3 2 11 6 15 6 5 0 13-4 15-6 4-3 5-11 5-11v-7l-1 6zM62 44s4 16 26 24l-3-8 10 7c3-7 7-16-2-21-8-6-28-15-31-2z"/><path style="fill:#303030" d="M55 66s2-18 8-22c-5-2-14 5-13 10l5 12z"/><path style="fill:#fb621e" d="M107 117c-3-5-14-9-23-12a12 12 0 0 1-23 0c-9 3-21 7-23 12l-7 28h82s-2-17-6-28z"/><path style="opacity:.2;fill:#e53d0c" d="M60 108c0 6 6 11 12 11 7 0 12-5 13-11 8 2 18 6 22 10v-1c-3-5-14-9-23-12a12 12 0 0 1-23 0c-9 3-21 7-23 12l-1 1c5-4 15-8 23-10z"/><path style="fill:#e53d0c" d="M57 106a15 15 0 0 0 30 0l-3-1a12 12 0 0 1-23 0l-4 1z"/><path style="fill:#fff" d="M76 91s-1-3-4-3c-2 0-3 3-3 3h7z"/></g></svg>',
email: '',
);
}
}
23 changes: 23 additions & 0 deletions app/src/Application/OAuth/AuthProviderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace App\Application\OAuth;

use Psr\Http\Message\UriInterface;
use Spiral\Http\Request\InputManager;

interface AuthProviderInterface
{
public function getLoginUrl(): UriInterface;

public function isAuthenticated(): bool;

public function getUser(): ?User;

public function authenticate(InputManager $input): void;

public function getLogoutUrl(): ?UriInterface;

public function logout(): void;
}
20 changes: 20 additions & 0 deletions app/src/Application/OAuth/AuthProviderRegistryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace App\Application\OAuth;

use App\Application\Exception\AuthProviderNotFound;

interface AuthProviderRegistryInterface
{
/**
* @param class-string<AuthProviderInterface> $provider
*/
public function register(string $name, string $provider): void;

/**
* @throws AuthProviderNotFound
*/
public function get(string $name): AuthProviderInterface;
}
41 changes: 41 additions & 0 deletions app/src/Application/OAuth/AuthProviderService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace App\Application\OAuth;

use App\Application\Exception\AuthProviderException;
use App\Application\Exception\AuthProviderNotFound;
use Psr\Container\ContainerInterface;
use Spiral\Core\Attribute\Singleton;

#[Singleton]
final class AuthProviderService implements AuthProviderRegistryInterface
{
/** @var array<non-empty-string, class-string<AuthProviderInterface>> */
private array $providers = [];

public function __construct(
private readonly ContainerInterface $container,
) {}

public function register(string $name, string $provider): void
{
if (!\is_subclass_of($provider, AuthProviderInterface::class)) {
throw new AuthProviderException(
\sprintf('Provider "%s" must implement AuthProviderInterface', $provider),
);
}

$this->providers[$name] = $provider;
}

public function get(string $name): AuthProviderInterface
{
if (!isset($this->providers[$name])) {
throw new AuthProviderNotFound(\sprintf('Auth provider "%s" not found', $name));
}

return $this->container->get($this->providers[$name]);
}
}
34 changes: 21 additions & 13 deletions app/src/Application/OAuth/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,32 @@

namespace App\Application\OAuth;

final readonly class User
readonly class User implements \JsonSerializable
{
public function __construct(
private array $data,
) {}

public function getUsername(): string
public static function fromArray(array $data): self
{
return $this->data['nickname'] ?? 'guest';
return new self(
provider: $data['provider'],
username: $data['username'],
avatar: $data['avatar'],
email: $data['email'],
);
}

public function getAvatar(): string
{
return $this->data['picture'];
}
public function __construct(
public ?string $provider,
public string $username,
public string $avatar,
public string $email,
) {}

public function getEmail(): string
public function jsonSerialize(): array
{
return $this->data['email'];
return [
'provider' => $this->provider,
'username' => $this->username,
'avatar' => $this->avatar,
'email' => $this->email,
];
}
}
Loading
Loading