fix(mobile): clear the app's cookies on logout (#32965)
This commit is contained in:
1 parent
9b5e1aae5b
commit
11e9a4b4da
7 files changed
+64
-4
No files matched your search
@@ -267,6 +267,10 @@ object HttpClientManager {
|
||||
}
|
||||
}
|
||||
|
||||
fun clearCookies() {
|
||||
cookieJar.clear()
|
||||
}
|
||||
|
||||
fun loadCookieHeader(url: String): String? {
|
||||
val httpUrl = url.toHttpUrlOrNull() ?: return null
|
||||
return cookieJar.loadForRequest(httpUrl).takeIf { it.isNotEmpty() }
|
||||
@@ -470,6 +474,12 @@ object HttpClientManager {
|
||||
return store.filter { it.matches(url) }
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun clear() {
|
||||
store.clear()
|
||||
persist()
|
||||
}
|
||||
|
||||
private fun syncAuthCookies(): Boolean {
|
||||
val serverHosts = serverUrls.map { it.host }.toSet()
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
@@ -84,4 +84,8 @@ private class NetworkApiImpl(private val context: Context) : NetworkApi {
|
||||
override fun setRequestHeaders(headers: Map<String, String>, serverUrls: List<String>, token: String?) {
|
||||
HttpClientManager.setRequestHeaders(headers, serverUrls, token)
|
||||
}
|
||||
|
||||
override fun clearCookies() {
|
||||
HttpClientManager.clearCookies()
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,10 @@ class NetworkApiImpl: NetworkApi {
|
||||
URLSessionManager.shared.recreateSession()
|
||||
}
|
||||
}
|
||||
|
||||
func clearCookies() throws {
|
||||
URLSessionManager.clearCookies()
|
||||
}
|
||||
}
|
||||
|
||||
private class CertImporter: NSObject, UIDocumentPickerDelegate {
|
||||
|
||||
@@ -58,6 +58,7 @@ class URLSessionManager: NSObject {
|
||||
static let cookieStorage = HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: APP_GROUP)
|
||||
private static var serverUrls: [String] = []
|
||||
private static var isSyncing = false
|
||||
private static let cookieQueue = DispatchQueue(label: "CookieSync")
|
||||
|
||||
var sessionPointer: UnsafeMutableRawPointer {
|
||||
Unmanaged.passUnretained(session).toOpaque()
|
||||
@@ -84,12 +85,20 @@ class URLSessionManager: NSObject {
|
||||
guard urls != serverUrls else { return }
|
||||
serverUrls = urls
|
||||
UserDefaults.group.set(urls, forKey: SERVER_URLS_KEY)
|
||||
syncAuthCookies()
|
||||
cookieQueue.sync { syncAuthCookies() }
|
||||
}
|
||||
|
||||
static func clearCookies() {
|
||||
cookieQueue.sync {
|
||||
for cookie in cookieStorage.cookies ?? [] {
|
||||
cookieStorage.deleteCookie(cookie)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private static func cookiesDidChange(_ notification: Notification) {
|
||||
guard !isSyncing, !serverUrls.isEmpty else { return }
|
||||
syncAuthCookies()
|
||||
cookieQueue.async { syncAuthCookies() }
|
||||
}
|
||||
|
||||
private static func syncAuthCookies() {
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||
import 'package:immich_mobile/repositories/auth.repository.dart';
|
||||
import 'package:immich_mobile/repositories/auth_api.repository.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
@@ -120,6 +121,7 @@ class AuthService {
|
||||
_authRepository.clearLocalData(),
|
||||
Store.delete(StoreKey.currentUser),
|
||||
Store.delete(StoreKey.accessToken),
|
||||
networkApi.clearCookies(),
|
||||
SettingsRepository.instance.clear(const [
|
||||
.networkAutoEndpointSwitching,
|
||||
.networkPreferredWifiName,
|
||||
|
||||
@@ -45,5 +45,7 @@ abstract class NetworkApi {
|
||||
|
||||
void setRequestHeaders(Map<String, String> headers, List<String> serverUrls, String? token);
|
||||
|
||||
void clearCookies();
|
||||
|
||||
String getAppGroupId();
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import 'package:drift/drift.dart' hide isNull;
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/data/db/main/database.dart';
|
||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||
import 'package:immich_mobile/platform/network_api.g.dart';
|
||||
import 'package:immich_mobile/services/auth.service.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
@@ -34,9 +36,10 @@ void main() {
|
||||
});
|
||||
|
||||
setUpAll(() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||
await StoreService.init(storeRepository: StoreRepository(db));
|
||||
await SettingsRepository.ensureInitialized(db);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
@@ -238,4 +241,30 @@ void main() {
|
||||
verify(() => apiService.resolveAndSetEndpoint('https://external.endpoint')).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('logout', () {
|
||||
test('Should clear native cookies after the server logout fails', () async {
|
||||
final calls = <String>[];
|
||||
when(() => authApiRepository.logout()).thenAnswer((_) async {
|
||||
calls.add('logout');
|
||||
throw ApiException(503, 'Server is not reachable');
|
||||
});
|
||||
when(() => backgroundSyncManager.cancel()).thenAnswer((_) async {});
|
||||
when(() => authRepository.clearLocalData()).thenAnswer((_) async {});
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockDecodedMessageHandler(
|
||||
const BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.immich_mobile.NetworkApi.clearCookies',
|
||||
NetworkApi.pigeonChannelCodec,
|
||||
),
|
||||
(_) async {
|
||||
calls.add('clearCookies');
|
||||
return <Object?>[null];
|
||||
},
|
||||
);
|
||||
|
||||
await sut.logout();
|
||||
|
||||
expect(calls, ['logout', 'clearCookies']);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user