Prompt for admin auth before privileged updater install

This commit is contained in:
h3p 2026-02-23 20:19:30 +01:00
parent 375ed65764
commit 5b1a887b50
2 changed files with 48 additions and 2 deletions

View file

@ -361,7 +361,7 @@
CODE_SIGNING_ALLOWED = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 328;
CURRENT_PROJECT_VERSION = 329;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = CS727NF72U;
ENABLE_APP_SANDBOX = YES;
@ -442,7 +442,7 @@
CODE_SIGNING_ALLOWED = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 328;
CURRENT_PROJECT_VERSION = 329;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = CS727NF72U;
ENABLE_APP_SANDBOX = YES;

View file

@ -439,6 +439,10 @@ final class AppUpdateManager: ObservableObject {
func completeInstalledUpdate(restart: Bool) {
#if os(macOS)
if awaitingInstallCompletionAction {
if requiresPrivilegedInstall,
!requestInstallerAuthorizationPrompt() {
return
}
guard launchBackgroundInstaller(relaunch: restart) else { return }
installMessage = restart
? "Installing update in background. App will restart after install."
@ -455,6 +459,48 @@ final class AppUpdateManager: ObservableObject {
#endif
}
#if os(macOS)
private var requiresPrivilegedInstall: Bool {
let targetAppURL = Bundle.main.bundleURL.standardizedFileURL
let destinationDir = targetAppURL.deletingLastPathComponent()
return !FileManager.default.isWritableFile(atPath: destinationDir.path)
}
private func requestInstallerAuthorizationPrompt() -> Bool {
do {
let process = Process()
let stderrPipe = Pipe()
process.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
process.arguments = ["-e", "do shell script \"/usr/bin/true\" with administrator privileges"]
process.standardError = stderrPipe
try process.run()
process.waitUntilExit()
if process.terminationStatus == 0 {
return true
}
let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile()
let stderrText = String(data: stderrData, encoding: .utf8)?
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if stderrText.localizedCaseInsensitiveContains("User canceled")
|| stderrText.localizedCaseInsensitiveContains("cancelled") {
installMessage = "Install cancelled. Administrator permission was not granted."
} else if stderrText.contains("-60005")
|| stderrText.localizedCaseInsensitiveContains("password")
|| stderrText.localizedCaseInsensitiveContains("administrator") {
installMessage = "Administrator authentication failed. Please retry and enter your macOS admin password."
} else if !stderrText.isEmpty {
installMessage = "Failed to verify administrator permission: \(stderrText)"
} else {
installMessage = "Failed to verify administrator permission (exit code \(process.terminationStatus))."
}
return false
} catch {
installMessage = "Failed to request administrator permission: \(error.localizedDescription)"
return false
}
}
#endif
private func shouldRunInitialCheckNow() -> Bool {
guard let lastCheckedAt else { return true }
return Date().timeIntervalSince(lastCheckedAt) >= updateInterval.seconds