Add all files

This commit is contained in:
root
2025-11-30 11:57:22 +01:00
parent 2ee51cac4f
commit 044271db3d
1476 changed files with 142544 additions and 0 deletions

View File

@@ -0,0 +1,143 @@
<?php
namespace Paymenter\Extensions\Others\ComplianceTools\Services;
use App\Helpers\ExtensionHelper;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Paymenter\Extensions\Others\ComplianceTools\Models\AccountDeletionRequest;
class AccountDeletionService
{
public function __construct(protected AuditLogService $auditLog)
{
}
public function submit(User $user, ?string $reason = null, array $metadata = []): AccountDeletionRequest
{
$request = AccountDeletionRequest::create([
'user_id' => $user->getKey(),
'reason' => $reason,
'status' => AccountDeletionRequest::STATUS_PENDING,
'metadata' => $metadata,
]);
$this->auditLog->record(
category: 'account_deletion',
event: 'requested',
context: ['deletion_request_id' => $request->getKey()],
subject: $user
);
$extension = ExtensionHelper::getExtension('other', 'ComplianceTools');
if ($extension && $extension->config('auto_approve_deletions')) {
$this->approve($request, null, '[auto-approved]');
}
return $request->fresh();
}
public function approve(AccountDeletionRequest $request, ?User $processor = null, ?string $notes = null): AccountDeletionRequest
{
if ($request->status === AccountDeletionRequest::STATUS_COMPLETED) {
return $request;
}
DB::transaction(function () use ($request, $processor, $notes) {
$request->forceFill([
'status' => AccountDeletionRequest::STATUS_APPROVED,
'admin_notes' => $notes,
'processed_by' => $processor?->getKey(),
'processed_at' => now(),
'snapshot' => $this->snapshotUser($request->user),
])->save();
$this->anonymizeUser($request->user);
$request->forceFill(['status' => AccountDeletionRequest::STATUS_COMPLETED])->save();
});
$this->auditLog->record(
category: 'account_deletion',
event: 'approved',
context: ['deletion_request_id' => $request->getKey()],
subject: $request->user,
actor: $processor
);
return $request;
}
public function deny(AccountDeletionRequest $request, User $processor, ?string $notes = null): AccountDeletionRequest
{
if ($request->status !== AccountDeletionRequest::STATUS_PENDING) {
return $request;
}
$request->forceFill([
'status' => AccountDeletionRequest::STATUS_DENIED,
'admin_notes' => $notes,
'processed_by' => $processor->getKey(),
'processed_at' => now(),
])->save();
$this->auditLog->record(
category: 'account_deletion',
event: 'denied',
context: ['deletion_request_id' => $request->getKey()],
subject: $request->user,
actor: $processor
);
return $request;
}
protected function snapshotUser(User $user): array
{
return [
'user' => $user->only([
'id',
'first_name',
'last_name',
'email',
'created_at',
]),
];
}
protected function anonymizeUser(User $user): void
{
$anonymizedEmail = sprintf('deleted-user-%s-%s@anonymous.invalid', $user->getKey(), Str::random(10));
$updates = [
'first_name' => null,
'last_name' => null,
'email' => $anonymizedEmail,
'password' => Hash::make(Str::random(32)),
'tfa_secret' => null,
];
if (method_exists($user, 'properties') && $user->properties()->exists()) {
$user->properties()->delete();
}
$user->forceFill($updates)->save();
if (method_exists($user, 'tokens')) {
$user->tokens()->delete();
}
if (method_exists($user, 'sessions')) {
$user->sessions()->delete();
}
$this->auditLog->record(
category: 'account_deletion',
event: 'anonymized',
context: ['user_id' => $user->getKey(), 'email' => $anonymizedEmail],
subject: $user
);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Paymenter\Extensions\Others\ComplianceTools\Services;
use App\Models\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\Auth;
use Paymenter\Extensions\Others\ComplianceTools\Models\AuditLog;
class AuditLogService
{
public static function record(
string $category,
string $event,
array $context = [],
?User $subject = null,
Authenticatable|User|null $actor = null
): AuditLog {
$actor ??= Auth::user();
return AuditLog::create([
'category' => $category,
'event' => $event,
'subject_user_id' => $subject?->getKey(),
'actor_type' => $actor?->getMorphClass(),
'actor_id' => $actor?->getAuthIdentifier(),
'ip_address' => request()->ip(),
'context' => $context,
]);
}
}

View File

@@ -0,0 +1,123 @@
<?php
namespace Paymenter\Extensions\Others\ComplianceTools\Services;
use App\Helpers\ExtensionHelper;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Paymenter\Extensions\Others\ComplianceTools\Models\SupportPin;
class SupportPinService
{
public function __construct(protected AuditLogService $auditLog)
{
}
public function generate(User $user): array
{
$extension = ExtensionHelper::getExtension('other', 'ComplianceTools');
$length = (int) ($extension?->config('pin_length') ?? 6);
$expiryMinutes = (int) ($extension?->config('pin_expiry_minutes') ?? 15);
$plainPin = $this->randomPin($length);
$lookup = hash('sha256', $plainPin);
SupportPin::query()
->where('user_id', $user->getKey())
->whereNull('revoked_at')
->update(['revoked_at' => now()]);
$pin = SupportPin::create([
'user_id' => $user->getKey(),
'pin_hash' => Hash::make($plainPin),
'pin_lookup' => $lookup,
'expires_at' => now()->addMinutes($expiryMinutes),
'created_ip' => request()->ip(),
'created_user_agent' => request()->header('User-Agent'),
]);
$this->auditLog->record(
category: 'support_pin',
event: 'generated',
context: [
'support_pin_id' => $pin->getKey(),
'expires_at' => $pin->expires_at?->toAtomString(),
],
subject: $user
);
return [$pin, $plainPin];
}
public function revoke(SupportPin $pin, ?User $actor = null): SupportPin
{
if ($pin->revoked_at) {
return $pin;
}
$pin->forceFill(['revoked_at' => now()])->save();
$this->auditLog->record(
category: 'support_pin',
event: 'revoked',
context: ['support_pin_id' => $pin->getKey()],
subject: $pin->user,
actor: $actor
);
return $pin;
}
public function validate(string $plainPin): ?SupportPin
{
$plainPin = strtoupper(trim($plainPin));
if ($plainPin === '') {
return null;
}
$lookup = hash('sha256', $plainPin);
$pin = SupportPin::query()
->where('pin_lookup', $lookup)
->first();
if (!$pin instanceof SupportPin) {
return null;
}
if (!Hash::check($plainPin, $pin->pin_hash)) {
return null;
}
if ($pin->revoked_at || $pin->expires_at->isPast()) {
return null;
}
$pin->forceFill(['last_validated_at' => now()])->save();
$this->auditLog->record(
category: 'support_pin',
event: 'validated',
context: [
'support_pin_id' => $pin->getKey(),
],
subject: $pin->user
);
return $pin;
}
protected function randomPin(int $length): string
{
$alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
$poolLength = strlen($alphabet) - 1;
$pin = '';
for ($i = 0; $i < $length; $i++) {
$pin .= $alphabet[random_int(0, $poolLength)];
}
return $pin;
}
}