You've already forked php-auth
generated from siteworxpro/Php-Template
Compare commits
5 Commits
v0.0.1
...
8dbf3c22b6
| Author | SHA1 | Date | |
|---|---|---|---|
|
8dbf3c22b6
|
|||
|
4ee830327e
|
|||
|
e9cb49d942
|
|||
|
b2b85b5261
|
|||
|
a1d7512ebc
|
@@ -10,5 +10,6 @@ return new LicenseConfigurationBuilder()
|
|||||||
'BSD-2-Clause',
|
'BSD-2-Clause',
|
||||||
'BSD-3-Clause',
|
'BSD-3-Clause',
|
||||||
'Apache-2.0',
|
'Apache-2.0',
|
||||||
|
'LGPL-2.1-only'
|
||||||
)
|
)
|
||||||
->build();
|
->build();
|
||||||
|
|||||||
@@ -83,9 +83,9 @@ services:
|
|||||||
- "traefik.http.routers.api.rule=Host(`localhost`) || Host(`127.0.0.1`)"
|
- "traefik.http.routers.api.rule=Host(`localhost`) || Host(`127.0.0.1`)"
|
||||||
- "traefik.http.routers.api.tls=true"
|
- "traefik.http.routers.api.tls=true"
|
||||||
- "traefik.http.routers.api.service=api"
|
- "traefik.http.routers.api.service=api"
|
||||||
- "traefik.http.services.api.loadbalancer.healthcheck.path=/healthz"
|
# - "traefik.http.services.api.loadbalancer.healthcheck.path=/healthz"
|
||||||
- "traefik.http.services.api.loadbalancer.healthcheck.interval=5s"
|
# - "traefik.http.services.api.loadbalancer.healthcheck.interval=5s"
|
||||||
- "traefik.http.services.api.loadbalancer.healthcheck.timeout=60s"
|
# - "traefik.http.services.api.loadbalancer.healthcheck.timeout=60s"
|
||||||
- "traefik.tcp.services.api.loadbalancer.server.port=9001"
|
- "traefik.tcp.services.api.loadbalancer.server.port=9001"
|
||||||
- "traefik.http.services.api.loadbalancer.server.port=9501"
|
- "traefik.http.services.api.loadbalancer.server.port=9501"
|
||||||
- "traefik.tcp.routers.grpc.entrypoints=grpc"
|
- "traefik.tcp.routers.grpc.entrypoints=grpc"
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "siteworxpro/app",
|
"name": "siteworxpro/app",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
|
"config": {
|
||||||
|
"optimize-autoloader": true,
|
||||||
|
"sort-packages": true,
|
||||||
|
"audit": {
|
||||||
|
"ignore": {
|
||||||
|
"PKSA-rkkf-636k-qjb3": "Dev only package",
|
||||||
|
"PKSA-z3gr-8qht-p93v": "Dev only package"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Siteworxpro\\App\\": "src/",
|
"Siteworxpro\\App\\": "src/",
|
||||||
@@ -32,15 +42,18 @@
|
|||||||
"ext-sodium": "*",
|
"ext-sodium": "*",
|
||||||
"league/climate": "^3.10",
|
"league/climate": "^3.10",
|
||||||
"hansott/psr7-cookies": "^4.0",
|
"hansott/psr7-cookies": "^4.0",
|
||||||
"symfony/console": "^v7.4.3"
|
"symfony/console": "^v7.4.3",
|
||||||
|
"twig/twig": "^3.22",
|
||||||
|
"phpmailer/phpmailer": "^7.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^12.4",
|
"jetbrains/phpstorm-attributes": "^1.2",
|
||||||
"mockery/mockery": "^1.6",
|
"kwn/php-rdkafka-stubs": "^2.2",
|
||||||
"squizlabs/php_codesniffer": "^4.0",
|
|
||||||
"lendable/composer-license-checker": "^1.3.0",
|
"lendable/composer-license-checker": "^1.3.0",
|
||||||
|
"mockery/mockery": "^1.6",
|
||||||
"phpstan/phpstan": "^2.1.31",
|
"phpstan/phpstan": "^2.1.31",
|
||||||
"kwn/php-rdkafka-stubs": "^2.2"
|
"phpunit/phpunit": "^12.4",
|
||||||
|
"squizlabs/php_codesniffer": "^4.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"tests:all": [
|
"tests:all": [
|
||||||
|
|||||||
205
composer.lock
generated
205
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "7aee32dcb1c3e870610dda4d399383f0",
|
"content-hash": "3063e521174c3d0c51a4b8143aee7e85",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
@@ -2442,6 +2442,88 @@
|
|||||||
},
|
},
|
||||||
"time": "2020-10-15T08:29:30+00:00"
|
"time": "2020-10-15T08:29:30+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "phpmailer/phpmailer",
|
||||||
|
"version": "v7.0.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
||||||
|
"reference": "ebf1655bd5b99b3f97e1a3ec0a69e5f4cd7ea088"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/ebf1655bd5b99b3f97e1a3ec0a69e5f4cd7ea088",
|
||||||
|
"reference": "ebf1655bd5b99b3f97e1a3ec0a69e5f4cd7ea088",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-filter": "*",
|
||||||
|
"ext-hash": "*",
|
||||||
|
"php": ">=5.5.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
|
||||||
|
"doctrine/annotations": "^1.2.6 || ^1.13.3",
|
||||||
|
"php-parallel-lint/php-console-highlighter": "^1.0.0",
|
||||||
|
"php-parallel-lint/php-parallel-lint": "^1.3.2",
|
||||||
|
"phpcompatibility/php-compatibility": "^10.0.0@dev",
|
||||||
|
"squizlabs/php_codesniffer": "^3.13.5",
|
||||||
|
"yoast/phpunit-polyfills": "^1.0.4"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
|
||||||
|
"directorytree/imapengine": "For uploading sent messages via IMAP, see gmail example",
|
||||||
|
"ext-imap": "Needed to support advanced email address parsing according to RFC822",
|
||||||
|
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
|
||||||
|
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
|
||||||
|
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
|
||||||
|
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
|
||||||
|
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
|
||||||
|
"psr/log": "For optional PSR-3 debug logging",
|
||||||
|
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
|
||||||
|
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"PHPMailer\\PHPMailer\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"LGPL-2.1-only"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Marcus Bointon",
|
||||||
|
"email": "phpmailer@synchromedia.co.uk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jim Jagielski",
|
||||||
|
"email": "jimjag@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Andy Prevost",
|
||||||
|
"email": "codeworxtech@users.sourceforge.net"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Brent R. Matzelle"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
|
||||||
|
"source": "https://github.com/PHPMailer/PHPMailer/tree/v7.0.2"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/Synchro",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-01-09T18:02:33+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpdoc-parser",
|
"name": "phpstan/phpdoc-parser",
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
@@ -5363,6 +5445,85 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-12-04T18:17:06+00:00"
|
"time": "2025-12-04T18:17:06+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "twig/twig",
|
||||||
|
"version": "v3.22.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/twigphp/Twig.git",
|
||||||
|
"reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/twigphp/Twig/zipball/946ddeafa3c9f4ce279d1f34051af041db0e16f2",
|
||||||
|
"reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1.0",
|
||||||
|
"symfony/deprecation-contracts": "^2.5|^3",
|
||||||
|
"symfony/polyfill-ctype": "^1.8",
|
||||||
|
"symfony/polyfill-mbstring": "^1.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"psr/container": "^1.0|^2.0",
|
||||||
|
"symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/Resources/core.php",
|
||||||
|
"src/Resources/debug.php",
|
||||||
|
"src/Resources/escaper.php",
|
||||||
|
"src/Resources/string_loader.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Twig\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com",
|
||||||
|
"homepage": "http://fabien.potencier.org",
|
||||||
|
"role": "Lead Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Twig Team",
|
||||||
|
"role": "Contributors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Armin Ronacher",
|
||||||
|
"email": "armin.ronacher@active-4.com",
|
||||||
|
"role": "Project Founder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Twig, the flexible, fast, and secure template language for PHP",
|
||||||
|
"homepage": "https://twig.symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"templating"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/twigphp/Twig/issues",
|
||||||
|
"source": "https://github.com/twigphp/Twig/tree/v3.22.2"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-12-14T11:28:47+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "voku/portable-ascii",
|
"name": "voku/portable-ascii",
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
@@ -5578,6 +5739,48 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-04-30T06:54:44+00:00"
|
"time": "2025-04-30T06:54:44+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "jetbrains/phpstorm-attributes",
|
||||||
|
"version": "1.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/JetBrains/phpstorm-attributes.git",
|
||||||
|
"reference": "64de815a4509c29e00d5e3474087fd24c171afc2"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/JetBrains/phpstorm-attributes/zipball/64de815a4509c29e00d5e3474087fd24c171afc2",
|
||||||
|
"reference": "64de815a4509c29e00d5e3474087fd24c171afc2",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"JetBrains\\PhpStorm\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "JetBrains",
|
||||||
|
"homepage": "https://www.jetbrains.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PhpStorm specific attributes",
|
||||||
|
"keywords": [
|
||||||
|
"attributes",
|
||||||
|
"jetbrains",
|
||||||
|
"phpstorm"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://youtrack.jetbrains.com/newIssue?project=WI",
|
||||||
|
"source": "https://github.com/JetBrains/phpstorm-attributes/tree/1.2"
|
||||||
|
},
|
||||||
|
"time": "2024-10-11T10:46:19+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "kwn/php-rdkafka-stubs",
|
"name": "kwn/php-rdkafka-stubs",
|
||||||
"version": "v2.2.1",
|
"version": "v2.2.1",
|
||||||
|
|||||||
17
config.php
17
config.php
@@ -9,6 +9,7 @@ return [
|
|||||||
'dev_mode' => Env::get('DEV_MODE', false, 'bool'),
|
'dev_mode' => Env::get('DEV_MODE', false, 'bool'),
|
||||||
'url' => Env::get('APP_URL', 'https://localhost'),
|
'url' => Env::get('APP_URL', 'https://localhost'),
|
||||||
'encryption_key' => Env::get('APP_ENCRYPTION_KEY', 'base64:change_me'),
|
'encryption_key' => Env::get('APP_ENCRYPTION_KEY', 'base64:change_me'),
|
||||||
|
'default_support_email' => Env::get('DEFAULT_SUPPORT_EMAIL', 'no@email.com'),
|
||||||
],
|
],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,6 +37,22 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'mailer' => [
|
||||||
|
'driver' => Env::get('MAILER_DRIVER', 'log'), // Options: 'log', 'smtp'
|
||||||
|
|
||||||
|
'log' => [
|
||||||
|
'log_file' => Env::get('MAILER_LOG_FILE', 'php://stdout'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'smtp' => [
|
||||||
|
'host' => Env::get('SMTP_HOST', 'localhost'),
|
||||||
|
'port' => Env::get('SMTP_PORT', 25, 'int'),
|
||||||
|
'username' => Env::get('SMTP_USERNAME', ''),
|
||||||
|
'password' => Env::get('SMTP_PASSWORD', ''),
|
||||||
|
'encryption' => Env::get('SMTP_ENCRYPTION', 'none'), // Options: 'ssl', 'starttls', 'none'
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
'cors' => [
|
'cors' => [
|
||||||
'allowed_origins' => Env::get('CORS_ALLOWED_ORIGINS', 'localhost:3000'),
|
'allowed_origins' => Env::get('CORS_ALLOWED_ORIGINS', 'localhost:3000'),
|
||||||
'allow_credentials' => Env::get('CORS_ALLOW_CREDENTIALS', true, 'bool'),
|
'allow_credentials' => Env::get('CORS_ALLOW_CREDENTIALS', true, 'bool'),
|
||||||
|
|||||||
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);
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Siteworxpro\App\Async\Handlers;
|
|
||||||
|
|
||||||
use Siteworxpro\App\Attributes\Async\HandlesMessage;
|
|
||||||
use Siteworxpro\App\Async\Messages\Message;
|
|
||||||
use Siteworxpro\App\Async\Messages\SayHelloMessage;
|
|
||||||
use Siteworxpro\App\Services\Facades\Logger;
|
|
||||||
|
|
||||||
#[HandlesMessage(SayHelloMessage::class)]
|
|
||||||
class SayHelloHandler implements HandlerInterface
|
|
||||||
{
|
|
||||||
public function __invoke(Message | SayHelloMessage $message): void
|
|
||||||
{
|
|
||||||
$name = $message->getPayload()['name'] ?? 'Guest';
|
|
||||||
|
|
||||||
Logger::info(sprintf("Hello, %s!", $name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,7 @@ namespace Siteworxpro\App\Async\Messages;
|
|||||||
use Siteworxpro\App\Async\Queues\DefaultQueue;
|
use Siteworxpro\App\Async\Queues\DefaultQueue;
|
||||||
use Siteworxpro\App\Async\Queues\Queue;
|
use Siteworxpro\App\Async\Queues\Queue;
|
||||||
use Siteworxpro\App\Helpers\Ulid;
|
use Siteworxpro\App\Helpers\Ulid;
|
||||||
|
use Siteworxpro\App\Services\Facades\Broker;
|
||||||
|
|
||||||
abstract class Message implements \Serializable
|
abstract class Message implements \Serializable
|
||||||
{
|
{
|
||||||
@@ -22,10 +23,6 @@ abstract class Message implements \Serializable
|
|||||||
|
|
||||||
protected const string DEFAULT_QUEUE = DefaultQueue::class;
|
protected const string DEFAULT_QUEUE = DefaultQueue::class;
|
||||||
|
|
||||||
abstract public static function dispatch(...$args): void;
|
|
||||||
|
|
||||||
abstract public static function dispatchLater(int $delay, ...$args): void;
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->uniqueId = Ulid::generate();
|
$this->uniqueId = Ulid::generate();
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Siteworxpro\App\Async\Messages;
|
|
||||||
|
|
||||||
use Siteworxpro\App\Services\Facades\Broker;
|
|
||||||
|
|
||||||
class SayHelloMessage extends Message
|
|
||||||
{
|
|
||||||
public static function dispatch(...$args): void
|
|
||||||
{
|
|
||||||
$name = $args[0] ?? 'World';
|
|
||||||
$message = new self($name);
|
|
||||||
Broker::publish(
|
|
||||||
$message->getQueue(),
|
|
||||||
$message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function dispatchLater(int $delay, ...$args): void
|
|
||||||
{
|
|
||||||
$name = $args[0] ?? 'World';
|
|
||||||
$message = new self($name);
|
|
||||||
Broker::publishLater(
|
|
||||||
$message->getQueue(),
|
|
||||||
$message,
|
|
||||||
$delay
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function __construct(
|
|
||||||
private readonly string $name
|
|
||||||
) {
|
|
||||||
parent::__construct();
|
|
||||||
|
|
||||||
$this->payload = [
|
|
||||||
'name' => $this->name,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
22
src/Async/Messages/SendUserReset.php
Normal file
22
src/Async/Messages/SendUserReset.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Async\Messages;
|
||||||
|
|
||||||
|
use Siteworxpro\App\Models\User;
|
||||||
|
use Siteworxpro\App\OAuth\Entities\Client;
|
||||||
|
|
||||||
|
class SendUserReset extends Message
|
||||||
|
{
|
||||||
|
public function __construct(User $user, Client $client, string $token)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->payload = [
|
||||||
|
'user' => $user,
|
||||||
|
'client' => $client,
|
||||||
|
'token' => $token,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,10 +7,12 @@ namespace Siteworxpro\App\Cli;
|
|||||||
use Siteworxpro\App\Cli\Commands\Crypt\GenerateKey;
|
use Siteworxpro\App\Cli\Commands\Crypt\GenerateKey;
|
||||||
use Siteworxpro\App\Cli\Commands\OAuth\AddRedirectUri;
|
use Siteworxpro\App\Cli\Commands\OAuth\AddRedirectUri;
|
||||||
use Siteworxpro\App\Cli\Commands\OAuth\AddScope;
|
use Siteworxpro\App\Cli\Commands\OAuth\AddScope;
|
||||||
|
use Siteworxpro\App\Cli\Commands\OAuth\ClientCapabilities;
|
||||||
use Siteworxpro\App\Cli\Commands\OAuth\CreateClient;
|
use Siteworxpro\App\Cli\Commands\OAuth\CreateClient;
|
||||||
use Siteworxpro\App\Cli\Commands\OAuth\ListClients;
|
use Siteworxpro\App\Cli\Commands\OAuth\ListClients;
|
||||||
use Siteworxpro\App\Cli\Commands\Queue\Start;
|
use Siteworxpro\App\Cli\Commands\Queue\Start;
|
||||||
use Siteworxpro\App\Cli\Commands\User\Add;
|
use Siteworxpro\App\Cli\Commands\User\Add;
|
||||||
|
use Siteworxpro\App\Cli\Commands\User\ResetPassword;
|
||||||
use Siteworxpro\App\Helpers\Version;
|
use Siteworxpro\App\Helpers\Version;
|
||||||
use Siteworxpro\App\Kernel;
|
use Siteworxpro\App\Kernel;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
@@ -38,6 +40,8 @@ class App
|
|||||||
$this->app->addCommand(new Start());
|
$this->app->addCommand(new Start());
|
||||||
$this->app->addCommand(new GenerateKey());
|
$this->app->addCommand(new GenerateKey());
|
||||||
$this->app->addCommand(new AddScope());
|
$this->app->addCommand(new AddScope());
|
||||||
|
$this->app->addCommand(new ResetPassword());
|
||||||
|
$this->app->addCommand(new ClientCapabilities());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function run(): int
|
public function run(): int
|
||||||
|
|||||||
@@ -4,8 +4,14 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Siteworxpro\App\Cli\Commands;
|
namespace Siteworxpro\App\Cli\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Siteworxpro\App\Cli\ClimateOutput;
|
||||||
|
use Siteworxpro\App\Models\User;
|
||||||
|
use Siteworxpro\App\OAuth\Entities\Client;
|
||||||
use Symfony\Component\Console\Helper\HelperSet;
|
use Symfony\Component\Console\Helper\HelperSet;
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
|
use Symfony\Component\Console\Question\Question as QuestionInput;
|
||||||
|
|
||||||
abstract class Command extends \Symfony\Component\Console\Command\Command implements CommandInterface
|
abstract class Command extends \Symfony\Component\Console\Command\Command implements CommandInterface
|
||||||
{
|
{
|
||||||
@@ -18,4 +24,65 @@ abstract class Command extends \Symfony\Component\Console\Command\Command implem
|
|||||||
$this->helper = new QuestionHelper();
|
$this->helper = new QuestionHelper();
|
||||||
$this->setHelperSet(new HelperSet([$this->helper]));
|
$this->setHelperSet(new HelperSet([$this->helper]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ClimateOutput $output
|
||||||
|
* @param ClimateOutput|ArgvInput $input
|
||||||
|
* @return Client | null
|
||||||
|
*/
|
||||||
|
protected function askForClient(ClimateOutput $output, ClimateOutput|ArgvInput $input): ?Client
|
||||||
|
{
|
||||||
|
/** @var Collection<Client> $clients */
|
||||||
|
$clients = Client::whereJsonContains('grant_types', 'authorization_code')
|
||||||
|
->get(['id', 'name']);
|
||||||
|
|
||||||
|
if ($clients->isEmpty()) {
|
||||||
|
$output->error('No OAuth clients available.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->info('Available OAuth Clients:');
|
||||||
|
foreach ($clients as $client) {
|
||||||
|
$output->out("[$client->id] $client->name");
|
||||||
|
}
|
||||||
|
|
||||||
|
$question = new QuestionInput('Enter the client ID to associate the new user with: ');
|
||||||
|
$question->setAutocompleterValues($clients->pluck('id')->toArray());
|
||||||
|
|
||||||
|
/** @var QuestionHelper $helper */
|
||||||
|
$helper = $this->getHelper('question');
|
||||||
|
$id = $helper->ask($input, $output, $question);
|
||||||
|
|
||||||
|
return Client::find($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function askForUser(
|
||||||
|
Client $client,
|
||||||
|
ClimateOutput $output,
|
||||||
|
ClimateOutput|ArgvInput $input
|
||||||
|
): ?User {
|
||||||
|
$users = $client->users;
|
||||||
|
|
||||||
|
if ($users->isEmpty()) {
|
||||||
|
$output->error('No users found for the specified client.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->info('Available Users for Client ' . $client->name . ':');
|
||||||
|
|
||||||
|
/** @var User $user */
|
||||||
|
foreach ($users as $user) {
|
||||||
|
$output->out("[$user->id] $user->email");
|
||||||
|
}
|
||||||
|
|
||||||
|
$question = new QuestionInput('Enter the user ID: ');
|
||||||
|
$question->setAutocompleterValues($users->pluck('id')->toArray());
|
||||||
|
|
||||||
|
/** @var QuestionHelper $helper */
|
||||||
|
$helper = $this->getHelper('question');
|
||||||
|
|
||||||
|
$id = $helper->ask($input, $output, $question);
|
||||||
|
|
||||||
|
return User::find($id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,12 @@ interface CommandInterface
|
|||||||
/**
|
/**
|
||||||
* Execute the command.
|
* Execute the command.
|
||||||
*
|
*
|
||||||
|
* @param ArgvInput|ClimateOutput $input
|
||||||
|
* @param ClimateOutput $output
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function __invoke(InputInterface | ArgvInput $input, OutputInterface | ClimateOutput $output): int;
|
public function __invoke(
|
||||||
|
ClimateOutput|ArgvInput $input,
|
||||||
|
ClimateOutput $output
|
||||||
|
): int;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use Siteworxpro\App\Cli\ClimateOutput;
|
|||||||
use Siteworxpro\App\Cli\Commands\Command;
|
use Siteworxpro\App\Cli\Commands\Command;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Input\ArgvInput;
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
|
||||||
#[AsCommand('crypt:generate-key', 'Generate a new encryption key for the application')]
|
#[AsCommand('crypt:generate-key', 'Generate a new encryption key for the application')]
|
||||||
class GenerateKey extends Command
|
class GenerateKey extends Command
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use Siteworxpro\App\OAuth\Entities\Client;
|
|||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\ArgvInput;
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Question\Question;
|
use Symfony\Component\Console\Question\Question;
|
||||||
|
|
||||||
#[AsCommand(name: 'oauth:redirect-uri:create', description: 'Add a redirect URI to an existing OAuth client.')]
|
#[AsCommand(name: 'oauth:redirect-uri:create', description: 'Add a redirect URI to an existing OAuth client.')]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Siteworxpro\App\Cli\Commands\OAuth;
|
namespace Siteworxpro\App\Cli\Commands\OAuth;
|
||||||
|
|
||||||
|
use Siteworxpro\App\Cli\ClimateOutput;
|
||||||
use Siteworxpro\App\Cli\Commands\Command;
|
use Siteworxpro\App\Cli\Commands\Command;
|
||||||
use Siteworxpro\App\OAuth\Entities\Scope;
|
use Siteworxpro\App\OAuth\Entities\Scope;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
@@ -22,7 +23,7 @@ class AddScope extends Command
|
|||||||
->addArgument('description', InputArgument::OPTIONAL, 'The description of the scope');
|
->addArgument('description', InputArgument::OPTIONAL, 'The description of the scope');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __invoke(ArgvInput | InputInterface $input, $output): int
|
public function __invoke($input, $output): int
|
||||||
{
|
{
|
||||||
$name = $input->getArgument('name');
|
$name = $input->getArgument('name');
|
||||||
$description = $input->getArgument('description') ?? '';
|
$description = $input->getArgument('description') ?? '';
|
||||||
|
|||||||
63
src/Cli/Commands/OAuth/ClientCapabilities.php
Normal file
63
src/Cli/Commands/OAuth/ClientCapabilities.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Cli\Commands\OAuth;
|
||||||
|
|
||||||
|
use Siteworxpro\App\Cli\ClimateOutput;
|
||||||
|
use Siteworxpro\App\Cli\Commands\Command;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Question\Question;
|
||||||
|
|
||||||
|
#[AsCommand(name: 'oauth:client:edit', description: 'Edit an OAuth client.')]
|
||||||
|
class ClientCapabilities extends Command
|
||||||
|
{
|
||||||
|
public function __invoke($input, ClimateOutput|OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$client = $this->askForClient($output, $input);
|
||||||
|
|
||||||
|
if ($client === null) {
|
||||||
|
$output->error('Client not found.');
|
||||||
|
return \Symfony\Component\Console\Command\Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
$output->info('Choose a capability to modify:');
|
||||||
|
$capabilities = $client->capabilities->toArray();
|
||||||
|
|
||||||
|
$output->info('[0] Exit');
|
||||||
|
$output->info('[1] Username Password: ' . ($capabilities['userPass'] ? 'Enabled' : 'Disabled'));
|
||||||
|
$output->info('[2] Magic Link: ' . ($capabilities['magicLink'] ? 'Enabled' : 'Disabled'));
|
||||||
|
$output->info('[3] Social Logins: ' . ($capabilities['socials'] ? 'Enabled' : 'Disabled'));
|
||||||
|
|
||||||
|
$question = new Question('What do you want to edit: ', '');
|
||||||
|
$selection = $this->helper->ask($input, $output, $question);
|
||||||
|
|
||||||
|
if ($selection === '0') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($selection) {
|
||||||
|
case '1':
|
||||||
|
$capabilities['userPass'] = !$capabilities['userPass'];
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
$capabilities['magicLink'] = !$capabilities['magicLink'];
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
$output->info('Social Logins cannot be modified via CLI at this time.');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$output->error('Invalid selection. Please try again.');
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$client->capabilities = $capabilities;
|
||||||
|
$client->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return \Symfony\Component\Console\Command\Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ namespace Siteworxpro\App\Cli\Commands\OAuth;
|
|||||||
use Siteworxpro\App\Cli\ClimateOutput;
|
use Siteworxpro\App\Cli\ClimateOutput;
|
||||||
use Siteworxpro\App\CommandBus\Commands\CreateClient as CreateClientCommand;
|
use Siteworxpro\App\CommandBus\Commands\CreateClient as CreateClientCommand;
|
||||||
use Siteworxpro\App\CommandBus\Exceptions\CommandHandlerException;
|
use Siteworxpro\App\CommandBus\Exceptions\CommandHandlerException;
|
||||||
|
use Siteworxpro\App\Models\Enums\ClientGrant as ClientGrantAlias;
|
||||||
use Siteworxpro\App\OAuth\Entities\Client;
|
use Siteworxpro\App\OAuth\Entities\Client;
|
||||||
use Siteworxpro\App\Services\Facades\CommandBus;
|
use Siteworxpro\App\Services\Facades\CommandBus;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
@@ -37,8 +38,13 @@ class CreateClient extends \Siteworxpro\App\Cli\Commands\Command
|
|||||||
$question->setMultiselect(true);
|
$question->setMultiselect(true);
|
||||||
|
|
||||||
$grants = $this->helper->ask($input, $output, $question);
|
$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 {
|
try {
|
||||||
/** @var Client $client */
|
/** @var Client $client */
|
||||||
$client = CommandBus::handle($command);
|
$client = CommandBus::handle($command);
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ class ListClients extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ArgvInput|InputInterface $input
|
* @param ArgvInput|ClimateOutput|InputInterface $input
|
||||||
* @param ClimateOutput $output
|
* @param ClimateOutput $output
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function __invoke(ArgvInput|InputInterface $input, $output): int
|
public function __invoke(ClimateOutput|ArgvInput|InputInterface $input, $output): int
|
||||||
{
|
{
|
||||||
if ($input->getArgument('client-id')) {
|
if ($input->getArgument('client-id')) {
|
||||||
$client = Client::find($input->getArgument('client-id'));
|
$client = Client::find($input->getArgument('client-id'));
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ declare(strict_types=1);
|
|||||||
namespace Siteworxpro\App\Cli\Commands\Queue;
|
namespace Siteworxpro\App\Cli\Commands\Queue;
|
||||||
|
|
||||||
use Siteworxpro\App\Async\Consumer;
|
use Siteworxpro\App\Async\Consumer;
|
||||||
|
use Siteworxpro\App\Cli\ClimateOutput;
|
||||||
use Siteworxpro\App\Cli\Commands\Command;
|
use Siteworxpro\App\Cli\Commands\Command;
|
||||||
use Siteworxpro\App\Cli\Commands\CommandInterface;
|
use Siteworxpro\App\Cli\Commands\CommandInterface;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
|
||||||
#[AsCommand('queue:start', 'Start the queue consumer')]
|
#[AsCommand('queue:start', 'Start the queue consumer')]
|
||||||
class Start extends Command implements CommandInterface
|
class Start extends Command implements CommandInterface
|
||||||
@@ -23,7 +26,7 @@ class Start extends Command implements CommandInterface
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __invoke($input, $output): int
|
public function __invoke(ClimateOutput|ArgvInput|InputInterface $input, $output): int
|
||||||
{
|
{
|
||||||
$queues = $input->getArgument('queues');
|
$queues = $input->getArgument('queues');
|
||||||
|
|
||||||
|
|||||||
@@ -4,38 +4,25 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Siteworxpro\App\Cli\Commands\User;
|
namespace Siteworxpro\App\Cli\Commands\User;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Siteworxpro\App\Cli\ClimateOutput;
|
||||||
use Siteworxpro\App\Cli\Commands\Command;
|
use Siteworxpro\App\Cli\Commands\Command;
|
||||||
|
use Siteworxpro\App\CommandBus\Commands\CreateUser;
|
||||||
use Siteworxpro\App\Models\ClientUser;
|
use Siteworxpro\App\Models\ClientUser;
|
||||||
use Siteworxpro\App\Models\User;
|
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\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Command\Command as SCommand;
|
use Symfony\Component\Console\Command\Command as SCommand;
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Question\Question as QuestionInput;
|
use Symfony\Component\Console\Question\Question as QuestionInput;
|
||||||
|
|
||||||
#[AsCommand('user:add', 'Add a new user associated with an OAuth client')]
|
#[AsCommand('user:add', 'Add a new user associated with an OAuth client')]
|
||||||
class Add extends Command
|
class Add extends Command
|
||||||
{
|
{
|
||||||
public function __invoke($input, $output): int
|
public function __invoke(ClimateOutput|ArgvInput|InputInterface $input, ClimateOutput $output): int
|
||||||
{
|
{
|
||||||
/** @var Collection<Client> $clients */
|
$client = $this->askForClient($output, $input);
|
||||||
$clients = Client::whereJsonContains('grant_types', 'authorization_code')
|
|
||||||
->get(['id', 'name']);
|
|
||||||
|
|
||||||
$output->info('Available OAuth Clients:');
|
|
||||||
foreach ($clients as $client) {
|
|
||||||
$output->out("[$client->id] $client->name");
|
|
||||||
}
|
|
||||||
|
|
||||||
$question = new QuestionInput('Enter the client ID to associate the new user with: ');
|
|
||||||
$question->setAutocompleterValues($clients->pluck('id')->toArray());
|
|
||||||
|
|
||||||
/** @var QuestionHelper $helper */
|
|
||||||
$helper = $this->getHelper('question');
|
|
||||||
$id = $helper->ask($input, $output, $question);
|
|
||||||
|
|
||||||
$client = Client::find($id);
|
|
||||||
if (!$client) {
|
if (!$client) {
|
||||||
$output->error('Client not found.');
|
$output->error('Client not found.');
|
||||||
return SCommand::FAILURE;
|
return SCommand::FAILURE;
|
||||||
@@ -51,6 +38,9 @@ class Add extends Command
|
|||||||
return $input;
|
return $input;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** @var QuestionHelper $helper */
|
||||||
|
$helper = $this->getHelper('question');
|
||||||
|
|
||||||
$email = $helper->ask($input, $output, $emailQuestion);
|
$email = $helper->ask($input, $output, $emailQuestion);
|
||||||
|
|
||||||
/** @var User| null $user */
|
/** @var User| null $user */
|
||||||
@@ -76,19 +66,16 @@ class Add extends Command
|
|||||||
$lastNameQuestion = new QuestionInput('Enter the user\'s last name: ');
|
$lastNameQuestion = new QuestionInput('Enter the user\'s last name: ');
|
||||||
$lastName = $helper->ask($input, $output, $lastNameQuestion);
|
$lastName = $helper->ask($input, $output, $lastNameQuestion);
|
||||||
|
|
||||||
$user = new User();
|
$createUserCommand = new CreateUser($client, $email, $password, $firstName, $lastName);
|
||||||
$user->email = $email;
|
|
||||||
$user->password = $password;
|
|
||||||
$user->first_name = $firstName;
|
|
||||||
$user->last_name = $lastName;
|
|
||||||
$user->save();
|
|
||||||
|
|
||||||
$clientUser = new ClientUser();
|
/** @var User $user */
|
||||||
$clientUser->client_id = $client->id;
|
$user = CommandBus::handle($createUserCommand);
|
||||||
$clientUser->user_id = $user->id;
|
|
||||||
$clientUser->save();
|
|
||||||
|
|
||||||
$output->green('User added and associated with the client successfully.');
|
$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;
|
return SCommand::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
73
src/Cli/Commands/User/ResetPassword.php
Normal file
73
src/Cli/Commands/User/ResetPassword.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Cli\Commands\User;
|
||||||
|
|
||||||
|
use Siteworxpro\App\Cli\ClimateOutput;
|
||||||
|
use Siteworxpro\App\Cli\Commands\Command;
|
||||||
|
use Siteworxpro\App\CommandBus\Commands\SendPasswordReset;
|
||||||
|
use Siteworxpro\App\Services\Facades\CommandBus;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Question\Question as QuestionInput;
|
||||||
|
|
||||||
|
#[AsCommand('user:reset-password', 'Reset a user\'s password')]
|
||||||
|
class ResetPassword extends Command
|
||||||
|
{
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->addOption(
|
||||||
|
'send-email',
|
||||||
|
's',
|
||||||
|
InputOption::VALUE_NONE,
|
||||||
|
description: 'Send password reset email to the user'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public function __invoke($input, $output): int
|
||||||
|
{
|
||||||
|
$client = $this->askForClient($output, $input);
|
||||||
|
|
||||||
|
if (!$client) {
|
||||||
|
$output->error('Client not found.');
|
||||||
|
return \Symfony\Component\Console\Command\Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->askForUser($client, $output, $input);
|
||||||
|
if (!$user) {
|
||||||
|
$output->error('User not found for the specified client.');
|
||||||
|
return \Symfony\Component\Console\Command\Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input->getOption('send-email')) {
|
||||||
|
CommandBus::handle(new SendPasswordReset($user, $client));
|
||||||
|
$output->info('Password reset email sent to the user.');
|
||||||
|
|
||||||
|
return \Symfony\Component\Console\Command\Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var QuestionHelper $helper */
|
||||||
|
$helper = $this->getHelper('question');
|
||||||
|
$question = new QuestionInput('Enter the new password: ');
|
||||||
|
$question->setValidator(function ($input): string {
|
||||||
|
if (empty($input) || strlen($input) < 8) {
|
||||||
|
throw new \InvalidArgumentException('Password must be at least 8 characters long.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $input;
|
||||||
|
});
|
||||||
|
|
||||||
|
$question->setHidden(true);
|
||||||
|
$question->setHiddenFallback(false);
|
||||||
|
|
||||||
|
$newPassword = $helper->ask($input, $output, $question);
|
||||||
|
|
||||||
|
$user->password = $newPassword;
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
$output->info('Password has been reset successfully.');
|
||||||
|
|
||||||
|
return \Symfony\Component\Console\Command\Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,24 +2,32 @@
|
|||||||
|
|
||||||
namespace Siteworxpro\App\CommandBus\Commands;
|
namespace Siteworxpro\App\CommandBus\Commands;
|
||||||
|
|
||||||
|
use Siteworxpro\App\Models\Enums\ClientGrant;
|
||||||
|
|
||||||
readonly class CreateClient extends Command
|
readonly class CreateClient extends Command
|
||||||
{
|
{
|
||||||
private const array VALID_GRANTS = [
|
/**
|
||||||
'authorization_code',
|
* @param string $clientName
|
||||||
'password',
|
* @param array<ClientGrant | mixed> $clientGrants
|
||||||
'client_credentials',
|
* @param string $clientDescription
|
||||||
'refresh_token',
|
*/
|
||||||
'implicit',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private string $clientName,
|
private string $clientName,
|
||||||
private array $clientGrants = [],
|
private array $clientGrants = [],
|
||||||
private string $clientDescription = ''
|
private string $clientDescription = ''
|
||||||
) {
|
) {
|
||||||
foreach ($this->clientGrants as $grant) {
|
foreach ($this->clientGrants as $grant) {
|
||||||
if (!in_array($grant, self::VALID_GRANTS, true)) {
|
if ($grant instanceof ClientGrant === false) {
|
||||||
throw new \InvalidArgumentException("Invalid grant type: $grant");
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/CommandBus/Commands/SendPasswordReset.php
Normal file
30
src/CommandBus/Commands/SendPasswordReset.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\CommandBus\Commands;
|
||||||
|
|
||||||
|
use Siteworxpro\App\Models\User;
|
||||||
|
use Siteworxpro\App\OAuth\Entities\Client;
|
||||||
|
|
||||||
|
readonly class SendPasswordReset extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private User $user,
|
||||||
|
private Client $client
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Client
|
||||||
|
*/
|
||||||
|
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\Exceptions\CommandHandlerException;
|
||||||
use Siteworxpro\App\CommandBus\Handlers\CommandHandler;
|
use Siteworxpro\App\CommandBus\Handlers\CommandHandler;
|
||||||
use Siteworxpro\App\OAuth\Entities\Client;
|
use Siteworxpro\App\OAuth\Entities\Client;
|
||||||
|
use Siteworxpro\App\OAuth\Entities\ClientCapabilities;
|
||||||
|
|
||||||
#[HandlesCommand(\Siteworxpro\App\CommandBus\Commands\CreateClient::class)]
|
#[HandlesCommand(\Siteworxpro\App\CommandBus\Commands\CreateClient::class)]
|
||||||
class CreateClient extends CommandHandler
|
class CreateClient extends CommandHandler
|
||||||
@@ -24,6 +25,7 @@ class CreateClient extends CommandHandler
|
|||||||
$client->name = $command->getClientName();
|
$client->name = $command->getClientName();
|
||||||
$client->description = $command->getClientDescription();
|
$client->description = $command->getClientDescription();
|
||||||
$client->grant_types = new Collection($command->getClientGrants()); // @phpstan-ignore-line assign.propertyType
|
$client->grant_types = new Collection($command->getClientGrants()); // @phpstan-ignore-line assign.propertyType
|
||||||
|
$client->capabilities = new ClientCapabilities();
|
||||||
|
|
||||||
$client->save();
|
$client->save();
|
||||||
|
|
||||||
|
|||||||
55
src/CommandBus/Handlers/SendPasswordResetHandler.php
Normal file
55
src/CommandBus/Handlers/SendPasswordResetHandler.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\CommandBus\Handlers;
|
||||||
|
|
||||||
|
use Random\RandomException;
|
||||||
|
use Siteworxpro\App\Attributes\CommandBus\HandlesCommand;
|
||||||
|
use Siteworxpro\App\CommandBus\Commands\Command;
|
||||||
|
use Siteworxpro\App\CommandBus\Commands\SendPasswordReset;
|
||||||
|
use Siteworxpro\App\CommandBus\Exceptions\CommandHandlerException;
|
||||||
|
use Siteworxpro\App\Helpers\Rand;
|
||||||
|
use Siteworxpro\App\Mailer\Message;
|
||||||
|
use Siteworxpro\App\Models\User;
|
||||||
|
use Siteworxpro\App\Services\Facades\Config;
|
||||||
|
use Siteworxpro\App\Services\Facades\Mailer;
|
||||||
|
use Siteworxpro\App\Services\Facades\Redis;
|
||||||
|
use Siteworxpro\App\Services\Facades\Twig;
|
||||||
|
|
||||||
|
#[HandlesCommand(SendPasswordReset::class)]
|
||||||
|
class SendPasswordResetHandler extends CommandHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws RandomException
|
||||||
|
*/
|
||||||
|
public function __invoke(Command|SendPasswordReset $command): User
|
||||||
|
{
|
||||||
|
if (!$command instanceof SendPasswordReset) {
|
||||||
|
throw new CommandHandlerException('Invalid command type provided to handler.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = Rand::string(64);
|
||||||
|
|
||||||
|
$content = Twig::render('password-reset.twig', [
|
||||||
|
'user' => $command->getUser(),
|
||||||
|
'resetLink' => Config::get('app.url') . '/reset-password?token=' . $token,
|
||||||
|
'client' => $command->getClient()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$message = new Message(
|
||||||
|
$command->getUser()->email,
|
||||||
|
$command->getClient()->capabilities->toArray()['support_email'] ?? Config::get('app.default_support_email'),
|
||||||
|
'Password Reset Request',
|
||||||
|
$content
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Mailer::send($message) === false) {
|
||||||
|
throw new CommandHandlerException('Failed to send password reset email.');
|
||||||
|
}
|
||||||
|
|
||||||
|
Redis::set('password_reset:' . $command->getUser()->id, $token, 'EX', Redis::MINUTE * 15);
|
||||||
|
|
||||||
|
return $command->getUser();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,11 +7,14 @@ namespace Siteworxpro\App\Controllers;
|
|||||||
use Defuse\Crypto\Exception\BadFormatException;
|
use Defuse\Crypto\Exception\BadFormatException;
|
||||||
use Defuse\Crypto\Exception\EnvironmentIsBrokenException;
|
use Defuse\Crypto\Exception\EnvironmentIsBrokenException;
|
||||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
|
use Nyholm\Psr7\Response;
|
||||||
use Nyholm\Psr7\ServerRequest;
|
use Nyholm\Psr7\ServerRequest;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Siteworxpro\App\Events\AccessToken\Issued;
|
||||||
use Siteworxpro\App\Http\JsonResponseFactory;
|
use Siteworxpro\App\Http\JsonResponseFactory;
|
||||||
use Siteworxpro\App\Http\Responses\GenericResponse;
|
use Siteworxpro\App\Http\Responses\GenericResponse;
|
||||||
use Siteworxpro\App\OAuth\Entities\Client;
|
use Siteworxpro\App\OAuth\Entities\Client;
|
||||||
|
use Siteworxpro\App\Services\Facades\Dispatcher;
|
||||||
use Siteworxpro\HttpStatus\CodesEnum;
|
use Siteworxpro\HttpStatus\CodesEnum;
|
||||||
|
|
||||||
final class AccessTokenController extends Controller
|
final class AccessTokenController extends Controller
|
||||||
@@ -34,9 +37,14 @@ final class AccessTokenController extends Controller
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $client
|
/** @var Response $response */
|
||||||
|
$response = $client
|
||||||
->getAuthorizationServer()
|
->getAuthorizationServer()
|
||||||
->respondToAccessTokenRequest($request, JsonResponseFactory::createJsonResponse([]));
|
->respondToAccessTokenRequest($request, JsonResponseFactory::createJsonResponse([]));
|
||||||
|
|
||||||
|
Dispatcher::push(new Issued($response));
|
||||||
|
|
||||||
|
return $response;
|
||||||
} catch (OAuthServerException $e) {
|
} catch (OAuthServerException $e) {
|
||||||
return JsonResponseFactory::createJsonResponse(
|
return JsonResponseFactory::createJsonResponse(
|
||||||
$e->getPayload(),
|
$e->getPayload(),
|
||||||
|
|||||||
@@ -12,10 +12,14 @@ use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
|
|||||||
use Nyholm\Psr7\Response;
|
use Nyholm\Psr7\Response;
|
||||||
use Nyholm\Psr7\ServerRequest;
|
use Nyholm\Psr7\ServerRequest;
|
||||||
use Nyholm\Psr7\Stream;
|
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\Helpers\Rand;
|
||||||
use Siteworxpro\App\Http\JsonResponseFactory;
|
use Siteworxpro\App\Http\JsonResponseFactory;
|
||||||
use Siteworxpro\App\Http\Responses\ServerErrorResponse;
|
use Siteworxpro\App\Http\Responses\ServerErrorResponse;
|
||||||
use Siteworxpro\App\OAuth\Entities\Client;
|
use Siteworxpro\App\OAuth\Entities\Client;
|
||||||
|
use Siteworxpro\App\Services\Facades\Dispatcher;
|
||||||
use Siteworxpro\App\Services\Facades\Logger;
|
use Siteworxpro\App\Services\Facades\Logger;
|
||||||
use Siteworxpro\App\Services\Facades\Redis;
|
use Siteworxpro\App\Services\Facades\Redis;
|
||||||
use Siteworxpro\HttpStatus\CodesEnum;
|
use Siteworxpro\HttpStatus\CodesEnum;
|
||||||
@@ -31,6 +35,8 @@ final class AuthorizeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function post(ServerRequest $request): Response
|
public function post(ServerRequest $request): Response
|
||||||
{
|
{
|
||||||
|
Dispatcher::push(new LoginAttempt($request));
|
||||||
|
|
||||||
$s = $request->getCookieParams()['s'] ?? '';
|
$s = $request->getCookieParams()['s'] ?? '';
|
||||||
|
|
||||||
$password = $request->getParsedBody()['password'] ?? '';
|
$password = $request->getParsedBody()['password'] ?? '';
|
||||||
@@ -63,6 +69,8 @@ final class AuthorizeController extends Controller
|
|||||||
$user = $client->loginUser($email, $password);
|
$user = $client->loginUser($email, $password);
|
||||||
|
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
|
Dispatcher::push(new LoginFailed($request));
|
||||||
|
|
||||||
return JsonResponseFactory::createJsonResponse([
|
return JsonResponseFactory::createJsonResponse([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'reason' => 'login failed'
|
'reason' => 'login failed'
|
||||||
@@ -76,6 +84,8 @@ final class AuthorizeController extends Controller
|
|||||||
|
|
||||||
Redis::del('session:' . $s);
|
Redis::del('session:' . $s);
|
||||||
|
|
||||||
|
Dispatcher::push(new LoginSuccess($request, $user));
|
||||||
|
|
||||||
return JsonResponseFactory::createJsonResponse([
|
return JsonResponseFactory::createJsonResponse([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'location' => $response->getHeader('Location')[0]
|
'location' => $response->getHeader('Location')[0]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Siteworxpro\App\Events\Listeners;
|
namespace Siteworxpro\App\EventListeners;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Listener
|
* Class Listener
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Siteworxpro\App\Events\Listeners;
|
namespace Siteworxpro\App\EventListeners;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface ListenerInterface
|
* 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 string | \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
|
* @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()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -63,7 +63,7 @@ class Dispatcher implements DispatcherContract, Arrayable
|
|||||||
private function registerListeners(): void
|
private function registerListeners(): void
|
||||||
{
|
{
|
||||||
// traverse the Listeners directory and register all listeners
|
// traverse the Listeners directory and register all listeners
|
||||||
$listenersPath = __DIR__ . '/Listeners';
|
$listenersPath = __DIR__ . '/../EventListeners';
|
||||||
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($listenersPath));
|
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($listenersPath));
|
||||||
|
|
||||||
foreach ($iterator as $file) {
|
foreach ($iterator as $file) {
|
||||||
@@ -193,12 +193,17 @@ class Dispatcher implements DispatcherContract, Arrayable
|
|||||||
/**
|
/**
|
||||||
* Push an event to be dispatched later.
|
* Push an event to be dispatched later.
|
||||||
*
|
*
|
||||||
* @param $event
|
* @param mixed $event
|
||||||
* @param array $payload
|
* @param array $payload
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function push($event, $payload = []): void
|
public function push($event, $payload = []): void
|
||||||
{
|
{
|
||||||
|
if (!is_string($event)) {
|
||||||
|
$payload = [$event];
|
||||||
|
$event = get_class($event);
|
||||||
|
}
|
||||||
|
|
||||||
$this->pushed->put($event, $payload);
|
$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ use Siteworxpro\App\Services\ServiceProviders\DispatcherServiceProvider;
|
|||||||
use Siteworxpro\App\Services\ServiceProviders\EncryptionServiceProvider;
|
use Siteworxpro\App\Services\ServiceProviders\EncryptionServiceProvider;
|
||||||
use Siteworxpro\App\Services\ServiceProviders\LoggerServiceProvider;
|
use Siteworxpro\App\Services\ServiceProviders\LoggerServiceProvider;
|
||||||
use Siteworxpro\App\Services\ServiceProviders\RedisServiceProvider;
|
use Siteworxpro\App\Services\ServiceProviders\RedisServiceProvider;
|
||||||
|
use Siteworxpro\App\Services\ServiceProviders\TwigProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Kernel
|
* Class Kernel
|
||||||
@@ -38,6 +39,7 @@ class Kernel
|
|||||||
BrokerServiceProvider::class,
|
BrokerServiceProvider::class,
|
||||||
CommandBusProvider::class,
|
CommandBusProvider::class,
|
||||||
EncryptionServiceProvider::class,
|
EncryptionServiceProvider::class,
|
||||||
|
TwigProvider::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
13
src/Mailer/Drivers/DriverInterface.php
Normal file
13
src/Mailer/Drivers/DriverInterface.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Mailer\Drivers;
|
||||||
|
|
||||||
|
use Siteworxpro\App\Mailer\Message;
|
||||||
|
|
||||||
|
interface DriverInterface
|
||||||
|
{
|
||||||
|
public function setFrom(string $address, string $name): void;
|
||||||
|
public function send(Message $message): bool;
|
||||||
|
}
|
||||||
48
src/Mailer/Drivers/Log.php
Normal file
48
src/Mailer/Drivers/Log.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Mailer\Drivers;
|
||||||
|
|
||||||
|
use Siteworxpro\App\Mailer\Message;
|
||||||
|
use Siteworxpro\App\Services\Facades\Logger;
|
||||||
|
|
||||||
|
class Log implements DriverInterface
|
||||||
|
{
|
||||||
|
private string $fromAddress = '';
|
||||||
|
|
||||||
|
public function send(Message $message): bool
|
||||||
|
{
|
||||||
|
$logMessage = sprintf(
|
||||||
|
"=================================" . PHP_EOL .
|
||||||
|
"Email sent via Log Driver" . PHP_EOL .
|
||||||
|
"From: %s" . PHP_EOL .
|
||||||
|
"To: %s" . PHP_EOL .
|
||||||
|
"Subject: %s" . PHP_EOL .
|
||||||
|
"Body: " . PHP_EOL . "%s" . PHP_EOL .
|
||||||
|
"=================================",
|
||||||
|
$this->fromAddress,
|
||||||
|
$message->getTo(),
|
||||||
|
$message->getSubject(),
|
||||||
|
$this->formatBodyForLog($message->getBody())
|
||||||
|
);
|
||||||
|
|
||||||
|
Logger::info($logMessage);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFrom(string $address, string $name): void
|
||||||
|
{
|
||||||
|
$this->fromAddress = sprintf('%s <%s>', $name, $address);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatBodyForLog(string $body): string
|
||||||
|
{
|
||||||
|
$body = str_replace('<br>', "\n", $body);
|
||||||
|
$body = str_replace('<br/>', "\n", $body);
|
||||||
|
$body = strip_tags($body);
|
||||||
|
|
||||||
|
return wordwrap($body, 80);
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/Mailer/Drivers/Smtp.php
Normal file
53
src/Mailer/Drivers/Smtp.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Mailer\Drivers;
|
||||||
|
|
||||||
|
use PHPMailer\PHPMailer\Exception;
|
||||||
|
use PHPMailer\PHPMailer\PHPMailer;
|
||||||
|
use Siteworxpro\App\Mailer\Message;
|
||||||
|
use Siteworxpro\App\Services\Facades\Config;
|
||||||
|
|
||||||
|
class Smtp implements DriverInterface
|
||||||
|
{
|
||||||
|
private PHPMailer $mailer;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$config = Config::get('mailer.smtp');
|
||||||
|
|
||||||
|
if (empty($config['host']) || empty($config['port'])) {
|
||||||
|
throw new \InvalidArgumentException(
|
||||||
|
'SMTP host and port must be specified in the configuration mailer.smtp.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($config['encryption'] && !in_array($config['encryption'], ['ssl', 'starttls', 'none'], true)) {
|
||||||
|
throw new \InvalidArgumentException(
|
||||||
|
'Invalid SMTP encryption type specified. Valid options are: ssl, starttls, or none.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->mailer = new PHPMailer(true);
|
||||||
|
$this->mailer->isSMTP();
|
||||||
|
$this->mailer->Host = $config['host'];
|
||||||
|
$this->mailer->Port = $config['port'];
|
||||||
|
$this->mailer->SMTPAuth = !empty($config['username']) && !empty($config['password']);
|
||||||
|
$this->mailer->Username = $config['username'] ?? '';
|
||||||
|
$this->mailer->Password = $config['password'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(Message $message): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function setFrom(string $address, string $name): void
|
||||||
|
{
|
||||||
|
$this->mailer->setFrom($address, $name, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/Mailer/Message.php
Normal file
42
src/Mailer/Message.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Mailer;
|
||||||
|
|
||||||
|
readonly class Message
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $to,
|
||||||
|
private string $from,
|
||||||
|
private string $subject,
|
||||||
|
private string $body
|
||||||
|
) {
|
||||||
|
if ($to != filter_var($to, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
throw new \InvalidArgumentException('Invalid email address provided.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTo(): string
|
||||||
|
{
|
||||||
|
return $this->to;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getFrom(): string
|
||||||
|
{
|
||||||
|
return $this->from;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubject(): string
|
||||||
|
{
|
||||||
|
return $this->subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBody(): string
|
||||||
|
{
|
||||||
|
return $this->body;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/Mailer/Sendmail.php
Normal file
26
src/Mailer/Sendmail.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Mailer;
|
||||||
|
|
||||||
|
use Siteworxpro\App\Services\Facades\Config;
|
||||||
|
|
||||||
|
class Sendmail
|
||||||
|
{
|
||||||
|
private Drivers\DriverInterface $driver;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->driver = match (Config::get('mailer.driver')) {
|
||||||
|
'log' => new Drivers\Log(),
|
||||||
|
'smtp' => new Drivers\Smtp(),
|
||||||
|
default => throw new \InvalidArgumentException('Invalid mailer driver specified.'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(Message $message): bool
|
||||||
|
{
|
||||||
|
return $this->driver->send($message);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/Mailer/Templates/_base.twig
Normal file
24
src/Mailer/Templates/_base.twig
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{{ subject }}</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
17
src/Mailer/Templates/password-reset.twig
Normal file
17
src/Mailer/Templates/password-reset.twig
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{% extends '_base.twig' %}
|
||||||
|
|
||||||
|
{% block subject %}
|
||||||
|
Password Reset Request
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<p>Dear {{ user.firstName }},</p>
|
||||||
|
|
||||||
|
<p>We received a request to reset your password. Click the link below to set a new password:</p>
|
||||||
|
|
||||||
|
<p><a href="{{ resetLink }}">Reset Your Password</a></p>
|
||||||
|
|
||||||
|
<p>If you did not request a password reset, please ignore this email.</p>
|
||||||
|
|
||||||
|
<p>Best regards,<br>{{ client.name }}</p>
|
||||||
|
{% endblock %}
|
||||||
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 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 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 Builder whereJsonContains(string $column, mixed $value, string $boolean = 'and', bool $not = false)
|
||||||
|
* @method static static create(array $attributes = [])
|
||||||
*/
|
*/
|
||||||
abstract class Model extends ORM
|
abstract class Model extends ORM
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,9 +38,10 @@ use Siteworxpro\App\OAuth\ScopeRepository;
|
|||||||
* @property Collection $grant_types
|
* @property Collection $grant_types
|
||||||
* @property bool $confidential
|
* @property bool $confidential
|
||||||
*
|
*
|
||||||
* @property-read ClientCapabilities $capabilities
|
* @property ClientCapabilities | array $capabilities
|
||||||
* @property-read Collection<ClientRedirectUri> $clientRedirectUris
|
* @property-read Collection<ClientRedirectUri> $clientRedirectUris
|
||||||
* @property-read Scope[]|Collection $scopes
|
* @property-read Scope[]|Collection $scopes
|
||||||
|
* @property-read Collection<User> $users
|
||||||
*/
|
*/
|
||||||
class Client extends Model implements ClientEntityInterface
|
class Client extends Model implements ClientEntityInterface
|
||||||
{
|
{
|
||||||
@@ -169,8 +170,12 @@ class Client extends Model implements ClientEntityInterface
|
|||||||
/**
|
/**
|
||||||
* @throws \JsonException
|
* @throws \JsonException
|
||||||
*/
|
*/
|
||||||
public function setCapabilitiesAttribute(ClientCapabilities $capabilities): void
|
public function setCapabilitiesAttribute(ClientCapabilities | array $capabilities): void
|
||||||
{
|
{
|
||||||
|
if (is_array($capabilities)) {
|
||||||
|
$capabilities = new ClientCapabilities($capabilities);
|
||||||
|
}
|
||||||
|
|
||||||
$this->attributes['capabilities'] = $capabilities->toJson();
|
$this->attributes['capabilities'] = $capabilities->toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace Siteworxpro\App\OAuth\Entities;
|
namespace Siteworxpro\App\OAuth\Entities;
|
||||||
|
|
||||||
use Illuminate\Contracts\Support\Arrayable;
|
use Illuminate\Contracts\Support\Arrayable;
|
||||||
|
use JetBrains\PhpStorm\ArrayShape;
|
||||||
|
|
||||||
class ClientCapabilities implements Arrayable
|
class ClientCapabilities implements Arrayable
|
||||||
{
|
{
|
||||||
@@ -13,13 +14,15 @@ class ClientCapabilities implements Arrayable
|
|||||||
private bool $passkey = false;
|
private bool $passkey = false;
|
||||||
private array $socials = [];
|
private array $socials = [];
|
||||||
|
|
||||||
|
private string $support_email = '';
|
||||||
|
|
||||||
private array $branding = [
|
private array $branding = [
|
||||||
'primaryColor' => '#000000',
|
'primaryColor' => '#000000',
|
||||||
'secondaryColor' => '#FFFFFF',
|
'secondaryColor' => '#FFFFFF',
|
||||||
'logoUrl' => null,
|
'logoUrl' => null,
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(array $capabilities)
|
public function __construct(array $capabilities = [])
|
||||||
{
|
{
|
||||||
if (isset($capabilities['userPass'])) {
|
if (isset($capabilities['userPass'])) {
|
||||||
$this->userPass = (bool)$capabilities['userPass'];
|
$this->userPass = (bool)$capabilities['userPass'];
|
||||||
@@ -40,6 +43,10 @@ class ClientCapabilities implements Arrayable
|
|||||||
if (isset($capabilities['branding']) && is_array($capabilities['branding'])) {
|
if (isset($capabilities['branding']) && is_array($capabilities['branding'])) {
|
||||||
$this->branding = array_merge($this->branding, $capabilities['branding']);
|
$this->branding = array_merge($this->branding, $capabilities['branding']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($capabilities['support_email'])) {
|
||||||
|
$this->support_email = (string)$capabilities['support_email'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fromJson(string $data): self
|
public static function fromJson(string $data): self
|
||||||
@@ -53,6 +60,14 @@ class ClientCapabilities implements Arrayable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[ArrayShape([
|
||||||
|
'userPass' => "bool",
|
||||||
|
'magicLink' => "bool",
|
||||||
|
'passkey' => "bool",
|
||||||
|
'socials' => "array",
|
||||||
|
'branding' => "array",
|
||||||
|
'support_email' => "string"
|
||||||
|
])]
|
||||||
public function toArray(): array
|
public function toArray(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@@ -61,6 +76,7 @@ class ClientCapabilities implements Arrayable
|
|||||||
'passkey' => $this->passkey,
|
'passkey' => $this->passkey,
|
||||||
'socials' => $this->socials,
|
'socials' => $this->socials,
|
||||||
'branding' => $this->branding,
|
'branding' => $this->branding,
|
||||||
|
'support_email' => $this->support_email,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
33
src/Services/Facades/Mailer.php
Normal file
33
src/Services/Facades/Mailer.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Services\Facades;
|
||||||
|
|
||||||
|
use Siteworxpro\App\Mailer\Message;
|
||||||
|
use Siteworxpro\App\Mailer\Sendmail;
|
||||||
|
use Siteworxpro\App\Services\Facade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method static bool send(Message $message)
|
||||||
|
*/
|
||||||
|
class Mailer extends Facade
|
||||||
|
{
|
||||||
|
public static function getFacadeRoot(): Sendmail
|
||||||
|
{
|
||||||
|
if (static::resolveFacadeInstance(self::getFacadeAccessor())) {
|
||||||
|
static::resolveFacadeInstance(self::getFacadeAccessor());
|
||||||
|
}
|
||||||
|
|
||||||
|
$sendMail = new Sendmail();
|
||||||
|
|
||||||
|
static::getFacadeContainer()->bind(self::getFacadeAccessor(), fn() => $sendMail);
|
||||||
|
|
||||||
|
return $sendMail;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function getFacadeAccessor(): string
|
||||||
|
{
|
||||||
|
return Sendmail::class;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,8 @@ use Siteworxpro\App\Services\Facade;
|
|||||||
*/
|
*/
|
||||||
class Redis extends Facade
|
class Redis extends Facade
|
||||||
{
|
{
|
||||||
|
public const MINUTE = 60;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the registered name of the component.
|
* Get the registered name of the component.
|
||||||
*
|
*
|
||||||
|
|||||||
24
src/Services/Facades/Twig.php
Normal file
24
src/Services/Facades/Twig.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Services\Facades;
|
||||||
|
|
||||||
|
use Siteworxpro\App\Services\Facade;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Twig
|
||||||
|
*
|
||||||
|
* Facade for accessing the Twig templating engine.
|
||||||
|
*
|
||||||
|
* @package Siteworxpro\App\Services\Facades
|
||||||
|
* @method static render(string $template, array $context = []): string
|
||||||
|
*/
|
||||||
|
class Twig extends Facade
|
||||||
|
{
|
||||||
|
protected static function getFacadeAccessor(): string
|
||||||
|
{
|
||||||
|
return Environment::class;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/Services/ServiceProviders/TwigProvider.php
Normal file
28
src/Services/ServiceProviders/TwigProvider.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Services\ServiceProviders;
|
||||||
|
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Twig\Cache\NullCache;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
class TwigProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function provides(): array
|
||||||
|
{
|
||||||
|
return [ Environment::class ];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->app->singleton(Environment::class, function () {
|
||||||
|
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../../Mailer/Templates');
|
||||||
|
|
||||||
|
return new Environment($loader, [
|
||||||
|
'cache' => new NullCache()
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Siteworxpro\Tests\Events\Listeners;
|
|
||||||
|
|
||||||
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\Log\Logger;
|
|
||||||
use Siteworxpro\Tests\Unit;
|
|
||||||
|
|
||||||
class ConnectedTest extends Unit
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @throws ContainerExceptionInterface
|
|
||||||
* @throws \ReflectionException
|
|
||||||
* @throws NotFoundExceptionInterface
|
|
||||||
*/
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
$inputBuffer = fopen('php://memory', 'r+');
|
|
||||||
$logger = new Logger(LogLevel::DEBUG, $inputBuffer);
|
|
||||||
\Siteworxpro\App\Services\Facades\Logger::getFacadeContainer()->bind(Logger::class, fn() => $logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHandlesEvent()
|
|
||||||
{
|
|
||||||
$this->expectNotToPerformAssertions();
|
|
||||||
|
|
||||||
$connectedEvent = $this->createMock(ConnectionEstablished::class);
|
|
||||||
$listener = new Connected();
|
|
||||||
|
|
||||||
$listener->__invoke($connectedEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testThrowsException()
|
|
||||||
{
|
|
||||||
$this->expectException(\TypeError::class);
|
|
||||||
$listener = new Connected();
|
|
||||||
$listener->__invoke(new \stdClass());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user