Proje dosyaları eklendi
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\EarnApiToken;
|
||||
use App\Services\EarnApiAccessLogger;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
|
||||
class EarnDeviceController extends Controller
|
||||
{
|
||||
public function bind(Request $request, EarnApiAccessLogger $logs): JsonResponse
|
||||
{
|
||||
$token = $request->attributes->get('earn_api_token');
|
||||
abort_unless($token instanceof EarnApiToken, 401);
|
||||
$hash = $request->attributes->get('earn_device_hash');
|
||||
$preview = $request->attributes->get('earn_device_preview');
|
||||
$result = DB::transaction(function () use ($token, $hash, $preview, $request): array {
|
||||
$locked = EarnApiToken::query()->whereKey($token->id)->lockForUpdate()->firstOrFail();
|
||||
if ($locked->device_id_hash !== null) {
|
||||
$match = hash_equals($locked->device_id_hash, $hash);
|
||||
if ($match) {
|
||||
$locked->forceFill(['last_ip' => $request->ip(), 'last_seen_at' => now()])->save();
|
||||
}
|
||||
|
||||
return ['token' => $locked, 'match' => $match];
|
||||
}
|
||||
$locked->forceFill(['device_id_hash' => $hash, 'device_id_preview' => $preview, 'device_bound_at' => now(),
|
||||
'first_ip' => $request->ip(), 'last_ip' => $request->ip(), 'last_seen_at' => now()])->save();
|
||||
|
||||
return ['token' => $locked, 'match' => true, 'created' => true];
|
||||
}, 3);
|
||||
if (! $result['match']) {
|
||||
$failedKey = 'earn-api:failed-device:'.hash('sha256', $result['token']->id.'|'.$request->ip());
|
||||
$failedLimit = (int) config('earn.api.failed_device_per_minute');
|
||||
if (RateLimiter::tooManyAttempts($failedKey, $failedLimit)) {
|
||||
$logs->record($request, 'rate_limited', 429, 'RATE_LIMITED', $result['token'], $hash, $preview);
|
||||
|
||||
return $this->error('RATE_LIMITED', 429)->header('Retry-After', (string) RateLimiter::availableIn($failedKey));
|
||||
}
|
||||
RateLimiter::hit($failedKey, 60);
|
||||
$result['token']->forceFill(['device_mismatch_seen' => true])->saveQuietly();
|
||||
$logs->record($request, 'device_mismatch', 409, 'DEVICE_MISMATCH', $result['token'], $hash, $preview);
|
||||
|
||||
return $this->error('DEVICE_MISMATCH', 409);
|
||||
}
|
||||
$already = ! ($result['created'] ?? false);
|
||||
$logs->record($request, $already ? 'device_bind_repeated' : 'device_bound', 200, null, $result['token'], $hash, $preview);
|
||||
|
||||
return response()->json(['success' => true, 'data' => ['device_bound' => true, 'already_bound' => $already,
|
||||
'device_id_preview' => $result['token']->device_id_preview, 'bound_at' => $result['token']->device_bound_at?->toISOString()]])
|
||||
->header('Cache-Control', 'no-store');
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user