Connect Flutter phone auth and Android platform
This commit is contained in:
@@ -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 قبل الإنتاج.
|
||||
|
||||
@@ -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
@@ -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
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user