mirror of
https://codeberg.org/secana/Forji.git
synced 2026-06-16 05:13:55 -07:00
Merge branch 'main' of ssh://codeberg.org/secana/Forji
This commit is contained in:
commit
b0f50eca38
4 changed files with 47 additions and 6 deletions
|
|
@ -99,11 +99,7 @@ class AuthenticationService {
|
|||
if let instance = currentInstance {
|
||||
let normalizedURL = ForgejoClient.normalizeServerURL(instance.serverURL)
|
||||
do {
|
||||
try await KeychainManager.shared.deletePassword(
|
||||
for: normalizedURL,
|
||||
username: instance.username,
|
||||
)
|
||||
try await KeychainManager.shared.deleteToken(
|
||||
try await KeychainManager.shared.deleteCredentials(
|
||||
for: normalizedURL,
|
||||
username: instance.username,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,15 @@ actor KeychainManager {
|
|||
try deleteItem(forKey: key(for: server, username: username, suffix: "_token"))
|
||||
}
|
||||
|
||||
// MARK: - Account (both items)
|
||||
|
||||
/// Removes every credential stored for an account: the password and the API token.
|
||||
/// Use when an account is removed entirely (logout, delete instance) so neither item is orphaned.
|
||||
func deleteCredentials(for server: String, username: String) throws {
|
||||
try deletePassword(for: server, username: username)
|
||||
try deleteToken(for: server, username: username)
|
||||
}
|
||||
|
||||
// MARK: - Synchronous token read (no actor hop)
|
||||
|
||||
nonisolated static func getTokenSync(for server: String, username: String) -> String? {
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ struct InstanceListView: View {
|
|||
assertionFailure("SwiftData save failed: \(error)")
|
||||
}
|
||||
Task {
|
||||
try? await KeychainManager.shared.deletePassword(for: normalizedURL, username: username)
|
||||
try? await KeychainManager.shared.deleteCredentials(for: normalizedURL, username: username)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -133,4 +133,40 @@ struct KeychainManagerTests {
|
|||
// Clean up
|
||||
try await KeychainManager.shared.deletePassword(for: normalized, username: "user")
|
||||
}
|
||||
|
||||
@Test func keychainDeleteCredentialsRemovesBothPasswordAndToken() async throws {
|
||||
let server = "https://delete-credentials.example.com"
|
||||
let username = "user"
|
||||
|
||||
try await KeychainManager.shared.savePassword("mypassword", for: server, username: username)
|
||||
try await KeychainManager.shared.saveToken("mytoken", for: server, username: username)
|
||||
|
||||
try await KeychainManager.shared.deleteCredentials(for: server, username: username)
|
||||
|
||||
do {
|
||||
_ = try await KeychainManager.shared.getPassword(for: server, username: username)
|
||||
Issue.record("Expected KeychainError.notFound for password after deleteCredentials")
|
||||
} catch is KeychainError {
|
||||
// Expected
|
||||
} catch {
|
||||
Issue.record("Unexpected error: \(error)")
|
||||
}
|
||||
|
||||
do {
|
||||
_ = try await KeychainManager.shared.getToken(for: server, username: username)
|
||||
Issue.record("Expected KeychainError.notFound for token after deleteCredentials")
|
||||
} catch is KeychainError {
|
||||
// Expected — this is the assertion that fails under the old leak
|
||||
} catch {
|
||||
Issue.record("Unexpected error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
@Test func keychainDeleteCredentialsNonExistentDoesNotThrow() async throws {
|
||||
// Deleting credentials for an account with nothing stored must not throw.
|
||||
try await KeychainManager.shared.deleteCredentials(
|
||||
for: "https://nonexistent-credentials.example.com",
|
||||
username: "nobody"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue