fix(avatar): attach the Bearer token — the real bug, not propagation delay
Your network capture of the working web-app request showed session cookies (accessToken, authelia_session) on the attachments.redirect call. This app authenticates every other request with an Authorization: Bearer header instead — AvatarBadge's fetch never attached one, sending a bare unauthenticated GET. Almost certainly a 401 the whole time, for every avatar image, not just a freshly-uploaded one — a failed fetch and "no avatar set" render identically here (placeholder icon, no visible error), so there was nothing on screen to reveal it before now. Uses KeychainTokenStore() directly, same keychain entry SessionStore already reads, rather than threading a token through every AvatarBadge call site. Kept the retry loop from the previous attempt too — genuinely useful insurance against upload-consistency timing, just not the actual cause here.
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import OutlineKit
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
import AppKit
|
import AppKit
|
||||||
@@ -45,16 +46,27 @@ struct AvatarBadge: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A freshly-uploaded attachment's redirect URL can 404/fail on the very
|
/// The real fix, confirmed against a live network capture: every other
|
||||||
/// first request right after upload (self-hosted reverse-proxied
|
/// request this app makes attaches `Authorization: Bearer <token>` —
|
||||||
/// storage, not necessarily instantly consistent) — since `.task(id:)`
|
/// this one never did, sending a bare unauthenticated GET. Outline's
|
||||||
/// only ever runs once per URL and never retries on its own, a single
|
/// browser session authenticates `attachments.redirect` via cookies
|
||||||
/// transient failure right after uploading a new avatar would leave the
|
/// instead, which a native app doesn't have; the API-token equivalent
|
||||||
/// placeholder showing forever, even though the exact same URL works
|
/// is the same Bearer header every RPC call already uses. Almost
|
||||||
/// fine moments later. Two short-delayed retries before giving up.
|
/// certainly means no avatar image (not just a freshly-uploaded one)
|
||||||
|
/// has ever actually loaded in this app — a 401 and a "no avatar set"
|
||||||
|
/// look identical here, both just fall back to the placeholder icon
|
||||||
|
/// with nothing on screen to flag it as an error.
|
||||||
|
///
|
||||||
|
/// The retry loop is a secondary, independent hardening — cheap
|
||||||
|
/// insurance against a self-hosted reverse-proxied storage backend not
|
||||||
|
/// being instantly consistent right after an upload — kept alongside
|
||||||
|
/// the auth fix rather than instead of it.
|
||||||
private static func loadImage(from url: URL) async -> PlatformImage? {
|
private static func loadImage(from url: URL) async -> PlatformImage? {
|
||||||
var request = URLRequest(url: url)
|
var request = URLRequest(url: url)
|
||||||
request.cachePolicy = .reloadIgnoringLocalCacheData
|
request.cachePolicy = .reloadIgnoringLocalCacheData
|
||||||
|
if let token = try? KeychainTokenStore().token() {
|
||||||
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
||||||
|
}
|
||||||
|
|
||||||
for attempt in 0..<3 {
|
for attempt in 0..<3 {
|
||||||
if attempt > 0 {
|
if attempt > 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user