mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-08-19 11:40:56 +00:00
WIP Edit
This commit is contained in:
parent
276ca02b39
commit
7aba3c374d
@ -7,10 +7,8 @@ public struct AnySecret: Secret, @unchecked Sendable {
|
|||||||
private let hashable: AnyHashable
|
private let hashable: AnyHashable
|
||||||
private let _id: () -> AnyHashable
|
private let _id: () -> AnyHashable
|
||||||
private let _name: () -> String
|
private let _name: () -> String
|
||||||
private let _keyType: () -> KeyType
|
|
||||||
private let _authenticationRequirement: () -> AuthenticationRequirement
|
|
||||||
private let _publicKey: () -> Data
|
private let _publicKey: () -> Data
|
||||||
private let _publicKeyAttribution: () -> String?
|
private let _attributes: () -> Attributes
|
||||||
|
|
||||||
public init<T>(_ secret: T) where T: Secret {
|
public init<T>(_ secret: T) where T: Secret {
|
||||||
if let secret = secret as? AnySecret {
|
if let secret = secret as? AnySecret {
|
||||||
@ -18,19 +16,15 @@ public struct AnySecret: Secret, @unchecked Sendable {
|
|||||||
hashable = secret.hashable
|
hashable = secret.hashable
|
||||||
_id = secret._id
|
_id = secret._id
|
||||||
_name = secret._name
|
_name = secret._name
|
||||||
_keyType = secret._keyType
|
|
||||||
_authenticationRequirement = secret._authenticationRequirement
|
|
||||||
_publicKey = secret._publicKey
|
_publicKey = secret._publicKey
|
||||||
_publicKeyAttribution = secret._publicKeyAttribution
|
_attributes = secret._attributes
|
||||||
} else {
|
} else {
|
||||||
base = secret as Any
|
base = secret as Any
|
||||||
self.hashable = secret
|
self.hashable = secret
|
||||||
_id = { secret.id as AnyHashable }
|
_id = { secret.id as AnyHashable }
|
||||||
_name = { secret.name }
|
_name = { secret.name }
|
||||||
_keyType = { secret.keyType }
|
|
||||||
_authenticationRequirement = { secret.authenticationRequirement }
|
|
||||||
_publicKey = { secret.publicKey }
|
_publicKey = { secret.publicKey }
|
||||||
_publicKeyAttribution = { secret.publicKeyAttribution }
|
_attributes = { secret.attributes }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,21 +36,12 @@ public struct AnySecret: Secret, @unchecked Sendable {
|
|||||||
_name()
|
_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
public var keyType: KeyType {
|
|
||||||
_keyType()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public var authenticationRequirement: AuthenticationRequirement {
|
|
||||||
_authenticationRequirement()
|
|
||||||
}
|
|
||||||
|
|
||||||
public var publicKey: Data {
|
public var publicKey: Data {
|
||||||
_publicKey()
|
_publicKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
public var publicKeyAttribution: String? {
|
public var attributes: Attributes {
|
||||||
_publicKeyAttribution()
|
_attributes()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func == (lhs: AnySecret, rhs: AnySecret) -> Bool {
|
public static func == (lhs: AnySecret, rhs: AnySecret) -> Bool {
|
||||||
|
@ -70,13 +70,13 @@ public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiab
|
|||||||
|
|
||||||
private let _create: @Sendable (String, Attributes) async throws -> Void
|
private let _create: @Sendable (String, Attributes) async throws -> Void
|
||||||
private let _delete: @Sendable (AnySecret) async throws -> Void
|
private let _delete: @Sendable (AnySecret) async throws -> Void
|
||||||
private let _update: @Sendable (AnySecret, String) async throws -> Void
|
private let _update: @Sendable (AnySecret, String, Attributes) async throws -> Void
|
||||||
private let _supportedKeyTypes: @Sendable () -> [KeyType]
|
private let _supportedKeyTypes: @Sendable () -> [KeyType]
|
||||||
|
|
||||||
public init<SecretStoreType>(modifiable secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
|
public init<SecretStoreType>(modifiable secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
|
||||||
_create = { try await secretStore.create(name: $0, attributes: $1) }
|
_create = { try await secretStore.create(name: $0, attributes: $1) }
|
||||||
_delete = { try await secretStore.delete(secret: $0.base as! SecretStoreType.SecretType) }
|
_delete = { try await secretStore.delete(secret: $0.base as! SecretStoreType.SecretType) }
|
||||||
_update = { try await secretStore.update(secret: $0.base as! SecretStoreType.SecretType, name: $1) }
|
_update = { try await secretStore.update(secret: $0.base as! SecretStoreType.SecretType, name: $1, attributes: $2) }
|
||||||
_supportedKeyTypes = { secretStore.supportedKeyTypes }
|
_supportedKeyTypes = { secretStore.supportedKeyTypes }
|
||||||
super.init(secretStore)
|
super.init(secretStore)
|
||||||
}
|
}
|
||||||
@ -89,8 +89,8 @@ public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiab
|
|||||||
try await _delete(secret)
|
try await _delete(secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(secret: AnySecret, name: String) async throws {
|
public func update(secret: AnySecret, name: String, attributes: Attributes) async throws {
|
||||||
try await _update(secret, name)
|
try await _update(secret, name, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var supportedKeyTypes: [KeyType] {
|
public var supportedKeyTypes: [KeyType] {
|
||||||
|
@ -18,8 +18,19 @@ public struct OpenSSHKeyWriter: Sendable {
|
|||||||
|
|
||||||
/// Generates an OpenSSH string representation of the secret.
|
/// Generates an OpenSSH string representation of the secret.
|
||||||
/// - Returns: OpenSSH string representation of the secret.
|
/// - Returns: OpenSSH string representation of the secret.
|
||||||
public func openSSHString<SecretType: Secret>(secret: SecretType, comment: String? = nil) -> String {
|
public func openSSHString<SecretType: Secret>(secret: SecretType) -> String {
|
||||||
[curveType(for: secret.keyType), data(secret: secret).base64EncodedString(), comment]
|
let resolvedComment: String
|
||||||
|
if let comment = secret.publicKeyAttribution {
|
||||||
|
resolvedComment = comment
|
||||||
|
} else {
|
||||||
|
let dashedKeyName = secret.name.replacingOccurrences(of: " ", with: "-")
|
||||||
|
let dashedHostName = ["secretive", Host.current().localizedName, "local"]
|
||||||
|
.compactMap { $0 }
|
||||||
|
.joined(separator: ".")
|
||||||
|
.replacingOccurrences(of: " ", with: "-")
|
||||||
|
resolvedComment = "\(dashedKeyName)@\(dashedHostName)"
|
||||||
|
}
|
||||||
|
return [curveType(for: secret.keyType), data(secret: secret).base64EncodedString(), resolvedComment]
|
||||||
.compactMap { $0 }
|
.compactMap { $0 }
|
||||||
.joined(separator: " ")
|
.joined(separator: " ")
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct Attributes: Sendable, Codable {
|
public struct Attributes: Sendable, Codable, Hashable {
|
||||||
|
|
||||||
/// The type of key involved.
|
/// The type of key involved.
|
||||||
public var keyType: KeyType
|
public let keyType: KeyType
|
||||||
|
|
||||||
/// The authentication requirements for the key. This is simply a description of the option recorded at creation – modifying it doers not modify the key's authentication requirements.
|
/// The authentication requirements for the key. This is simply a description of the option recorded at creation – modifying it doers not modify the key's authentication requirements.
|
||||||
public let authentication: AuthenticationRequirement
|
public let authentication: AuthenticationRequirement
|
||||||
|
@ -5,14 +5,28 @@ public protocol Secret: Identifiable, Hashable, Sendable {
|
|||||||
|
|
||||||
/// A user-facing string identifying the Secret.
|
/// A user-facing string identifying the Secret.
|
||||||
var name: String { get }
|
var name: String { get }
|
||||||
/// The algorithm this secret uses.
|
|
||||||
var keyType: KeyType { get }
|
|
||||||
/// Whether the secret requires authentication before use.
|
|
||||||
var authenticationRequirement: AuthenticationRequirement { get }
|
|
||||||
/// The public key data for the secret.
|
/// The public key data for the secret.
|
||||||
var publicKey: Data { get }
|
var publicKey: Data { get }
|
||||||
|
/// The attributes of the key.
|
||||||
|
var attributes: Attributes { get }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Secret {
|
||||||
|
|
||||||
|
/// The algorithm and key size this secret uses.
|
||||||
|
var keyType: KeyType {
|
||||||
|
attributes.keyType
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the secret requires authentication before use.
|
||||||
|
var authenticationRequirement: AuthenticationRequirement {
|
||||||
|
attributes.authentication
|
||||||
|
}
|
||||||
/// An attribution string to apply to the generated public key.
|
/// An attribution string to apply to the generated public key.
|
||||||
var publicKeyAttribution: String? { get }
|
var publicKeyAttribution: String? {
|
||||||
|
attributes.publicKeyAttribution
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,8 @@ public protocol SecretStoreModifiable: SecretStore {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - secret: The ``Secret`` to update.
|
/// - secret: The ``Secret`` to update.
|
||||||
/// - name: The new name for the Secret.
|
/// - name: The new name for the Secret.
|
||||||
func update(secret: SecretType, name: String) async throws
|
/// - attributes: The new attributes for the secret.
|
||||||
|
func update(secret: SecretType, name: String, attributes: Attributes) async throws
|
||||||
|
|
||||||
var supportedKeyTypes: [KeyType] { get }
|
var supportedKeyTypes: [KeyType] { get }
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ extension SecureEnclave {
|
|||||||
await reloadSecretsInternal()
|
await reloadSecretsInternal()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(secret: Secret, name: String) async throws {
|
public func update(secret: Secret, name: String, attributes: Attributes) async throws {
|
||||||
let updateQuery = KeychainDictionary([
|
let updateQuery = KeychainDictionary([
|
||||||
kSecClass: kSecClassKey,
|
kSecClass: kSecClassKey,
|
||||||
kSecAttrApplicationLabel: secret.id as CFData
|
kSecAttrApplicationLabel: secret.id as CFData
|
||||||
|
@ -9,10 +9,8 @@ extension SecureEnclave {
|
|||||||
|
|
||||||
public let id: Data
|
public let id: Data
|
||||||
public let name: String
|
public let name: String
|
||||||
public let keyType: KeyType
|
|
||||||
public let authenticationRequirement: AuthenticationRequirement
|
|
||||||
public let publicKeyAttribution: String?
|
|
||||||
public let publicKey: Data
|
public let publicKey: Data
|
||||||
|
public let attributes: Attributes
|
||||||
|
|
||||||
init(
|
init(
|
||||||
id: Data,
|
id: Data,
|
||||||
@ -22,10 +20,15 @@ extension SecureEnclave {
|
|||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.name = name
|
self.name = name
|
||||||
self.keyType = .init(algorithm: .ecdsa, size: 256)
|
|
||||||
self.authenticationRequirement = authenticationRequirement
|
|
||||||
self.publicKeyAttribution = nil
|
|
||||||
self.publicKey = publicKey
|
self.publicKey = publicKey
|
||||||
|
self.attributes = Attributes(
|
||||||
|
keyType: .init(
|
||||||
|
algorithm: .ecdsa,
|
||||||
|
size: 256
|
||||||
|
),
|
||||||
|
authentication: authenticationRequirement,
|
||||||
|
publicKeyAttribution: nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -36,10 +39,8 @@ extension SecureEnclave {
|
|||||||
) {
|
) {
|
||||||
self.id = Data(id.utf8)
|
self.id = Data(id.utf8)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.keyType = attributes.keyType
|
|
||||||
self.authenticationRequirement = attributes.authentication
|
|
||||||
self.publicKeyAttribution = attributes.publicKeyAttribution
|
|
||||||
self.publicKey = publicKey
|
self.publicKey = publicKey
|
||||||
|
self.attributes = attributes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ extension SecureEnclave {
|
|||||||
await reloadSecretsInternal()
|
await reloadSecretsInternal()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(secret: Secret, name: String) async throws {
|
public func update(secret: Secret, name: String, attributes: Attributes) async throws {
|
||||||
let updateQuery = KeychainDictionary([
|
let updateQuery = KeychainDictionary([
|
||||||
kSecClass: kSecClassKey,
|
kSecClass: kSecClassKey,
|
||||||
kSecAttrApplicationLabel: secret.id as CFData
|
kSecAttrApplicationLabel: secret.id as CFData
|
||||||
|
@ -9,10 +9,8 @@ extension SmartCard {
|
|||||||
|
|
||||||
public let id: Data
|
public let id: Data
|
||||||
public let name: String
|
public let name: String
|
||||||
public let keyType: KeyType
|
|
||||||
public let authenticationRequirement: AuthenticationRequirement = .unknown
|
|
||||||
public let publicKey: Data
|
public let publicKey: Data
|
||||||
public var publicKeyAttribution: String? = nil
|
public var attributes: Attributes
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,7 +180,8 @@ extension SmartCard.Store {
|
|||||||
let publicKeySecRef = SecKeyCopyPublicKey(publicKeyRef)!
|
let publicKeySecRef = SecKeyCopyPublicKey(publicKeyRef)!
|
||||||
let publicKeyAttributes = SecKeyCopyAttributes(publicKeySecRef) as! [CFString: Any]
|
let publicKeyAttributes = SecKeyCopyAttributes(publicKeySecRef) as! [CFString: Any]
|
||||||
let publicKey = publicKeyAttributes[kSecValueData] as! Data
|
let publicKey = publicKeyAttributes[kSecValueData] as! Data
|
||||||
return SmartCard.Secret(id: tokenID, name: name, keyType: KeyType(secAttr: algorithmSecAttr, size: keySize)!, publicKey: publicKey)
|
let attributes = Attributes(keyType: KeyType(secAttr: algorithmSecAttr, size: keySize)!)
|
||||||
|
return SmartCard.Secret(id: tokenID, name: name, publicKey: publicKey, attributes: attributes)
|
||||||
}
|
}
|
||||||
state.secrets.append(contentsOf: wrapped)
|
state.secrets.append(contentsOf: wrapped)
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
2C4A9D2F2636FFD3008CC8E2 /* RenameSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4A9D2E2636FFD3008CC8E2 /* RenameSecretView.swift */; };
|
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */; };
|
||||||
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
|
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
|
||||||
50033AC327813F1700253856 /* BundleIDs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50033AC227813F1700253856 /* BundleIDs.swift */; };
|
50033AC327813F1700253856 /* BundleIDs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50033AC227813F1700253856 /* BundleIDs.swift */; };
|
||||||
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3A278005E800DF2006 /* SecretKit */; };
|
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3A278005E800DF2006 /* SecretKit */; };
|
||||||
@ -98,7 +98,7 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
2C4A9D2E2636FFD3008CC8E2 /* RenameSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenameSecretView.swift; sourceTree = "<group>"; };
|
2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSecretView.swift; sourceTree = "<group>"; };
|
||||||
50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
50033AC227813F1700253856 /* BundleIDs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleIDs.swift; sourceTree = "<group>"; };
|
50033AC227813F1700253856 /* BundleIDs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleIDs.swift; sourceTree = "<group>"; };
|
||||||
5003EF39278005C800DF2006 /* Packages */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Packages; sourceTree = "<group>"; };
|
5003EF39278005C800DF2006 /* Packages */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Packages; sourceTree = "<group>"; };
|
||||||
@ -246,7 +246,7 @@
|
|||||||
50C385A42407A76D00AF2719 /* SecretDetailView.swift */,
|
50C385A42407A76D00AF2719 /* SecretDetailView.swift */,
|
||||||
5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */,
|
5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */,
|
||||||
50B8550C24138C4F009958AC /* DeleteSecretView.swift */,
|
50B8550C24138C4F009958AC /* DeleteSecretView.swift */,
|
||||||
2C4A9D2E2636FFD3008CC8E2 /* RenameSecretView.swift */,
|
2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */,
|
||||||
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */,
|
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */,
|
||||||
506772C82425BB8500034DED /* NoStoresView.swift */,
|
506772C82425BB8500034DED /* NoStoresView.swift */,
|
||||||
50153E1F250AFCB200525160 /* UpdateView.swift */,
|
50153E1F250AFCB200525160 /* UpdateView.swift */,
|
||||||
@ -430,7 +430,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
2C4A9D2F2636FFD3008CC8E2 /* RenameSecretView.swift in Sources */,
|
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
|
||||||
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
||||||
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
|
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
|
||||||
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */,
|
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */,
|
||||||
|
@ -9,10 +9,13 @@ extension Preview {
|
|||||||
|
|
||||||
let id = UUID().uuidString
|
let id = UUID().uuidString
|
||||||
let name: String
|
let name: String
|
||||||
let keyType = KeyType(algorithm: .ecdsa, size: 256)
|
|
||||||
let authenticationRequirement = AuthenticationRequirement.presenceRequired
|
|
||||||
let publicKey = UUID().uuidString.data(using: .utf8)!
|
let publicKey = UUID().uuidString.data(using: .utf8)!
|
||||||
var publicKeyAttribution: String?
|
var attributes: Attributes {
|
||||||
|
Attributes(
|
||||||
|
keyType: .init(algorithm: .ecdsa, size: 256),
|
||||||
|
authentication: .presenceRequired,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -99,7 +102,7 @@ extension Preview {
|
|||||||
func delete(secret: Preview.Secret) throws {
|
func delete(secret: Preview.Secret) throws {
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(secret: Preview.Secret, name: String) throws {
|
func update(secret: Preview.Secret, name: String, attributes: Attributes) throws {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
52
Sources/Secretive/Views/EditSecretView.swift
Normal file
52
Sources/Secretive/Views/EditSecretView.swift
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
|
struct EditSecretView<StoreType: SecretStoreModifiable>: View {
|
||||||
|
|
||||||
|
let store: StoreType
|
||||||
|
let secret: StoreType.SecretType
|
||||||
|
let dismissalBlock: (_ renamed: Bool) -> ()
|
||||||
|
|
||||||
|
@State private var name: String
|
||||||
|
@State private var publicKeyAttribution: String
|
||||||
|
|
||||||
|
init(store: StoreType, secret: StoreType.SecretType, dismissalBlock: @escaping (Bool) -> ()) {
|
||||||
|
self.store = store
|
||||||
|
self.secret = secret
|
||||||
|
self.dismissalBlock = dismissalBlock
|
||||||
|
name = secret.name
|
||||||
|
publicKeyAttribution = secret.publicKeyAttribution ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .trailing) {
|
||||||
|
Form {
|
||||||
|
Section {
|
||||||
|
TextField(String(localized: .createSecretNameLabel), text: $name, prompt: Text(.createSecretNamePlaceholder))
|
||||||
|
TextField("Key Attribution", text: $publicKeyAttribution, prompt: Text("test@example.com"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
Button(.renameRenameButton, action: rename)
|
||||||
|
.disabled(name.isEmpty)
|
||||||
|
.keyboardShortcut(.return)
|
||||||
|
Button(.renameCancelButton) {
|
||||||
|
dismissalBlock(false)
|
||||||
|
}.keyboardShortcut(.cancelAction)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.formStyle(.grouped)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rename() {
|
||||||
|
var attributes = secret.attributes
|
||||||
|
if !publicKeyAttribution.isEmpty {
|
||||||
|
attributes.publicKeyAttribution = publicKeyAttribution
|
||||||
|
}
|
||||||
|
Task {
|
||||||
|
try? await store.update(secret: secret, name: name, attributes: attributes)
|
||||||
|
dismissalBlock(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,52 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
import SecretKit
|
|
||||||
|
|
||||||
struct RenameSecretView<StoreType: SecretStoreModifiable>: View {
|
|
||||||
|
|
||||||
@State var store: StoreType
|
|
||||||
let secret: StoreType.SecretType
|
|
||||||
var dismissalBlock: (_ renamed: Bool) -> ()
|
|
||||||
|
|
||||||
@State private var newName = ""
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
HStack {
|
|
||||||
Image(nsImage: NSApplication.shared.applicationIconImage)
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 64, height: 64)
|
|
||||||
.padding()
|
|
||||||
VStack {
|
|
||||||
HStack {
|
|
||||||
Text(.renameTitle(secretName: secret.name))
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
HStack {
|
|
||||||
TextField(secret.name, text: $newName).focusable()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
Button(.renameRenameButton, action: rename)
|
|
||||||
.disabled(newName.count == 0)
|
|
||||||
.keyboardShortcut(.return)
|
|
||||||
Button(.renameCancelButton) {
|
|
||||||
dismissalBlock(false)
|
|
||||||
}.keyboardShortcut(.cancelAction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.frame(minWidth: 400)
|
|
||||||
.onExitCommand {
|
|
||||||
dismissalBlock(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func rename() {
|
|
||||||
Task {
|
|
||||||
try? await store.update(secret: secret, name: newName)
|
|
||||||
dismissalBlock(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -30,19 +30,9 @@ struct SecretDetailView<SecretType: Secret>: View {
|
|||||||
.frame(minHeight: 200, maxHeight: .infinity)
|
.frame(minHeight: 200, maxHeight: .infinity)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dashedKeyName: String {
|
|
||||||
secret.name.replacingOccurrences(of: " ", with: "-")
|
|
||||||
}
|
|
||||||
|
|
||||||
var dashedHostName: String {
|
|
||||||
["secretive", Host.current().localizedName, "local"]
|
|
||||||
.compactMap { $0 }
|
|
||||||
.joined(separator: ".")
|
|
||||||
.replacingOccurrences(of: " ", with: "-")
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyString: String {
|
var keyString: String {
|
||||||
keyWriter.openSSHString(secret: secret, comment: "\(dashedKeyName)@\(dashedHostName)")
|
keyWriter.openSSHString(secret: secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ struct SecretListItemView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.popover(isPresented: showingPopup) {
|
.sheet(isPresented: showingPopup) {
|
||||||
if let modifiable = store as? AnySecretStoreModifiable {
|
if let modifiable = store as? AnySecretStoreModifiable {
|
||||||
if isDeleting {
|
if isDeleting {
|
||||||
DeleteSecretView(store: modifiable, secret: secret) { deleted in
|
DeleteSecretView(store: modifiable, secret: secret) { deleted in
|
||||||
@ -56,7 +56,7 @@ struct SecretListItemView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if isRenaming {
|
} else if isRenaming {
|
||||||
RenameSecretView(store: modifiable, secret: secret) { renamed in
|
EditSecretView(store: modifiable, secret: secret) { renamed in
|
||||||
isRenaming = false
|
isRenaming = false
|
||||||
if renamed {
|
if renamed {
|
||||||
renamedSecret(secret)
|
renamedSecret(secret)
|
||||||
|
Loading…
Reference in New Issue
Block a user