Files
exchange-2.0/app/Http/Middleware/TrackAffiliateClick.php
Edmond Lang 30ed77f304 Merge branch 'affiliate-program' of https://gitlab.com/CIEFWorldwideSdnBhd/exchange-2.0 into vapor/production
# Conflicts:
#	resources/assets/vue/components/accounts/forms/RegistrationFormComponent.vue
#	routes/api.php
2026-01-26 00:13:10 +08:00

147 lines
4.0 KiB
PHP

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\{Log, Auth, Session};
use Tymon\JWTAuth\Facades\JWTAuth;
use App\Classes\Modules\Affiliate\Services\TracksAffiliateClick;
class TrackAffiliateClick
{
public function handle(Request $request, Closure $next)
{
$isAuthenticated = $this->checkWebAuth() || $this->checkJwtAuth($request);
if (!$isAuthenticated && $request->filled('tracking')) {
$trackingCode = $this->validateTrackingCode($request->get('tracking'));
if ($trackingCode) {
$this->trackClick($trackingCode);
}
}
return $next($request);
}
/**
* Validate and sanitize tracking code
*
* @param mixed $code
* @return string|null
*/
private function validateTrackingCode($code): ?string
{
if (!is_string($code)) {
return null;
}
// Trim whitespace
$code = trim($code);
// Check length (max 255 chars based on database schema)
if (strlen($code) > 255 || strlen($code) < 1) {
return null;
}
// Only allow alphanumeric characters, hyphens, and underscores
// This matches typical affiliate code formats
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $code)) {
return null;
}
return $code;
}
/**
* Check if user is authenticated via WEB session
*/
private function checkWebAuth(): bool
{
try {
if (Auth::guard('web')->check()) {
return true;
}
// fallback session check
foreach (Session::all() as $key => $value) {
if (strpos($key, 'login_web_') === 0 && !empty($value)) {
return true;
}
}
} catch (\Exception $e) {
Log::warning('AffiliateClickMiddleware: Web auth error', ['error' => $e->getMessage()]);
}
return false;
}
/**
* Check if user is authenticated via JWT token
*/
private function checkJwtAuth(Request $request): bool
{
try {
$token =
$this->getBearerToken($request) ??
$request->get('token') ??
$request->cookie('access_token') ??
$request->cookie('user-token');
if (!$token) {
return false;
}
JWTAuth::setToken($token);
$user = JWTAuth::authenticate();
if ($user) {
return true;
}
} catch (\Exception $e) {
Log::debug('AffiliateClickMiddleware: JWT invalid', ['error' => $e->getMessage()]);
}
return false;
}
/**
* Extract Bearer token
*/
private function getBearerToken(Request $request): ?string
{
if (!$request->hasHeader('Authorization')) return null;
return preg_match('/Bearer\s+(.*)$/i', $request->header('Authorization'), $m)
? $m[1]
: null;
}
/**
* Track affiliate click once per session
*/
private function trackClick(string $trackingCode): void
{
// Use a single session key with an array to prevent session bloat
$trackedClicks = Session::get('affiliate_clicks_tracked', []);
// Check if this code has already been tracked in this session
if (in_array($trackingCode, $trackedClicks)) {
return; // Already tracked
}
// Execute tracking
app(TracksAffiliateClick::class)->execute($trackingCode);
// Add to tracked list
$trackedClicks[] = $trackingCode;
// Limit array size to prevent DoS (keep only last 10 clicks)
if (count($trackedClicks) > 10) {
$trackedClicks = array_slice($trackedClicks, -10);
}
Session::put('affiliate_clicks_tracked', $trackedClicks);
}
}