fix(ios): restrict authentication capabilities and validate bootstrap types

Require iOS 18.4 for explicit upload denial in the isolated authentication window. Add origin-bound generated download policy, app-switcher privacy, typed metadata regressions and original community icon assets. Seven portable tests and Swift formatting checks pass; Apple SDK validation remains external.
This commit is contained in:
Yuvomi Mobile Contributors committed 2026-10-20 23:45:01 +02:00
1 parent 74c2c9e6e8
commit 69c48169e2
15 files changed
+341 -112

No files matched your search

+48 -18
View File
@@ -22,7 +22,10 @@ struct ContentView: View {
Image(systemName: "wifi.exclamationmark").font(.largeTitle)
Text("Connection interrupted").font(.title2).bold()
Text(error)
Button("Retry") { browser.error = nil; browser.web.reload() }
Button("Retry") {
browser.error = nil
browser.web.reload()
}
Button("Keep viewing") { browser.error = nil }
Button("Server settings") { settings = true }
}
@@ -30,12 +33,18 @@ struct ContentView: View {
.background(Color(uiColor: .systemGroupedBackground))
.accessibilityElement(children: .contain)
}
if browser.loading { ProgressView("Loading Yuvomi").padding().background(.regularMaterial).cornerRadius(12) }
if browser.loading {
ProgressView("Loading Yuvomi").padding().background(.regularMaterial).cornerRadius(12)
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button { browser.web.goBack() } label: { Image(systemName: "chevron.backward") }
.disabled(!browser.canGoBack).accessibilityLabel("Back")
Button {
browser.web.goBack()
} label: {
Image(systemName: "chevron.backward")
}
.disabled(!browser.canGoBack).accessibilityLabel("Back")
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Server settings") { settings = true }
@@ -70,10 +79,11 @@ struct ContentView: View {
}
.task {
if server == nil, let existing = try? ServerAddress(input) {
server = existing; browser.open(existing)
server = existing
browser.open(existing)
}
}
.onChange(of: scenePhase) { phase in
.onChange(of: scenePhase) { _, phase in
if phase == .background { browser.rememberPath() }
}
.overlay {
@@ -93,31 +103,49 @@ struct ContentView: View {
}
}
Section {
Text("Changing servers erases cookies, storage and cached household data. Exported files remain. Sign out in Yuvomi first to revoke the server session.")
Text(
"Changing servers erases cookies, storage and cached household data. Exported files remain. Sign out in Yuvomi first to revoke the server session."
)
Button("Erase local data and change server", role: .destructive) {
settings = false; erasing = true
settings = false
erasing = true
browser.erase {
UserDefaults.standard.removeObject(forKey: "origin")
UserDefaults.standard.removeObject(forKey: "path")
server = nil; input = ""; erasing = false
server = nil
input = ""
erasing = false
}
}
}
Section("About") {
Text("Independent community contribution. MIT licensed. Not endorsed by upstream Yuvomi. No mobile telemetry. Your server's own privacy settings still apply.")
Text(
"Independent community contribution. MIT licensed. Not endorsed by upstream Yuvomi. No mobile telemetry. Your server's own privacy settings still apply."
)
}
}.navigationTitle("Server settings")
.toolbar { Button("Done") { settings = false } }
}
}
.alert("External destination", isPresented: Binding(
get: { browser.externalURL != nil }, set: { if !$0 { browser.externalURL = nil } }
), presenting: browser.externalURL) { url in
Button("Open in browser") { UIApplication.shared.open(url); browser.externalURL = nil }
Button("Isolated sign-in window") { browser.startAuthentication(url); browser.externalURL = nil }
.alert(
"External destination",
isPresented: Binding(
get: { browser.externalURL != nil }, set: { if !$0 { browser.externalURL = nil } }
), presenting: browser.externalURL
) { url in
Button("Open in browser") {
UIApplication.shared.open(url)
browser.externalURL = nil
}
Button("Isolated sign-in window") {
browser.startAuthentication(url)
browser.externalURL = nil
}
Button("Cancel", role: .cancel) { browser.externalURL = nil }
} message: { url in
Text("Destination: \(url.host ?? "")\nFor SSO, use the isolated window only if you trust this provider. Browser sessions do not transfer into the app.")
Text(
"Destination: \(url.host ?? "")\nFor SSO, use the isolated window only if you trust this provider. Browser sessions do not transfer into the app."
)
}
.sheet(item: $browser.authentication) { destination in
AuthenticationView(destination: destination.url, model: browser)
@@ -130,13 +158,15 @@ struct ContentView: View {
private func connect() {
do {
let candidate = try ServerAddress(input)
connecting = true; message = nil
connecting = true
message = nil
Task { @MainActor in
defer { connecting = false }
do {
try await ServerProbe.check(candidate)
UserDefaults.standard.set(candidate.url.absoluteString, forKey: "origin")
server = candidate; browser.open(candidate)
server = candidate
browser.open(candidate)
} catch { message = ConnectionError.unavailable.localizedDescription }
}
} catch { message = ConnectionError.invalidAddress.localizedDescription }