This commit is contained in:
Phuoc Nguyen
2025-11-10 14:21:27 +07:00
parent 2a71c65577
commit 36bdf6613b
33 changed files with 2206 additions and 252 deletions

View File

@@ -15,7 +15,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:worker/core/constants/api_constants.dart';
import 'package:worker/core/errors/exceptions.dart';
import 'package:worker/features/auth/data/datasources/auth_local_datasource.dart';
part 'api_interceptor.g.dart';
@@ -35,15 +34,16 @@ class AuthStorageKeys {
// Auth Interceptor
// ============================================================================
/// Interceptor for adding ERPNext session tokens to requests
/// Interceptor for adding Frappe/ERPNext session tokens to requests
///
/// Adds SID (Session ID) and CSRF token from Hive storage to request headers.
/// Adds Cookie (with SID) and X-Frappe-CSRF-Token from FlutterSecureStorage.
/// Uses the centralized FrappeAuthService for session management.
class AuthInterceptor extends Interceptor {
AuthInterceptor(this._prefs, this._dio, this._authLocalDataSource);
AuthInterceptor(this._prefs, this._dio, this._secureStorage);
final SharedPreferences _prefs;
final Dio _dio;
final AuthLocalDataSource _authLocalDataSource;
final FlutterSecureStorage _secureStorage;
@override
void onRequest(
@@ -52,13 +52,24 @@ class AuthInterceptor extends Interceptor {
) async {
// Check if this endpoint requires authentication
if (_requiresAuth(options.path)) {
// Get session data from secure storage (async)
final sid = await _authLocalDataSource.getSid();
final csrfToken = await _authLocalDataSource.getCsrfToken();
// Get session data from secure storage
final sid = await _secureStorage.read(key: 'frappe_sid');
final csrfToken = await _secureStorage.read(key: 'frappe_csrf_token');
final fullName = await _secureStorage.read(key: 'frappe_full_name');
final userId = await _secureStorage.read(key: 'frappe_user_id');
if (sid != null && csrfToken != null) {
// Add ERPNext session headers
options.headers['Cookie'] = 'sid=$sid';
// Build cookie header with all required fields
final cookieHeader = [
'sid=$sid',
'full_name=${fullName ?? "User"}',
'system_user=no',
'user_id=${userId != null ? Uri.encodeComponent(userId) : ApiConstants.frappePublicUserId}',
'user_image=',
].join('; ');
// Add Frappe session headers
options.headers['Cookie'] = cookieHeader;
options.headers['X-Frappe-CSRF-Token'] = csrfToken;
}
@@ -276,9 +287,20 @@ class LoggingInterceptor extends Interceptor {
name: 'HTTP Response',
);
developer.log(
'Data: ${_truncateData(response.data, 500)}',
'Headers: ${response.headers.map}',
name: 'HTTP Response',
);
// Log full response data (not truncated for debugging)
final responseData = response.data;
if (responseData != null) {
if (responseData is String) {
developer.log('║ Response: $responseData', name: 'HTTP Response');
} else {
developer.log('║ Response: ${_truncateData(responseData, 2000)}', name: 'HTTP Response');
}
}
developer.log(
'╚══════════════════════════════════════════════════════════════',
name: 'HTTP Response',
@@ -534,14 +556,13 @@ Future<SharedPreferences> sharedPreferences(Ref ref) async {
Future<AuthInterceptor> authInterceptor(Ref ref, Dio dio) async {
final prefs = await ref.watch(sharedPreferencesProvider.future);
// Create AuthLocalDataSource with FlutterSecureStorage
// Use FlutterSecureStorage for Frappe session
const secureStorage = FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
);
final authLocalDataSource = AuthLocalDataSource(secureStorage);
return AuthInterceptor(prefs, dio, authLocalDataSource);
return AuthInterceptor(prefs, dio, secureStorage);
}
/// Provider for LoggingInterceptor

View File

@@ -114,7 +114,7 @@ final class AuthInterceptorProvider
}
}
String _$authInterceptorHash() => r'3f964536e03e204d09cc9120dd9d961b6d6d4b71';
String _$authInterceptorHash() => r'1221aab024b7c4d9fd393f7681f3ba094286a375';
/// Provider for AuthInterceptor

View File

@@ -8,6 +8,7 @@
/// - Retry logic
library;
import 'package:curl_logger_dio_interceptor/curl_logger_dio_interceptor.dart';
import 'package:dio/dio.dart';
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart';
@@ -382,19 +383,21 @@ Future<Dio> dio(Ref ref) async {
},
)
// Add interceptors in order
// 1. Logging interceptor (first to log everything)
// 1. Curl interceptor (first to log cURL commands)
..interceptors.add(CurlLoggerDioInterceptor())
// 2. Logging interceptor
..interceptors.add(ref.watch(loggingInterceptorProvider))
// 2. Auth interceptor (add tokens to requests)
// 3. Auth interceptor (add tokens to requests)
..interceptors.add(await ref.watch(authInterceptorProvider(dio).future))
// 3. Cache interceptor
// 4. Cache interceptor
..interceptors.add(
DioCacheInterceptor(
options: await ref.watch(cacheOptionsProvider.future),
),
)
// 4. Retry interceptor
// 5. Retry interceptor
..interceptors.add(RetryInterceptor(ref.watch(networkInfoProvider)))
// 5. Error transformer (last to transform all errors)
// 6. Error transformer (last to transform all errors)
..interceptors.add(ref.watch(errorTransformerInterceptorProvider));
return dio;

View File

@@ -131,7 +131,7 @@ final class DioProvider
}
}
String _$dioHash() => r'f15495e99d11744c245e2be892657748aeeb8ae7';
String _$dioHash() => r'40bb4b1008c8259c9db4b19bcee674aa6732810c';
/// Provider for DioClient