You've already forked php-auth
generated from siteworxpro/Php-Template
Add audit logging functionality with database schema and event handling
Some checks failed
🧪✨ Tests Workflow / 🛡️ 🔒 License Check (push) Successful in 2m23s
🧪✨ Tests Workflow / 🛡️ 🔒 Library Audit (push) Successful in 2m35s
🧪✨ Tests Workflow / 📝 ✨ Code Lint (push) Successful in 2m25s
🧪✨ Tests Workflow / 🧪 ✨ Database Migrations (push) Successful in 2m39s
🧪✨ Tests Workflow / 🐙 🔍 Code Sniffer (push) Failing after 2m26s
🧪✨ Tests Workflow / 🧪 ✅ Unit Tests (push) Failing after 1m5s
Some checks failed
🧪✨ Tests Workflow / 🛡️ 🔒 License Check (push) Successful in 2m23s
🧪✨ Tests Workflow / 🛡️ 🔒 Library Audit (push) Successful in 2m35s
🧪✨ Tests Workflow / 📝 ✨ Code Lint (push) Successful in 2m25s
🧪✨ Tests Workflow / 🧪 ✨ Database Migrations (push) Successful in 2m39s
🧪✨ Tests Workflow / 🐙 🔍 Code Sniffer (push) Failing after 2m26s
🧪✨ Tests Workflow / 🧪 ✅ Unit Tests (push) Failing after 1m5s
This commit is contained in:
@@ -83,9 +83,9 @@ services:
|
||||
- "traefik.http.routers.api.rule=Host(`localhost`) || Host(`127.0.0.1`)"
|
||||
- "traefik.http.routers.api.tls=true"
|
||||
- "traefik.http.routers.api.service=api"
|
||||
- "traefik.http.services.api.loadbalancer.healthcheck.path=/healthz"
|
||||
- "traefik.http.services.api.loadbalancer.healthcheck.interval=5s"
|
||||
- "traefik.http.services.api.loadbalancer.healthcheck.timeout=60s"
|
||||
# - "traefik.http.services.api.loadbalancer.healthcheck.path=/healthz"
|
||||
# - "traefik.http.services.api.loadbalancer.healthcheck.interval=5s"
|
||||
# - "traefik.http.services.api.loadbalancer.healthcheck.timeout=60s"
|
||||
- "traefik.tcp.services.api.loadbalancer.server.port=9001"
|
||||
- "traefik.http.services.api.loadbalancer.server.port=9501"
|
||||
- "traefik.tcp.routers.grpc.entrypoints=grpc"
|
||||
|
||||
1
db/migrations/000002_create_audit_table.down.sql
Normal file
1
db/migrations/000002_create_audit_table.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
drop table if exists audit_logs;
|
||||
14
db/migrations/000002_create_audit_table.up.sql
Normal file
14
db/migrations/000002_create_audit_table.up.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
create table audit_logs
|
||||
(
|
||||
id VARCHAR(26) not null
|
||||
constraint audit_logs_pkey
|
||||
primary key,
|
||||
user_id integer,
|
||||
action varchar(255) not null,
|
||||
timestamp timestamptz default current_timestamp,
|
||||
details jsonb
|
||||
);
|
||||
|
||||
create index idx_audit_logs_action on audit_logs (action);
|
||||
create index idx_audit_logs_user_id on audit_logs (user_id);
|
||||
create index idx_audit_logs_timestamp on audit_logs (timestamp);
|
||||
@@ -7,6 +7,7 @@ namespace Siteworxpro\App\Cli\Commands\OAuth;
|
||||
use Siteworxpro\App\Cli\ClimateOutput;
|
||||
use Siteworxpro\App\CommandBus\Commands\CreateClient as CreateClientCommand;
|
||||
use Siteworxpro\App\CommandBus\Exceptions\CommandHandlerException;
|
||||
use Siteworxpro\App\Models\Enums\ClientGrant as ClientGrantAlias;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
use Siteworxpro\App\Services\Facades\CommandBus;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
@@ -37,8 +38,13 @@ class CreateClient extends \Siteworxpro\App\Cli\Commands\Command
|
||||
$question->setMultiselect(true);
|
||||
|
||||
$grants = $this->helper->ask($input, $output, $question);
|
||||
$grantsEnum = [];
|
||||
|
||||
$command = new CreateClientCommand($clientName, $grants, $clientDescription);
|
||||
foreach ($grants as $grant) {
|
||||
$grantsEnum[] = ClientGrantAlias::from($grant);
|
||||
}
|
||||
|
||||
$command = new CreateClientCommand($clientName, $grantsEnum, $clientDescription);
|
||||
try {
|
||||
/** @var Client $client */
|
||||
$client = CommandBus::handle($command);
|
||||
|
||||
@@ -6,9 +6,11 @@ namespace Siteworxpro\App\Cli\Commands\User;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Siteworxpro\App\Cli\Commands\Command;
|
||||
use Siteworxpro\App\CommandBus\Commands\CreateUser;
|
||||
use Siteworxpro\App\Models\ClientUser;
|
||||
use Siteworxpro\App\Models\User;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
use Siteworxpro\App\Services\Facades\CommandBus;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command as SCommand;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
@@ -76,19 +78,16 @@ class Add extends Command
|
||||
$lastNameQuestion = new QuestionInput('Enter the user\'s last name: ');
|
||||
$lastName = $helper->ask($input, $output, $lastNameQuestion);
|
||||
|
||||
$user = new User();
|
||||
$user->email = $email;
|
||||
$user->password = $password;
|
||||
$user->first_name = $firstName;
|
||||
$user->last_name = $lastName;
|
||||
$user->save();
|
||||
$createUserCommand = new CreateUser($client, $email, $password, $firstName, $lastName);
|
||||
|
||||
$clientUser = new ClientUser();
|
||||
$clientUser->client_id = $client->id;
|
||||
$clientUser->user_id = $user->id;
|
||||
$clientUser->save();
|
||||
/** @var User $user */
|
||||
$user = CommandBus::handle($createUserCommand);
|
||||
|
||||
$output->green('User added and associated with the client successfully.');
|
||||
$output->info('User Details:');
|
||||
$output->out("ID: $user->id");
|
||||
$output->out("Email: $user->email");
|
||||
$output->out("Name: $user->first_name $user->last_name");
|
||||
|
||||
return SCommand::SUCCESS;
|
||||
}
|
||||
|
||||
@@ -2,24 +2,32 @@
|
||||
|
||||
namespace Siteworxpro\App\CommandBus\Commands;
|
||||
|
||||
use Siteworxpro\App\Models\Enums\ClientGrant;
|
||||
|
||||
readonly class CreateClient extends Command
|
||||
{
|
||||
private const array VALID_GRANTS = [
|
||||
'authorization_code',
|
||||
'password',
|
||||
'client_credentials',
|
||||
'refresh_token',
|
||||
'implicit',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string $clientName
|
||||
* @param array<ClientGrant> $clientGrants
|
||||
* @param string $clientDescription
|
||||
*/
|
||||
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");
|
||||
if ($grant instanceof ClientGrant === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf(
|
||||
'Invalid client grant provided: %s. Valid grants are: %s',
|
||||
is_string($grant) ? $grant : gettype($grant),
|
||||
implode(', ', array_map(
|
||||
fn(ClientGrant $validGrant) => $validGrant->value,
|
||||
ClientGrant::getValidGrants()
|
||||
))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
51
src/CommandBus/Commands/CreateUser.php
Normal file
51
src/CommandBus/Commands/CreateUser.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\CommandBus\Commands;
|
||||
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
|
||||
readonly class CreateUser extends Command
|
||||
{
|
||||
private string $email;
|
||||
|
||||
public function __construct(
|
||||
private Client $client,
|
||||
string $email,
|
||||
private string $password,
|
||||
private string $firstName,
|
||||
private string $lastName,
|
||||
) {
|
||||
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new \InvalidArgumentException('Invalid email address.');
|
||||
}
|
||||
|
||||
$this->email = $email;
|
||||
}
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function getFirstName(): string
|
||||
{
|
||||
return $this->firstName;
|
||||
}
|
||||
|
||||
public function getLastName(): string
|
||||
{
|
||||
return $this->lastName;
|
||||
}
|
||||
|
||||
public function getClient(): Client
|
||||
{
|
||||
return $this->client;
|
||||
}
|
||||
}
|
||||
31
src/CommandBus/Handlers/CreateUserHandler.php
Normal file
31
src/CommandBus/Handlers/CreateUserHandler.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\CommandBus\Handlers;
|
||||
|
||||
use Siteworxpro\App\Attributes\CommandBus\HandlesCommand;
|
||||
use Siteworxpro\App\CommandBus\Commands\CreateUser;
|
||||
use Siteworxpro\App\Models\ClientUser;
|
||||
use Siteworxpro\App\Models\User;
|
||||
|
||||
#[HandlesCommand(CreateUser::class)]
|
||||
class CreateUserHandler
|
||||
{
|
||||
public function __invoke(CreateUser $command): User
|
||||
{
|
||||
$user = User::create([
|
||||
'email' => $command->getEmail(),
|
||||
'password' => password_hash($command->getPassword(), PASSWORD_BCRYPT),
|
||||
'first_name' => $command->getFirstName(),
|
||||
'last_name' => $command->getLastName(),
|
||||
]);
|
||||
|
||||
$clientUser = new ClientUser();
|
||||
$clientUser->client_id = $command->getClient()->id;
|
||||
$clientUser->user_id = $user->id;
|
||||
$clientUser->save();
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use Siteworxpro\App\CommandBus\Commands\Command;
|
||||
use Siteworxpro\App\CommandBus\Exceptions\CommandHandlerException;
|
||||
use Siteworxpro\App\CommandBus\Handlers\CommandHandler;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
use Siteworxpro\App\OAuth\Entities\ClientCapabilities;
|
||||
|
||||
#[HandlesCommand(\Siteworxpro\App\CommandBus\Commands\CreateClient::class)]
|
||||
class CreateClient extends CommandHandler
|
||||
@@ -24,6 +25,7 @@ class CreateClient extends CommandHandler
|
||||
$client->name = $command->getClientName();
|
||||
$client->description = $command->getClientDescription();
|
||||
$client->grant_types = new Collection($command->getClientGrants()); // @phpstan-ignore-line assign.propertyType
|
||||
$client->capabilities = new ClientCapabilities();
|
||||
|
||||
$client->save();
|
||||
|
||||
|
||||
@@ -7,11 +7,14 @@ namespace Siteworxpro\App\Controllers;
|
||||
use Defuse\Crypto\Exception\BadFormatException;
|
||||
use Defuse\Crypto\Exception\EnvironmentIsBrokenException;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use Nyholm\Psr7\Response;
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Siteworxpro\App\Events\AccessToken\Issued;
|
||||
use Siteworxpro\App\Http\JsonResponseFactory;
|
||||
use Siteworxpro\App\Http\Responses\GenericResponse;
|
||||
use Siteworxpro\App\OAuth\Entities\Client;
|
||||
use Siteworxpro\App\Services\Facades\Dispatcher;
|
||||
use Siteworxpro\HttpStatus\CodesEnum;
|
||||
|
||||
final class AccessTokenController extends Controller
|
||||
@@ -34,9 +37,14 @@ final class AccessTokenController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
return $client
|
||||
/** @var Response $response */
|
||||
$response = $client
|
||||
->getAuthorizationServer()
|
||||
->respondToAccessTokenRequest($request, JsonResponseFactory::createJsonResponse([]));
|
||||
|
||||
Dispatcher::push(new Issued($response));
|
||||
|
||||
return $response;
|
||||
} catch (OAuthServerException $e) {
|
||||
return JsonResponseFactory::createJsonResponse(
|
||||
$e->getPayload(),
|
||||
|
||||
@@ -12,10 +12,14 @@ use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
|
||||
use Nyholm\Psr7\Response;
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
use Nyholm\Psr7\Stream;
|
||||
use Siteworxpro\App\Events\Login\LoginAttempt;
|
||||
use Siteworxpro\App\Events\Login\LoginFailed;
|
||||
use Siteworxpro\App\Events\Login\LoginSuccess;
|
||||
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\Dispatcher;
|
||||
use Siteworxpro\App\Services\Facades\Logger;
|
||||
use Siteworxpro\App\Services\Facades\Redis;
|
||||
use Siteworxpro\HttpStatus\CodesEnum;
|
||||
@@ -31,6 +35,8 @@ final class AuthorizeController extends Controller
|
||||
*/
|
||||
public function post(ServerRequest $request): Response
|
||||
{
|
||||
Dispatcher::push(new LoginAttempt($request));
|
||||
|
||||
$s = $request->getCookieParams()['s'] ?? '';
|
||||
|
||||
$password = $request->getParsedBody()['password'] ?? '';
|
||||
@@ -63,6 +69,8 @@ final class AuthorizeController extends Controller
|
||||
$user = $client->loginUser($email, $password);
|
||||
|
||||
if (!$user) {
|
||||
Dispatcher::push(new LoginFailed($request));
|
||||
|
||||
return JsonResponseFactory::createJsonResponse([
|
||||
'success' => false,
|
||||
'reason' => 'login failed'
|
||||
@@ -76,6 +84,8 @@ final class AuthorizeController extends Controller
|
||||
|
||||
Redis::del('session:' . $s);
|
||||
|
||||
Dispatcher::push(new LoginSuccess($request, $user));
|
||||
|
||||
return JsonResponseFactory::createJsonResponse([
|
||||
'success' => true,
|
||||
'location' => $response->getHeader('Location')[0]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Events\Listeners;
|
||||
namespace Siteworxpro\App\EventListeners;
|
||||
|
||||
/**
|
||||
* Class Listener
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Events\Listeners;
|
||||
namespace Siteworxpro\App\EventListeners;
|
||||
|
||||
/**
|
||||
* Interface ListenerInterface
|
||||
40
src/EventListeners/LoginFailed.php
Normal file
40
src/EventListeners/LoginFailed.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\EventListeners;
|
||||
|
||||
use Siteworxpro\App\Attributes\Events\ListensFor;
|
||||
use Siteworxpro\App\Models\AuditLog;
|
||||
use Siteworxpro\App\Models\Enums\AuditLogAction;
|
||||
|
||||
#[ListensFor(\Siteworxpro\App\Events\Login\LoginFailed::class)]
|
||||
class LoginFailed extends Listener
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param \Siteworxpro\App\Events\Login\LoginFailed $event
|
||||
* @param array $payload
|
||||
* @return AuditLog|null
|
||||
*/
|
||||
public function __invoke(mixed $event, array $payload = []): ?AuditLog
|
||||
{
|
||||
if (is_string($event)) {
|
||||
$event = $payload[0] ?? null;
|
||||
}
|
||||
|
||||
if (!$event instanceof \Siteworxpro\App\Events\Login\LoginFailed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return AuditLog::create([
|
||||
'user_id' => $event->getUser()?->id,
|
||||
'action' => AuditLogAction::LOGIN_FAIL,
|
||||
'details' => [
|
||||
'username_attempted' => $event->getUsernameAttempted(),
|
||||
'ip_address' => $event->getRequestIp(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
19
src/Events/AccessToken/Issued.php
Normal file
19
src/Events/AccessToken/Issued.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Events\AccessToken;
|
||||
|
||||
use Nyholm\Psr7\Response;
|
||||
|
||||
readonly class Issued
|
||||
{
|
||||
public function __construct(private Response $response)
|
||||
{
|
||||
}
|
||||
|
||||
public function getResponse(): Response
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ class Dispatcher implements DispatcherContract, Arrayable
|
||||
/**
|
||||
* @var string LISTENERS_NAMESPACE The namespace where listeners are located
|
||||
*/
|
||||
private const string LISTENERS_NAMESPACE = 'Siteworxpro\\App\\Events\\Listeners\\';
|
||||
private const string LISTENERS_NAMESPACE = 'Siteworxpro\\App\\EventListeners\\';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -63,7 +63,7 @@ class Dispatcher implements DispatcherContract, Arrayable
|
||||
private function registerListeners(): void
|
||||
{
|
||||
// traverse the Listeners directory and register all listeners
|
||||
$listenersPath = __DIR__ . '/Listeners';
|
||||
$listenersPath = __DIR__ . '/../EventListeners';
|
||||
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($listenersPath));
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
@@ -199,6 +199,11 @@ class Dispatcher implements DispatcherContract, Arrayable
|
||||
*/
|
||||
public function push($event, $payload = []): void
|
||||
{
|
||||
if (!is_string($event)) {
|
||||
$payload = [$event];
|
||||
$event = get_class($event);
|
||||
}
|
||||
|
||||
$this->pushed->put($event, $payload);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Events\Listeners\Database;
|
||||
|
||||
use Illuminate\Database\Events\ConnectionEstablished;
|
||||
use Illuminate\Database\Events\ConnectionEvent;
|
||||
use Siteworxpro\App\Attributes\Events\ListensFor;
|
||||
use Siteworxpro\App\Events\Listeners\Listener;
|
||||
use Siteworxpro\App\Services\Facades\Logger;
|
||||
|
||||
/**
|
||||
* Class Connected
|
||||
* @package Siteworxpro\App\Events\Listeners\Database
|
||||
*/
|
||||
#[ListensFor(ConnectionEstablished::class)]
|
||||
class Connected extends Listener
|
||||
{
|
||||
/**
|
||||
* @param mixed $event
|
||||
* @param array $payload
|
||||
* @return null
|
||||
*/
|
||||
public function __invoke(mixed $event, array $payload = []): null
|
||||
{
|
||||
if (!($event instanceof ConnectionEvent)) {
|
||||
throw new \TypeError("Invalid event type passed to listener " . static::class);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
20
src/Events/Login/LoginAttempt.php
Normal file
20
src/Events/Login/LoginAttempt.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Events\Login;
|
||||
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
|
||||
readonly class LoginAttempt
|
||||
{
|
||||
public function __construct(
|
||||
private ServerRequest $request,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->request->getParsedBody()['email'] ?? '';
|
||||
}
|
||||
}
|
||||
50
src/Events/Login/LoginFailed.php
Normal file
50
src/Events/Login/LoginFailed.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Events\Login;
|
||||
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
use Siteworxpro\App\Models\User;
|
||||
|
||||
readonly class LoginFailed
|
||||
{
|
||||
public function __construct(
|
||||
private ServerRequest $request,
|
||||
private ?User $user = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getRequestIp(): string
|
||||
{
|
||||
if ($this->request->getHeader('X-Forwarded-For')) {
|
||||
$ipAddresses = explode(',', $this->request->getHeaderLine('X-Forwarded-For'));
|
||||
return trim($ipAddresses[0]);
|
||||
}
|
||||
|
||||
if ($this->request->getHeader('X-Real-IP')) {
|
||||
return $this->request->getHeaderLine('X-Real-IP');
|
||||
}
|
||||
|
||||
if ($this->request->getServerParams()['HTTP_CLIENT_IP'] ?? false) {
|
||||
return $this->request->getServerParams()['HTTP_CLIENT_IP'];
|
||||
}
|
||||
|
||||
if ($this->request->getServerParams()['HTTP_X_FORWARDED_FOR'] ?? false) {
|
||||
$ipAddresses = explode(',', $this->request->getServerParams()['HTTP_X_FORWARDED_FOR']);
|
||||
return trim($ipAddresses[0]);
|
||||
}
|
||||
|
||||
return $this->request->getServerParams()['REMOTE_ADDR'] ?? 'unknown';
|
||||
}
|
||||
|
||||
public function getUsernameAttempted(): string
|
||||
{
|
||||
return $this->request->getParsedBody()['email'] ?? '';
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
}
|
||||
27
src/Events/Login/LoginSuccess.php
Normal file
27
src/Events/Login/LoginSuccess.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Events\Login;
|
||||
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
use Siteworxpro\App\Models\User;
|
||||
|
||||
readonly class LoginSuccess
|
||||
{
|
||||
public function __construct(
|
||||
private ServerRequest $serverRequest,
|
||||
private User $user
|
||||
) {
|
||||
}
|
||||
|
||||
public function getRequest(): ServerRequest
|
||||
{
|
||||
return $this->serverRequest;
|
||||
}
|
||||
|
||||
public function getUser(): User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
}
|
||||
55
src/Models/AuditLog.php
Normal file
55
src/Models/AuditLog.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Siteworxpro\App\Models\Enums\AuditLogAction;
|
||||
|
||||
/**
|
||||
* Class AuditLog
|
||||
*
|
||||
* @package Siteworxpro\App\Models
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $user_id
|
||||
* @property AuditLogAction $action
|
||||
* @property-read Carbon $timestamp
|
||||
* @property array $details
|
||||
*/
|
||||
class AuditLog extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
|
||||
protected $casts = [
|
||||
'details' => 'array',
|
||||
'timestamp' => 'datetime',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'action',
|
||||
'details',
|
||||
];
|
||||
|
||||
public function getActionAttribute(string $value): AuditLogAction
|
||||
{
|
||||
return AuditLogAction::from($value);
|
||||
}
|
||||
|
||||
public function setActionAttribute(AuditLogAction $value): void
|
||||
{
|
||||
$this->attributes['action'] = $value->value;
|
||||
}
|
||||
|
||||
public function setDetailsAttribute(array $value): void
|
||||
{
|
||||
$this->attributes['details'] = json_encode($value);
|
||||
}
|
||||
|
||||
public function getDetailsAttribute($value): array
|
||||
{
|
||||
return json_decode($value, true) ?? [];
|
||||
}
|
||||
}
|
||||
12
src/Models/Enums/AuditLogAction.php
Normal file
12
src/Models/Enums/AuditLogAction.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Models\Enums;
|
||||
|
||||
enum AuditLogAction: string
|
||||
{
|
||||
case LOGIN_SUCCESS = 'login_success';
|
||||
case LOGIN_FAIL = 'login_fail';
|
||||
case LOGOUT = 'logout';
|
||||
}
|
||||
37
src/Models/Enums/ClientGrant.php
Normal file
37
src/Models/Enums/ClientGrant.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Models\Enums;
|
||||
|
||||
enum ClientGrant: string
|
||||
{
|
||||
case AUTHORIZATION_CODE = 'authorization_code';
|
||||
case PASSWORD = 'password';
|
||||
case CLIENT_CREDENTIALS = 'client_credentials';
|
||||
case REFRESH_TOKEN = 'refresh_token';
|
||||
case IMPLICIT = 'implicit';
|
||||
|
||||
public static function fromString(string $grant): ?ClientGrant
|
||||
{
|
||||
return match ($grant) {
|
||||
'authorization_code' => self::AUTHORIZATION_CODE,
|
||||
'password' => self::PASSWORD,
|
||||
'client_credentials' => self::CLIENT_CREDENTIALS,
|
||||
'refresh_token' => self::REFRESH_TOKEN,
|
||||
'implicit' => self::IMPLICIT,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
public static function getValidGrants(): array
|
||||
{
|
||||
return [
|
||||
self::AUTHORIZATION_CODE,
|
||||
self::PASSWORD,
|
||||
self::CLIENT_CREDENTIALS,
|
||||
self::REFRESH_TOKEN,
|
||||
self::IMPLICIT,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ use Siteworxpro\App\Helpers\Ulid;
|
||||
* @method static static|null find(string $id, array $columns = ['*'])
|
||||
* @method static Builder where(string $column, string $operator = null, string $value = null, string $boolean = 'and')
|
||||
* @method static Builder whereJsonContains(string $column, mixed $value, string $boolean = 'and', bool $not = false)
|
||||
* @method static static create(array $attributes = [])
|
||||
*/
|
||||
abstract class Model extends ORM
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@ use Siteworxpro\App\OAuth\ScopeRepository;
|
||||
* @property Collection $grant_types
|
||||
* @property bool $confidential
|
||||
*
|
||||
* @property-read ClientCapabilities $capabilities
|
||||
* @property ClientCapabilities $capabilities
|
||||
* @property-read Collection<ClientRedirectUri> $clientRedirectUris
|
||||
* @property-read Scope[]|Collection $scopes
|
||||
*/
|
||||
|
||||
@@ -19,7 +19,7 @@ class ClientCapabilities implements Arrayable
|
||||
'logoUrl' => null,
|
||||
];
|
||||
|
||||
public function __construct(array $capabilities)
|
||||
public function __construct(array $capabilities = [])
|
||||
{
|
||||
if (isset($capabilities['userPass'])) {
|
||||
$this->userPass = (bool)$capabilities['userPass'];
|
||||
|
||||
@@ -8,7 +8,7 @@ use Illuminate\Database\Events\ConnectionEstablished;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use Siteworxpro\App\Events\Listeners\Database\Connected;
|
||||
use Siteworxpro\App\Events\EventListeners\Database\Connected;
|
||||
use Siteworxpro\App\Log\Logger;
|
||||
use Siteworxpro\Tests\Unit;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user