Add all files
This commit is contained in:
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
123
extensions/Others/ComplianceTools/Services/SupportPinService.php
Normal file
123
extensions/Others/ComplianceTools/Services/SupportPinService.php
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user