From cd6277f5aac13927ad10a661c3ce9f3e6b0867a8 Mon Sep 17 00:00:00 2001 From: psavarmattas Date: Sat, 15 Aug 2026 16:44:45 +0100 Subject: [PATCH] =?UTF-8?q?fix(avatar):=20attach=20the=20Bearer=20token=20?= =?UTF-8?q?=E2=80=94=20the=20real=20bug,=20not=20propagation=20delay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- Outpost/Support/AvatarBadge.swift | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/Outpost/Support/AvatarBadge.swift b/Outpost/Support/AvatarBadge.swift index a5920a7..13da9dd 100644 --- a/Outpost/Support/AvatarBadge.swift +++ b/Outpost/Support/AvatarBadge.swift @@ -1,4 +1,5 @@ import SwiftUI +import OutlineKit #if os(macOS) import AppKit @@ -45,16 +46,27 @@ struct AvatarBadge: View { } } - /// A freshly-uploaded attachment's redirect URL can 404/fail on the very - /// first request right after upload (self-hosted reverse-proxied - /// storage, not necessarily instantly consistent) — since `.task(id:)` - /// only ever runs once per URL and never retries on its own, a single - /// transient failure right after uploading a new avatar would leave the - /// placeholder showing forever, even though the exact same URL works - /// fine moments later. Two short-delayed retries before giving up. + /// The real fix, confirmed against a live network capture: every other + /// request this app makes attaches `Authorization: Bearer ` — + /// this one never did, sending a bare unauthenticated GET. Outline's + /// browser session authenticates `attachments.redirect` via cookies + /// instead, which a native app doesn't have; the API-token equivalent + /// is the same Bearer header every RPC call already uses. Almost + /// 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? { var request = URLRequest(url: url) request.cachePolicy = .reloadIgnoringLocalCacheData + if let token = try? KeychainTokenStore().token() { + request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + } for attempt in 0..<3 { if attempt > 0 {