Files
sure/mobile/lib/services/custom_proxy_headers_service.dart
Michal Tajchert 96c893ec18 Mobile: custom proxy headers + small login UX fixes (#1748)
* Add mobile custom proxy headers

* Clear login placeholders on focus

Email/password fields ship with example values pre-filled. Tapping the
field now clears the placeholder so users don't have to delete it
manually. Skips clearing if the user has already edited the value.

* Push Configuration as a route from Sign in

Opening Configuration from the Sign in screen now uses Navigator.push
instead of toggling a state flag, so Android back returns to Sign in
instead of quitting the app. Saving the URL auto-pops the route.

* Address PR review on custom proxy headers

- Test Connection no longer leaves global ApiConfig headers mutated;
  unsaved edits are restored in a finally block after the probe.
- _loadSavedUrl / _loadCustomHeaders wrap storage reads in try/catch and
  always finish initialization with sensible defaults.
- Sanitization is now a single CustomProxyHeader.sanitize() reused by
  ApiConfig.setCustomProxyHeaders and CustomProxyHeadersService.
- Brief comment on redactedValue explaining the length-obscuring design.

* Harden custom proxy header validation and load path

- validateValue now rejects ASCII control characters (CR/LF/tab/etc.)
  to prevent header-injection via crafted values.
- loadHeaders moves the secure-storage read inside the try block so
  platform exceptions are caught the same way JSON parse errors are.
2026-05-11 23:09:21 +02:00

48 lines
1.3 KiB
Dart

import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../models/custom_proxy_header.dart';
class CustomProxyHeadersService {
static const String storageKey = 'custom_proxy_headers';
static CustomProxyHeadersService? _instance;
CustomProxyHeadersService._();
static CustomProxyHeadersService get instance {
_instance ??= CustomProxyHeadersService._();
return _instance!;
}
Future<List<CustomProxyHeader>> loadHeaders() async {
const storage = FlutterSecureStorage();
try {
final raw = await storage.read(key: storageKey);
if (raw == null || raw.isEmpty) return [];
final decoded = jsonDecode(raw);
if (decoded is! List) return [];
return CustomProxyHeader.sanitize(
decoded
.whereType<Map>()
.map((item) => CustomProxyHeader.fromJson(Map<String, dynamic>.from(item)))
.toList(),
);
} catch (_) {
return [];
}
}
Future<void> saveHeaders(List<CustomProxyHeader> headers) async {
const storage = FlutterSecureStorage();
final sanitized = CustomProxyHeader.sanitize(headers);
await storage.write(
key: storageKey,
value: jsonEncode(sanitized.map((header) => header.toJson()).toList()),
);
}
}