iOS: authenticate each request by its own header only (no cookies/credential cache)

Two accounts can share one server; the Share Extension's folder listing and
the upload sessions now ignore cookies and stored credentials so one account's
session can never answer for another. Logs each PROPFIND (user, path, status).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Ayushya Amitabh
2026-10-06 20:18:20 -04:00
co-authored by Claude Sonnet 5.5
parent 02d612bfec
commit 02d9d799a6
3 changed files with 25 additions and 1 deletions
+5
View File
@@ -34,6 +34,11 @@ final class TransferManager: NSObject {
config.sessionSendsLaunchEvents = true config.sessionSendsLaunchEvents = true
config.isDiscretionary = false config.isDiscretionary = false
config.waitsForConnectivity = true config.waitsForConnectivity = true
// Two accounts can share a server: authenticate by the request's own
// header only, never a cookie another account's request left behind.
config.httpShouldSetCookies = false
config.httpCookieAcceptPolicy = .never
config.urlCredentialStorage = nil
return URLSession(configuration: config, delegate: self, delegateQueue: nil) return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}() }()
+16 -1
View File
@@ -149,6 +149,20 @@ enum DavClient {
/// Every child folder, hidden and external ones included - the sheet's /// Every child folder, hidden and external ones included - the sheet's
/// toggles filter that list in memory, so changing one doesn't refetch. /// toggles filter that list in memory, so changing one doesn't refetch.
/// Two accounts can live on the same server, so a request must be
/// authenticated by its own `Authorization` header alone - never by a
/// session cookie or credential a previous account's request left behind
/// in the shared stores, or the second account could be answered as the
/// first.
private static let session: URLSession = {
let config = URLSessionConfiguration.ephemeral
config.httpShouldSetCookies = false
config.httpCookieAcceptPolicy = .never
config.urlCredentialStorage = nil
config.requestCachePolicy = .reloadIgnoringLocalCacheData
return URLSession(configuration: config)
}()
static func listFolders(account: SharedAccount, path: String) async throws -> [DavFolder] { static func listFolders(account: SharedAccount, path: String) async throws -> [DavFolder] {
guard let url = WebDAV.fileURL(serverUrl: account.serverUrl, username: account.username, remotePath: path) guard let url = WebDAV.fileURL(serverUrl: account.serverUrl, username: account.username, remotePath: path)
else { throw TransferError(message: "Invalid server address.") } else { throw TransferError(message: "Invalid server address.") }
@@ -160,8 +174,9 @@ enum DavClient {
request.httpBody = body.data(using: .utf8) request.httpBody = body.data(using: .utf8)
request.timeoutInterval = 20 request.timeoutInterval = 20
let (data, response) = try await URLSession.shared.data(for: request) let (data, response) = try await session.data(for: request)
let status = (response as? HTTPURLResponse)?.statusCode ?? 0 let status = (response as? HTTPURLResponse)?.statusCode ?? 0
NSLog("[DavClient] PROPFIND %@ as %@ -> %d", url.path, account.username, status)
guard status == 207 else { guard status == 207 else {
throw TransferError(message: status == 401 ? "Signed out - open Noo to sign in again." : "Server returned \(status).") throw TransferError(message: status == 401 ? "Signed out - open Noo to sign in again." : "Server returned \(status).")
} }
+4
View File
@@ -16,6 +16,10 @@ enum ShareUpload {
config.sessionSendsLaunchEvents = true config.sessionSendsLaunchEvents = true
config.isDiscretionary = false config.isDiscretionary = false
config.waitsForConnectivity = true config.waitsForConnectivity = true
// Authenticate by the request's own header only (see DavClient.session).
config.httpShouldSetCookies = false
config.httpCookieAcceptPolicy = .never
config.urlCredentialStorage = nil
return config return config
} }