Configure iPhone Git commit identity (#76)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user