You've already forked php-auth
generated from siteworxpro/Php-Template
Basics of auth
This commit is contained in:
10
src/Api.php
10
src/Api.php
@@ -9,6 +9,8 @@ use League\Route\Http\Exception\NotFoundException;
|
||||
use League\Route\RouteGroup;
|
||||
use League\Route\Router;
|
||||
use Nyholm\Psr7\Factory\Psr17Factory;
|
||||
use Siteworxpro\App\Controllers\AuthorizeController;
|
||||
use Siteworxpro\App\Controllers\CapabilitiesController;
|
||||
use Siteworxpro\App\Controllers\HealthcheckController;
|
||||
use Siteworxpro\App\Controllers\IndexController;
|
||||
use Siteworxpro\App\Controllers\OpenApiController;
|
||||
@@ -71,8 +73,6 @@ class Api
|
||||
);
|
||||
|
||||
$this->router = new Router();
|
||||
$this->router->get('/', IndexController::class . '::get');
|
||||
$this->router->post('/', IndexController::class . '::post');
|
||||
$this->router->get('/healthz', HealthcheckController::class . '::get');
|
||||
|
||||
$this->router->group('/.well-known', function (RouteGroup $router) {
|
||||
@@ -80,6 +80,12 @@ class Api
|
||||
$router->get('/swagger.json', OpenApiController::class . '::get');
|
||||
});
|
||||
|
||||
$this->router->group('/client', function (RouteGroup $group) {
|
||||
$group->get('/capabilities', CapabilitiesController::class . '::get');
|
||||
});
|
||||
|
||||
$this->router->get('/authorize', AuthorizeController::class . '::get');
|
||||
|
||||
$this->router->middleware(new CorsMiddleware());
|
||||
$this->router->middleware(new JwtMiddleware());
|
||||
$this->router->middleware(new ScopeMiddleware());
|
||||
|
||||
@@ -5,12 +5,11 @@ declare(strict_types=1);
|
||||
namespace Siteworxpro\App\Cli;
|
||||
|
||||
use Ahc\Cli\Application;
|
||||
use Siteworxpro\App\Cli\Commands\DemoCommand;
|
||||
use Siteworxpro\App\Cli\Commands\OAuth\AddRedirectUri;
|
||||
use Siteworxpro\App\Cli\Commands\OAuth\CreateClient;
|
||||
use Siteworxpro\App\Cli\Commands\Queue\Start;
|
||||
use Siteworxpro\App\Cli\Commands\Queue\TestJob;
|
||||
use Siteworxpro\App\Helpers\Version;
|
||||
use Siteworxpro\App\Kernel;
|
||||
use Siteworxpro\App\Services\Facades\Config;
|
||||
|
||||
class App
|
||||
{
|
||||
@@ -22,11 +21,11 @@ class App
|
||||
public function __construct()
|
||||
{
|
||||
Kernel::boot();
|
||||
$this->app = new Application('Php-Template', Version::VERSION);
|
||||
$this->app = new Application('Php-Auth', Version::VERSION);
|
||||
|
||||
$this->app->add(new DemoCommand());
|
||||
$this->app->add(new CreateClient());
|
||||
$this->app->add(new AddRedirectUri());
|
||||
$this->app->add(new Start());
|
||||
$this->app->add(new TestJob());
|
||||
}
|
||||
|
||||
public function run(): int
|
||||
|
||||
20
src/Cli/Commands/Command.php
Normal file
20
src/Cli/Commands/Command.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Cli\Commands;
|
||||
|
||||
use Ahc\Cli\Application as App;
|
||||
use League\CLImate\CLImate;
|
||||
|
||||
abstract class Command extends \Ahc\Cli\Input\Command
|
||||
{
|
||||
protected Climate $climate;
|
||||
|
||||
public function __construct(string $_name, string $_desc = '', bool $_allowUnknown = false, ?App $_app = null)
|
||||
{
|
||||
parent::__construct($_name, $_desc, $_allowUnknown, $_app);
|
||||
|
||||
$this->climate = new CLImate();
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Cli\Commands;
|
||||
|
||||
use Ahc\Cli\Input\Command;
|
||||
use Siteworxpro\App\CommandBus\Commands\ExampleCommand;
|
||||
use Siteworxpro\App\Services\Facades\CommandBus;
|
||||
|
||||
class DemoCommand extends Command implements CommandInterface
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('api:demo', 'A demo command to showcase the CLI functionality.');
|
||||
|
||||
$this->argument('[name]', 'Your name')
|
||||
->option('-g, --greet', 'Include a greeting message');
|
||||
}
|
||||
|
||||
public function execute(): int
|
||||
{
|
||||
$pb = $this->progress(100);
|
||||
|
||||
for ($i = 0; $i < 100; $i += 10) {
|
||||
usleep(100000); // Simulate work
|
||||
$pb->advance(10);
|
||||
}
|
||||
|
||||
$pb->finish();
|
||||
|
||||
$this->writer()->boldBlue("Demo Command Executed!\n");
|
||||
$name = $this->values()['name'];
|
||||
$greet = $this->values()['greet'] ?? false;
|
||||
|
||||
if ($greet) {
|
||||
$this->writer()->green("Hello, $name! Welcome to the CLI demo.\n");
|
||||
} else {
|
||||
$exampleCommand = new ExampleCommand($name);
|
||||
$this->writer()->yellow(CommandBus::handle($exampleCommand));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
62
src/Cli/Commands/OAuth/AddRedirectUri.php
Normal file
62
src/Cli/Commands/OAuth/AddRedirectUri.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Cli\Commands\OAuth;
|
||||
|
||||
use League\CLImate\TerminalObject\Dynamic\Input;
|
||||
use Siteworxpro\App\Models\ClientRedirectUri;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
|
||||
class AddRedirectUri extends \Siteworxpro\App\Cli\Commands\Command
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('oauth:redirect-uri:add', 'Add a redirect URI to an existing OAuth client.');
|
||||
}
|
||||
|
||||
public function execute(): int
|
||||
{
|
||||
$clients = Client::all('id', 'name');
|
||||
|
||||
/** @var Input $input */
|
||||
$input = $this->climate->input(
|
||||
'Select the OAuth client to add a redirect URI to' . PHP_EOL .
|
||||
$clients->map(fn(Client $client) => "[$client->id $client->name]")->implode(PHP_EOL) .
|
||||
PHP_EOL .
|
||||
'Enter the client ID: '
|
||||
);
|
||||
$input->accept(
|
||||
$clients->pluck('id')->toArray()
|
||||
);
|
||||
|
||||
$id = $input->prompt();
|
||||
|
||||
$client = Client::find($id);
|
||||
if (!$client) {
|
||||
$this->climate->error('Client not found.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** @var Input $uriInput */
|
||||
$uriInput = $this->climate->input('Enter the redirect URI to add: ');
|
||||
$uriInput->accept(function (string $value) {
|
||||
return filter_var($value, FILTER_VALIDATE_URL) !== false;
|
||||
}, 'Please enter a valid URL.');
|
||||
|
||||
$redirectUri = $uriInput->prompt();
|
||||
|
||||
$redirectUris = $client->clientRedirectUris;
|
||||
if (in_array($redirectUri, $redirectUris->toArray(), true)) {
|
||||
$this->climate->error('The redirect URI already exists for this client.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$clientRedirectUri = new ClientRedirectUri();
|
||||
$clientRedirectUri->client_id = $client->id;
|
||||
$clientRedirectUri->redirect_uri = $redirectUri;
|
||||
$clientRedirectUri->save();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
48
src/Cli/Commands/OAuth/CreateClient.php
Normal file
48
src/Cli/Commands/OAuth/CreateClient.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Cli\Commands\OAuth;
|
||||
|
||||
use Ahc\Cli\IO\Interactor;
|
||||
use Siteworxpro\App\CommandBus\Commands\CreateClient as CreateClientCommand;
|
||||
use Siteworxpro\App\CommandBus\Exceptions\CommandHandlerException;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
use Siteworxpro\App\Services\Facades\CommandBus;
|
||||
|
||||
class CreateClient extends \Siteworxpro\App\Cli\Commands\Command
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('oauth:client:create', 'Create a new OAuth client.');
|
||||
}
|
||||
|
||||
public function execute(): int
|
||||
{
|
||||
$interactor = new Interactor();
|
||||
$clientName = $interactor->prompt('Enter client name');
|
||||
$clientDescription = $interactor->prompt('Enter client description (optional)', '');
|
||||
$clientGrantsString = $interactor->prompt(
|
||||
'Enter client grants (comma separated, e.g. "authorization_code,refresh_token")',
|
||||
false
|
||||
);
|
||||
|
||||
$grants = explode(',', $clientGrantsString);
|
||||
|
||||
$command = new CreateClientCommand($clientName, $grants, $clientDescription);
|
||||
try {
|
||||
/** @var Client $client */
|
||||
$client = CommandBus::handle($command);
|
||||
|
||||
$this->climate->green('OAuth client created successfully');
|
||||
$this->climate->info('Client ID: ' . $client->client_id);
|
||||
$this->climate->info('Client Secret: ' . $client->client_secret)->br(2);
|
||||
} catch (CommandHandlerException $exception) {
|
||||
$this->climate->error($exception->getMessage());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Cli\Commands\Queue;
|
||||
|
||||
use Ahc\Cli\Input\Command;
|
||||
use Siteworxpro\App\Async\Messages\SayHelloMessage;
|
||||
use Siteworxpro\App\Cli\Commands\CommandInterface;
|
||||
|
||||
/**
|
||||
* Class TestJob
|
||||
*
|
||||
* A CLI command to schedule a demo job that dispatches a SayHelloMessage.
|
||||
*/
|
||||
class TestJob extends Command implements CommandInterface
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('queue:demo', 'Schedule a demo job.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the command to dispatch a SayHelloMessage.
|
||||
*
|
||||
* @return int Exit code
|
||||
*/
|
||||
public function execute(): int
|
||||
{
|
||||
SayHelloMessage::dispatch('World from TestJob Command!');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,50 @@ class AttributeLocator implements HandlerLocator
|
||||
public function __construct()
|
||||
{
|
||||
$directory = __DIR__ . '/Handlers';
|
||||
$this->scanDir($directory);
|
||||
}
|
||||
|
||||
public function getHandlerForCommand($commandName)
|
||||
{
|
||||
if (isset($this->handlers[$commandName])) {
|
||||
$handlerClass = $this->handlers[$commandName];
|
||||
return new $handlerClass();
|
||||
}
|
||||
|
||||
throw new CanNotInvokeHandlerException("No handler found for command: " . $commandName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $directory
|
||||
* @return void
|
||||
*/
|
||||
public function scanDir(string $directory): void
|
||||
{
|
||||
$files = scandir($directory);
|
||||
foreach ($files as $file) {
|
||||
if ($file === '.' || $file === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fullPath = $directory . DIRECTORY_SEPARATOR . $file;
|
||||
|
||||
if (is_dir($fullPath)) {
|
||||
$this->scanDir($fullPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pathinfo($file, PATHINFO_EXTENSION) === 'php') {
|
||||
$className = pathinfo($file, PATHINFO_FILENAME);
|
||||
$fullClassName = self::HANDLER_NAMESPACE . $className;
|
||||
|
||||
$relativePath = str_replace(__DIR__ . '/Handlers/', '', $fullPath);
|
||||
$namespacePath = str_replace(DIRECTORY_SEPARATOR, '\\', dirname($relativePath));
|
||||
if ($namespacePath === '.') {
|
||||
$namespacePath = '';
|
||||
} else {
|
||||
$namespacePath .= '\\';
|
||||
}
|
||||
|
||||
$fullClassName = self::HANDLER_NAMESPACE . $namespacePath . $className;
|
||||
|
||||
if (class_exists($fullClassName)) {
|
||||
$reflectionClass = new \ReflectionClass($fullClassName);
|
||||
@@ -36,14 +75,4 @@ class AttributeLocator implements HandlerLocator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getHandlerForCommand($commandName)
|
||||
{
|
||||
if (isset($this->handlers[$commandName])) {
|
||||
$handlerClass = $this->handlers[$commandName];
|
||||
return new $handlerClass();
|
||||
}
|
||||
|
||||
throw new CanNotInvokeHandlerException("No handler found for command: " . $commandName);
|
||||
}
|
||||
}
|
||||
|
||||
50
src/CommandBus/Commands/CreateClient.php
Normal file
50
src/CommandBus/Commands/CreateClient.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Siteworxpro\App\CommandBus\Commands;
|
||||
|
||||
readonly class CreateClient extends Command
|
||||
{
|
||||
private const array VALID_GRANTS = [
|
||||
'authorization_code',
|
||||
'password',
|
||||
'client_credentials',
|
||||
'refresh_token',
|
||||
'implicit',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private string $clientName,
|
||||
private array $clientGrants = [],
|
||||
private string $clientDescription = ''
|
||||
) {
|
||||
foreach ($this->clientGrants as $grant) {
|
||||
if (!in_array($grant, self::VALID_GRANTS, true)) {
|
||||
throw new \InvalidArgumentException("Invalid grant type: $grant");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getClientName(): string
|
||||
{
|
||||
return $this->clientName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getClientDescription(): string
|
||||
{
|
||||
return $this->clientDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getClientGrants(): array
|
||||
{
|
||||
return $this->clientGrants;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\CommandBus\Commands;
|
||||
|
||||
readonly class ExampleCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private string $name
|
||||
) {
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
9
src/CommandBus/Exceptions/CommandHandlerException.php
Normal file
9
src/CommandBus/Exceptions/CommandHandlerException.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\CommandBus\Exceptions;
|
||||
|
||||
class CommandHandlerException extends \InvalidArgumentException
|
||||
{
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\CommandBus\Handlers;
|
||||
|
||||
use Siteworxpro\App\Attributes\CommandBus\HandlesCommand;
|
||||
use Siteworxpro\App\CommandBus\Commands\Command;
|
||||
use Siteworxpro\App\CommandBus\Commands\ExampleCommand;
|
||||
use Siteworxpro\App\Services\Facades\Logger;
|
||||
|
||||
#[HandlesCommand(ExampleCommand::class)]
|
||||
class ExampleHandler extends CommandHandler
|
||||
{
|
||||
/**
|
||||
* @param Command|ExampleCommand $command
|
||||
* @return string
|
||||
*/
|
||||
public function __invoke(Command|ExampleCommand $command): string
|
||||
{
|
||||
if (!method_exists($command, 'getName')) {
|
||||
throw new \TypeError('Invalid command type provided to ExampleHandler.');
|
||||
}
|
||||
|
||||
$name = $command->getName();
|
||||
Logger::info('Handling ExampleCommand for name: ' . $name);
|
||||
|
||||
return 'Hello, ' . $name . '!';
|
||||
}
|
||||
}
|
||||
31
src/CommandBus/Handlers/OAuth/CreateClient.php
Normal file
31
src/CommandBus/Handlers/OAuth/CreateClient.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\CommandBus\Handlers\OAuth;
|
||||
|
||||
use Siteworxpro\App\Attributes\CommandBus\HandlesCommand;
|
||||
use Siteworxpro\App\CommandBus\Commands\Command;
|
||||
use Siteworxpro\App\CommandBus\Exceptions\CommandHandlerException;
|
||||
use Siteworxpro\App\CommandBus\Handlers\CommandHandler;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
|
||||
#[HandlesCommand(\Siteworxpro\App\CommandBus\Commands\CreateClient::class)]
|
||||
class CreateClient extends CommandHandler
|
||||
{
|
||||
public function __invoke(Command $command): Client
|
||||
{
|
||||
if (!$command instanceof \Siteworxpro\App\CommandBus\Commands\CreateClient) {
|
||||
throw new CommandHandlerException('Invalid command type');
|
||||
}
|
||||
|
||||
$client = new Client();
|
||||
$client->name = $command->getClientName();
|
||||
$client->description = $command->getClientDescription();
|
||||
$client->grant_types = $command->getClientGrants();
|
||||
|
||||
$client->save();
|
||||
|
||||
return $client;
|
||||
}
|
||||
}
|
||||
165
src/Controllers/AuthorizeController.php
Normal file
165
src/Controllers/AuthorizeController.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Controllers;
|
||||
|
||||
use HansOtt\PSR7Cookies\SetCookie;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use Nyholm\Psr7\Response;
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
use Nyholm\Psr7\Stream;
|
||||
use Psr\SimpleCache\InvalidArgumentException;
|
||||
use Siteworxpro\App\Helpers\Rand;
|
||||
use Siteworxpro\App\Http\JsonResponseFactory;
|
||||
use Siteworxpro\App\Http\Responses\ServerErrorResponse;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
use Siteworxpro\App\Services\Facades\Logger;
|
||||
use Siteworxpro\App\Services\Facades\Redis;
|
||||
use Siteworxpro\HttpStatus\CodesEnum;
|
||||
|
||||
final class AuthorizeController extends Controller
|
||||
{
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
// #[\Override] public function post(ServerRequest $request): Response
|
||||
// {
|
||||
// $s = $request->getCookieParams()['s'] ?? '';
|
||||
//
|
||||
// $password = $request->getParsedBody()['password'] ?? '';
|
||||
// $email = $request->getParsedBody()['email'] ?? '';
|
||||
//
|
||||
// if (!$this->redis->get('session:' . $s)) {
|
||||
// $this->log->error('Session Timed out', ['session' => $s]);
|
||||
//
|
||||
// return $this->sendJsonResponse(
|
||||
// [
|
||||
// 'error' => "your login session has timed out. please try again."
|
||||
// ],
|
||||
// 400
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// /** @var AuthorizationRequest $authRequest */
|
||||
// $authRequest = unserialize($this->redis->get('session:' . $s));
|
||||
//
|
||||
// if ($authRequest->isAuthorizationApproved()) {
|
||||
// $response = $this
|
||||
// ->authorizationServer
|
||||
// ->completeAuthorizationRequest($authRequest, $this->sendJsonResponse());
|
||||
//
|
||||
// return $this->sendJsonResponse(
|
||||
// [
|
||||
// 'success' => true,
|
||||
// 'location' => $response->getHeader('Location')[0]
|
||||
// ]
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// /** @var Client $client */
|
||||
// $client = $authRequest->getClient();
|
||||
//
|
||||
// /** @var LoginInterface $entitiesModel */
|
||||
// $entitiesModel = $client->entities_model;
|
||||
//
|
||||
// /** @var User | null $entity */
|
||||
// $entity = $entitiesModel::performLogin($email, $password);
|
||||
// if (!$entity) {
|
||||
// return $this->sendJsonResponse(
|
||||
// [
|
||||
// 'success' => false,
|
||||
// 'reason' => 'login failed'
|
||||
// ],
|
||||
// 401
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// $authRequest->setUser($entity);
|
||||
// $authRequest->setAuthorizationApproved(true);
|
||||
// $response = $this
|
||||
// ->authorizationServer
|
||||
// ->completeAuthorizationRequest($authRequest, $this->sendJsonResponse());
|
||||
//
|
||||
// $this->redis->delete('session:' . $s);
|
||||
//
|
||||
// return $this->sendJsonResponse(
|
||||
// [
|
||||
// 'success' => true,
|
||||
// 'location' => $response->getHeader('Location')[0]
|
||||
// ]
|
||||
// );
|
||||
// }
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function get(ServerRequest $request): Response
|
||||
{
|
||||
try {
|
||||
if (!file_exists('public/index.html')) {
|
||||
throw new \RuntimeException('Frontend not built. Please run `npm run build`.');
|
||||
}
|
||||
$contents = file_get_contents('public/index.html');
|
||||
|
||||
if ($request->getQueryParams()['e']) {
|
||||
return new Response(
|
||||
200,
|
||||
['content-type' => 'text/html'],
|
||||
Stream::create($contents)
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
isset($request->getCookieParams()['s']) &&
|
||||
Redis::exists('session:' . $request->getCookieParams()['s'] ?? '')
|
||||
) {
|
||||
$s = $request->getCookieParams()['s'];
|
||||
} else {
|
||||
$s = Rand::string();
|
||||
}
|
||||
|
||||
$clientId = $request->getQueryParams()['client_id'] ?? '';
|
||||
Logger::info('Authorization request', ['client_id' => $clientId]);
|
||||
|
||||
$client = Client::byClientId($clientId);
|
||||
if ($client === null) {
|
||||
Logger::warning('Invalid client in authorization request', ['client_id' => $clientId]);
|
||||
throw OAuthServerException::invalidClient($request);
|
||||
}
|
||||
|
||||
$authRequest = $client->getAuthorizationServer()->validateAuthorizationRequest($request);
|
||||
Redis::set('session:' . $s, serialize($authRequest), 'EX', 60 * 60 * 24);
|
||||
|
||||
$response = new Response(
|
||||
200,
|
||||
['content-type' => 'text/html'],
|
||||
Stream::create($contents)
|
||||
);
|
||||
|
||||
$cookie = new SetCookie('s', $s, time() + 3600, '/', secure: true);
|
||||
|
||||
/** @var Response $response */
|
||||
$response = $cookie->addToResponse($response);
|
||||
|
||||
return $response;
|
||||
} catch (OAuthServerException $e) {
|
||||
return new Response(
|
||||
CodesEnum::TEMPORARY_REDIRECT->value,
|
||||
[
|
||||
'Location' => sprintf(
|
||||
'/authorize?e=%s&client_id=%s&response_type=%s&redirect_uri=%s#/error',
|
||||
$e->getMessage(),
|
||||
$request->getQueryParams()['client_id'] ?? '',
|
||||
$request->getQueryParams()['response_type'] ?? '',
|
||||
$request->getQueryParams()['redirect_uri'] ?? ''
|
||||
)
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Logger::error($e->getMessage(), ['exception' => $e]);
|
||||
|
||||
return JsonResponseFactory::createJsonResponse(new ServerErrorResponse($e));
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/Controllers/CapabilitiesController.php
Normal file
30
src/Controllers/CapabilitiesController.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Controllers;
|
||||
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Siteworxpro\App\Http\JsonResponseFactory;
|
||||
use Siteworxpro\App\Http\Responses\NotFoundResponse;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
|
||||
final class CapabilitiesController extends Controller
|
||||
{
|
||||
/**
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function get(ServerRequest $request): ResponseInterface
|
||||
{
|
||||
$clientId = $request->getQueryParams()['client_id'] ?? '0';
|
||||
|
||||
$client = Client::byClientId($clientId);
|
||||
|
||||
if (!$client) {
|
||||
return JsonResponseFactory::createJsonResponse(new NotFoundResponse($request->getUri()->getPath()));
|
||||
}
|
||||
|
||||
return JsonResponseFactory::createJsonResponse($client->capabilities->toArray());
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ use OpenApi\Attributes as OA;
|
||||
*
|
||||
* @package Siteworxpro\App\Controllers
|
||||
*/
|
||||
class HealthcheckController extends Controller
|
||||
final class HealthcheckController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handles the GET request for health check.
|
||||
|
||||
@@ -20,7 +20,7 @@ use Siteworxpro\App\Services\Facades\CommandBus;
|
||||
*
|
||||
* This class handles the index route of the application.
|
||||
*/
|
||||
class IndexController extends Controller
|
||||
final class IndexController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handles the GET request for the index route.
|
||||
|
||||
@@ -9,7 +9,7 @@ use Nyholm\Psr7\ServerRequest;
|
||||
use OpenApi\Generator;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class OpenApiController extends Controller
|
||||
final class OpenApiController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handles the GET request to generate and return the OpenAPI specification.
|
||||
|
||||
@@ -28,8 +28,6 @@ class Connected extends Listener
|
||||
throw new \TypeError("Invalid event type passed to listener " . static::class);
|
||||
}
|
||||
|
||||
Logger::info("Database connection event", [get_class($event), $event->connectionName]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
24
src/Helpers/Rand.php
Normal file
24
src/Helpers/Rand.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Helpers;
|
||||
|
||||
use Random\RandomException;
|
||||
|
||||
class Rand
|
||||
{
|
||||
/**
|
||||
* @throws RandomException
|
||||
*/
|
||||
public static function string(int $length = 16): string
|
||||
{
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$charactersLength = strlen($characters);
|
||||
$randomString = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$randomString .= $characters[random_int(0, $charactersLength - 1)];
|
||||
}
|
||||
return $randomString;
|
||||
}
|
||||
}
|
||||
26
src/Models/ClientRedirectUri.php
Normal file
26
src/Models/ClientRedirectUri.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
|
||||
/**
|
||||
* Class ClientRedirectUrl
|
||||
* @package Siteworxpro\App\Models
|
||||
*
|
||||
* @property string $id
|
||||
* @property string $client_id
|
||||
* @property string $redirect_uri
|
||||
*/
|
||||
class ClientRedirectUri extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
|
||||
public function client(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Client::class);
|
||||
}
|
||||
}
|
||||
18
src/Models/ClientScope.php
Normal file
18
src/Models/ClientScope.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Models;
|
||||
|
||||
/**
|
||||
* Class ClientScope
|
||||
* @package Siteworxpro\App\Models
|
||||
*
|
||||
* @property string $id
|
||||
* @property string $client_id
|
||||
* @property string $scope_id
|
||||
*/
|
||||
class ClientScope extends Model
|
||||
{
|
||||
|
||||
}
|
||||
18
src/Models/ClientUser.php
Normal file
18
src/Models/ClientUser.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Models;
|
||||
|
||||
/**
|
||||
* Class ClientUser
|
||||
* @package Siteworxpro\App\Models
|
||||
*
|
||||
* @property string $id
|
||||
* @property string $client_id
|
||||
* @property string $user_id
|
||||
*/
|
||||
class ClientUser extends Model
|
||||
{
|
||||
|
||||
}
|
||||
@@ -10,6 +10,8 @@ use Illuminate\Database\Eloquent\Model as ORM;
|
||||
* Class Model
|
||||
*
|
||||
* @package Siteworxpro\App\Models
|
||||
* @method static static|null find(string $id, array $columns = ['*'])
|
||||
* @method static where(string $column, string $operator = null, string $value = null, string $boolean = 'and')
|
||||
*/
|
||||
abstract class Model extends ORM
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Siteworxpro\App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Siteworxpro\App\Helpers\Ulid;
|
||||
|
||||
@@ -40,6 +41,8 @@ use Siteworxpro\App\Helpers\Ulid;
|
||||
)]
|
||||
class User extends Model
|
||||
{
|
||||
use EntityTrait;
|
||||
|
||||
protected $casts = [
|
||||
'created_at' => 'datetime',
|
||||
];
|
||||
|
||||
50
src/OAuth/AccessTokenRepository.php
Normal file
50
src/OAuth/AccessTokenRepository.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth;
|
||||
|
||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
|
||||
use Siteworxpro\App\OAuth\Entities\AccessToken;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
|
||||
class AccessTokenRepository implements AccessTokenRepositoryInterface
|
||||
{
|
||||
public function getNewToken(
|
||||
ClientEntityInterface | Client $clientEntity,
|
||||
array $scopes,
|
||||
?string $userIdentifier = null
|
||||
): AccessTokenEntityInterface | AccessToken {
|
||||
$accessToken = new AccessToken();
|
||||
$accessToken->setClient($clientEntity);
|
||||
foreach ($scopes as $scope) {
|
||||
$accessToken->addScope($scope);
|
||||
}
|
||||
$accessToken->setUserIdentifier($userIdentifier);
|
||||
|
||||
return $accessToken;
|
||||
}
|
||||
|
||||
public function persistNewAccessToken(AccessTokenEntityInterface | AccessToken $accessTokenEntity): void
|
||||
{
|
||||
$accessTokenEntity->save();
|
||||
}
|
||||
|
||||
public function revokeAccessToken(string $tokenId): void
|
||||
{
|
||||
$accessToken = AccessToken::find($tokenId);
|
||||
|
||||
if ($accessToken) {
|
||||
$accessToken->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public function isAccessTokenRevoked(string $tokenId): bool
|
||||
{
|
||||
$accessToken = AccessToken::find($tokenId);
|
||||
|
||||
return $accessToken === null;
|
||||
}
|
||||
}
|
||||
31
src/OAuth/AuthCodeRepository.php
Normal file
31
src/OAuth/AuthCodeRepository.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth;
|
||||
|
||||
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
|
||||
|
||||
class AuthCodeRepository implements AuthCodeRepositoryInterface
|
||||
{
|
||||
public function getNewAuthCode(): AuthCodeEntityInterface
|
||||
{
|
||||
// TODO: Implement getNewAuthCode() method.
|
||||
}
|
||||
|
||||
public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity): void
|
||||
{
|
||||
// TODO: Implement persistNewAuthCode() method.
|
||||
}
|
||||
|
||||
public function revokeAuthCode(string $codeId): void
|
||||
{
|
||||
// TODO: Implement revokeAuthCode() method.
|
||||
}
|
||||
|
||||
public function isAuthCodeRevoked(string $codeId): bool
|
||||
{
|
||||
// TODO: Implement isAuthCodeRevoked() method.
|
||||
}
|
||||
}
|
||||
59
src/OAuth/ClientRepository.php
Normal file
59
src/OAuth/ClientRepository.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth;
|
||||
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
|
||||
readonly class ClientRepository implements ClientRepositoryInterface
|
||||
{
|
||||
public function __construct(private Client $client)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* get a client entity.
|
||||
*
|
||||
* @param string $clientIdentifier
|
||||
* @return ClientEntityInterface|null
|
||||
*/
|
||||
public function getClientEntity(string $clientIdentifier): ?ClientEntityInterface
|
||||
{
|
||||
|
||||
if ($this->client->client_id === $clientIdentifier) {
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* validate a client with given data.
|
||||
*
|
||||
* @param string $clientIdentifier
|
||||
* @param string|null $clientSecret
|
||||
* @param string|null $grantType
|
||||
* @return bool
|
||||
*/
|
||||
public function validateClient(string $clientIdentifier, ?string $clientSecret, ?string $grantType): bool
|
||||
{
|
||||
$client = Client::find($clientIdentifier);
|
||||
|
||||
if ($client === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($clientSecret && $client->client_secret != $clientSecret) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($grantType && !in_array($grantType, $client->grant_types)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
61
src/OAuth/Entities/AccessToken.php
Normal file
61
src/OAuth/Entities/AccessToken.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth\Entities;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
||||
use League\OAuth2\Server\Entities\Traits\AccessTokenTrait;
|
||||
|
||||
class AccessToken extends RedisModel implements AccessTokenEntityInterface
|
||||
{
|
||||
use AccessTokenTrait;
|
||||
|
||||
public function getClient(): ClientEntityInterface
|
||||
{
|
||||
// TODO: Implement getClient() method.
|
||||
}
|
||||
|
||||
public function getExpiryDateTime(): DateTimeImmutable
|
||||
{
|
||||
// TODO: Implement getExpiryDateTime() method.
|
||||
}
|
||||
|
||||
public function getUserIdentifier(): string|null
|
||||
{
|
||||
// TODO: Implement getUserIdentifier() method.
|
||||
}
|
||||
|
||||
public function getScopes(): array
|
||||
{
|
||||
// TODO: Implement getScopes() method.
|
||||
}
|
||||
|
||||
protected static function getRedisPrefix(): string
|
||||
{
|
||||
return 'oauth_access_token';
|
||||
}
|
||||
|
||||
public function setExpiryDateTime(DateTimeImmutable $dateTime): void
|
||||
{
|
||||
// TODO: Implement setExpiryDateTime() method.
|
||||
}
|
||||
|
||||
public function setUserIdentifier(string $identifier): void
|
||||
{
|
||||
// TODO: Implement setUserIdentifier() method.
|
||||
}
|
||||
|
||||
public function setClient(ClientEntityInterface $client): void
|
||||
{
|
||||
// TODO: Implement setClient() method.
|
||||
}
|
||||
|
||||
public function addScope(ScopeEntityInterface $scope): void
|
||||
{
|
||||
// TODO: Implement addScope() method.
|
||||
}
|
||||
}
|
||||
61
src/OAuth/Entities/AuthorizationCode.php
Normal file
61
src/OAuth/Entities/AuthorizationCode.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth\Entities;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
||||
use League\OAuth2\Server\Entities\Traits\AuthCodeTrait;
|
||||
|
||||
class AuthorizationCode extends RedisModel implements AuthCodeEntityInterface
|
||||
{
|
||||
use AuthCodeTrait;
|
||||
|
||||
protected static function getRedisPrefix(): string
|
||||
{
|
||||
return 'oauth_auth_code';
|
||||
}
|
||||
|
||||
public function getExpiryDateTime(): DateTimeImmutable
|
||||
{
|
||||
// TODO: Implement getExpiryDateTime() method.
|
||||
}
|
||||
|
||||
public function setExpiryDateTime(DateTimeImmutable $dateTime): void
|
||||
{
|
||||
// TODO: Implement setExpiryDateTime() method.
|
||||
}
|
||||
|
||||
public function setUserIdentifier(string $identifier): void
|
||||
{
|
||||
// TODO: Implement setUserIdentifier() method.
|
||||
}
|
||||
|
||||
public function getUserIdentifier(): string|null
|
||||
{
|
||||
// TODO: Implement getUserIdentifier() method.
|
||||
}
|
||||
|
||||
public function getClient(): ClientEntityInterface
|
||||
{
|
||||
// TODO: Implement getClient() method.
|
||||
}
|
||||
|
||||
public function setClient(ClientEntityInterface $client): void
|
||||
{
|
||||
// TODO: Implement setClient() method.
|
||||
}
|
||||
|
||||
public function addScope(ScopeEntityInterface $scope): void
|
||||
{
|
||||
// TODO: Implement addScope() method.
|
||||
}
|
||||
|
||||
public function getScopes(): array
|
||||
{
|
||||
// TODO: Implement getScopes() method.
|
||||
}
|
||||
}
|
||||
206
src/OAuth/Entities/Client.php
Normal file
206
src/OAuth/Entities/Client.php
Normal file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth\Entities;
|
||||
|
||||
use Defuse\Crypto\Exception\BadFormatException;
|
||||
use Defuse\Crypto\Exception\EnvironmentIsBrokenException;
|
||||
use Defuse\Crypto\Key;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use League\OAuth2\Server\AuthorizationServer;
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
|
||||
use Random\RandomException;
|
||||
use Siteworxpro\App\Helpers\Rand;
|
||||
use Siteworxpro\App\Models\ClientRedirectUri;
|
||||
use Siteworxpro\App\Models\ClientScope;
|
||||
use Siteworxpro\App\Models\ClientUser;
|
||||
use Siteworxpro\App\Models\Model;
|
||||
use Siteworxpro\App\Models\User;
|
||||
use Siteworxpro\App\OAuth\AccessTokenRepository;
|
||||
use Siteworxpro\App\OAuth\ClientRepository;
|
||||
use Siteworxpro\App\OAuth\ScopeRepository;
|
||||
|
||||
/**
|
||||
* Class Client
|
||||
* @package Siteworxpro\App\Models
|
||||
*
|
||||
* @property string $id
|
||||
* @property string $client_id
|
||||
* @property string $client_secret
|
||||
* @property string $name
|
||||
* @property string $description
|
||||
* @property string $private_key
|
||||
* @property string $encryption_key
|
||||
* @property string[] $grant_types
|
||||
* @property bool $confidential
|
||||
*
|
||||
* @property-read ClientCapabilities $capabilities
|
||||
* @property-read Collection<ClientRedirectUri> $clientRedirectUris
|
||||
* @property-read Scope[]|Collection $scopes
|
||||
*/
|
||||
class Client extends Model implements ClientEntityInterface
|
||||
{
|
||||
use EntityTrait;
|
||||
|
||||
protected $casts = [
|
||||
'id' => 'string',
|
||||
'grant_types' => 'collection',
|
||||
'confidential' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws RandomException|EnvironmentIsBrokenException
|
||||
*/
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
parent::__construct($attributes);
|
||||
|
||||
$this->client_id = Rand::string(32);
|
||||
$this->client_secret = Rand::string(64);
|
||||
$this->generatePrivateKey();
|
||||
}
|
||||
|
||||
public static function byClientId(string $clientId): ?Client
|
||||
{
|
||||
return self::where('client_id', $clientId)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws EnvironmentIsBrokenException
|
||||
*/
|
||||
private function generatePrivateKey(): void
|
||||
{
|
||||
// generate rsa private and public key pair
|
||||
$config = [
|
||||
"digest_alg" => "sha256",
|
||||
"private_key_bits" => 4096,
|
||||
"private_key_type" => OPENSSL_KEYTYPE_RSA,
|
||||
];
|
||||
|
||||
$res = openssl_pkey_new($config);
|
||||
openssl_pkey_export($res, $privateKey);
|
||||
$this->private_key = $privateKey;
|
||||
$this->encryption_key = Key::createNewRandomKey()->saveToAsciiSafeString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany
|
||||
*/
|
||||
public function clientRedirectUris(): HasMany
|
||||
{
|
||||
return $this->hasMany(ClientRedirectUri::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasManyThrough
|
||||
*/
|
||||
public function scopes(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(Scope::class, ClientScope::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasManyThrough
|
||||
*/
|
||||
public function users(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(User::class, ClientUser::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|array
|
||||
*/
|
||||
public function getRedirectUri(): string|array
|
||||
{
|
||||
return $this->clientRedirectUris->pluck('redirect_uri')->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isConfidential(): bool
|
||||
{
|
||||
return $this->confidential;
|
||||
}
|
||||
|
||||
public function getCapabilitiesAttribute(string $capabilities): ClientCapabilities
|
||||
{
|
||||
return ClientCapabilities::fromJson($capabilities);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function setCapabilitiesAttribute(ClientCapabilities $capabilities): void
|
||||
{
|
||||
$this->attributes->capabilities = $capabilities->toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadFormatException
|
||||
* @throws EnvironmentIsBrokenException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getAuthorizationServer(): AuthorizationServer
|
||||
{
|
||||
|
||||
$authorizationServer = new AuthorizationServer(
|
||||
new ClientRepository($this),
|
||||
new AccessTokenRepository(),
|
||||
new ScopeRepository(),
|
||||
$this->private_key,
|
||||
Key::loadFromAsciiSafeString($this->encryption_key)
|
||||
);
|
||||
|
||||
if (!empty($this->grant_types)) {
|
||||
foreach ($this->grant_types as $grantType) {
|
||||
switch ($grantType) {
|
||||
case 'authorization_code':
|
||||
$grant = new \League\OAuth2\Server\Grant\AuthCodeGrant(
|
||||
new \Siteworxpro\App\OAuth\AuthCodeRepository(),
|
||||
new \Siteworxpro\App\OAuth\RefreshTokenRepository(),
|
||||
new \DateInterval('PT10M')
|
||||
);
|
||||
$grant->setRefreshTokenTTL(new \DateInterval('P1M'));
|
||||
break;
|
||||
case 'client_credentials':
|
||||
$grant = new \League\OAuth2\Server\Grant\ClientCredentialsGrant();
|
||||
break;
|
||||
case 'refresh_token':
|
||||
$grant = new \League\OAuth2\Server\Grant\RefreshTokenGrant(
|
||||
new \Siteworxpro\App\OAuth\RefreshTokenRepository()
|
||||
);
|
||||
$grant->setRefreshTokenTTL(new \DateInterval('P1M'));
|
||||
break;
|
||||
default:
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$authorizationServer->enableGrantType($grant);
|
||||
}
|
||||
}
|
||||
|
||||
return $authorizationServer;
|
||||
}
|
||||
}
|
||||
74
src/OAuth/Entities/ClientCapabilities.php
Normal file
74
src/OAuth/Entities/ClientCapabilities.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth\Entities;
|
||||
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
|
||||
class ClientCapabilities implements Arrayable
|
||||
{
|
||||
private bool $userPass = false;
|
||||
private bool $magicLink = false;
|
||||
private bool $passkey = false;
|
||||
private array $socials = [];
|
||||
|
||||
private array $theme = [
|
||||
'primaryColor' => '#000000',
|
||||
'secondaryColor' => '#FFFFFF',
|
||||
'logoUrl' => null,
|
||||
];
|
||||
|
||||
public function __construct(array $capabilities)
|
||||
{
|
||||
if (isset($capabilities['userPass'])) {
|
||||
$this->userPass = (bool)$capabilities['userPass'];
|
||||
}
|
||||
|
||||
if (isset($capabilities['magicLink'])) {
|
||||
$this->magicLink = (bool)$capabilities['magicLink'];
|
||||
}
|
||||
|
||||
if (isset($capabilities['passkey'])) {
|
||||
$this->passkey = (bool)$capabilities['passkey'];
|
||||
}
|
||||
|
||||
if (isset($capabilities['socials']) && is_array($capabilities['socials'])) {
|
||||
$this->socials = $capabilities['socials'];
|
||||
}
|
||||
|
||||
if (isset($capabilities['theme']) && is_array($capabilities['theme'])) {
|
||||
$this->theme = array_merge($this->theme, $capabilities['theme']);
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromJson(string $data): self
|
||||
{
|
||||
try {
|
||||
$arrayData = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
return new self($arrayData);
|
||||
} catch (\JsonException $e) {
|
||||
return new self([]);
|
||||
}
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'userPass' => $this->userPass,
|
||||
'magicLink' => $this->magicLink,
|
||||
'passkey' => $this->passkey,
|
||||
'socials' => $this->socials,
|
||||
'theme' => $this->theme,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function toJson(): string
|
||||
{
|
||||
return json_encode($this->toArray(), JSON_THROW_ON_ERROR);
|
||||
}
|
||||
}
|
||||
60
src/OAuth/Entities/RedisModel.php
Normal file
60
src/OAuth/Entities/RedisModel.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth\Entities;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DateTimeImmutable;
|
||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
|
||||
use Psr\SimpleCache\InvalidArgumentException;
|
||||
use Siteworxpro\App\Services\Facades\Redis;
|
||||
|
||||
abstract class RedisModel
|
||||
{
|
||||
use EntityTrait;
|
||||
|
||||
private \Predis\Client $redis;
|
||||
|
||||
protected ?DateTimeImmutable $expireTime;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->redis = Redis::getFacadeRoot();
|
||||
}
|
||||
|
||||
abstract protected static function getRedisPrefix(): string;
|
||||
|
||||
public static function find(string $identifier): ?self
|
||||
{
|
||||
$instance = Redis::get(static::getRedisPrefix() . ':' . $identifier);
|
||||
|
||||
if ($instance !== null) {
|
||||
return unserialize($instance);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$diff = 0;
|
||||
if ($this->expireTime) {
|
||||
$diff = $this->expireTime->getTimestamp() - Carbon::now()->timestamp;
|
||||
}
|
||||
|
||||
$this->redis->set(
|
||||
static::getRedisPrefix() . ':' . $this->getIdentifier(),
|
||||
serialize($this),
|
||||
$diff
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
$this->redis->delete(static::getRedisPrefix() . ':' . $this->getIdentifier());
|
||||
}
|
||||
}
|
||||
18
src/OAuth/Entities/RefreshToken.php
Normal file
18
src/OAuth/Entities/RefreshToken.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth\Entities;
|
||||
|
||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\Traits\RefreshTokenTrait;
|
||||
|
||||
class RefreshToken extends RedisModel implements RefreshTokenEntityInterface
|
||||
{
|
||||
use RefreshTokenTrait;
|
||||
|
||||
protected static function getRedisPrefix(): string
|
||||
{
|
||||
return 'oauth_refresh_token';
|
||||
}
|
||||
}
|
||||
27
src/OAuth/Entities/Scope.php
Normal file
27
src/OAuth/Entities/Scope.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth\Entities;
|
||||
|
||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
||||
use League\OAuth2\Server\Entities\Traits\ScopeTrait;
|
||||
use Siteworxpro\App\Models\Model;
|
||||
|
||||
/**
|
||||
* Class Scope
|
||||
* @package Siteworxpro\App\Models
|
||||
*
|
||||
* @property string $id
|
||||
* @property string $name
|
||||
* @property string $description
|
||||
*/
|
||||
class Scope extends Model implements ScopeEntityInterface
|
||||
{
|
||||
use ScopeTrait;
|
||||
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
32
src/OAuth/RefreshTokenRepository.php
Normal file
32
src/OAuth/RefreshTokenRepository.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth;
|
||||
|
||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
|
||||
|
||||
class RefreshTokenRepository implements RefreshTokenRepositoryInterface
|
||||
{
|
||||
|
||||
public function getNewRefreshToken(): ?RefreshTokenEntityInterface
|
||||
{
|
||||
// TODO: Implement getNewRefreshToken() method.
|
||||
}
|
||||
|
||||
public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity): void
|
||||
{
|
||||
// TODO: Implement persistNewRefreshToken() method.
|
||||
}
|
||||
|
||||
public function revokeRefreshToken(string $tokenId): void
|
||||
{
|
||||
// TODO: Implement revokeRefreshToken() method.
|
||||
}
|
||||
|
||||
public function isRefreshTokenRevoked(string $tokenId): bool
|
||||
{
|
||||
// TODO: Implement isRefreshTokenRevoked() method.
|
||||
}
|
||||
}
|
||||
28
src/OAuth/ScopeRepository.php
Normal file
28
src/OAuth/ScopeRepository.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth;
|
||||
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
|
||||
use Siteworxpro\App\OAuth\Entities\Scope;
|
||||
|
||||
class ScopeRepository implements ScopeRepositoryInterface
|
||||
{
|
||||
public function getScopeEntityByIdentifier(string $identifier): ?ScopeEntityInterface
|
||||
{
|
||||
return Scope::where('name', $identifier)->first();
|
||||
}
|
||||
|
||||
public function finalizeScopes(
|
||||
array $scopes,
|
||||
string $grantType,
|
||||
ClientEntityInterface $clientEntity,
|
||||
?string $userIdentifier = null,
|
||||
?string $authCodeId = null
|
||||
): array {
|
||||
return $scopes;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ use Siteworxpro\App\Services\Facade;
|
||||
* @method static Status|null set(string $key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
|
||||
* @method static array keys(string $pattern)
|
||||
* @method static int del(string $key)
|
||||
* @method static bool exists(string $key)
|
||||
* @method static Status ping()
|
||||
*/
|
||||
class Redis extends Facade
|
||||
|
||||
Reference in New Issue
Block a user