worldhopper/lib/services/auth_service.dart
Felipe M. 58a562abe8
feat: refactor server software abstraction with Kavita REST API integration
Rename all OPDS-prefixed models to generic names (Server, Feed, Entry,
Link, StreamLink), expand the ServerSoftware interface to cover all
server interactions, and implement a full Kavita REST API client that
replaces the OPDS delegation.

- Rename OPDS* models to generic names across ~60 files
- Add database migrations 13 (opds_id → entry_id) and 14 (unify credentials)
- Create OPDSServerSoftware wrapping existing OPDS services
- Create KavitaApiClient for direct Kavita REST API calls
- Create KavitaFeedMapper to convert Kavita JSON to Feed/Entry models
- Rewrite KavitaServerSoftware to use native API (no OPDS delegation)
- Unify server credentials (remove softwareUsername/softwarePassword)
- Simplify add/edit server UI to single auth section
- Add test connection button to server add/edit screens
- Add progress indicator to PublicationCard using local and server data
- Eliminate softwareType branching in reader screens
- Add EntryProgress and fetchEntryProgress to ServerSoftware interface
- Fix Entry.acquisitionLink crash on empty links
- Add 59 new tests covering models, services, and providers
2026-04-06 17:46:48 +02:00

86 lines
2.5 KiB
Dart

import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:worldhopper/models/server.dart';
import 'package:worldhopper/services/dio_factory.dart';
/// Service for handling HTTP Basic Authentication
class AuthService {
/// Create Dio client with authentication for a server
Dio createAuthenticatedClient(Server server) {
final dio = createDio(
BaseOptions(
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
headers: {
'User-Agent': 'Worldhopper/1.0',
'Accept': 'application/atom+xml, application/xml, text/xml, */*',
},
),
);
// Add authentication interceptor if credentials are provided
if (server.requiresAuth) {
dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
final authHeader = _generateBasicAuthHeader(
server.username!,
server.password!,
);
options.headers['Authorization'] = authHeader;
return handler.next(options);
},
onError: (error, handler) {
// Handle 401 Unauthorized errors
if (error.response?.statusCode == 401) {
// Could trigger re-authentication flow here
}
return handler.next(error);
},
),
);
}
// Add logging interceptor for debugging
dio.interceptors.add(
LogInterceptor(
requestBody: false,
responseBody: false,
logPrint: (obj) {
// Custom logging (can be integrated with a logging package)
// print(obj);
},
),
);
return dio;
}
/// Generate HTTP Basic Auth header
String _generateBasicAuthHeader(String username, String password) {
final credentials = '$username:$password';
final encoded = base64Encode(utf8.encode(credentials));
return 'Basic $encoded';
}
/// Get authentication headers for image requests
Map<String, String> getAuthHeaders(Server server) {
if (!server.requiresAuth) {
return {};
}
return {
'Authorization': _generateBasicAuthHeader(
server.username!,
server.password!,
),
};
}
/// Generate Basic Auth header from username and password
static String generateBasicAuthHeader(String username, String password) {
final credentials = '$username:$password';
final encoded = base64Encode(utf8.encode(credentials));
return 'Basic $encoded';
}
}