Connect Flutter phone auth and Android platform

This commit is contained in:
Hamza-Ayed
2026-10-04 01:44:07 +03:00
parent 5b90da6b18
commit 98490a17e6
44 changed files with 1271 additions and 168 deletions
+1 -1
View File
@@ -33,4 +33,4 @@ OTP_HASH_KEY=<independent random secret, at least 32 bytes>
## ما لم يكتمل بعد
هذا API غير مربوط بعد بتطبيق Flutter، ونقاط التتبع القديمة ما زالت تستخدم HMAC الانتقالي. قبل الإنتاج يجب نقل التطبيق إلى OTP/refresh الآمن، ثم إيقاف بيانات HMAC القديمة أو حصرها بفترة انتقال معلومة. لم يُختبر اتصال MySQL الفعلي أو إرسال SMS؛ فحوص PHP المتاحة حتى الآن ساكنة فقط. نفّذ migration `001_phone_auth_and_sessions.sql` على قاعدة staging احتياطية أولًا، وتحقق من rate limits والتدوير والإبطال مع مزود OTP في sandbox.
تطبيق Flutter مرتبط الآن بنقاط OTP والجلسات ويخزن الرموز في secure storage؛ مزامنة التمارين ترسل Bearer وتجدد الجلسة عند الحاجة. نقاط التتبع تقبل HMAC فقط إذا ضُبط `LEGACY_HMAC_ENABLED=true` صراحة لفترة ترحيل عميل قديم، وقيمته في المثال `false`. لم يُختبر اتصال MySQL الفعلي أو إرسال SMS؛ فحوص PHP/Dart حتى الآن ساكنة وتحليلية فقط. نفّذ migrations `001_phone_auth_and_sessions.sql` و`002_workout_idempotency.sql` و`003_training_content.sql` على staging بعد backup، وتحقق من rate limits والتدوير والإبطال مع مزود OTP في sandbox قبل الإنتاج.
+2 -2
View File
@@ -8,9 +8,9 @@ class WorkoutValidator {
private $errors = [];
private const VALID_WORKOUT_TYPES = ['running', 'walking'];
private const MIN_DISTANCE = 100; // meters
private const MIN_DISTANCE = 0; // meters; short/aborted sessions remain syncable
private const MAX_DISTANCE = 100000; // 100km
private const MIN_DURATION = 60; // seconds
private const MIN_DURATION = 0; // seconds; timestamp order is validated separately
private const MAX_DURATION = 36000; // 10 hours
private const MIN_COORDINATES = 2;
private const MAX_COORDINATES = 50000;
+26 -18
View File
@@ -5,29 +5,37 @@
*/
header('Content-Type: application/json');
require_once 'Database.php';
require_once 'AuthenticationHandler.php';
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key, X-Signature, X-Timestamp');
require_once __DIR__ . '/Database.php';
require_once __DIR__ . '/Config.php';
require_once __DIR__ . '/AuthenticationHandler.php';
require_once __DIR__ . '/JwtToken.php';
require_once __DIR__ . '/ApiAuth.php';
require_once dirname(__DIR__) . '/public/api/v1/_bootstrap.php';
try {
$api_key = $_SERVER['HTTP_X_API_KEY'] ?? null;
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? null;
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? null;
if (!$api_key || !$signature || !$timestamp) {
throw new Exception('Unauthorized', 401);
}
$auth = new AuthenticationHandler();
$db = Database::getInstance();
// Verification (Using empty body for GET request signature)
$authResult = $auth->validateHmacSignature($api_key, $signature, '', $timestamp);
if (!$authResult['valid']) {
throw new Exception($authResult['error'], 401);
if (isset($_SERVER['HTTP_AUTHORIZATION']) && preg_match('/^Bearer\s+/i', $_SERVER['HTTP_AUTHORIZATION'])) {
$user_id = ApiAuth::bearerClaims()['user_id'];
} else {
AppConfig::loadEnvironment();
if (getenv('LEGACY_HMAC_ENABLED') !== 'true') {
throw new Exception('Bearer authentication required', 401);
}
$api_key = $_SERVER['HTTP_X_API_KEY'] ?? null;
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? null;
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? null;
if (!$api_key || !$signature || !$timestamp) {
throw new Exception('Unauthorized', 401);
}
$auth = new AuthenticationHandler();
$authResult = $auth->validateHmacSignature($api_key, $signature, '', $timestamp);
if (!$authResult['valid']) {
throw new Exception($authResult['error'], 401);
}
$user_id = $authResult['user_id'];
}
$user_id = $authResult['user_id'];
// Fetch workouts
$stmt = $db->prepare('SELECT * FROM workouts WHERE user_id = ? ORDER BY created_at DESC LIMIT 50');
$stmt->bind_param('i', $user_id);
+28 -23
View File
@@ -9,7 +9,7 @@
header('Content-Type: application/json');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, X-API-Key, X-Signature, X-Timestamp');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key, X-Signature, X-Timestamp');
// Handle CORS preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
@@ -23,39 +23,44 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
die(json_encode(['error' => 'Method not allowed']));
}
require_once 'Database.php';
require_once 'AuthenticationHandler.php';
require_once 'PolylineUtility.php';
require_once 'WorkoutValidator.php';
require_once __DIR__ . '/Database.php';
require_once __DIR__ . '/Config.php';
require_once __DIR__ . '/AuthenticationHandler.php';
require_once __DIR__ . '/JwtToken.php';
require_once __DIR__ . '/ApiAuth.php';
require_once __DIR__ . '/PolylineUtility.php';
require_once __DIR__ . '/WorkoutValidator.php';
require_once dirname(__DIR__) . '/public/api/v1/_bootstrap.php';
try {
// Get request headers
$api_key = getHeader('X-API-Key');
$signature = getHeader('X-Signature');
$timestamp = getHeader('X-Timestamp');
if (!$api_key || !$signature || !$timestamp) {
throw new Exception('Missing required authentication headers', 400);
}
// Get raw request body for signature verification
$rawBody = file_get_contents('php://input');
if (empty($rawBody)) {
throw new Exception('Empty request body', 400);
}
// Initialize handlers
$auth = new AuthenticationHandler();
$db = Database::getInstance();
// Validate HMAC signature
$authResult = $auth->validateHmacSignature($api_key, $signature, $rawBody, $timestamp);
if (!$authResult['valid']) {
throw new Exception($authResult['error'], 401);
if (isset($_SERVER['HTTP_AUTHORIZATION']) && preg_match('/^Bearer\s+/i', $_SERVER['HTTP_AUTHORIZATION'])) {
$user_id = ApiAuth::bearerClaims()['user_id'];
} else {
AppConfig::loadEnvironment();
if (getenv('LEGACY_HMAC_ENABLED') !== 'true') {
throw new Exception('Bearer authentication required', 401);
}
$api_key = getHeader('X-API-Key');
$signature = getHeader('X-Signature');
$timestamp = getHeader('X-Timestamp');
if (!$api_key || !$signature || !$timestamp) {
throw new Exception('Missing required authentication headers', 401);
}
$auth = new AuthenticationHandler();
$authResult = $auth->validateHmacSignature($api_key, $signature, $rawBody, $timestamp);
if (!$authResult['valid']) {
throw new Exception($authResult['error'], 401);
}
$user_id = $authResult['user_id'];
}
$user_id = $authResult['user_id'];
// Parse and validate JSON payload
$payload = json_decode($rawBody, true);
if (!is_array($payload)) {