You've already forked php-auth
generated from siteworxpro/Php-Template
Basics of auth
Some checks failed
🧪✨ Tests Workflow / 🛡️ 🔒 Library Audit (push) Successful in 2m31s
🧪✨ Tests Workflow / 📝 ✨ Code Lint (push) Successful in 2m24s
🧪✨ Tests Workflow / 🛡️ 🔒 License Check (push) Successful in 2m57s
🧪✨ Tests Workflow / 🧪 ✨ Database Migrations (push) Successful in 3m14s
🧪✨ Tests Workflow / 🐙 🔍 Code Sniffer (push) Failing after 2m58s
🧪✨ Tests Workflow / 🧪 ✅ Unit Tests (push) Failing after 1m24s
Some checks failed
🧪✨ Tests Workflow / 🛡️ 🔒 Library Audit (push) Successful in 2m31s
🧪✨ Tests Workflow / 📝 ✨ Code Lint (push) Successful in 2m24s
🧪✨ Tests Workflow / 🛡️ 🔒 License Check (push) Successful in 2m57s
🧪✨ Tests Workflow / 🧪 ✨ Database Migrations (push) Successful in 3m14s
🧪✨ Tests Workflow / 🐙 🔍 Code Sniffer (push) Failing after 2m58s
🧪✨ Tests Workflow / 🧪 ✅ Unit Tests (push) Failing after 1m24s
This commit is contained in:
16
.dev/.env
Normal file
16
.dev/.env
Normal file
@@ -0,0 +1,16 @@
|
||||
JWT_ISSUER: https://auth.siteworxpro.com/application/o/postman/
|
||||
JWT_AUDIENCE: 1RWyqJFlyA4hmsDzq6kSxs0LXvk7UgEAfgmBCpQ9
|
||||
JWT_SIGNING_KEY: https://auth.siteworxpro.com/application/o/postman/.well-known/openid-configuration
|
||||
QUEUE_BROKER: redis
|
||||
PHP_IDE_CONFIG: serverName=localhost
|
||||
WORKERS: 1
|
||||
GRPC_WORKERS: 1
|
||||
DEBUG: 1
|
||||
REDIS_HOST: redis
|
||||
DB_HOST: postgres
|
||||
DEV_MODE: 1
|
||||
APP_ENCRYPTION_KEY: base64:40U+IWaPTpp5o23quMfxcZJ0lOzkNy07SQ1rH6AV13o=
|
||||
DB_USERNAME: siteworxpro
|
||||
DB_PASSWORD: password
|
||||
DB_DATABASE: siteworxpro
|
||||
DB_PORT: 5432
|
||||
@@ -73,12 +73,8 @@ services:
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DB_USERNAME: ${DB_USERNAME:-siteworxpro}
|
||||
DB_PASSWORD: ${DB_PASSWORD:-password}
|
||||
DB_DATABASE: ${DB_DATABASE:-siteworxpro}
|
||||
DB_HOST: ${DB_HOST-postgres}
|
||||
DB_PORT: ${DB_PORT-5432}
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
dev-runtime:
|
||||
labels:
|
||||
@@ -116,18 +112,8 @@ services:
|
||||
condition: service_healthy
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
JWT_ISSUER: https://auth.siteworxpro.com/application/o/postman/
|
||||
JWT_AUDIENCE: 1RWyqJFlyA4hmsDzq6kSxs0LXvk7UgEAfgmBCpQ9
|
||||
JWT_SIGNING_KEY: https://auth.siteworxpro.com/application/o/postman/.well-known/openid-configuration
|
||||
QUEUE_BROKER: redis
|
||||
PHP_IDE_CONFIG: serverName=localhost
|
||||
WORKERS: 1
|
||||
GRPC_WORKERS: 1
|
||||
DEBUG: 1
|
||||
REDIS_HOST: redis
|
||||
DB_HOST: postgres
|
||||
DEV_MODE: 1
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
## Kafka and Zookeeper for local development
|
||||
kafka-ui:
|
||||
|
||||
@@ -7,6 +7,8 @@ return [
|
||||
'app' => [
|
||||
'log_level' => Env::get('LOG_LEVEL', 'debug'),
|
||||
'dev_mode' => Env::get('DEV_MODE', false, 'bool'),
|
||||
'url' => Env::get('APP_URL', 'https://localhost'),
|
||||
'encryption_key' => Env::get('APP_ENCRYPTION_KEY', 'base64:change_me'),
|
||||
],
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
create table clients
|
||||
(
|
||||
id uuid default gen_random_uuid()
|
||||
id VARCHAR(26) not null
|
||||
constraint client_pk
|
||||
primary key,
|
||||
client_id varchar not null
|
||||
@@ -20,10 +20,10 @@ create table clients
|
||||
|
||||
create table client_redirect_uris
|
||||
(
|
||||
id uuid default gen_random_uuid()
|
||||
id VARCHAR(26) not null
|
||||
constraint client_redirect_uris_pk
|
||||
primary key,
|
||||
client_id uuid not null
|
||||
client_id VARCHAR(26) not null
|
||||
constraint client_redirect_uris_client_id_fk
|
||||
references clients
|
||||
on delete cascade,
|
||||
@@ -32,7 +32,7 @@ create table client_redirect_uris
|
||||
|
||||
create table scopes
|
||||
(
|
||||
id uuid default gen_random_uuid()
|
||||
id VARCHAR(26) not null
|
||||
constraint scopes_pk
|
||||
primary key,
|
||||
name varchar not null
|
||||
@@ -43,14 +43,14 @@ create table scopes
|
||||
|
||||
create table client_scopes
|
||||
(
|
||||
id uuid default gen_random_uuid()
|
||||
id VARCHAR(26) not null
|
||||
constraint client_scopes_pk
|
||||
primary key,
|
||||
client_id uuid not null
|
||||
client_id VARCHAR(26) not null
|
||||
constraint client_scopes_client_id_fk
|
||||
references clients
|
||||
on delete cascade,
|
||||
scope_id uuid not null
|
||||
scope_id VARCHAR(26) not null
|
||||
constraint client_scopes_scope_id_fk
|
||||
references scopes
|
||||
on delete cascade,
|
||||
@@ -60,7 +60,7 @@ create table client_scopes
|
||||
|
||||
create table users
|
||||
(
|
||||
id uuid default gen_random_uuid()
|
||||
id VARCHAR(26) not null
|
||||
constraint users_pk
|
||||
primary key,
|
||||
first_name varchar not null,
|
||||
@@ -75,14 +75,14 @@ create table users
|
||||
|
||||
create table client_users
|
||||
(
|
||||
id uuid default gen_random_uuid()
|
||||
id VARCHAR(26) not null
|
||||
constraint client_users_pk
|
||||
primary key,
|
||||
client_id uuid not null
|
||||
client_id VARCHAR(26) not null
|
||||
constraint client_users_client_id_fk
|
||||
references clients
|
||||
on delete cascade,
|
||||
user_id uuid not null
|
||||
user_id VARCHAR(26) not null
|
||||
constraint client_users_user_id_fk
|
||||
references users
|
||||
on delete cascade,
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
<template #header>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<div>
|
||||
<Image width="300"
|
||||
src="https://i.careeruprising.com/_Pa5TnsUJ5v-EHQQZy3BHnbaiCjMGxusd7qNcvhd8jA/pr:sm/sm:1/enc/Ec8S-CxpyLc2M5XdibEf85vGU5KNfdR0Dx8Qf6DI2nbZG85hSSFtDV7TuynR5djSw5jhdTIyjd5xDX5z-Dgemw"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div class="text-2xl mt-5">
|
||||
<span v-if="capabilities.client_name !== ''">{{ capabilities.client_name }}</span>
|
||||
@@ -270,7 +268,7 @@ export default defineComponent({
|
||||
|
||||
this.loading = true
|
||||
|
||||
axios.post('/login', this.form).then(r => {
|
||||
axios.post('/authorize', this.form).then(r => {
|
||||
window.location.href = r.data.location
|
||||
}).catch(() => {
|
||||
this.$toast.add({
|
||||
|
||||
17
src/Api.php
17
src/Api.php
@@ -9,11 +9,13 @@ use League\Route\Http\Exception\NotFoundException;
|
||||
use League\Route\RouteGroup;
|
||||
use League\Route\Router;
|
||||
use Nyholm\Psr7\Factory\Psr17Factory;
|
||||
use Siteworxpro\App\Controllers\AccessTokenController;
|
||||
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;
|
||||
use Siteworxpro\App\Controllers\OpenIdController;
|
||||
use Siteworxpro\App\Http\JsonResponseFactory;
|
||||
use Siteworxpro\App\Http\Middleware\CorsMiddleware;
|
||||
use Siteworxpro\App\Http\Middleware\JwtMiddleware;
|
||||
@@ -22,6 +24,7 @@ use Siteworxpro\App\Http\Responses\NotFoundResponse;
|
||||
use Siteworxpro\App\Http\Responses\ServerErrorResponse;
|
||||
use Siteworxpro\App\Services\Facades\Config;
|
||||
use Siteworxpro\App\Services\Facades\Logger;
|
||||
use Siteworxpro\HttpStatus\CodesEnum;
|
||||
use Spiral\RoadRunner\Http\PSR7Worker;
|
||||
use Spiral\RoadRunner\Worker;
|
||||
|
||||
@@ -84,7 +87,14 @@ class Api
|
||||
$group->get('/capabilities', CapabilitiesController::class . '::get');
|
||||
});
|
||||
|
||||
// Authorize URL
|
||||
$this->router->get('/authorize', AuthorizeController::class . '::get');
|
||||
$this->router->post('/authorize', AuthorizeController::class . '::post');
|
||||
|
||||
$this->router->group('/client/{client_id}', function (RouteGroup $group) {
|
||||
$group->get('/.well-known/openid-configuration', OpenIdController::class . '::get');
|
||||
$group->post('/access_token', AccessTokenController::class . '::post');
|
||||
});
|
||||
|
||||
$this->router->middleware(new CorsMiddleware());
|
||||
$this->router->middleware(new JwtMiddleware());
|
||||
@@ -125,14 +135,17 @@ class Api
|
||||
}
|
||||
|
||||
$this->worker->respond(
|
||||
JsonResponseFactory::createJsonResponse(new NotFoundResponse($uri))
|
||||
JsonResponseFactory::createJsonResponse(new NotFoundResponse($uri), CodesEnum::NOT_FOUND)
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
Logger::error($e->getMessage());
|
||||
Logger::error($e->getTraceAsString());
|
||||
|
||||
$this->worker->respond(
|
||||
JsonResponseFactory::createJsonResponse(new ServerErrorResponse($e))
|
||||
JsonResponseFactory::createJsonResponse(
|
||||
new ServerErrorResponse($e),
|
||||
CodesEnum::INTERNAL_SERVER_ERROR
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ declare(strict_types=1);
|
||||
namespace Siteworxpro\App\Cli;
|
||||
|
||||
use Ahc\Cli\Application;
|
||||
use Siteworxpro\App\Cli\Commands\Crypt\GenerateKey;
|
||||
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\User\Add;
|
||||
use Siteworxpro\App\Helpers\Version;
|
||||
use Siteworxpro\App\Kernel;
|
||||
|
||||
@@ -25,7 +27,9 @@ class App
|
||||
|
||||
$this->app->add(new CreateClient());
|
||||
$this->app->add(new AddRedirectUri());
|
||||
$this->app->add(new Add());
|
||||
$this->app->add(new Start());
|
||||
$this->app->add(new GenerateKey());
|
||||
}
|
||||
|
||||
public function run(): int
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Siteworxpro\App\Cli\Commands;
|
||||
use Ahc\Cli\Application as App;
|
||||
use League\CLImate\CLImate;
|
||||
|
||||
abstract class Command extends \Ahc\Cli\Input\Command
|
||||
abstract class Command extends \Ahc\Cli\Input\Command implements CommandInterface
|
||||
{
|
||||
protected Climate $climate;
|
||||
|
||||
|
||||
30
src/Cli/Commands/Crypt/GenerateKey.php
Normal file
30
src/Cli/Commands/Crypt/GenerateKey.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Cli\Commands\Crypt;
|
||||
|
||||
use Random\RandomException;
|
||||
use Siteworxpro\App\Cli\Commands\Command;
|
||||
|
||||
class GenerateKey extends Command
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('crypt:generate-key', 'Generate a new encryption key for the application');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RandomException
|
||||
*/
|
||||
public function execute(): int
|
||||
{
|
||||
$key = base64_encode(random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES));
|
||||
|
||||
$this->climate->info('Generated Encryption Key:');
|
||||
$this->climate->out('base64:' . $key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
80
src/Cli/Commands/User/Add.php
Normal file
80
src/Cli/Commands/User/Add.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Cli\Commands\User;
|
||||
|
||||
use Siteworxpro\App\Cli\Commands\Command;
|
||||
use Siteworxpro\App\Models\ClientUser;
|
||||
use Siteworxpro\App\Models\User;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
|
||||
class Add extends Command
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('user:add', 'Add a new user to the system');
|
||||
}
|
||||
|
||||
public function execute(): int
|
||||
{
|
||||
$clients = Client::all(['id', 'name']);
|
||||
|
||||
$this->climate->info('Available OAuth Clients:');
|
||||
foreach ($clients as $client) {
|
||||
$this->climate->out("[$client->id] $client->name");
|
||||
}
|
||||
|
||||
$input = $this->climate->input('Enter the client ID to associate the new user with: ');
|
||||
$input->accept(
|
||||
$clients->pluck('id')->toArray()
|
||||
);
|
||||
|
||||
$id = $input->prompt();
|
||||
|
||||
$client = Client::find($id);
|
||||
if (!$client) {
|
||||
$this->climate->error('Client not found.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->climate->info("Adding a new user for client: $client->name");
|
||||
|
||||
$input = $this->climate->input('Enter the user\'s email:');
|
||||
$email = $input->prompt();
|
||||
|
||||
$user = User::where('email', $email)->first();
|
||||
if ($user) {
|
||||
$this->climate->error('A user with this email already exists. Associating the user with the client.');
|
||||
$clientUser = new ClientUser();
|
||||
$clientUser->client_id = $client->id;
|
||||
$clientUser->user_id = $user->id;
|
||||
$clientUser->save();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$passwordInput = $this->climate->password('Enter the user\'s password: 🔐');
|
||||
$password = $passwordInput->prompt();
|
||||
|
||||
$firstNameInput = $this->climate->input('Enter the user\'s first name: ');
|
||||
$firstName = $firstNameInput->prompt();
|
||||
|
||||
$lastNameInput = $this->climate->input('Enter the user\'s last name: ');
|
||||
$lastName = $lastNameInput->prompt();
|
||||
|
||||
$user = new User();
|
||||
$user->email = $email;
|
||||
$user->password = $password;
|
||||
$user->first_name = $firstName;
|
||||
$user->last_name = $lastName;
|
||||
$user->save();
|
||||
|
||||
$clientUser = new ClientUser();
|
||||
$clientUser->client_id = $client->id;
|
||||
$clientUser->user_id = $user->id;
|
||||
$clientUser->save();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
66
src/Controllers/AccessTokenController.php
Normal file
66
src/Controllers/AccessTokenController.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Controllers;
|
||||
|
||||
use Defuse\Crypto\Exception\BadFormatException;
|
||||
use Defuse\Crypto\Exception\EnvironmentIsBrokenException;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Siteworxpro\App\Http\JsonResponseFactory;
|
||||
use Siteworxpro\App\Http\Responses\GenericResponse;
|
||||
use Siteworxpro\App\OAuth\Entities\AuthorizationCode;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
use Siteworxpro\HttpStatus\CodesEnum;
|
||||
|
||||
final class AccessTokenController extends Controller
|
||||
{
|
||||
/**
|
||||
* @param ServerRequest $request
|
||||
* @return ResponseInterface
|
||||
* @throws BadFormatException
|
||||
* @throws EnvironmentIsBrokenException
|
||||
* @throws \JsonException
|
||||
* @throws OAuthServerException
|
||||
*/
|
||||
public function post(ServerRequest $request): ResponseInterface
|
||||
{
|
||||
try {
|
||||
$grantType = $request->getParsedBody()['grant_type'] ?? null;
|
||||
$client = Client::find($request->getAttribute('client_id'));
|
||||
if ($client === null) {
|
||||
return JsonResponseFactory::createJsonResponse(
|
||||
new GenericResponse('Invalid client'),
|
||||
CodesEnum::BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
switch ($grantType) {
|
||||
case 'authorization_code':
|
||||
return $client
|
||||
->getAuthorizationServer()
|
||||
->respondToAccessTokenRequest($request, JsonResponseFactory::createJsonResponse([]));
|
||||
|
||||
case 'refresh_token':
|
||||
break;
|
||||
default:
|
||||
return JsonResponseFactory::createJsonResponse(
|
||||
new GenericResponse('Unsupported grant type'),
|
||||
CodesEnum::BAD,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$response = $this->authorizationServer->respondToAccessTokenRequest(
|
||||
$request,
|
||||
new Response(),
|
||||
);
|
||||
|
||||
return $response;
|
||||
} catch (OAuthServerException $e) {
|
||||
return $e->generateHttpResponse(new Response());
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/Controllers/Admin/ClientsController.php
Normal file
28
src/Controllers/Admin/ClientsController.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Controllers\Admin;
|
||||
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Siteworxpro\App\Attributes\Guards\Jwt;
|
||||
use Siteworxpro\App\Attributes\Guards\Scope;
|
||||
use Siteworxpro\App\Controllers\Controller;
|
||||
use Siteworxpro\App\Http\JsonResponseFactory;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
|
||||
#[Jwt]
|
||||
final class ClientsController extends Controller
|
||||
{
|
||||
/**
|
||||
* @throws \JsonException
|
||||
*/
|
||||
#[Scope(['admin:clients:view'])]
|
||||
public function get(ServerRequest $request): ResponseInterface
|
||||
{
|
||||
$clients = Client::all();
|
||||
|
||||
return JsonResponseFactory::createJsonResponse($clients->toArray());
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Controllers;
|
||||
|
||||
use Defuse\Crypto\Exception\BadFormatException;
|
||||
use Defuse\Crypto\Exception\EnvironmentIsBrokenException;
|
||||
use HansOtt\PSR7Cookies\SetCookie;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
|
||||
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;
|
||||
@@ -21,75 +23,64 @@ use Siteworxpro\HttpStatus\CodesEnum;
|
||||
final class AuthorizeController extends Controller
|
||||
{
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
* @param ServerRequest $request
|
||||
* @return Response
|
||||
* @throws BadFormatException
|
||||
* @throws EnvironmentIsBrokenException
|
||||
* @throws \JsonException
|
||||
*/
|
||||
// #[\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]
|
||||
// ]
|
||||
// );
|
||||
// }
|
||||
public function post(ServerRequest $request): Response
|
||||
{
|
||||
$s = $request->getCookieParams()['s'] ?? '';
|
||||
|
||||
$password = $request->getParsedBody()['password'] ?? '';
|
||||
$email = $request->getParsedBody()['email'] ?? '';
|
||||
|
||||
if (!Redis::get('session:' . $s)) {
|
||||
Logger::warning('Session Timed out', ['session' => $s]);
|
||||
|
||||
return JsonResponseFactory::createJsonResponse([]);
|
||||
}
|
||||
|
||||
/** @var AuthorizationRequest $authRequest */
|
||||
$authRequest = unserialize(Redis::get('session:' . $s));
|
||||
|
||||
/** @var Client $client */
|
||||
$client = $authRequest->getClient();
|
||||
|
||||
$authorizationServer = $client->getAuthorizationServer();
|
||||
|
||||
if ($authRequest->isAuthorizationApproved()) {
|
||||
$response = $authorizationServer
|
||||
->completeAuthorizationRequest($authRequest, JsonResponseFactory::createJsonResponse([]));
|
||||
|
||||
return JsonResponseFactory::createJsonResponse([
|
||||
'success' => true,
|
||||
'location' => $response->getHeader('Location')[0]
|
||||
]);
|
||||
}
|
||||
|
||||
$user = $client->loginUser($email, $password);
|
||||
|
||||
if (!$user) {
|
||||
return JsonResponseFactory::createJsonResponse([
|
||||
'success' => false,
|
||||
'reason' => 'login failed'
|
||||
], CodesEnum::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$authRequest->setUser($user);
|
||||
$authRequest->setAuthorizationApproved(true);
|
||||
$response = $authorizationServer
|
||||
->completeAuthorizationRequest($authRequest, JsonResponseFactory::createJsonResponse([]));
|
||||
|
||||
Redis::del('session:' . $s);
|
||||
|
||||
return JsonResponseFactory::createJsonResponse([
|
||||
'success' => true,
|
||||
'location' => $response->getHeader('Location')[0]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
|
||||
@@ -25,6 +25,8 @@ final class CapabilitiesController extends Controller
|
||||
return JsonResponseFactory::createJsonResponse(new NotFoundResponse($request->getUri()->getPath()));
|
||||
}
|
||||
|
||||
return JsonResponseFactory::createJsonResponse($client->capabilities->toArray());
|
||||
return JsonResponseFactory::createJsonResponse(array_merge($client->capabilities->toArray(), [
|
||||
'client_name' => $client->name,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
48
src/Controllers/OpenIdController.php
Normal file
48
src/Controllers/OpenIdController.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?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\OAuth\Entities\Client;
|
||||
use Siteworxpro\App\Services\Facades\Config;
|
||||
use Siteworxpro\HttpStatus\CodesEnum;
|
||||
|
||||
final class OpenIdController extends Controller
|
||||
{
|
||||
/**
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function get(ServerRequest $request): ResponseInterface
|
||||
{
|
||||
$clientId = $request->getAttribute('client_id');
|
||||
|
||||
$client = Client::find($clientId);
|
||||
if (!$client) {
|
||||
return JsonResponseFactory::createJsonResponse(
|
||||
['error' => 'invalid_client'],
|
||||
CodesEnum::BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$data = [
|
||||
'issuer' => Config::get('app.url') . '/' . $clientId,
|
||||
'authorization_endpoint' => Config::get('app.url') . '/authorize',
|
||||
'token_endpoint' => Config::get('app.url') . '/client/' . $clientId . '/access_token',
|
||||
'userinfo_endpoint' => Config::get('app.url') . '/user_info',
|
||||
'end_session_endpoint' => Config::get('app.url') . '/end-session',
|
||||
'introspection_endpoint' => Config::get('app.url') . '/introspect',
|
||||
'revocation_endpoint' => Config::get('app.url') . '/revoke',
|
||||
'device_authorization_endpoint' => Config::get('app.url') . '/device',
|
||||
'jwks_uri' => Config::get('app.url') . '/client/' . $clientId . '/jwks',
|
||||
'grant_types_supported' => $client->grant_types,
|
||||
'scopes_supported' => $client->scopes->pluck('name')->toArray()
|
||||
];
|
||||
|
||||
return JsonResponseFactory::createJsonResponse($data);
|
||||
}
|
||||
}
|
||||
52
src/Helpers/Encryption.php
Normal file
52
src/Helpers/Encryption.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Helpers;
|
||||
|
||||
use Random\RandomException;
|
||||
|
||||
readonly class Encryption
|
||||
{
|
||||
public function __construct(private string $key)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the given data with AES256-GCM
|
||||
* @param string $data
|
||||
* @return string
|
||||
* @throws RandomException
|
||||
* @throws \SodiumException
|
||||
*/
|
||||
public function encrypt(string $data): string
|
||||
{
|
||||
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
|
||||
|
||||
$enc = sodium_crypto_secretbox(
|
||||
$data,
|
||||
$nonce,
|
||||
$this->key
|
||||
);
|
||||
|
||||
return $nonce . $enc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the given data with AES256-GCM
|
||||
* @param string $encryptedData
|
||||
* @return string
|
||||
* @throws \SodiumException
|
||||
*/
|
||||
public function decrypt(string $encryptedData): string
|
||||
{
|
||||
$nonce = substr($encryptedData, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
|
||||
$ciphertext = substr($encryptedData, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
|
||||
|
||||
return sodium_crypto_secretbox_open(
|
||||
$ciphertext,
|
||||
$nonce,
|
||||
$this->key
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,15 @@ abstract class Env
|
||||
{
|
||||
/**
|
||||
* @param string $key
|
||||
* @param null $default
|
||||
* @param mixed $default
|
||||
* @param string $castTo
|
||||
* @return float|bool|int|string
|
||||
*/
|
||||
public static function get(string $key, $default = null, string $castTo = 'string'): float | bool | int | string
|
||||
{
|
||||
public static function get(
|
||||
string $key,
|
||||
mixed $default = null,
|
||||
string $castTo = 'string'
|
||||
): float | bool | int | string {
|
||||
$env = getenv($key) !== false ? getenv($key) : $default;
|
||||
|
||||
return match ($castTo) {
|
||||
|
||||
@@ -17,6 +17,6 @@ class Ulid
|
||||
*/
|
||||
public static function generate(): string
|
||||
{
|
||||
return \Ulid\Ulid::generate()->getRandomness();
|
||||
return (string) \Ulid\Ulid::generate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use Siteworxpro\App\Services\Facades\Dispatcher;
|
||||
use Siteworxpro\App\Services\ServiceProviders\BrokerServiceProvider;
|
||||
use Siteworxpro\App\Services\ServiceProviders\CommandBusProvider;
|
||||
use Siteworxpro\App\Services\ServiceProviders\DispatcherServiceProvider;
|
||||
use Siteworxpro\App\Services\ServiceProviders\EncryptionServiceProvider;
|
||||
use Siteworxpro\App\Services\ServiceProviders\LoggerServiceProvider;
|
||||
use Siteworxpro\App\Services\ServiceProviders\RedisServiceProvider;
|
||||
|
||||
@@ -36,6 +37,7 @@ class Kernel
|
||||
DispatcherServiceProvider::class,
|
||||
BrokerServiceProvider::class,
|
||||
CommandBusProvider::class,
|
||||
EncryptionServiceProvider::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,5 +14,4 @@ namespace Siteworxpro\App\Models;
|
||||
*/
|
||||
class ClientScope extends Model
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -14,5 +14,5 @@ namespace Siteworxpro\App\Models;
|
||||
*/
|
||||
class ClientUser extends Model
|
||||
{
|
||||
|
||||
public $timestamps = false;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Siteworxpro\App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model as ORM;
|
||||
use Siteworxpro\App\Helpers\Ulid;
|
||||
|
||||
/**
|
||||
* Class Model
|
||||
@@ -16,4 +17,10 @@ use Illuminate\Database\Eloquent\Model as ORM;
|
||||
abstract class Model extends ORM
|
||||
{
|
||||
protected $dateFormat = 'Y-m-d H:i:s';
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
parent::__construct($attributes);
|
||||
$this->attributes['id'] = $this->attributes['id'] ?? Ulid::generate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Siteworxpro\App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
|
||||
use League\OAuth2\Server\Entities\UserEntityInterface;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Siteworxpro\App\Helpers\Ulid;
|
||||
|
||||
@@ -39,11 +40,10 @@ use Siteworxpro\App\Helpers\Ulid;
|
||||
new OA\Property(property: "created_at", type: "string", format: "date-time"),
|
||||
]
|
||||
)]
|
||||
class User extends Model
|
||||
class User extends Model implements UserEntityInterface
|
||||
{
|
||||
use EntityTrait;
|
||||
|
||||
protected $casts = [
|
||||
'id' => 'string',
|
||||
'created_at' => 'datetime',
|
||||
];
|
||||
|
||||
@@ -58,12 +58,6 @@ class User extends Model
|
||||
'password',
|
||||
];
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
parent::__construct($attributes);
|
||||
$this->attributes['id'] = $this->attributes['id'] ?? Ulid::generate();
|
||||
}
|
||||
|
||||
public function getFullNameAttribute(): string
|
||||
{
|
||||
return "$this->first_name $this->last_name";
|
||||
@@ -77,4 +71,20 @@ class User extends Model
|
||||
strtolower($this->email)
|
||||
);
|
||||
}
|
||||
|
||||
public function verifyPassword(string $password): bool
|
||||
{
|
||||
// Verify the provided password against the stored hashed password
|
||||
return password_verify($password, $this->password);
|
||||
}
|
||||
|
||||
public function setPasswordAttribute(string $password): void
|
||||
{
|
||||
$this->attributes['password'] = password_hash($password, PASSWORD_ARGON2ID);
|
||||
}
|
||||
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,26 +6,35 @@ namespace Siteworxpro\App\OAuth;
|
||||
|
||||
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
|
||||
use Psr\SimpleCache\InvalidArgumentException;
|
||||
use Siteworxpro\App\OAuth\Entities\AuthorizationCode;
|
||||
|
||||
class AuthCodeRepository implements AuthCodeRepositoryInterface
|
||||
{
|
||||
public function getNewAuthCode(): AuthCodeEntityInterface
|
||||
{
|
||||
// TODO: Implement getNewAuthCode() method.
|
||||
return new AuthorizationCode();
|
||||
}
|
||||
|
||||
public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity): void
|
||||
public function persistNewAuthCode(AuthCodeEntityInterface | AuthorizationCode $authCodeEntity): void
|
||||
{
|
||||
// TODO: Implement persistNewAuthCode() method.
|
||||
$authCodeEntity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function revokeAuthCode(string $codeId): void
|
||||
{
|
||||
// TODO: Implement revokeAuthCode() method.
|
||||
$authCode = AuthorizationCode::find($codeId);
|
||||
|
||||
$authCode?->delete();
|
||||
}
|
||||
|
||||
public function isAuthCodeRevoked(string $codeId): bool
|
||||
{
|
||||
// TODO: Implement isAuthCodeRevoked() method.
|
||||
$authCode = AuthorizationCode::find($codeId);
|
||||
|
||||
return $authCode === null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,17 +40,15 @@ readonly class ClientRepository implements ClientRepositoryInterface
|
||||
*/
|
||||
public function validateClient(string $clientIdentifier, ?string $clientSecret, ?string $grantType): bool
|
||||
{
|
||||
$client = Client::find($clientIdentifier);
|
||||
|
||||
if ($client === null) {
|
||||
if ($this->client->client_id != $clientIdentifier) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($clientSecret && $client->client_secret != $clientSecret) {
|
||||
if ($clientSecret && $this->client->client_secret != $clientSecret) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($grantType && !in_array($grantType, $client->grant_types)) {
|
||||
if ($grantType && !in_array($grantType, $this->client->grant_types->toArray())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth\Entities;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DateTimeImmutable;
|
||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
@@ -14,24 +15,31 @@ class AccessToken extends RedisModel implements AccessTokenEntityInterface
|
||||
{
|
||||
use AccessTokenTrait;
|
||||
|
||||
private Client |null $client = null;
|
||||
|
||||
private string | null $userIdentifier = null;
|
||||
|
||||
/** @var ScopeEntityInterface|Scope[] */
|
||||
private array $scopes = [];
|
||||
|
||||
public function getClient(): ClientEntityInterface
|
||||
{
|
||||
// TODO: Implement getClient() method.
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
public function getExpiryDateTime(): DateTimeImmutable
|
||||
{
|
||||
// TODO: Implement getExpiryDateTime() method.
|
||||
return $this->expiryDateTime->toDateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getUserIdentifier(): string|null
|
||||
{
|
||||
// TODO: Implement getUserIdentifier() method.
|
||||
return $this->userIdentifier;
|
||||
}
|
||||
|
||||
public function getScopes(): array
|
||||
{
|
||||
// TODO: Implement getScopes() method.
|
||||
return $this->scopes;
|
||||
}
|
||||
|
||||
protected static function getRedisPrefix(): string
|
||||
@@ -41,21 +49,21 @@ class AccessToken extends RedisModel implements AccessTokenEntityInterface
|
||||
|
||||
public function setExpiryDateTime(DateTimeImmutable $dateTime): void
|
||||
{
|
||||
// TODO: Implement setExpiryDateTime() method.
|
||||
$this->expiryDateTime = Carbon::instance($dateTime);
|
||||
}
|
||||
|
||||
public function setUserIdentifier(string $identifier): void
|
||||
{
|
||||
// TODO: Implement setUserIdentifier() method.
|
||||
$this->userIdentifier = $identifier;
|
||||
}
|
||||
|
||||
public function setClient(ClientEntityInterface $client): void
|
||||
{
|
||||
// TODO: Implement setClient() method.
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
public function addScope(ScopeEntityInterface $scope): void
|
||||
{
|
||||
// TODO: Implement addScope() method.
|
||||
$this->scopes[] = $scope;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth\Entities;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DateTimeImmutable;
|
||||
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
@@ -14,6 +15,15 @@ class AuthorizationCode extends RedisModel implements AuthCodeEntityInterface
|
||||
{
|
||||
use AuthCodeTrait;
|
||||
|
||||
private Client | null $client = null;
|
||||
|
||||
private string | null $userIdentifier = null;
|
||||
|
||||
/**
|
||||
* @var array<ScopeEntityInterface | Scope> $scopes
|
||||
*/
|
||||
private array $scopes = [];
|
||||
|
||||
protected static function getRedisPrefix(): string
|
||||
{
|
||||
return 'oauth_auth_code';
|
||||
@@ -21,41 +31,41 @@ class AuthorizationCode extends RedisModel implements AuthCodeEntityInterface
|
||||
|
||||
public function getExpiryDateTime(): DateTimeImmutable
|
||||
{
|
||||
// TODO: Implement getExpiryDateTime() method.
|
||||
return $this->expiryDateTime->toDateTimeImmutable();
|
||||
}
|
||||
|
||||
public function setExpiryDateTime(DateTimeImmutable $dateTime): void
|
||||
{
|
||||
// TODO: Implement setExpiryDateTime() method.
|
||||
$this->expiryDateTime = Carbon::instance($dateTime);
|
||||
}
|
||||
|
||||
public function setUserIdentifier(string $identifier): void
|
||||
{
|
||||
// TODO: Implement setUserIdentifier() method.
|
||||
$this->userIdentifier = $identifier;
|
||||
}
|
||||
|
||||
public function getUserIdentifier(): string|null
|
||||
{
|
||||
// TODO: Implement getUserIdentifier() method.
|
||||
return $this->userIdentifier;
|
||||
}
|
||||
|
||||
public function getClient(): ClientEntityInterface
|
||||
public function getClient(): ClientEntityInterface | Client
|
||||
{
|
||||
// TODO: Implement getClient() method.
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
public function setClient(ClientEntityInterface $client): void
|
||||
{
|
||||
// TODO: Implement setClient() method.
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
public function addScope(ScopeEntityInterface $scope): void
|
||||
{
|
||||
// TODO: Implement addScope() method.
|
||||
$this->scopes[] = $scope;
|
||||
}
|
||||
|
||||
public function getScopes(): array
|
||||
{
|
||||
// TODO: Implement getScopes() method.
|
||||
return $this->scopes;
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ use Siteworxpro\App\OAuth\ScopeRepository;
|
||||
* @property string $description
|
||||
* @property string $private_key
|
||||
* @property string $encryption_key
|
||||
* @property string[] $grant_types
|
||||
* @property Collection<string> $grant_types
|
||||
* @property bool $confidential
|
||||
*
|
||||
* @property-read ClientCapabilities $capabilities
|
||||
@@ -101,7 +101,14 @@ class Client extends Model implements ClientEntityInterface
|
||||
*/
|
||||
public function scopes(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(Scope::class, ClientScope::class);
|
||||
return $this->hasManyThrough(
|
||||
Scope::class,
|
||||
ClientScope::class,
|
||||
'client_id',
|
||||
'id',
|
||||
'id',
|
||||
'scope_id'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,7 +116,14 @@ class Client extends Model implements ClientEntityInterface
|
||||
*/
|
||||
public function users(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(User::class, ClientUser::class);
|
||||
return $this->hasManyThrough(
|
||||
User::class,
|
||||
ClientUser::class,
|
||||
'client_id',
|
||||
'id',
|
||||
'id',
|
||||
'user_id'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +168,7 @@ class Client extends Model implements ClientEntityInterface
|
||||
*/
|
||||
public function setCapabilitiesAttribute(ClientCapabilities $capabilities): void
|
||||
{
|
||||
$this->attributes->capabilities = $capabilities->toJson();
|
||||
$this->attributes['capabilities'] = $capabilities->toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,4 +217,16 @@ class Client extends Model implements ClientEntityInterface
|
||||
|
||||
return $authorizationServer;
|
||||
}
|
||||
|
||||
public function loginUser(string $username, string $password): ?User
|
||||
{
|
||||
/** @var User|null $user */
|
||||
$user = $this->users()->where('email', $username)->first();
|
||||
|
||||
if (!$user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $user->verifyPassword($password) ? $user : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class ClientCapabilities implements Arrayable
|
||||
private bool $passkey = false;
|
||||
private array $socials = [];
|
||||
|
||||
private array $theme = [
|
||||
private array $branding = [
|
||||
'primaryColor' => '#000000',
|
||||
'secondaryColor' => '#FFFFFF',
|
||||
'logoUrl' => null,
|
||||
@@ -37,8 +37,8 @@ class ClientCapabilities implements Arrayable
|
||||
$this->socials = $capabilities['socials'];
|
||||
}
|
||||
|
||||
if (isset($capabilities['theme']) && is_array($capabilities['theme'])) {
|
||||
$this->theme = array_merge($this->theme, $capabilities['theme']);
|
||||
if (isset($capabilities['branding']) && is_array($capabilities['branding'])) {
|
||||
$this->branding = array_merge($this->branding, $capabilities['branding']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class ClientCapabilities implements Arrayable
|
||||
'magicLink' => $this->magicLink,
|
||||
'passkey' => $this->passkey,
|
||||
'socials' => $this->socials,
|
||||
'theme' => $this->theme,
|
||||
'branding' => $this->branding,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -8,28 +8,24 @@ use Carbon\Carbon;
|
||||
use DateTimeImmutable;
|
||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
|
||||
use Psr\SimpleCache\InvalidArgumentException;
|
||||
use Siteworxpro\App\Services\Facades\Encryption;
|
||||
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();
|
||||
}
|
||||
protected ?Carbon $expiryDateTime;
|
||||
|
||||
abstract protected static function getRedisPrefix(): string;
|
||||
|
||||
public static function find(string $identifier): ?self
|
||||
public static function find(string $identifier): ?static
|
||||
{
|
||||
$instance = Redis::get(static::getRedisPrefix() . ':' . $identifier);
|
||||
|
||||
if ($instance !== null) {
|
||||
$instance = Encryption::decrypt($instance);
|
||||
|
||||
return unserialize($instance);
|
||||
}
|
||||
|
||||
@@ -39,22 +35,20 @@ abstract class RedisModel
|
||||
public function save(): void
|
||||
{
|
||||
$diff = 0;
|
||||
if ($this->expireTime) {
|
||||
$diff = $this->expireTime->getTimestamp() - Carbon::now()->timestamp;
|
||||
if ($this->expiryDateTime) {
|
||||
$diff = $this->expiryDateTime->getTimestamp() - Carbon::now()->timestamp;
|
||||
}
|
||||
|
||||
$this->redis->set(
|
||||
Redis::set(
|
||||
static::getRedisPrefix() . ':' . $this->getIdentifier(),
|
||||
serialize($this),
|
||||
Encryption::encrypt(serialize($this)),
|
||||
'EX',
|
||||
$diff
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
$this->redis->delete(static::getRedisPrefix() . ':' . $this->getIdentifier());
|
||||
Redis::del(static::getRedisPrefix() . ':' . $this->getIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,37 @@ declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\OAuth\Entities;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DateTimeImmutable;
|
||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\Traits\RefreshTokenTrait;
|
||||
|
||||
class RefreshToken extends RedisModel implements RefreshTokenEntityInterface
|
||||
{
|
||||
use RefreshTokenTrait;
|
||||
private AccessToken | null $accessToken = null;
|
||||
|
||||
protected static function getRedisPrefix(): string
|
||||
{
|
||||
return 'oauth_refresh_token';
|
||||
}
|
||||
|
||||
public function getExpiryDateTime(): DateTimeImmutable
|
||||
{
|
||||
return $this->expiryDateTime->toDateTimeImmutable();
|
||||
}
|
||||
|
||||
public function setExpiryDateTime(DateTimeImmutable $dateTime): void
|
||||
{
|
||||
$this->expiryDateTime = Carbon::instance($dateTime);
|
||||
}
|
||||
|
||||
public function setAccessToken(AccessTokenEntityInterface $accessToken): void
|
||||
{
|
||||
$this->accessToken = $accessToken;
|
||||
}
|
||||
|
||||
public function getAccessToken(): AccessTokenEntityInterface
|
||||
{
|
||||
return $this->accessToken;
|
||||
}
|
||||
}
|
||||
@@ -6,27 +6,33 @@ namespace Siteworxpro\App\OAuth;
|
||||
|
||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
|
||||
use Psr\SimpleCache\InvalidArgumentException;
|
||||
use Siteworxpro\App\OAuth\Entities\RefreshToken;
|
||||
|
||||
class RefreshTokenRepository implements RefreshTokenRepositoryInterface
|
||||
{
|
||||
|
||||
public function getNewRefreshToken(): ?RefreshTokenEntityInterface
|
||||
{
|
||||
// TODO: Implement getNewRefreshToken() method.
|
||||
return new RefreshToken();
|
||||
}
|
||||
|
||||
public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity): void
|
||||
public function persistNewRefreshToken(RefreshTokenEntityInterface | RefreshToken $refreshTokenEntity): void
|
||||
{
|
||||
// TODO: Implement persistNewRefreshToken() method.
|
||||
$refreshTokenEntity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function revokeRefreshToken(string $tokenId): void
|
||||
{
|
||||
// TODO: Implement revokeRefreshToken() method.
|
||||
$token = RefreshToken::find($tokenId);
|
||||
$token?->delete();
|
||||
}
|
||||
|
||||
public function isRefreshTokenRevoked(string $tokenId): bool
|
||||
{
|
||||
// TODO: Implement isRefreshTokenRevoked() method.
|
||||
$token = RefreshToken::find($tokenId);
|
||||
return $token === null;
|
||||
}
|
||||
}
|
||||
19
src/Services/Facades/Encryption.php
Normal file
19
src/Services/Facades/Encryption.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Services\Facades;
|
||||
|
||||
use Siteworxpro\App\Services\Facade;
|
||||
|
||||
/**
|
||||
* @method static string encrypt(string $data)
|
||||
* @method static string decrypt(string $data)
|
||||
*/
|
||||
class Encryption extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return \Siteworxpro\App\Helpers\Encryption::class;
|
||||
}
|
||||
}
|
||||
33
src/Services/ServiceProviders/EncryptionServiceProvider.php
Normal file
33
src/Services/ServiceProviders/EncryptionServiceProvider.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Services\ServiceProviders;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Siteworxpro\App\Helpers\Encryption;
|
||||
use Siteworxpro\App\Services\Facades\Config;
|
||||
|
||||
class EncryptionServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function provides(): array
|
||||
{
|
||||
return [Encryption::class];
|
||||
}
|
||||
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(Encryption::class, function () {
|
||||
$key = Config::get('app.encryption_key');
|
||||
if (empty($key)) {
|
||||
throw new \RuntimeException('Encryption key is not set in configuration.');
|
||||
}
|
||||
|
||||
if (str_contains($key, 'base64:')) {
|
||||
$key = base64_decode(substr($key, 7));
|
||||
}
|
||||
|
||||
return new Encryption($key);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user