mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-06-19 11:28:58 +02:00
WIP
This commit is contained in:
@@ -365,6 +365,16 @@
|
||||
},
|
||||
"shouldTranslate" : false
|
||||
},
|
||||
"%@ - %@" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "%1$@ - %2$@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"about_build_log_button" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -19967,6 +19977,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Review" : {
|
||||
|
||||
},
|
||||
"Review All" : {
|
||||
|
||||
},
|
||||
"secret_detail_certificate_path_label" : {
|
||||
"extractionState" : "manual",
|
||||
|
||||
@@ -35,13 +35,15 @@ import XPCWrappers
|
||||
self.osVersion = osVersion
|
||||
self.currentVersion = currentVersion
|
||||
Task {
|
||||
if checkOnLaunch {
|
||||
try await checkForUpdates()
|
||||
}
|
||||
while !Task.isCancelled {
|
||||
try? await Task.sleep(for: .seconds(Int(checkFrequency)))
|
||||
try await checkForUpdates()
|
||||
}
|
||||
do {
|
||||
if checkOnLaunch {
|
||||
try await checkForUpdates()
|
||||
}
|
||||
while !Task.isCancelled {
|
||||
try? await Task.sleep(for: .seconds(Int(checkFrequency)))
|
||||
try await checkForUpdates()
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,8 @@ import SSHProtocolKit
|
||||
public final class Agent: Sendable {
|
||||
|
||||
private let storeList: SecretStoreList
|
||||
<<<<<<< HEAD
|
||||
private let authenticationHandler: AuthenticationHandler
|
||||
=======
|
||||
private let certificateStore: CertificateStore
|
||||
>>>>>>> main
|
||||
private let witness: SigningWitness?
|
||||
private let publicKeyWriter = OpenSSHPublicKeyWriter()
|
||||
private let signatureWriter = OpenSSHSignatureWriter()
|
||||
@@ -24,17 +21,16 @@ public final class Agent: Sendable {
|
||||
/// - Parameters:
|
||||
/// - storeList: The `SecretStoreList` to make available.
|
||||
/// - witness: A witness to notify of requests.
|
||||
<<<<<<< HEAD
|
||||
public init(storeList: SecretStoreList, authenticationHandler: AuthenticationHandler, witness: SigningWitness? = nil) {
|
||||
logger.debug("Agent is running")
|
||||
self.storeList = storeList
|
||||
self.authenticationHandler = authenticationHandler
|
||||
=======
|
||||
public init(storeList: SecretStoreList, certificateStore: CertificateStore, witness: SigningWitness? = nil) {
|
||||
public init(
|
||||
storeList: SecretStoreList,
|
||||
certificateStore: CertificateStore,
|
||||
authenticationHandler: AuthenticationHandler,
|
||||
witness: SigningWitness? = nil
|
||||
) {
|
||||
logger.debug("Agent is running")
|
||||
self.storeList = storeList
|
||||
self.certificateStore = certificateStore
|
||||
>>>>>>> main
|
||||
self.authenticationHandler = authenticationHandler
|
||||
self.witness = witness
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ public actor AuthenticationHandler {
|
||||
}
|
||||
|
||||
public func waitForAuthentication(for request: SignatureRequest) async throws -> any AuthenticationContextProtocol {
|
||||
logger.log("Entering waitForAuthentication for \(request.id)")
|
||||
if let existing = existingAuthenticationContext(for: request) {
|
||||
logger.log("Short circuiting wait, existing valid context already exists.")
|
||||
return existing
|
||||
|
||||
@@ -94,7 +94,7 @@ extension SocketController {
|
||||
guard !data.isEmpty else {
|
||||
logger.debug("Socket controller received empty data, ending continuation.")
|
||||
messagesContinuation.finish()
|
||||
try fileHandle.close()
|
||||
try? fileHandle.close()
|
||||
return
|
||||
}
|
||||
messagesContinuation.yield(data)
|
||||
@@ -126,8 +126,8 @@ private extension SocketPort {
|
||||
convenience init(path: String) {
|
||||
var addr = sockaddr_un()
|
||||
|
||||
let length = unsafe withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in
|
||||
unsafe path.withCString { cstring in
|
||||
let length = withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in
|
||||
path.withCString { cstring in
|
||||
let len = unsafe strlen(cstring)
|
||||
unsafe strncpy(pointer, cstring, len)
|
||||
return len
|
||||
|
||||
@@ -8,6 +8,7 @@ import Brief
|
||||
import Observation
|
||||
import Common
|
||||
import SwiftUI
|
||||
import CertificateKit
|
||||
|
||||
@main
|
||||
struct SecretAgent: App {
|
||||
@@ -21,10 +22,12 @@ struct SecretAgent: App {
|
||||
list.add(store: SmartCard.Store())
|
||||
return list
|
||||
}()
|
||||
@MainActor private let certificateStore: CertificateStore = CertificateStore()
|
||||
|
||||
private let updater = Updater(checkOnLaunch: true)
|
||||
private let notifier = Notifier()
|
||||
private let authenticationHandler = AuthenticationHandler()
|
||||
private let publicKeyFileStoreController = PublicKeyFileStoreController(directory: URL.publicKeyDirectory)
|
||||
private let publicKeyFileStoreController = PublicKeyFileStoreController(publicKeysURL: URL.publicKeyDirectory, certificatesURL: URL.certificatesDirectory)
|
||||
|
||||
@State var pending: ([[SignatureRequest]], (Set<SignatureRequest>) async throws -> Void)?
|
||||
@Environment(\.openWindow) var openWindow
|
||||
@@ -42,18 +45,23 @@ struct SecretAgent: App {
|
||||
}
|
||||
.task {
|
||||
let socketController = SocketController(path: URL.socketPath)
|
||||
let agent = Agent(storeList: storeList, authenticationHandler: authenticationHandler, witness: notifier)
|
||||
let agent = Agent(
|
||||
storeList: storeList,
|
||||
certificateStore: certificateStore,
|
||||
authenticationHandler: authenticationHandler,
|
||||
witness: notifier
|
||||
)
|
||||
for await session in socketController.sessions {
|
||||
Task {
|
||||
let inputParser = try await XPCAgentInputParser()
|
||||
do {
|
||||
let inputParser = try await XPCAgentInputParser()
|
||||
for await message in session.messages {
|
||||
let request = try await inputParser.parse(data: message)
|
||||
let agentResponse = await agent.handle(request: request, provenance: session.provenance)
|
||||
try session.write(agentResponse)
|
||||
}
|
||||
} catch {
|
||||
try session.close()
|
||||
try? session.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,10 +86,19 @@ struct SecretAgent: App {
|
||||
// }
|
||||
// }
|
||||
.task {
|
||||
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
||||
for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) {
|
||||
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
||||
}
|
||||
}
|
||||
.task {
|
||||
let certsMigrator = CertificateMigrator(homeDirectory: URL.homeDirectory, certificateStore: certificateStore)
|
||||
try? certsMigrator.migrate()
|
||||
try? publicKeyFileStoreController.generateCertificates(for: certificateStore.certificates, clear: true)
|
||||
for await _ in NotificationCenter.default.notifications(named: .certificateStoreReloaded) {
|
||||
try? publicKeyFileStoreController.generateCertificates(for: certificateStore.certificates, clear: true)
|
||||
}
|
||||
}
|
||||
.task {
|
||||
await authenticationHandler.setBatchAuthHandler { @MainActor pending, authorize in
|
||||
self.pending = (pending, authorize)
|
||||
@@ -90,7 +107,6 @@ struct SecretAgent: App {
|
||||
|
||||
}
|
||||
.task {
|
||||
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
||||
notifier.prompt()
|
||||
_ = withObservationTracking {
|
||||
updater.update
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import Cocoa
|
||||
import OSLog
|
||||
import SecretKit
|
||||
import SecureEnclaveSecretKit
|
||||
import SmartCardSecretKit
|
||||
import SecretAgentKit
|
||||
import Brief
|
||||
import Observation
|
||||
import SSHProtocolKit
|
||||
import CertificateKit
|
||||
import Common
|
||||
import SwiftUI
|
||||
|
||||
extension EnvironmentValues {
|
||||
|
||||
@MainActor fileprivate static let _certificateStore: CertificateStore = CertificateStore()
|
||||
|
||||
@MainActor var certificateStore: CertificateStore {
|
||||
EnvironmentValues._certificateStore
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@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())
|
||||
let certsMigrator = CertificateMigrator(homeDirectory: URL.homeDirectory, certificateStore: EnvironmentValues._certificateStore)
|
||||
try? certsMigrator.migrate()
|
||||
return list
|
||||
}()
|
||||
private let updater = Updater(checkOnLaunch: true)
|
||||
private let notifier = Notifier()
|
||||
private let publicKeyFileStoreController = PublicKeyFileStoreController(publicKeysURL: URL.publicKeyDirectory, certificatesURL: URL.certificatesDirectory)
|
||||
@MainActor private lazy var agent: Agent = {
|
||||
Agent(storeList: storeList, certificateStore: EnvironmentValues._certificateStore, witness: notifier)
|
||||
}()
|
||||
private lazy var socketController: SocketController = {
|
||||
let path = URL.socketPath 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 {
|
||||
let inputParser = try await XPCAgentInputParser()
|
||||
do {
|
||||
for await message in session.messages {
|
||||
let request = try await inputParser.parse(data: message)
|
||||
let agentResponse = await agent.handle(request: request, provenance: session.provenance)
|
||||
try session.write(agentResponse)
|
||||
}
|
||||
} catch {
|
||||
try session.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Task {
|
||||
for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) {
|
||||
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
||||
}
|
||||
}
|
||||
Task {
|
||||
for await _ in NotificationCenter.default.notifications(named: .certificateStoreReloaded) {
|
||||
try? publicKeyFileStoreController.generateCertificates(for: EnvironmentValues._certificateStore.certificates, clear: true)
|
||||
}
|
||||
}
|
||||
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
||||
try? publicKeyFileStoreController.generateCertificates(for: EnvironmentValues._certificateStore.certificates, clear: true)
|
||||
notifier.prompt()
|
||||
_ = withObservationTracking {
|
||||
updater.update
|
||||
} onChange: { [updater, notifier] in
|
||||
Task {
|
||||
guard !updater.currentVersion.isTestBuild else { return }
|
||||
await notifier.notify(update: updater.update!) { release in
|
||||
await updater.ignore(release: release)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ struct BatchedRequestsView: View {
|
||||
Spacer()
|
||||
Button("Review") {
|
||||
Task {
|
||||
try await review([pending.element])
|
||||
try? await review([pending.element])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ struct BatchedRequestsView: View {
|
||||
Spacer()
|
||||
Button("Review All") {
|
||||
Task {
|
||||
try await review(Set(group.element))
|
||||
try? await review(Set(group.element))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ struct Secretive: App {
|
||||
guard !agentLaunchController.developmentBuild else { return }
|
||||
if justUpdatedChecker.justUpdatedBuild || !agentLaunchController.running {
|
||||
// Relaunch the agent, since it'll be running from earlier update still
|
||||
try await agentLaunchController.forceLaunch()
|
||||
try? await agentLaunchController.forceLaunch()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import SecretKit
|
||||
import LocalAuthentication
|
||||
|
||||
enum Preview {}
|
||||
|
||||
@@ -38,17 +39,10 @@ extension Preview {
|
||||
self.init(secrets: new)
|
||||
}
|
||||
|
||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, context: AuthenticationContextProtocol?) throws -> Data {
|
||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, context: LAContext?) async throws -> Data {
|
||||
return data
|
||||
}
|
||||
|
||||
func existingAuthenticationContextProtocol(secret: Preview.Secret) -> AuthenticationContextProtocol? {
|
||||
nil
|
||||
}
|
||||
|
||||
func persistAuthentication(secret: Preview.Secret, forDuration duration: TimeInterval) throws {
|
||||
}
|
||||
|
||||
func reloadSecrets() {
|
||||
}
|
||||
|
||||
@@ -82,16 +76,10 @@ extension Preview {
|
||||
self.init(secrets: new)
|
||||
}
|
||||
|
||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, context: AuthenticationContextProtocol?) throws -> Data {
|
||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, context: LAContext?) async throws -> Data {
|
||||
return data
|
||||
}
|
||||
|
||||
func existingAuthenticationContextProtocol(secret: Preview.Secret) -> AuthenticationContextProtocol? {
|
||||
nil
|
||||
}
|
||||
|
||||
func persistAuthentication(secret: Preview.Secret, forDuration duration: TimeInterval) throws {
|
||||
}
|
||||
|
||||
func reloadSecrets() {
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ struct AgentNotRunningView: View {
|
||||
guard !loading else { return }
|
||||
loading = true
|
||||
Task {
|
||||
try await agentLaunchController.forceLaunch()
|
||||
try? await agentLaunchController.forceLaunch()
|
||||
loading = false
|
||||
|
||||
if !agentLaunchController.running {
|
||||
|
||||
@@ -163,10 +163,9 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
||||
} else {
|
||||
if #available(macOS 26.0, *) {
|
||||
content
|
||||
// Very thin opacity lets user hover anywhere over the view, glassEffect doesn't allow.
|
||||
.background(.white.opacity(0.01), in: RoundedRectangle(cornerRadius: 15))
|
||||
.glassEffect(.regular.tint(backgroundColor(interactionState: interactionState)), in: RoundedRectangle(cornerRadius: 15))
|
||||
.mask(RoundedRectangle(cornerRadius: 15))
|
||||
.contentShape(RoundedRectangle(cornerRadius: 15))
|
||||
.shadow(color: .black.opacity(0.1), radius: 5)
|
||||
} else {
|
||||
content
|
||||
@@ -182,7 +181,7 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
||||
let base = colorScheme == .dark ? Color(white: 0.2) : Color(white: 1)
|
||||
switch interactionState {
|
||||
case .normal:
|
||||
return base
|
||||
return base.mix(with: .accentColor, by: 0)
|
||||
case .hovering:
|
||||
return base.mix(with: .accentColor, by: colorScheme == .dark ? 0.2 : 0.1)
|
||||
case .clicking, .dragging:
|
||||
|
||||
Reference in New Issue
Block a user