Compare commits

...

10 Commits

Author SHA1 Message Date
Max Goedjen
f6f5e98302 POC 2025-09-01 20:28:47 -07:00
Max Goedjen
c352ed4cc9 Merge branch 'newsetup' into menubar 2025-09-01 20:19:05 -07:00
Max Goedjen
ea71993801 WIP 2025-09-01 20:07:58 -07:00
Max Goedjen
6dc93806a8 Enable GitHub private security issue reporting and update policies (#653)
* Revise security vulnerability reporting process

Updated security reporting instructions in README.md.

* Change vulnerability reporting email to GitHub feature

Updated the vulnerability reporting method to use GitHub's private reporting feature.
2025-09-02 01:46:06 +00:00
Max Goedjen
99a6d48e53 Specify private key usage explicitly (#652) 2025-09-01 02:41:47 +00:00
Max Goedjen
935ac32ea2 Factor out comment writer. (#651) 2025-08-31 23:07:35 +00:00
Max Goedjen
a0a632f245 Save nil if empty string. (#650) 2025-08-31 23:03:59 +00:00
Max Goedjen
51fed9e593 Fix potential timing bug (#649) 2025-08-31 14:22:08 -07:00
Max Goedjen
f652d1d961 Return name as identities comment. (#647) 2025-08-31 20:50:16 +00:00
Max Goedjen
8aacd428b1 Fix deleting key attribution (#648) 2025-08-30 22:41:15 +00:00
14 changed files with 124 additions and 526 deletions

View File

@@ -61,4 +61,4 @@ Because secrets in the Secure Enclave are not exportable, they are not able to b
## Security
If you discover any vulnerabilities in this project, please notify [max.goedjen@gmail.com](mailto:max.goedjen@gmail.com) with the subject containing "SECRETIVE SECURITY."
Secretive's security policy is detailed in [SECURITY.md](SECURITY.md). To report security issues, please use [GitHub's private reporting feature.](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability)

View File

@@ -24,4 +24,4 @@ The latest version on the [Releases page](https://github.com/maxgoedjen/secretiv
## Reporting a Vulnerability
If you discover any vulnerabilities in this project, please notify max.goedjen@gmail.com with the subject containing "SECRETIVE SECURITY."
To report security issues, please use [GitHub's private reporting feature.](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability)

View File

@@ -4270,6 +4270,7 @@
}
},
"Setup" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -4607,154 +4608,6 @@
}
}
},
"setup_ssh_add_for_me_button" : {
"extractionState" : "manual",
"localizations" : {
"ca" : {
"stringUnit" : {
"state" : "translated",
"value" : "Afegeix-ho per mi"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Für Mich Einfügen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Add it For Me"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ajoutez-le pour moi"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aggiungila per me"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "自動で追加する"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "나를 위해 추가해주세요"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dodaj za mnie"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Adicionar para mim"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Добавить для меня"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "为我添加"
}
}
}
},
"setup_ssh_add_to_config_button" : {
"extractionState" : "manual",
"localizations" : {
"ca" : {
"stringUnit" : {
"state" : "translated",
"value" : "Afegeix a %1$(configPath)@"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "In %1$(configPath)@ einfügen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Add to %1$(configPath)@"
}
},
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Add to %1$(configPath)@"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ajouter à %1$(configPath)@"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aggiungi a %1$(configPath)@"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "%1$(configPath)@に追加"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "%1$(configPath)@에 추가"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dodaj do %1$(configPath)@"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Adicionar para %1$(configPath)@"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Добавить к %1$(configPath)@"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "添加到 %1$(configPath)@"
}
}
}
},
"setup_ssh_added_manually_button" : {
"extractionState" : "manual",
"localizations" : {
@@ -4826,290 +4679,6 @@
}
}
},
"setup_ssh_description" : {
"extractionState" : "manual",
"localizations" : {
"ca" : {
"stringUnit" : {
"state" : "translated",
"value" : "Afegeix aquesta línia a la teua configuració del shell per que SSH es comunique amb Secretive quan vulga autenticar. Secretive pot fer aquest procediment automàticament, o pots copiar i pegar açò al teu fitxer de configuració."
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Füge diese Zeile in deine Shell-Konfiguration ein, damit SSH zur Authentifizierung mit dem Secret Agent kommuniziert. Secretive kann dies automatisch tun, oder du kopierst diese Zeile in deine Konfigurationsdatei."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Add this line to your shell config telling SSH to talk to Secret Agent when it wants to authenticate. Secretive can either do this for you automatically, or you can copy and paste this into your config file."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ajoutez cette ligne à votre configuration shell pour indiquer à SSH de communiquer à Secret Agent quand il veut s'authentifier. Secretive peut le faire automatiquement pour vous, ou vous pouvez copier et coller cette ligne dans votre fichier de configuration."
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aggiungi questa riga alla configurazione del Terminale per dire a SSH di parlare con Secret Agent quando vuole autenticarsi. Secretive può farlo automaticamente per te, oppure puoi copiare e incollare questa riga nel file di configurazione."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "以下の行をシェルの設定に追加してSSHが認証の際にSecretAgentを利用できるようにしてください。Secretiveが自動で追加するか、手動でコピーして設定に追加することもできます。"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "SSH가 인증을 원할 때 Secret Agent와 통신하도록 지시하는 이 줄을 쉘 구성에 추가하세요. Secretive는 이 작업을 자동으로 수행하거나 사용자가 이를 복사하여 구성 파일에 붙여넣을 수 있습니다."
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dodaj tą linijkę to pliku konfiguracyjnego SSH, aby nawiązać połączenie z Secret Agent kiedy potrzebna jest autoryzacja. Secretive może ustawić to automatycznie lub możesz to zrobić samodzielnie kopiując to do pliku konfiguracyjnego."
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Adicione esta linha nas configurações do seu shell para dizer ao SSH para falar com o Secret Agent quando ele necessitar de autenticação. Secretive pode fazer isto para você automaticamente ou você pode copiar e colar isso no seu arquivo de configuração."
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Добавьте эту строчку к вашему конфигу shell, так SSH будет использовать SecretAgent в процессе аутентификации. Secretive может сделать это за Вас, либо Вы можете это скопировать сами."
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "将以下文本添加到您的SSH 配置中以使用Secret Agent. Secretive 无法自动帮您完成该过程,或者您可以选择拷贝并粘贴该文本到您的配置文件中"
}
}
}
},
"setup_ssh_title" : {
"extractionState" : "manual",
"localizations" : {
"ca" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configura el teu agent SSH"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Konfiguriere deinen SSH Agent"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configure your SSH Agent"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configurer votre Agent SSH"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configura il tuo Agente SSH"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "SSHエージェントを設定"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "SSH Agent 설정"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Skonfiguruj twojego klienta SSH"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configurar seu agente SSH"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Настройте Ваш SSH Agent"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "设置您的SSH 代理"
}
}
}
},
"setup_step_complete_symbol" : {
"extractionState" : "manual",
"localizations" : {
"ca" : {
"stringUnit" : {
"state" : "translated",
"value" : "✓"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "✓"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "✓"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "✓"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "✓"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "✓"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "✓"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "✓"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "✓"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "✓"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "✓"
}
}
}
},
"setup_third_party_faq_link" : {
"extractionState" : "manual",
"localizations" : {
"ca" : {
"stringUnit" : {
"state" : "translated",
"value" : "Si tractes de configurar una aplicació de tercers, comprova el FAQ."
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Schaue dir die FAQs an, um eine Drittanbieter-App einzurichten."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "If you're trying to set up a third party app, check out the FAQ."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Si vous essayez de configurer une application tierce, consultez la FAQ."
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Se stai cercando di impostare unapp di terze parti, dai un'occhiata alla FAQ."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "その他のアプリから使う場合はよくある質問をご覧ください。"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "타사 앱을 설정하려는 경우 FAQ를 확인하세요."
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Jeżeli próbujesz ustawić aplikacje stron trzecich, sprawdź FAQ."
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Se você estiver tentando configurar um aplicativo de terceiros, verifique o FAQ."
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Если Вы пытаетесь настроить сторонее приложение, ознакомьтесь с FAQ."
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "如果您想设置第三方APP请阅读 FAQ。"
}
}
}
},
"setup_updates_description" : {
"extractionState" : "manual",
"localizations" : {
@@ -5631,6 +5200,9 @@
}
}
}
},
"Test" : {
},
"unnamed_secret" : {
"extractionState" : "manual",

View File

@@ -89,9 +89,8 @@ extension Agent {
for secret in secrets {
let keyBlob = publicKeyWriter.data(secret: secret)
let curveData = publicKeyWriter.openSSHIdentifier(for: secret.keyType)
keyData.append(keyBlob.lengthAndData)
keyData.append(curveData.lengthAndData)
keyData.append(publicKeyWriter.comment(secret: secret).lengthAndData)
count += 1
if let (certificateData, name) = try? await certificateHandler.keyBlobAndName(for: secret) {

View File

@@ -78,7 +78,6 @@ extension SocketController {
provenance = SigningRequestTracer().provenance(from: fileHandle)
(messages, messagesContinuation) = AsyncStream.makeStream()
Task { [messagesContinuation, logger] in
await fileHandle.waitForDataInBackgroundAndNotifyOnMainActor()
for await _ in NotificationCenter.default.notifications(named: .NSFileHandleDataAvailable, object: fileHandle) {
let data = fileHandle.availableData
guard !data.isEmpty else {
@@ -91,6 +90,9 @@ extension SocketController {
logger.debug("Socket controller yielded data.")
}
}
Task {
await fileHandle.waitForDataInBackgroundAndNotifyOnMainActor()
}
}
/// Writes new data to the socket.

View File

@@ -31,18 +31,7 @@ public struct OpenSSHPublicKeyWriter: Sendable {
/// Generates an OpenSSH string representation of the secret.
/// - Returns: OpenSSH string representation of the secret.
public func openSSHString<SecretType: Secret>(secret: SecretType) -> String {
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 [openSSHIdentifier(for: secret.keyType), data(secret: secret).base64EncodedString(), resolvedComment]
return [openSSHIdentifier(for: secret.keyType), data(secret: secret).base64EncodedString(), comment(secret: secret)]
.compactMap { $0 }
.joined(separator: " ")
}
@@ -65,6 +54,19 @@ public struct OpenSSHPublicKeyWriter: Sendable {
.joined(separator: ":")
}
public func comment<SecretType: Secret>(secret: SecretType) -> String {
if let comment = secret.publicKeyAttribution {
return comment
} else {
let dashedKeyName = secret.name.replacingOccurrences(of: " ", with: "-")
let dashedHostName = ["secretive", Host.current().localizedName, "local"]
.compactMap { $0 }
.joined(separator: ".")
.replacingOccurrences(of: " ", with: "-")
return "\(dashedKeyName)@\(dashedHostName)"
}
}
}
extension OpenSSHPublicKeyWriter {

View File

@@ -112,7 +112,7 @@ extension SecureEnclave {
var accessError: SecurityError?
let flags: SecAccessControlCreateFlags = switch attributes.authentication {
case .notRequired:
[]
[.privateKeyUsage]
case .presenceRequired:
[.userPresence, .privateKeyUsage]
case .biometryCurrent:

View File

@@ -6,65 +6,85 @@ import SmartCardSecretKit
import SecretAgentKit
import Brief
import Observation
import SwiftUI
extension EnvironmentValues {
private static let _agentStatusChecker = AgentStatusChecker()
@Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker
}
@main
class AppDelegate: NSObject, NSApplicationDelegate {
struct SecretAgent: App {
@MainActor private let storeList: SecretStoreList = {
let list = SecretStoreList()
let cryptoKit = SecureEnclave.Store()
let migrator = SecureEnclave.CryptoKitMigrator()
try? migrator.migrate(to: cryptoKit)
list.add(store: cryptoKit)
list.add(store: SmartCard.Store())
return list
}()
private let updater = Updater(checkOnLaunch: true)
private let notifier = Notifier()
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.homeDirectory)
private lazy var agent: Agent = {
Agent(storeList: storeList, witness: notifier)
}()
private lazy var socketController: SocketController = {
let path = (NSHomeDirectory() as NSString).appendingPathComponent("socket.ssh") as String
return SocketController(path: path)
}()
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "AppDelegate")
func applicationDidFinishLaunching(_ aNotification: Notification) {
logger.debug("SecretAgent finished launching")
Task {
for await session in socketController.sessions {
Task {
do {
for await message in session.messages {
let agentResponse = try await agent.handle(data: message, provenance: session.provenance)
try await session.write(agentResponse)
}
} catch {
try session.close()
}
}
}
}
Task {
for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) {
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
}
}
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
notifier.prompt()
_ = withObservationTracking {
updater.update
} onChange: { [updater, notifier] in
Task {
guard !updater.testBuild else { return }
await notifier.notify(update: updater.update!) { release in
await updater.ignore(release: release)
}
}
@SceneBuilder var body: some Scene {
MenuBarExtra("Test", systemImage: "lock") {
AgentStatusView()
.fixedSize()
}
.menuBarExtraStyle(.window)
}
}
//@main
//class AppDelegate: NSObject, NSApplicationDelegate {
//
// @MainActor private let storeList: SecretStoreList = {
// let list = SecretStoreList()
// let cryptoKit = SecureEnclave.Store()
// let migrator = SecureEnclave.CryptoKitMigrator()
// try? migrator.migrate(to: cryptoKit)
// list.add(store: cryptoKit)
// list.add(store: SmartCard.Store())
// return list
// }()
// private let updater = Updater(checkOnLaunch: true)
// private let notifier = Notifier()
// private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.homeDirectory)
// private lazy var agent: Agent = {
// Agent(storeList: storeList, witness: notifier)
// }()
// private lazy var socketController: SocketController = {
// let path = (NSHomeDirectory() as NSString).appendingPathComponent("socket.ssh") as String
// return SocketController(path: path)
// }()
// private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "AppDelegate")
//
// func applicationDidFinishLaunching(_ aNotification: Notification) {
// logger.debug("SecretAgent finished launching")
// Task {
// for await session in socketController.sessions {
// Task {
// do {
// for await message in session.messages {
// let agentResponse = try await agent.handle(data: message, provenance: session.provenance)
// try await session.write(agentResponse)
// }
// } catch {
// try session.close()
// }
// }
// }
// }
// Task {
// for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) {
// try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
// }
// }
// try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
// notifier.prompt()
// _ = withObservationTracking {
// updater.update
// } onChange: { [updater, notifier] in
// Task {
// guard !updater.testBuild else { return }
// await notifier.notify(update: updater.update!) { release in
// await updater.ignore(release: release)
// }
// }
// }
// }
//
//}
//

View File

@@ -33,6 +33,11 @@
50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; };
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; };
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.swift */; };
5064ADD32E669B1100B1382C /* AgentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */; };
5064ADD42E669B2300B1382C /* AgentStatusChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */; };
5064ADD52E669B3000B1382C /* BundleIDs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50033AC227813F1700253856 /* BundleIDs.swift */; };
5064ADD62E669B5F00B1382C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; };
5064ADD72E669B7E00B1382C /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; };
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */; };
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C12516F303004B5A36 /* SetupView.swift */; };
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C72516FE6E004B5A36 /* CopyableView.swift */; };
@@ -476,8 +481,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5064ADD32E669B1100B1382C /* AgentStatusView.swift in Sources */,
5064ADD72E669B7E00B1382C /* ConfigurationItemView.swift in Sources */,
50020BB024064869003D4025 /* AppDelegate.swift in Sources */,
5064ADD52E669B3000B1382C /* BundleIDs.swift in Sources */,
5018F54F24064786002EB505 /* Notifier.swift in Sources */,
5064ADD62E669B5F00B1382C /* LaunchAgentController.swift in Sources */,
5064ADD42E669B2300B1382C /* AgentStatusChecker.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -80,11 +80,6 @@ struct Secretive: App {
NSWorkspace.shared.open(Constants.helpURL)
}
}
CommandGroup(after: .help) {
Button("Setup") {
showingSetup = true
}
}
SidebarCommands()
}
}

View File

@@ -127,7 +127,7 @@ struct AgentNotRunningView: View {
}
}
}
.primaryButton()
// .primaryButton()
} else {
Text(.agentDetailsCouldNotStartError)
.bold()
@@ -144,11 +144,11 @@ struct AgentNotRunningView: View {
}
#Preview {
AgentStatusView()
.environment(\.agentStatusChecker, PreviewAgentStatusChecker(running: false))
}
#Preview {
AgentStatusView()
.environment(\.agentStatusChecker, PreviewAgentStatusChecker(running: true, process: .current))
}
//#Preview {
// AgentStatusView()
// .environment(\.agentStatusChecker, PreviewAgentStatusChecker(running: false))
//}
//#Preview {
// AgentStatusView()
// .environment(\.agentStatusChecker, PreviewAgentStatusChecker(running: true, process: .current))
//}

View File

@@ -32,7 +32,7 @@ struct ConfigurationItemView<Content: View>: View {
Spacer()
switch action {
case .copy(let string):
Button(.copyButton, systemImage: "document.on.document") {
Button(.copyableClickToCopyButton, systemImage: "document.on.document") {
NSPasteboard.general.declareTypes([.string], owner: nil)
NSPasteboard.general.setString(string, forType: .string)
}

View File

@@ -76,10 +76,10 @@ struct CopyableView: View {
switch interactionState {
case .hovering:
Image(systemName: "document.on.document")
.accessibilityLabel(String(localized: "copyable_click_to_copy_button"))
.accessibilityLabel(String(localized: .copyableClickToCopyButton))
case .clicking:
Image(systemName: "checkmark.circle.fill")
.accessibilityLabel(String(localized: "copyable_copied"))
.accessibilityLabel(String(localized: .copyableCopied))
case .normal, .dragging:
EmptyView()
}
@@ -168,9 +168,9 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
struct CopyableView_Previews: PreviewProvider {
static var previews: some View {
Group {
CopyableView(title: "secret_detail_sha256_fingerprint_label", image: Image(systemName: "figure.wave"), text: "Hello world.")
CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "figure.wave"), text: "Hello world.")
.padding()
CopyableView(title: "secret_detail_sha256_fingerprint_label", image: Image(systemName: "figure.wave"), text: "Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. ")
CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "figure.wave"), text: "Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. ")
.padding()
}
}

View File

@@ -54,9 +54,7 @@ struct EditSecretView<StoreType: SecretStoreModifiable>: View {
func rename() {
var attributes = secret.attributes
if !publicKeyAttribution.isEmpty {
attributes.publicKeyAttribution = publicKeyAttribution
}
attributes.publicKeyAttribution = publicKeyAttribution.isEmpty ? nil : publicKeyAttribution
Task {
do {
try await store.update(secret: secret, name: name, attributes: attributes)