mirror of
https://github.com/yattee/yattee.git
synced 2026-11-02 18:07:19 +00:00
Stop a status refresh from erasing a rejected credential
The API layer marking an account `credentialRejected` posts the same notification that recomputes statuses, which saw the Keychain item still present and immediately reported "signed in" again. The rejection could therefore never survive the moment it was recorded, and the user was never asked to sign in again. Only the server can clear a rejection now; storing a new credential resets it explicitly. Cache invalidation follows the effective context rather than the stored one. A credential arriving or disappearing flips reads between the account and local without the stored context moving, so the global feed caches kept whichever side had populated them. The login sheet stays open when the credential could not be stored — an unresolved identity scope or a Keychain failure — instead of dismissing and discarding what the user typed.
This commit is contained in:
@@ -389,8 +389,11 @@ private struct EditRemoteServerContent: View {
|
||||
///
|
||||
/// The identifier is stored beside the credential in the Keychain rather
|
||||
/// than on the account record, so it never travels as synced metadata.
|
||||
private func storeLogin(credential: String, username: String, target: AccountLoginTarget) {
|
||||
guard let appEnvironment else { return }
|
||||
/// - Returns: whether the credential was stored. A login the server
|
||||
/// accepted is not the same as one this device could keep.
|
||||
@discardableResult
|
||||
private func storeLogin(credential: String, username: String, target: AccountLoginTarget) -> Bool {
|
||||
guard let appEnvironment else { return false }
|
||||
|
||||
// Credential storage refuses to write under an unresolved identity
|
||||
// scope, so creating the account first would leave one with no
|
||||
@@ -400,13 +403,13 @@ private struct EditRemoteServerContent: View {
|
||||
appEnvironment.toastManager.showError(
|
||||
String(localized: "accounts.signIn.identityUnresolved")
|
||||
)
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
let account = target.account
|
||||
?? appEnvironment.accountsManager.addAccount(instanceID: instance.id)
|
||||
|
||||
guard let credentials = appEnvironment.accountCredentialsManager(for: account) else { return }
|
||||
guard let credentials = appEnvironment.accountCredentialsManager(for: account) else { return false }
|
||||
|
||||
credentials.setCredential(credential, for: account)
|
||||
|
||||
@@ -420,7 +423,7 @@ private struct EditRemoteServerContent: View {
|
||||
appEnvironment.toastManager.showError(
|
||||
String(localized: "accounts.signIn.storeFailed")
|
||||
)
|
||||
return
|
||||
return false
|
||||
}
|
||||
if !username.isEmpty {
|
||||
credentials.setUsername(username, for: account)
|
||||
@@ -442,6 +445,7 @@ private struct EditRemoteServerContent: View {
|
||||
appEnvironment.accountCredentialDidChange(for: account)
|
||||
|
||||
isLoggedIn = true
|
||||
return true
|
||||
}
|
||||
|
||||
private func loadServerInfo() async {
|
||||
|
||||
@@ -9,13 +9,18 @@ import SwiftUI
|
||||
|
||||
struct InstanceLoginView: View {
|
||||
let instance: Instance
|
||||
/// Receives the credential **and** the identifier the user signed in with.
|
||||
/// Receives the credential **and** the identifier the user signed in with,
|
||||
/// and reports whether it was stored.
|
||||
///
|
||||
/// The identifier is handed back rather than stored here because it belongs
|
||||
/// beside the credential in the Keychain, not in synced account metadata —
|
||||
/// so only the caller, which knows which account this login is for, can
|
||||
/// file it correctly.
|
||||
let onLoginSuccess: (_ credential: String, _ username: String) -> Void
|
||||
///
|
||||
/// The return value matters because signing in successfully is not the same
|
||||
/// as having somewhere to keep the result: dismissing on a Keychain failure
|
||||
/// would throw away what the user typed and make them do it all again.
|
||||
let onLoginSuccess: (_ credential: String, _ username: String) -> Bool
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.appEnvironment) private var appEnvironment
|
||||
@@ -153,7 +158,13 @@ struct InstanceLoginView: View {
|
||||
let credential = try await performLogin(appEnvironment: appEnvironment)
|
||||
|
||||
await MainActor.run {
|
||||
onLoginSuccess(credential, username)
|
||||
guard onLoginSuccess(credential, username) else {
|
||||
// The failure is reported by a toast from the storing
|
||||
// side; staying open keeps the form filled in so the
|
||||
// user can retry once it can succeed.
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
} catch APIError.unauthorized {
|
||||
@@ -213,13 +224,13 @@ struct InstanceLoginView: View {
|
||||
#Preview("Invidious") {
|
||||
InstanceLoginView(
|
||||
instance: Instance(type: .invidious, url: URL(string: "https://invidious.example.com")!)
|
||||
) { _, _ in }
|
||||
) { _, _ in true }
|
||||
.appEnvironment(.preview)
|
||||
}
|
||||
|
||||
#Preview("Piped") {
|
||||
InstanceLoginView(
|
||||
instance: Instance(type: .piped, url: URL(string: "https://piped.example.com")!)
|
||||
) { _, _ in }
|
||||
) { _, _ in true }
|
||||
.appEnvironment(.preview)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user