Configure iPhone Git commit identity (#76)

This commit is contained in:
2026-08-12 19:03:54 +02:00
parent e45ad07c89
commit 009db5cbcf
10 changed files with 392 additions and 37 deletions

View File

@@ -625,6 +625,8 @@ public protocol MobileAuthenticationProtocol: AnyObject, Sendable {
func generateEntryEditorPassword(editor: UInt64, length: UInt32?, noSymbols: Bool) throws -> MobileEntryEditorPage
func gitIdentity() throws -> MobileGitIdentity
func manualLock() throws
func performEntryMutation(request: MobileMutationRequest) throws -> MobileMutationOutcome
@@ -643,6 +645,8 @@ public protocol MobileAuthenticationProtocol: AnyObject, Sendable {
func setBiometricUnlock(enabled: Bool) throws -> MobileAuthenticationState
func setGitIdentity(name: String, email: String) throws -> MobileGitIdentity
func setTotpWatchShared(path: String, shared: Bool, unixSeconds: UInt64) throws -> MobileTotpDetail
func state() throws -> MobileAuthenticationState
@@ -829,6 +833,15 @@ open func generateEntryEditorPassword(editor: UInt64, length: UInt32?, noSymbols
})
}
open func gitIdentity()throws -> MobileGitIdentity {
return try FfiConverterTypeMobileGitIdentity_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_apple_fn_method_mobileauthentication_git_identity(
self.uniffiCloneHandle(),uniffiCallStatus
)
})
}
open func manualLock()throws {try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_apple_fn_method_mobileauthentication_manual_lock(
@@ -925,6 +938,17 @@ open func setBiometricUnlock(enabled: Bool)throws -> MobileAuthenticationState
})
}
open func setGitIdentity(name: String, email: String)throws -> MobileGitIdentity {
return try FfiConverterTypeMobileGitIdentity_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_apple_fn_method_mobileauthentication_set_git_identity(
self.uniffiCloneHandle(),
FfiConverterString.lower(name),
FfiConverterString.lower(email),uniffiCallStatus
)
})
}
open func setTotpWatchShared(path: String, shared: Bool, unixSeconds: UInt64)throws -> MobileTotpDetail {
return try FfiConverterTypeMobileTotpDetail_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
uniffiCallStatus in
@@ -2437,6 +2461,60 @@ public func FfiConverterTypeMobileEntrySection_lower(_ value: MobileEntrySection
}
public struct MobileGitIdentity: Equatable, Hashable {
public var name: String
public var email: String
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(name: String, email: String) {
self.name = name
self.email = email
}
}
#if compiler(>=6)
extension MobileGitIdentity: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMobileGitIdentity: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileGitIdentity {
return
try MobileGitIdentity(
name: FfiConverterString.read(from: &buf),
email: FfiConverterString.read(from: &buf)
)
}
public static func write(_ value: MobileGitIdentity, into buf: inout [UInt8]) {
FfiConverterString.write(value.name, into: &buf)
FfiConverterString.write(value.email, into: &buf)
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileGitIdentity_lift(_ buf: RustBuffer) throws -> MobileGitIdentity {
return try FfiConverterTypeMobileGitIdentity.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileGitIdentity_lower(_ value: MobileGitIdentity) -> RustBuffer {
return FfiConverterTypeMobileGitIdentity.lower(value)
}
public struct MobileHomeChange: Equatable, Hashable {
public var id: String
public var title: String
@@ -6864,6 +6942,9 @@ private let initializationResult: InitializationResult = {
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_generate_entry_editor_password() != 34289) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_git_identity() != 29496) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_manual_lock() != 57220) {
return InitializationResult.apiChecksumMismatch
}
@@ -6891,6 +6972,9 @@ private let initializationResult: InitializationResult = {
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_biometric_unlock() != 9486) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_git_identity() != 62371) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_totp_watch_shared() != 57472) {
return InitializationResult.apiChecksumMismatch
}

View File

@@ -308,6 +308,11 @@ RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_entry_page(ui
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_generate_entry_editor_password(uint64_t ptr, uint64_t editor, RustBuffer length, int8_t no_symbols, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_GIT_IDENTITY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_GIT_IDENTITY
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_git_identity(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_MANUAL_LOCK
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_MANUAL_LOCK
void uniffi_ironstorage_apple_fn_method_mobileauthentication_manual_lock(uint64_t ptr, RustCallStatus *_Nonnull out_status
@@ -353,6 +358,11 @@ RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_save_entry_ed
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_set_biometric_unlock(uint64_t ptr, int8_t enabled, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_GIT_IDENTITY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_GIT_IDENTITY
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_set_git_identity(uint64_t ptr, RustBuffer name, RustBuffer email, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_TOTP_WATCH_SHARED
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_TOTP_WATCH_SHARED
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_set_totp_watch_shared(uint64_t ptr, RustBuffer path, int8_t shared, uint64_t unix_seconds, RustCallStatus *_Nonnull out_status
@@ -990,6 +1000,12 @@ uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_entry_pag
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_GENERATE_ENTRY_EDITOR_PASSWORD
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_generate_entry_editor_password(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_GIT_IDENTITY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_GIT_IDENTITY
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_git_identity(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_MANUAL_LOCK
@@ -1044,6 +1060,12 @@ uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_save_entr
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SET_BIOMETRIC_UNLOCK
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_biometric_unlock(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SET_GIT_IDENTITY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SET_GIT_IDENTITY
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_git_identity(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SET_TOTP_WATCH_SHARED

View File

@@ -712,6 +712,7 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
private let authentication: MobileAuthentication?
private let keyTransfer = try? mobileKeyTransfer()
private var state: MobileAuthenticationState?
private var gitIdentity: MobileGitIdentity?
private var preferenceTask: Task<Void, Never>?
private var loadTask: Task<Void, Never>?
private var loadGeneration = 0
@@ -753,14 +754,14 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
}
override func numberOfSections(in tableView: UITableView) -> Int {
page.state == .ready ? 3 : 0
page.state == .ready ? 4 : 0
}
override func tableView(
_ tableView: UITableView,
numberOfRowsInSection section: Int
) -> Int {
section == 0 ? 2 : (section == 1 ? 1 : 2)
section == 1 ? 2 : (section == 3 ? 2 : 1)
}
override func tableView(
@@ -768,8 +769,9 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
titleForHeaderInSection section: Int
) -> String? {
switch section {
case 0: "GPG Key Transfer"
case 1: "Secure Unlock"
case 0: "Git Commit Identity"
case 1: "GPG Key Transfer"
case 2: "Secure Unlock"
default: "Authentication Session"
}
}
@@ -779,9 +781,12 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
titleForFooterInSection section: Int
) -> String? {
if section == 0 {
return "Scan or display ASCII-armored GPG keys. Private-key transfers require explicit confirmation and passphrase validation."
return "Used as the author for commits created by the iPhone app."
}
if section == 1 {
return "Scan or display ASCII-armored GPG keys. Private-key transfers require explicit confirmation and passphrase validation."
}
if section == 2 {
return "When enabled, the GPG passphrase is device-only, requires a device passcode, and is invalidated when enrolled biometrics change."
}
return "Manual lock and inactivity expiry immediately revoke the shared Rust authentication lease."
@@ -794,6 +799,13 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
var content = cell.defaultContentConfiguration()
if indexPath.section == 0 {
content.image = UIImage(systemName: "person.crop.circle")
content.text = gitIdentity?.name ?? "Git Commit Identity"
content.secondaryText = gitIdentity?.email ?? "Set the commit author name and email"
cell.accessoryType = .disclosureIndicator
cell.isUserInteractionEnabled = authentication != nil
cell.contentView.alpha = authentication == nil ? 0.45 : 1
} else if indexPath.section == 1 {
let importing = indexPath.row == 0
content.image = UIImage(systemName: importing ? "qrcode.viewfinder" : "qrcode")
content.text = importing ? "Import GPG Key" : "Export GPG Key"
@@ -803,7 +815,7 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
cell.accessoryType = .disclosureIndicator
cell.isUserInteractionEnabled = keyTransfer != nil
cell.contentView.alpha = keyTransfer == nil ? 0.45 : 1
} else if indexPath.section == 1 {
} else if indexPath.section == 2 {
content.image = UIImage(systemName: "faceid")
content.text = "Biometric Unlock"
content.secondaryText = state?.biometricUnlockEnabled == true
@@ -841,6 +853,10 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if indexPath.section == 0 {
editGitIdentity()
return
}
if indexPath.section == 1 {
guard let keyTransfer else { return }
if indexPath.row == 0 {
requestKeyScanner(keyTransfer)
@@ -852,7 +868,7 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
}
return
}
guard indexPath.section == 2, indexPath.row == 1, let authentication else { return }
guard indexPath.section == 3, indexPath.row == 1, let authentication else { return }
do {
try authentication.manualLock()
refreshState()
@@ -970,6 +986,81 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
present(alert, animated: true)
}
private func editGitIdentity() {
guard let authentication else {
presentAuthenticationFailure(.unavailable)
return
}
let alert = UIAlertController(
title: "Git Commit Identity",
message: "Enter the author used for commits created on this iPhone.",
preferredStyle: .alert
)
alert.addTextField { [gitIdentity] field in
field.placeholder = "Name"
field.text = gitIdentity?.name
field.textContentType = .name
field.autocapitalizationType = .words
field.clearButtonMode = .whileEditing
}
alert.addTextField { [gitIdentity] field in
field.placeholder = "Email"
field.text = gitIdentity?.email
field.textContentType = .emailAddress
field.keyboardType = .emailAddress
field.autocapitalizationType = .none
field.autocorrectionType = .no
field.clearButtonMode = .whileEditing
}
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
alert.addAction(UIAlertAction(title: "Save", style: .default) { [weak self, weak alert] _ in
guard
let self,
let fields = alert?.textFields,
fields.count == 2
else { return }
saveGitIdentity(
authentication,
name: fields[0].text ?? "",
email: fields[1].text ?? ""
)
})
present(alert, animated: true)
}
private func saveGitIdentity(
_ authentication: MobileAuthentication,
name: String,
email: String
) {
preferenceTask?.cancel()
preferenceTask = Task { [weak self] in
let result = await Task.detached(priority: .userInitiated) {
do {
return Result<MobileGitIdentity, AuthenticationFailure>.success(
try authentication.setGitIdentity(name: name, email: email)
)
} catch let error as MobileAuthenticationFfiError {
return .failure(AuthenticationFailure(error))
} catch {
return .failure(.unexpected)
}
}.value
guard !Task.isCancelled, let self else { return }
switch result {
case let .success(identity):
gitIdentity = identity
tableView.reloadSections(IndexSet(integer: 0), with: .automatic)
UIAccessibility.post(
notification: .announcement,
argument: "Git commit identity updated"
)
case let .failure(failure):
presentAuthenticationFailure(failure)
}
}
}
@objc private func authenticationDidChange() {
refreshState()
}
@@ -1014,6 +1105,7 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
private func refreshState() {
state = try? authentication?.state()
gitIdentity = try? authentication?.gitIdentity()
tableView.reloadData()
}
}