Update: 2026-05-06 02:59:42

This commit is contained in:
Hamza-Ayed
2026-05-06 02:59:43 +03:00
parent dc2ba2ebcb
commit 9952e0eca5
78 changed files with 3490 additions and 48 deletions

View File

@@ -0,0 +1,34 @@
import 'package:dio/dio.dart';
import 'hmac_interceptor.dart';
import '../storage/secure_storage.dart';
class DioClient {
static const String baseUrl = 'https://musadaq.intaleqapp.com/api/v1/'; // Update with actual URL
late final Dio dio;
DioClient() {
dio = Dio(
BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(seconds: 15),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
),
);
// Add Interceptors
dio.interceptors.add(HmacInterceptor(SecureStorage()));
// Logging interceptor for debug
dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
error: true,
));
}
Dio get client => dio;
}

View File

@@ -0,0 +1,54 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';
import '../storage/secure_storage.dart';
class HmacInterceptor extends Interceptor {
final SecureStorage secureStorage;
HmacInterceptor(this.secureStorage);
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
final token = await secureStorage.getToken();
final deviceSecret = await secureStorage.getDeviceSecret();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
// Only sign if we have a device secret (after login)
if (deviceSecret != null) {
final timestamp = DateTime.now().millisecondsSinceEpoch.toString();
// Create signature payload
String payload = '${options.method}:${options.path}:$timestamp';
// Include body in signature if present
if (options.data != null && options.data is Map) {
payload += ':${jsonEncode(options.data)}';
}
// Generate HMAC-SHA256
final key = utf8.encode(deviceSecret);
final bytes = utf8.encode(payload);
final hmac = Hmac(sha256, key);
final digest = hmac.convert(bytes);
// Attach headers
options.headers['X-Timestamp'] = timestamp;
options.headers['X-Signature'] = digest.toString();
}
super.onRequest(options, handler);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
if (err.response?.statusCode == 401) {
// Handle Token Expiry / Unauthorized
// TODO: Trigger logout or token refresh
}
super.onError(err, handler);
}
}

View File

@@ -0,0 +1,29 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStorage {
final FlutterSecureStorage _storage = const FlutterSecureStorage();
static const String _keyToken = 'jwt_token';
static const String _keyDeviceSecret = 'device_secret';
static const String _keyUserId = 'user_id';
Future<void> saveToken(String token) async {
await _storage.write(key: _keyToken, value: token);
}
Future<String?> getToken() async {
return await _storage.read(key: _keyToken);
}
Future<void> saveDeviceSecret(String secret) async {
await _storage.write(key: _keyDeviceSecret, value: secret);
}
Future<String?> getDeviceSecret() async {
return await _storage.read(key: _keyDeviceSecret);
}
Future<void> clearAll() async {
await _storage.deleteAll();
}
}