Initial commit: Uruk Prize Platform architecture, backend core, mobile app, gateway caller & deployment pipeline

This commit is contained in:
Hamza-Ayed
2026-09-18 17:12:05 +03:00
commit 879dd36f1b
1149 changed files with 97122 additions and 0 deletions
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace App\Middlewares;
use Core\Middleware;
use Core\Request;
use Core\Response;
use Core\Database;
use PDO;
class AdminMiddleware extends Middleware
{
public function handle(Request $request, callable $next): void
{
$userId = $request->getHeader('x-user-id');
try {
$pdo = Database::getConnection();
$stmt = $pdo->prepare('SELECT role FROM users WHERE id = :user_id LIMIT 1');
$stmt->execute([':user_id' => $userId]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user || !in_array($user['role'], ['ADMIN', 'SUPER_ADMIN'], true)) {
Response::forbidden('Administrative privileges required to access this resource.');
}
} catch (\Throwable $e) {
Response::error('Role verification failure: ' . $e->getMessage(), 500);
}
$next($request);
}
}
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace App\Middlewares;
use Core\Middleware;
use Core\Request;
use Core\Response;
use Core\Database;
use PDO;
class AuthMiddleware extends Middleware
{
public function handle(Request $request, callable $next): void
{
$authHeader = $request->getHeader('authorization');
if (empty($authHeader) || !str_starts_with($authHeader, 'Bearer ')) {
Response::unauthorized('Bearer token required in Authorization header.');
}
$token = substr($authHeader, 7);
$userId = $request->getHeader('x-user-id');
try {
$pdo = Database::getConnection();
$stmt = $pdo->prepare('
SELECT u.id, u.phone, u.full_name, u.role, u.status, us.last_token
FROM users u
JOIN user_security us ON us.user_id = u.id
WHERE u.id = :user_id AND us.last_token = :token
LIMIT 1
');
$stmt->execute([
':user_id' => $userId,
':token' => $token,
]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user) {
Response::unauthorized('Invalid or expired authentication session token.');
}
if ($user['status'] !== 'ACTIVE' && $user['status'] !== 'PENDING_VERIFICATION') {
Response::forbidden('User account is not currently active.');
}
} catch (\Throwable $e) {
Response::error('Authentication verification failure: ' . $e->getMessage(), 500);
}
$next($request);
}
}
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace App\Middlewares;
use Core\Middleware;
use Core\Request;
use Core\Response;
use Core\Security;
use Core\Database;
use PDO;
class SecurityHeaderMiddleware extends Middleware
{
public function handle(Request $request, callable $next): void
{
$userId = $request->getHeader('x-user-id');
$fingerprint = $request->getHeader('x-device-fingerprint');
$timestamp = (int)$request->getHeader('x-timestamp');
$nonce = $request->getHeader('x-nonce');
$signature = $request->getHeader('x-signature');
// 1. Check presence of all required security headers
if (empty($userId) || empty($fingerprint) || empty($timestamp) || empty($nonce) || empty($signature)) {
Response::unauthorized('Security headers missing: X-User-Id, X-Device-Fingerprint, X-Timestamp, X-Nonce, and X-Signature are mandatory.');
}
// 2. Anti-replay timestamp check (window of 120 seconds)
$currentTime = time();
if (abs($currentTime - $timestamp) > 120) {
Response::unauthorized('Request timestamp expired or skewed. Anti-replay check failed.');
}
// 3. Query user security record from Database
try {
$pdo = Database::getConnection();
$stmt = $pdo->prepare('
SELECT us.device_fingerprint, us.device_secret, us.is_locked, u.status
FROM user_security us
JOIN users u ON u.id = us.user_id
WHERE us.user_id = :user_id AND us.device_fingerprint = :fingerprint
LIMIT 1
');
$stmt->execute([
':user_id' => $userId,
':fingerprint' => $fingerprint,
]);
$secRecord = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$secRecord) {
Response::forbidden('Unrecognized device fingerprint or unauthorized device binding.');
}
if ((int)$secRecord['is_locked'] === 1 || in_array($secRecord['status'], ['SUSPENDED', 'BANNED'], true)) {
Response::forbidden('User device or account has been locked or suspended.');
}
// 4. Verify HMAC-SHA256 signature
$rawBody = $request->getRawBody();
$bodyHash = hash('sha256', $rawBody);
$signPayload = implode("\n", [
$request->getMethod(),
$request->getPath(),
(string)$timestamp,
$nonce,
$bodyHash,
]);
if (!Security::verifyHmacSignature($signPayload, $signature, $secRecord['device_secret'])) {
Response::unauthorized('Invalid cryptographic signature for this device and payload.');
}
// 5. Update last active timestamp and IP asynchronously / inline
$updateStmt = $pdo->prepare('
UPDATE user_security
SET last_active_at = NOW(), last_ip = :ip
WHERE user_id = :user_id AND device_fingerprint = :fingerprint
');
$updateStmt->execute([
':ip' => $request->getIp(),
':user_id' => $userId,
':fingerprint' => $fingerprint,
]);
} catch (\Throwable $e) {
Response::error('Security verification error: ' . $e->getMessage(), 500);
}
$next($request);
}
}