Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
44adb86
Make KeychainProtocol API not async
samwyndham Apr 1, 2026
c3aa3c2
Delete a couple of unused Failures
samwyndham Apr 1, 2026
ea027eb
Make storing a cookie safer and faster in the upload case
samwyndham Apr 1, 2026
4461614
Refactor `CookieStorage`
samwyndham Apr 1, 2026
8a1a588
Make CookieStorage an class instead of actor and have a non async API
samwyndham Apr 2, 2026
3a14c6d
Protect access to keychain with lock
samwyndham Apr 2, 2026
5fd2073
Split CookieStorage into two objects to simplify code around locking
samwyndham Apr 2, 2026
f671c33
Delete ZMPersistentCookieStorageMigrator
samwyndham Apr 2, 2026
da85f89
Delete `ZMPersistentCookieStorage.deleteAllKeychainItems`
samwyndham Apr 2, 2026
21b2e5a
Revert "Delete `ZMPersistentCookieStorage.deleteAllKeychainItems`"
samwyndham Apr 2, 2026
fc8b674
Remove legacy code paths as user id is never nil
samwyndham Apr 2, 2026
d0d3386
Remove cookieKey property
samwyndham Apr 2, 2026
3cf584e
Delete serverName property
samwyndham Apr 2, 2026
1cdc0d5
Add some additional tests as preparation for refactoring ZMPersistent…
samwyndham Apr 3, 2026
6d49a44
Introduce PersistentCookieStorage
samwyndham Apr 6, 2026
6aa51aa
Use `PersistentCookieStorage` instead of `ZMPersistentCookieStorage`
samwyndham Apr 7, 2026
a8cc1b6
Delete ZMPersistentCookieStorage
samwyndham Apr 7, 2026
4fdf3d4
Add cache to CookieStorage
samwyndham Apr 7, 2026
7b16072
Split authenticationCookieData into a getter and setter
samwyndham Apr 7, 2026
208d4f7
Use setAuthenticationCookies instead of setAuthenticationCookieData
samwyndham Apr 7, 2026
dceae70
Make authenticationCookieData private and authenticationCookies public
samwyndham Apr 7, 2026
b1d32e8
Delete `Assembly`.
samwyndham Apr 7, 2026
92de50e
Introduce `CookieStorageCache`
samwyndham Apr 7, 2026
519b73a
Small refactor and bug fix
samwyndham Apr 7, 2026
07b5a61
Remove static PersistentCookieStorage.storage(forUserIdentifier:useCa…
samwyndham Apr 7, 2026
ec7ef45
Pass CookieStorage into PersistentCookieStorage in init
samwyndham Apr 8, 2026
558ca00
Use CookieStorage in PersistentCookieStorage and simplify API
samwyndham Apr 8, 2026
52ab505
Remove cookie policy methods
samwyndham Apr 8, 2026
8315271
Fix typo
samwyndham Apr 8, 2026
092c542
Remove useCache parameter
samwyndham Apr 8, 2026
2f54ab1
Delete hasAccessibleAuthenticationCookieData
samwyndham Apr 8, 2026
70a3c75
Make PersistentCookieStorageTests swift
samwyndham Apr 8, 2026
b684ba3
Fix compile error
samwyndham Apr 8, 2026
3206462
Move CookieProvider to WireSyncEngine
samwyndham Apr 8, 2026
43505b0
[WIP] Move PersistentCookieStorage to WireNetwork.
samwyndham Apr 8, 2026
245d030
Revert "[WIP] Move PersistentCookieStorage to WireNetwork."
samwyndham Apr 8, 2026
dac875d
Duplicate CookieStorageProtocol
samwyndham Apr 9, 2026
2668b84
Rename PersistentCookieStorage -> LegacyCookieStorage
samwyndham Apr 9, 2026
a6a47fc
Make LegacyCookieStorage.removeCookies match CookieStorage.removeCookies
samwyndham Apr 9, 2026
e893ac7
Delete LegacyCookieStorage.authenticationCookies()
samwyndham Apr 9, 2026
2f5e695
Make LegacyCookieStorage.setAuthenticationCookies match CookieStorage…
samwyndham Apr 9, 2026
5c821b5
Fix share extension build error
samwyndham Apr 9, 2026
33a2b9e
Delete `LegacyCookieStorageProtocol`
samwyndham Apr 9, 2026
d731610
Make authenticationCookieExpirationDate non obj-c
samwyndham Apr 9, 2026
caafc43
Add new tests for LegacyCookieStorage
samwyndham Apr 10, 2026
48908dc
Refactor LegacyCookieStorage
samwyndham Apr 10, 2026
e3b1247
Delete old LegacyCookieStorage tests
samwyndham Apr 10, 2026
8b0e527
Introduce CookieStorageEpoch
samwyndham Apr 13, 2026
1247194
Use epoch stored in Keychain metadata to validate cache
samwyndham Apr 16, 2026
e8ce723
Refactor so that CookieStorage is for all users, not a single user
samwyndham Apr 17, 2026
06cb435
Have `CookieStorage` owned by AppDelegate
samwyndham Apr 17, 2026
037967a
Introduce CookieStorageIntegrationTests and add tests for store cookies
samwyndham Apr 20, 2026
3bd61ac
Test fetching cookies
samwyndham Apr 20, 2026
39d1fb1
Test removing cookies
samwyndham Apr 20, 2026
d328913
Add tests for cookie caching
samwyndham Apr 20, 2026
742002d
Small refactor
samwyndham Apr 21, 2026
d77d4bb
Test fetching with different encryption key
samwyndham Apr 21, 2026
f2557df
Delete old CookieStorageTests & rename.
samwyndham Apr 21, 2026
0364d42
Small refactor of CookieStorage
samwyndham Apr 21, 2026
5e86906
LInt & format
samwyndham Apr 21, 2026
5d42f9a
Fix linting error
samwyndham Apr 21, 2026
3c43a58
Remove incorrect #if DEBUG
samwyndham Apr 21, 2026
d023d2e
Log some errors as critical
samwyndham Apr 21, 2026
ae59f19
Add ticket number to comment
samwyndham Apr 21, 2026
5e92b0a
Lint & format
samwyndham Apr 21, 2026
0e0f57e
Merge branch 'develop' into chore/review-cookie-handling-WPB-23404
samwyndham Apr 22, 2026
5443b40
Merge branch 'develop' into chore/review-cookie-handling-WPB-23404
samwyndham Apr 23, 2026
84eafe5
chore: test re-login with multiple backends - WPB-24958 (#4619)
samwyndham Apr 29, 2026
1ed1392
Merge branch 'develop' into chore/review-cookie-handling-WPB-23404
samwyndham May 5, 2026
2ced36f
Fix commented out code
samwyndham May 5, 2026
799f528
Make some errors safe public.
samwyndham May 6, 2026
498fe0f
Merge branch 'develop' into chore/review-cookie-handling-WPB-23404
samwyndham May 6, 2026
2e07111
Fix compile issue in release builds
samwyndham May 7, 2026
441b964
Fix crash when missing HTTPResponse
samwyndham May 8, 2026
98643d8
Merge branch 'develop' into chore/review-cookie-handling-WPB-23404
samwyndham May 8, 2026
ed8bd04
Merge branch 'develop' into chore/review-cookie-handling-WPB-23404
johnxnguyen May 11, 2026
31542ec
fix tests
johnxnguyen May 13, 2026
ca45899
Merge branch 'develop' into chore/review-cookie-handling-WPB-23404
johnxnguyen May 13, 2026
92e9e69
Merge branch 'develop' into chore/review-cookie-handling-WPB-23404
samwyndham May 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion WireDomain/Sources/WireDomain/Account/AccountManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public final class AccountManager: NSObject, Sendable {

public static func delete(at root: URL) {
AccountStore.delete(directory: AccountURLs(root: root).accounts)
UserDefaults.shared().selectedAccountIdentifier = nil
UserDefaults.shared()!.selectedAccountIdentifier = nil
Comment thread
samwyndham marked this conversation as resolved.
}

// MARK: - Retrieve
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import Combine
import Foundation
import WireCoreCrypto
import WireDataModel
import WireFoundation
import WireLogging
import WireNetwork

Expand Down Expand Up @@ -54,7 +55,7 @@ public final class ClientSessionComponent {

private let isMLSEnabled: Bool

private let cookieStorage: any CookieStorageProtocol
private let cookieStorage: any WireNetwork.CookieStorageProtocol
private let sharedContainerURL: URL?
private let sharedUserDefaults: UserDefaults
private let syncContext: NSManagedObjectContext
Expand All @@ -76,7 +77,7 @@ public final class ClientSessionComponent {
websocketNetworkService: NetworkService,
backendMetadata: ResolvedBackendMetadata,
isMLSEnabled: Bool,
cookieStorage: any CookieStorageProtocol,
cookieStorage: any WireNetwork.CookieStorageProtocol,
sharedContainerURL: URL?,
sharedUserDefaults: UserDefaults,
syncContext: NSManagedObjectContext,
Expand Down Expand Up @@ -110,6 +111,7 @@ public final class ClientSessionComponent {
}

public private(set) lazy var authenticationManager = AuthenticationManager(
userID: selfUserID,
clientID: selfClientID,
cookieStorage: cookieStorage,
networkService: restNetworkService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public final class UserSessionComponent {
public init(
currentBuildNumber: String,
selfUserID: UUID,
cookieStorage: any CookieStorageProtocol,
cookieStorage: any WireNetwork.CookieStorageProtocol,
restNetworkService: NetworkService,
websocketNetworkService: NetworkService,
blacklistNetworkService: NetworkService,
Expand Down Expand Up @@ -86,7 +86,7 @@ public final class UserSessionComponent {
self.faultyMLSRemovalKeysByDomain = faultyMLSRemovalKeysByDomain
}

private let cookieStorage: any CookieStorageProtocol
private let cookieStorage: any WireNetwork.CookieStorageProtocol

// MARK: - Children

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ final class NSEClientScope: Component<NSEClientScopeDependency> {
private var authenticationManager: AuthenticationManager {
shared {
AuthenticationManager(
userID: dependency.accountID,
clientID: clientID,
cookieStorage: dependency.cookieStorage,
networkService: restNetworkService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,7 @@ final class NSEUserScope: Component<NSEUserScopeDependency> {
public var cookieStorage: CookieStorage {
shared {
CookieStorage(
userID: accountID,
cookieEncryptionKey: dependency.cookieEncryptionKey,
keychain: Keychain()
cookieEncryptionKey: dependency.cookieEncryptionKey
)
}
}
Expand Down Expand Up @@ -286,7 +284,7 @@ final class NSEUserScope: Component<NSEUserScopeDependency> {
private func isAuthenticated() async throws -> Bool {
let cookies: [HTTPCookie]
do {
cookies = try await cookieStorage.fetchCookies()
cookies = try cookieStorage.fetchCookies(userID: accountID)
} catch {
throw Failure.failedToFetchCookies(error)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//

import WireDataModel
import WireFoundation
import WireLogging
import WireNetwork

Expand All @@ -39,16 +40,19 @@ struct VerifyUserSessionUseCase {

// MARK: - Properties

private let userID: UUID
private let journal: any JournalProtocol
private let cookieStorage: any CookieStorageProtocol
private let cookieStorage: any WireNetwork.CookieStorageProtocol
private let coreData: any CoreDataStackProtocol
private let logger = WireLogger.notifications

init(
userID: UUID,
journal: any JournalProtocol,
cookieStorage: any CookieStorageProtocol,
cookieStorage: any WireNetwork.CookieStorageProtocol,
coreData: any CoreDataStackProtocol
) {
self.userID = userID
self.journal = journal
self.cookieStorage = cookieStorage
self.coreData = coreData
Expand All @@ -74,7 +78,7 @@ struct VerifyUserSessionUseCase {
attributes: .newNSE
)

let cookies = try await cookieStorage.fetchCookies()
let cookies = try cookieStorage.fetchCookies(userID: userID)

for cookie in cookies where cookie.name == Constants.cookieName {
if let cookieExpirationDate = cookie.expiresDate {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ final class VerifyUserSessionUseCaseTests: XCTestCase {
cookieStorage = MockCookieStorageProtocol()

sut = VerifyUserSessionUseCase(
userID: Scaffolding.userID,
journal: journal,
cookieStorage: cookieStorage,
coreData: stack
Expand All @@ -64,13 +65,13 @@ final class VerifyUserSessionUseCaseTests: XCTestCase {
stack.needsMigration = false
stack.load_MockMethod = {}
let validCookie = try XCTUnwrap(Scaffolding.validCookie)
cookieStorage.fetchCookies_MockValue = [validCookie]
cookieStorage.fetchCookiesUserID_MockValue = [validCookie]

// When
try await sut.invoke()

// Then
XCTAssertEqual(cookieStorage.fetchCookies_Invocations.count, 1)
XCTAssertEqual(cookieStorage.fetchCookiesUserID_Invocations.count, 1)

}

Expand All @@ -90,7 +91,7 @@ final class VerifyUserSessionUseCaseTests: XCTestCase {
func testVerify_It_Throws_User_Unauthenticated_Error() async throws {

// Mock
cookieStorage.fetchCookies_MockValue = [.init()]
cookieStorage.fetchCookiesUserID_MockValue = [.init()]

// Then
await XCTAssertThrowsErrorAsync(VerifyUserSessionUseCase.Failure.userUnauthenticated) { [self] in
Expand All @@ -104,7 +105,7 @@ final class VerifyUserSessionUseCaseTests: XCTestCase {

// Mock
let expiredCookie = try XCTUnwrap(Scaffolding.expiredCookie)
cookieStorage.fetchCookies_MockValue = [expiredCookie]
cookieStorage.fetchCookiesUserID_MockValue = [expiredCookie]

// Then
await XCTAssertThrowsErrorAsync(VerifyUserSessionUseCase.Failure.userUnauthenticated) { [self] in
Expand All @@ -117,7 +118,7 @@ final class VerifyUserSessionUseCaseTests: XCTestCase {
func testVerify_It_Throws_User_Unauthenticated_Error_When_No_Cookies_Found() async throws {

// Mock
cookieStorage.fetchCookies_MockValue = []
cookieStorage.fetchCookiesUserID_MockValue = []

// Then
await XCTAssertThrowsErrorAsync(VerifyUserSessionUseCase.Failure.userUnauthenticated) { [self] in
Expand All @@ -130,7 +131,7 @@ final class VerifyUserSessionUseCaseTests: XCTestCase {
func testStartSyncingEvents_It_Throws_Core_Data_Missing_Shared_Container() async throws {
// Mock
let validCookie = try XCTUnwrap(Scaffolding.validCookie)
cookieStorage.fetchCookies_MockValue = [validCookie]
cookieStorage.fetchCookiesUserID_MockValue = [validCookie]
stack.storesExists = false

// Then
Expand All @@ -145,7 +146,7 @@ final class VerifyUserSessionUseCaseTests: XCTestCase {
stack.storesExists = true
stack.needsMigration = true
let validCookie = try XCTUnwrap(Scaffolding.validCookie)
cookieStorage.fetchCookies_MockValue = [validCookie]
cookieStorage.fetchCookiesUserID_MockValue = [validCookie]

// Then
await XCTAssertThrowsErrorAsync(VerifyUserSessionUseCase.Failure.coreDataMigrationRequired) { [self] in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public struct Keychain: KeychainProtocol {

public func addItem(
query: Set<KeychainQueryItem>
) async throws {
) throws {
let status = SecItemAdd(
query.toCFDictionary(),
nil
Expand All @@ -50,7 +50,7 @@ public struct Keychain: KeychainProtocol {
public func updateItem(
query: Set<KeychainQueryItem>,
attributesToUpdate: Set<KeychainQueryItem>
) async throws {
) throws {
let status = SecItemUpdate(
query.toCFDictionary(),
attributesToUpdate.toCFDictionary()
Expand All @@ -67,7 +67,7 @@ public struct Keychain: KeychainProtocol {

public func fetchItem<T>(
query: Set<KeychainQueryItem>
) async throws -> T? {
) throws -> T? {
var result: CFTypeRef?

let status = SecItemCopyMatching(
Expand Down Expand Up @@ -96,7 +96,7 @@ public struct Keychain: KeychainProtocol {

public func deleteItem(
query: Set<KeychainQueryItem>
) async throws {
) throws {
let status = SecItemDelete(
query.toCFDictionary()
)
Expand All @@ -106,6 +106,27 @@ public struct Keychain: KeychainProtocol {
}
}

/// Delete all items from the keychain.

public func reset() throws {
let items: [KeychainQueryItem] = [
.itemClass(.genericPassword),
.itemClass(.internetPassword),
.itemClass(.certificate),
.itemClass(.key),
.itemClass(.identity)
]

for item in items {
let query = Set<KeychainQueryItem>([item]).toCFDictionary()
let status = SecItemDelete(query)
guard status == errSecSuccess || status == errSecItemNotFound else {
throw KeychainError.errorStatus(status)
}

}
}

}

private extension Set<KeychainQueryItem> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,20 @@ public protocol KeychainProtocol: Sendable {

func addItem(
query: Set<KeychainQueryItem>
) async throws
) throws

func updateItem(
query: Set<KeychainQueryItem>,
attributesToUpdate: Set<KeychainQueryItem>
) async throws
) throws

func fetchItem<T>(
query: Set<KeychainQueryItem>
) async throws -> T?
) throws -> T?

func deleteItem(
query: Set<KeychainQueryItem>
) async throws
) throws

}

Expand All @@ -51,14 +51,20 @@ public enum KeychainQueryItem: Hashable, Equatable, Sendable {

case service(String)
case account(String)
case generic(Data)
case itemClass(ItemClass)
case accessible(ItemAccessibility)
case returningData(Bool)
case data(Data)
case returningAttributes(Bool)

public enum ItemClass: Equatable, Sendable {

case genericPassword
case internetPassword
case certificate
case key
case identity

}

Expand All @@ -76,9 +82,24 @@ public enum KeychainQueryItem: Hashable, Equatable, Sendable {
case let .account(string):
(kSecAttrAccount, string)

case let .generic(data):
(kSecAttrGeneric, data)

case .itemClass(.genericPassword):
(kSecClass, kSecClassGenericPassword)

case .itemClass(.internetPassword):
(kSecClass, kSecClassInternetPassword)

case .itemClass(.certificate):
(kSecClass, kSecClassCertificate)

case .itemClass(.key):
(kSecClass, kSecClassKey)

case .itemClass(.identity):
(kSecClass, kSecClassIdentity)

case .accessible(.afterFirstUnlock):
(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlock)

Expand All @@ -87,6 +108,9 @@ public enum KeychainQueryItem: Hashable, Equatable, Sendable {

case let .data(data):
(kSecValueData, data)

case let .returningAttributes(bool):
(kSecReturnAttributes, bool)
}
}

Expand Down
Loading
Loading