latest
This commit is contained in:
1 parent
d0834df8df
commit
6dc35134fa
171 files changed
+19241
-49
No files matched your search
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Audit;
|
||||
|
||||
use App\Models\AuditLog;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
|
||||
class AuditLogger
|
||||
{
|
||||
public function log(User $actor, string $action, ?Model $auditable = null, array $metadata = []): AuditLog
|
||||
{
|
||||
return AuditLog::create([
|
||||
'user_id' => $actor->id,
|
||||
'action' => $action,
|
||||
'auditable_type' => $auditable?->getMorphClass(),
|
||||
'auditable_id' => $auditable?->getKey(),
|
||||
'ip_address' => Request::ip(),
|
||||
'user_agent' => Request::userAgent(),
|
||||
'metadata' => $metadata,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Scaffolded ahead of any actual upload feature (e.g. avatars) so hardening
|
||||
* isn't an afterthought bolted on later. Not wired to a route yet.
|
||||
*/
|
||||
class FileUploadValidator
|
||||
{
|
||||
private const ALLOWED_MIME_TYPES = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/webp',
|
||||
];
|
||||
|
||||
private const MAX_BYTES = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
public function validate(UploadedFile $file): bool
|
||||
{
|
||||
if ($file->getSize() > self::MAX_BYTES) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sniff actual content type rather than trusting the client-supplied
|
||||
// extension or Content-Type header.
|
||||
return in_array($file->getMimeType(), self::ALLOWED_MIME_TYPES, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* A randomized filename that never trusts the client-supplied name.
|
||||
*/
|
||||
public function randomizedFilename(UploadedFile $file): string
|
||||
{
|
||||
return Str::uuid().'.'.$file->extension();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Mqtt;
|
||||
|
||||
use PhpMqtt\Client\Facades\MQTT;
|
||||
use PhpMqtt\Client\MqttClient;
|
||||
|
||||
class MqttPublisher
|
||||
{
|
||||
public static function publish(string $topic, array $payload, int $qos = MqttClient::QOS_AT_LEAST_ONCE): void
|
||||
{
|
||||
MQTT::connection()->publish($topic, json_encode($payload), $qos);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\TwoFactorRecoveryCode;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
|
||||
class TwoFactorService
|
||||
{
|
||||
public function __construct(private readonly Google2FA $google2fa) {}
|
||||
|
||||
public function generateSecret(): string
|
||||
{
|
||||
return $this->google2fa->generateSecretKey();
|
||||
}
|
||||
|
||||
public function otpAuthUrl(User $user, string $secret): string
|
||||
{
|
||||
return $this->google2fa->getQRCodeUrl(
|
||||
config('app.name'),
|
||||
$user->email,
|
||||
$secret,
|
||||
);
|
||||
}
|
||||
|
||||
public function verifyCode(string $secret, string $code): bool
|
||||
{
|
||||
return $this->google2fa->verifyKey($secret, $code, window: 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string> the plaintext recovery codes (shown once, never stored plaintext)
|
||||
*/
|
||||
public function generateRecoveryCodes(User $user): array
|
||||
{
|
||||
$user->twoFactorRecoveryCodes()->delete();
|
||||
|
||||
$plaintextCodes = [];
|
||||
|
||||
foreach (range(1, 10) as $_) {
|
||||
$code = Str::random(10);
|
||||
$plaintextCodes[] = $code;
|
||||
|
||||
TwoFactorRecoveryCode::create([
|
||||
'user_id' => $user->id,
|
||||
'code_hash' => Hash::make($code),
|
||||
]);
|
||||
}
|
||||
|
||||
return $plaintextCodes;
|
||||
}
|
||||
|
||||
public function redeemRecoveryCode(User $user, string $code): bool
|
||||
{
|
||||
$recoveryCode = $user->twoFactorRecoveryCodes()
|
||||
->whereNull('used_at')
|
||||
->get()
|
||||
->first(fn (TwoFactorRecoveryCode $recoveryCode) => Hash::check($code, $recoveryCode->code_hash));
|
||||
|
||||
if (! $recoveryCode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$recoveryCode->update(['used_at' => now()]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user