Proje dosyaları eklendi
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Support\PortalSession;
|
||||
use Closure;
|
||||
use Filament\Facades\Filament;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class AuthenticateAdminPortal
|
||||
{
|
||||
public function __construct(private readonly PortalSession $sessions) {}
|
||||
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$guard = Filament::auth();
|
||||
if (! $guard->check()) {
|
||||
return redirect()->guest(Filament::getLoginUrl());
|
||||
}
|
||||
|
||||
$user = $guard->user();
|
||||
if (! $user->canAccessAdminPortal()) {
|
||||
$this->sessions->terminate($request);
|
||||
|
||||
return redirect()->route('login')->withErrors(['email' => __('auth.portal_denied')]);
|
||||
}
|
||||
|
||||
Auth::shouldUse(Filament::getAuthGuard());
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Enums\EarnApiTokenStatus;
|
||||
use App\Models\EarnApiToken;
|
||||
use App\Models\EarnMember;
|
||||
use App\Services\EarnApiAccessLogger;
|
||||
use App\Services\EarnDeviceIdentity;
|
||||
use Closure;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class AuthenticateEarnApiToken
|
||||
{
|
||||
public function __construct(private EarnDeviceIdentity $devices, private EarnApiAccessLogger $logs) {}
|
||||
|
||||
/**
|
||||
* Earn token yaşam döngüsünü, yeteneğini, cihaz bağını ve moda özel hız sınırını doğrular.
|
||||
*
|
||||
* @author Gökhan Engin - İletişim : https://bit.ly/YazılımUzmanı
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, string $ability, string $mode = 'tasks'): Response
|
||||
{
|
||||
$plain = $request->bearerToken();
|
||||
if (! is_string($plain) || $plain === '') {
|
||||
return $this->deny($request, 'TOKEN_MISSING', 401);
|
||||
}
|
||||
$token = EarnApiToken::findToken($plain);
|
||||
if (! $token instanceof EarnApiToken || ! $token->tokenable instanceof EarnMember) {
|
||||
return $this->deny($request, 'TOKEN_INVALID', 401);
|
||||
}
|
||||
$request->attributes->set('earn_api_token', $token);
|
||||
$code = match ($token->status()) {
|
||||
EarnApiTokenStatus::Inactive => 'TOKEN_INACTIVE', EarnApiTokenStatus::Expired => 'TOKEN_EXPIRED',
|
||||
EarnApiTokenStatus::Revoked => 'TOKEN_REVOKED', default => null,
|
||||
};
|
||||
if ($code) {
|
||||
return $this->deny($request, $code, 401, 'token_'.strtolower(substr($code, 6)));
|
||||
}
|
||||
$member = $token->tokenable;
|
||||
if ($member->trashed() || $member->status->value !== 'active') {
|
||||
return $this->deny($request, 'MEMBER_INACTIVE', 403, 'member_inactive');
|
||||
}
|
||||
if (! $member->hasVerifiedEmail()) {
|
||||
return $this->deny($request, 'EMAIL_NOT_VERIFIED', 403);
|
||||
}
|
||||
if (! $token->can($ability)) {
|
||||
return $this->deny($request, 'TOKEN_INVALID', 401);
|
||||
}
|
||||
$deviceId = $request->header('X-Device-ID');
|
||||
if (! is_string($deviceId)) {
|
||||
return $this->deny($request, 'DEVICE_ID_MISSING', 422);
|
||||
}
|
||||
$deviceId = $this->devices->normalize($deviceId);
|
||||
if (! $this->devices->valid($deviceId)) {
|
||||
return $this->deny($request, 'DEVICE_ID_INVALID', 422);
|
||||
}
|
||||
$hash = $this->devices->hash($deviceId);
|
||||
$preview = $this->devices->preview($deviceId);
|
||||
$request->attributes->set('earn_device_hash', $hash);
|
||||
$request->attributes->set('earn_device_preview', $preview);
|
||||
$limit = (int) config(match ($mode) {
|
||||
'bind' => 'earn.api.bind_per_minute',
|
||||
'results' => 'earn.api.task_results_per_minute',
|
||||
default => 'earn.api.tasks_per_minute',
|
||||
});
|
||||
$rateKey = 'earn-api:'.$mode.':'.hash('sha256', $token->id.'|'.$hash.'|'.$request->ip());
|
||||
if (RateLimiter::tooManyAttempts($rateKey, $limit)) {
|
||||
$this->logs->record($request, $mode === 'results' ? 'task_result_rate_limited' : 'rate_limited', 429, 'RATE_LIMITED', $token, $hash, $preview);
|
||||
|
||||
return $this->error('RATE_LIMITED', 429)->header('Retry-After', (string) RateLimiter::availableIn($rateKey));
|
||||
}
|
||||
RateLimiter::hit($rateKey, 60);
|
||||
if (in_array($mode, ['tasks', 'results'], true) && $token->device_id_hash === null) {
|
||||
return $this->deny($request, 'DEVICE_NOT_BOUND', 409, 'device_not_bound', $hash, $preview);
|
||||
}
|
||||
if (in_array($mode, ['tasks', 'results'], true) && ! hash_equals($token->device_id_hash, $hash)) {
|
||||
$failedKey = 'earn-api:failed-device:'.hash('sha256', $token->id.'|'.$request->ip());
|
||||
$failedLimit = (int) config('earn.api.failed_device_per_minute');
|
||||
if (RateLimiter::tooManyAttempts($failedKey, $failedLimit)) {
|
||||
$this->logs->record($request, 'rate_limited', 429, 'RATE_LIMITED', $token, $hash, $preview);
|
||||
|
||||
return $this->error('RATE_LIMITED', 429)->header('Retry-After', (string) RateLimiter::availableIn($failedKey));
|
||||
}
|
||||
RateLimiter::hit($failedKey, 60);
|
||||
$token->forceFill(['device_mismatch_seen' => true])->saveQuietly();
|
||||
|
||||
return $this->deny($request, 'DEVICE_MISMATCH', 409, 'device_mismatch', $hash, $preview);
|
||||
}
|
||||
$member->withAccessToken($token);
|
||||
$request->setUserResolver(fn () => $member);
|
||||
$response = $next($request);
|
||||
if (in_array($mode, ['tasks', 'results'], true) && $response->isSuccessful()) {
|
||||
if ($token->last_ip !== null && $token->last_ip !== $request->ip()) {
|
||||
$this->logs->record($request, 'ip_changed', $response->getStatusCode(), null, $token, $hash, $preview);
|
||||
}
|
||||
$token->forceFill(['last_used_at' => now(), 'last_seen_at' => now(), 'last_ip' => $request->ip()])->saveQuietly();
|
||||
$this->logs->record($request, $mode === 'results' ? 'task_results_access_granted' : 'tasks_access_granted', $response->getStatusCode(), null, $token, $hash, $preview);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function deny(Request $request, string $code, int $status, string $event = 'tasks_access_denied', ?string $hash = null, ?string $preview = null): JsonResponse
|
||||
{
|
||||
$this->logs->record($request, $event, $status, $code, null, $hash, $preview);
|
||||
|
||||
return $this->error($code, $status);
|
||||
}
|
||||
|
||||
private function error(string $code, int $status): JsonResponse
|
||||
{
|
||||
return response()->json(['success' => false, 'error' => ['code' => $code, 'message' => __('earn_api.'.$code)]], $status)->header('Cache-Control', 'no-store');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Support\PortalSession;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureAdminPortalAccess
|
||||
{
|
||||
public function __construct(private readonly PortalSession $sessions) {}
|
||||
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$user = $request->user();
|
||||
if (! $user || $user->canAccessAdminPortal()) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$this->sessions->terminate($request);
|
||||
|
||||
return redirect()->route('login')->withErrors(['email' => __('auth.portal_denied')]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Support\PortalSession;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureCustomerPortalAccess
|
||||
{
|
||||
public function __construct(private readonly PortalSession $sessions) {}
|
||||
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$user = $request->user();
|
||||
if ($user?->canAccessCustomerPortal()) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$admin = $user?->canAccessAdminPortal() ?? false;
|
||||
$this->sessions->terminate($request);
|
||||
|
||||
return redirect()->to($admin ? '/admin/login' : route('login'))
|
||||
->withErrors(['email' => __('auth.portal_denied')]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureEarnEmailIsVerified
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (! auth('earn')->user()?->hasVerifiedEmail()) {
|
||||
return redirect()->route('earn.verification.notice');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Enums\EarnMemberStatus;
|
||||
use App\Models\EarnMember;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureEarnMemberActive
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$member = auth('earn')->user();
|
||||
|
||||
if (! $member instanceof EarnMember || $member->trashed() || $member->status !== EarnMemberStatus::Active) {
|
||||
auth('earn')->logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect()->route('earn.login')->withErrors([
|
||||
'email' => __('earn_portal.account_unavailable'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureUserIsActive
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if ($request->user() && ! $request->user()->isActive()) {
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect()->route('login')->withErrors(['email' => __('auth.inactive')]);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class SetLocale
|
||||
{
|
||||
public const SUPPORTED = ['tr', 'en'];
|
||||
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$userLocale = $request->user()?->locale;
|
||||
$storedLocale = $request->session()->get('locale', $request->cookie('locale'));
|
||||
$locale = in_array($userLocale, self::SUPPORTED, true)
|
||||
? $userLocale
|
||||
: (in_array($storedLocale, self::SUPPORTED, true) ? $storedLocale : config('app.locale', 'tr'));
|
||||
|
||||
if (! in_array($locale, self::SUPPORTED, true)) {
|
||||
$locale = 'tr';
|
||||
}
|
||||
|
||||
App::setLocale($locale);
|
||||
$request->session()->put('locale', $locale);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user