(auth): Add proxy authentication WebView for server login

This commit is contained in:
cogwheel
2025-12-18 11:40:16 +05:30
parent 5b91616e35
commit 9da9f9e2b3
60 changed files with 1453 additions and 22 deletions

View File

@@ -0,0 +1,62 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import '../utils/debug_logger.dart';
/// Native cookie manager for accessing cookies from the platform's cookie store.
///
/// On iOS, this accesses WKHTTPCookieStore (shared with WKWebView).
/// On Android, this accesses CookieManager (shared with WebView).
///
/// This is necessary because dart:io HttpClient has its own isolated cookie
/// store that doesn't share with WebView.
class NativeCookieManager {
static const _channel = MethodChannel('com.conduit.app/cookies');
/// Gets all cookies for a given URL from the native cookie store.
///
/// Returns a map of cookie name -> value.
/// Returns empty map on web or if native method fails.
static Future<Map<String, String>> getCookiesForUrl(String url) async {
if (kIsWeb) return {};
try {
final result = await _channel.invokeMethod<Map<dynamic, dynamic>>(
'getCookies',
{'url': url},
);
if (result == null) return {};
final cookies = <String, String>{};
for (final entry in result.entries) {
cookies[entry.key.toString()] = entry.value.toString();
}
DebugLogger.auth('Retrieved ${cookies.length} cookies from native store');
return cookies;
} on MissingPluginException {
// Platform channels not implemented - fall back gracefully
DebugLogger.log(
'Native cookie manager not available on this platform',
scope: 'auth/cookies',
);
return {};
} catch (e) {
DebugLogger.warning(
'Failed to get native cookies',
scope: 'auth/cookies',
data: {'error': e.toString()},
);
return {};
}
}
/// Formats cookies as a Cookie header string.
static String formatCookieHeader(Map<String, String> cookies) {
if (cookies.isEmpty) return '';
return cookies.entries.map((e) => '${e.key}=${e.value}').join('; ');
}
}

View File

@@ -11,7 +11,7 @@ import '../utils/debug_logger.dart';
bool get isWebViewSupported =>
!kIsWeb && (Platform.isIOS || Platform.isAndroid);
/// Helper for clearing WebView data on supported platforms.
/// Helper for managing WebView data and cookies.
///
/// This is isolated in its own file to prevent platform coupling issues
/// when the webview_flutter package isn't available.
@@ -71,4 +71,68 @@ class WebViewCookieHelper {
return success;
}
/// Gets cookies from a WebView controller via JavaScript.
///
/// This can be used to extract session cookies set by proxy authentication
/// and pass them to HTTP clients like Dio.
///
/// Note: Only works for cookies without the HttpOnly flag.
/// For HttpOnly cookies, iOS/Android platforms may share cookies
/// automatically through the shared cookie store.
///
/// Returns a map of cookie names to values, or empty map if unavailable.
static Future<Map<String, String>> getCookiesFromController(
WebViewController controller,
) async {
if (!isWebViewSupported) return {};
try {
final result = await controller.runJavaScriptReturningResult(
'document.cookie',
);
final cookieString = result.toString();
// Remove surrounding quotes if present
final cleaned =
cookieString.startsWith('"') && cookieString.endsWith('"')
? cookieString.substring(1, cookieString.length - 1)
: cookieString;
if (cleaned.isEmpty || cleaned == 'null') return {};
final cookieMap = <String, String>{};
final pairs = cleaned.split(';');
for (final pair in pairs) {
final trimmed = pair.trim();
final idx = trimmed.indexOf('=');
if (idx > 0) {
final name = trimmed.substring(0, idx).trim();
final value = trimmed.substring(idx + 1).trim();
cookieMap[name] = value;
}
}
DebugLogger.auth(
'Retrieved ${cookieMap.length} cookies from WebView',
);
return cookieMap;
} catch (e) {
DebugLogger.warning(
'webview-get-cookies-failed',
scope: 'auth/webview',
data: {'error': e.toString()},
);
return {};
}
}
/// Formats cookies as a Cookie header string.
///
/// This converts a map of cookie names to values into a properly formatted
/// Cookie header that can be sent with HTTP requests.
static String formatCookieHeader(Map<String, String> cookies) {
if (cookies.isEmpty) return '';
return cookies.entries.map((e) => '${e.key}=${e.value}').join('; ');
}
}