mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-09-19 14:48:01 +02:00
WIP
This commit is contained in:
@@ -5372,6 +5372,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"auth_context_request_multiple" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "sign multiple requests from “%1$(appName)@” using secret “%2$(secretName)@”"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"auth_context_request_signature_description" : {
|
"auth_context_request_signature_description" : {
|
||||||
"comment" : "When the user performs a signature action using a secret, they are shown a prompt to approve the action. This is the description, showing which secret will be used, and where the request is coming from. The first placeholder is the name of the app requesting the operation. The second placeholder is the name of the secret.",
|
"comment" : "When the user performs a signature action using a secret, they are shown a prompt to approve the action. This is the description, showing which secret will be used, and where the request is coming from. The first placeholder is the name of the app requesting the operation. The second placeholder is the name of the secret.",
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
@@ -18843,6 +18854,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Multiple authenticated requests are pending. You can approve them batches, or request they all proceed individually." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"no_secure_storage_description" : {
|
"no_secure_storage_description" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
|
|||||||
+61
-47
@@ -1,57 +1,71 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
struct MultilineInfoView: View {
|
public struct MultilineInfoView<TitleView: View, ItemView: View>: View {
|
||||||
|
|
||||||
struct Item {
|
// public struct Item {
|
||||||
let text: String
|
// public let text: String
|
||||||
let action: (Image, () -> Void)?
|
// public let action: (Image, () -> Void)?
|
||||||
|
//
|
||||||
|
// public init(text: String, action: (Image, () -> Void)?) {
|
||||||
|
// self.text = text
|
||||||
|
// self.action = action
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
var titleView: TitleView
|
||||||
|
var items: [ItemView]
|
||||||
|
|
||||||
|
public init(titleView: () -> TitleView, items: () -> [ItemView]) {
|
||||||
|
self.titleView = titleView()
|
||||||
|
self.items = items()
|
||||||
}
|
}
|
||||||
|
|
||||||
var title: LocalizedStringResource
|
public init(title: LocalizedStringResource, subtitle: LocalizedStringResource, image: Image, items: [String]) where TitleView == HStack<TupleView<(Image, Text, Spacer)>> , ItemView == Text {
|
||||||
var image: Image
|
self.init {
|
||||||
var items: [Item]
|
|
||||||
|
|
||||||
init(title: LocalizedStringResource, image: Image, items: [Item]) {
|
|
||||||
self.title = title
|
|
||||||
self.image = image
|
|
||||||
self.items = items
|
|
||||||
}
|
|
||||||
|
|
||||||
init(title: LocalizedStringResource, image: Image, items: [String]) {
|
|
||||||
self.title = title
|
|
||||||
self.image = image
|
|
||||||
self.items = items.map({ Item(text: $0, action: nil) })
|
|
||||||
}
|
|
||||||
|
|
||||||
@State private var interactionState: InteractionState = .normal
|
|
||||||
@State private var interactionStateIndex: Int?
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
|
||||||
HStack {
|
HStack {
|
||||||
image
|
image
|
||||||
.renderingMode(.template)
|
.renderingMode(.template)
|
||||||
.imageScale(.large)
|
// .imageScale(.large)
|
||||||
.foregroundColor(primaryTextColor)
|
.foregroundColor(primaryTextColor)
|
||||||
Text(title)
|
Text(title)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(primaryTextColor)
|
.foregroundColor(primaryTextColor)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
} items: {
|
||||||
|
[Text("Hello")]
|
||||||
|
}
|
||||||
|
|
||||||
|
// self.init {
|
||||||
|
// } items: {
|
||||||
|
// ForEach(items) { item in
|
||||||
|
// return HStack {
|
||||||
|
// Text(item)
|
||||||
|
// Spacer()
|
||||||
|
// // if let (image, _) = $0.1 {
|
||||||
|
// // image
|
||||||
|
// // .foregroundStyle(.secondary)
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@State private var interactionState: InteractionState = .normal
|
||||||
|
@State private var interactionStateIndex: Int?
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
titleView
|
||||||
.safeAreaPadding(20)
|
.safeAreaPadding(20)
|
||||||
ForEach(Array(items.enumerated()), id: \.offset) { item in
|
ForEach(Array(items.enumerated()), id: \.offset) { item in
|
||||||
Divider()
|
Divider()
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.opacity(item.offset == 0 ? 1 : 0.75)
|
.opacity(item.offset == 0 ? 1 : 0.75)
|
||||||
HStack {
|
items.element
|
||||||
Text(item.element.text)
|
|
||||||
Spacer()
|
|
||||||
if let (image, _) = item.element.action {
|
|
||||||
image
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.safeAreaPadding(20)
|
.safeAreaPadding(20)
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
withAnimation {
|
withAnimation {
|
||||||
@@ -155,16 +169,16 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
//#Preview {
|
||||||
MultilineInfoView(title: "Multiple", image: Image(systemName: "figure.wave"), items: [
|
// MultilineInfoView(title: "Multiple", image: Image(systemName: "figure.wave"), items: [
|
||||||
MultilineInfoView.Item(text: "hello", action: (Image(systemName: "chevron.forward"), {})),
|
// MultilineInfoView.Item(text: "hello", action: (Image(systemName: "chevron.forward"), {})),
|
||||||
MultilineInfoView.Item(text: "World", action: (Image(systemName: "chevron.forward"), {})),
|
// MultilineInfoView.Item(text: "World", action: (Image(systemName: "chevron.forward"), {})),
|
||||||
])
|
// ])
|
||||||
.padding()
|
// .padding()
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
|
//
|
||||||
#Preview {
|
//#Preview {
|
||||||
MultilineInfoView(title: "One", image: Image(systemName: "figure.wave"), items: ["Hello world."])
|
// MultilineInfoView(title: "One", image: Image(systemName: "figure.wave"), items: ["Hello world."])
|
||||||
.padding()
|
// .padding()
|
||||||
}
|
//}
|
||||||
@@ -133,20 +133,6 @@ extension Agent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func signWithRequiredAuthentication(data: Data, store: AnySecretStore, secret: AnySecret, provenance: SigningRequestProvenance) async throws -> Data {
|
func signWithRequiredAuthentication(data: Data, store: AnySecretStore, secret: AnySecret, provenance: SigningRequestProvenance) async throws -> Data {
|
||||||
// let context: any AuthenticationContextProtocol
|
|
||||||
// let offerPersistence: Bool
|
|
||||||
// if let existing = await authenticationHandler.existingAuthenticationContextProtocol(for: SignatureRequest(secret: secret, provenance: provenance)) {
|
|
||||||
// context = existing
|
|
||||||
// offerPersistence = false
|
|
||||||
// logger.debug("Using existing auth context")
|
|
||||||
// } else {
|
|
||||||
// context = authenticationHandler.createAuthenticationContext(for: SignatureRequest(secret: secret, provenance: provenance))
|
|
||||||
// offerPersistence = secret.authenticationRequirement.required
|
|
||||||
// logger.debug("Creating fresh auth context")
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let context = try await authenticationHandler.waitForAuthentication(for: SignatureRequest(secret: secret, provenance: provenance))
|
let context = try await authenticationHandler.waitForAuthentication(for: SignatureRequest(secret: secret, provenance: provenance))
|
||||||
let result = try await store.sign(data: data, with: secret, for: provenance, context: context.laContext)
|
let result = try await store.sign(data: data, with: secret, for: provenance, context: context.laContext)
|
||||||
let signedData = signatureWriter.data(secret: secret, signature: result)
|
let signedData = signatureWriter.data(secret: secret, signature: result)
|
||||||
|
|||||||
@@ -57,20 +57,28 @@ public final class AuthenticationContext: AuthenticationContextProtocol {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public actor AuthenticationHandler {
|
@MainActor public protocol AuthenticationHandlerProtocol: Observable {
|
||||||
|
func setBatchAuthHandler(_ handler: @escaping () async throws -> Void)
|
||||||
|
func waitForAuthentication(for request: SignatureRequest) async throws -> any AuthenticationContextProtocol
|
||||||
|
var batchableRequests: [[SignatureRequest]] { get }
|
||||||
|
func persistAuthentication<SecretType: Secret>(secret: SecretType, forDuration duration: TimeInterval) async throws
|
||||||
|
func requestAuthentication(for requests: Set<SignatureRequest>) async throws
|
||||||
|
}
|
||||||
|
|
||||||
|
@Observable @MainActor public class AuthenticationHandler: AuthenticationHandlerProtocol {
|
||||||
|
|
||||||
private var persistedContexts: [AnySecret: AuthenticationContext] = [:]
|
private var persistedContexts: [AnySecret: AuthenticationContext] = [:]
|
||||||
private var holdingRequests: Set<SignatureRequest> = []
|
private var holdingRequests: Set<SignatureRequest> = []
|
||||||
private var activeTask: Task<Void, any Error>?
|
private var activeTask: Task<Void, any Error>?
|
||||||
|
|
||||||
private var lastBatchAuthPresentation: Set<SignatureRequest>?
|
private var lastBatchAuthPresentation: Set<SignatureRequest>?
|
||||||
private var presentBatchAuth: (([[SignatureRequest]], @escaping @Sendable (Set<SignatureRequest>) async throws -> Void) async throws -> Void)?
|
private var presentBatchAuth: (() async throws -> Void)?
|
||||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "Agent")
|
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "Agent")
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setBatchAuthHandler(_ handler: @escaping (@Sendable ([[SignatureRequest]], @escaping @Sendable (Set<SignatureRequest>) async throws -> Void) async throws -> Void)) {
|
public func setBatchAuthHandler(_ handler: @escaping () async throws -> Void) {
|
||||||
self.presentBatchAuth = handler
|
self.presentBatchAuth = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +100,7 @@ public actor AuthenticationHandler {
|
|||||||
activeTask?.cancel()
|
activeTask?.cancel()
|
||||||
lastBatchAuthPresentation = holdingRequests
|
lastBatchAuthPresentation = holdingRequests
|
||||||
logger.log("Requesting batch auth presentation")
|
logger.log("Requesting batch auth presentation")
|
||||||
try await presentBatchAuth?(batchableRequests, persistAuthentication(for:))
|
try await presentBatchAuth?()
|
||||||
logger.log("Requested batch auth presentation")
|
logger.log("Requested batch auth presentation")
|
||||||
}
|
}
|
||||||
if let preauthorized = existingAuthenticationContext(for: request) {
|
if let preauthorized = existingAuthenticationContext(for: request) {
|
||||||
@@ -110,11 +118,10 @@ public actor AuthenticationHandler {
|
|||||||
|
|
||||||
activeTask = Task {
|
activeTask = Task {
|
||||||
logger.log("Beginning individual auth prompt")
|
logger.log("Beginning individual auth prompt")
|
||||||
try await Task.sleep(for: .seconds(1000))
|
_ = try? await laContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: laContext.localizedReason)
|
||||||
// _ = try? await laContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: laContext.localizedReason)
|
|
||||||
logger.log("Ended individual auth prompt")
|
logger.log("Ended individual auth prompt")
|
||||||
}
|
}
|
||||||
_ = try await activeTask?.value
|
_ = try? await activeTask?.value
|
||||||
// TODO: Check something beyond cancellation? id?
|
// TODO: Check something beyond cancellation? id?
|
||||||
// Is this okay? Do we always assume that a cancelled task will be the proceeded on?
|
// Is this okay? Do we always assume that a cancelled task will be the proceeded on?
|
||||||
if activeTask?.isCancelled ?? false {
|
if activeTask?.isCancelled ?? false {
|
||||||
@@ -131,7 +138,7 @@ public actor AuthenticationHandler {
|
|||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
private var batchableRequests: [[SignatureRequest]] {
|
public var batchableRequests: [[SignatureRequest]] {
|
||||||
holdingRequests.reduce(into: [:]) { partialResult, next in
|
holdingRequests.reduce(into: [:]) { partialResult, next in
|
||||||
partialResult[next.batchID, default: []].append(next)
|
partialResult[next.batchID, default: []].append(next)
|
||||||
}
|
}
|
||||||
@@ -167,14 +174,19 @@ public actor AuthenticationHandler {
|
|||||||
persistedContexts[AnySecret(secret)] = context
|
persistedContexts[AnySecret(secret)] = context
|
||||||
}
|
}
|
||||||
|
|
||||||
private func persistAuthentication(for requests: Set<SignatureRequest>) async throws {
|
public func requestAuthentication(for requests: Set<SignatureRequest>) async throws {
|
||||||
activeTask?.cancel()
|
activeTask?.cancel()
|
||||||
guard let first = requests.first else { return }
|
guard let first = requests.first else { return }
|
||||||
let newContext = LAContext()
|
let newContext = LAContext()
|
||||||
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
|
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
|
||||||
|
|
||||||
newContext.localizedReason = String("Multiple")
|
let appNames = Set(requests.map(\.provenance.origin.displayName)).joined(separator: ", ")
|
||||||
// newContext.localizedReason = String(localized: .authContextPersistForDuration(secretName: secret.name, duration: durationString))
|
let secretNames = Set(requests.map(\.secret.name)).joined(separator: ", ")
|
||||||
|
if requests.count > 1 {
|
||||||
|
newContext.localizedReason = String(localized: .authContextRequestMultiple(appName: appNames, secretName: secretNames))
|
||||||
|
} else {
|
||||||
|
newContext.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: appNames, secretName: secretNames))
|
||||||
|
}
|
||||||
let success = try await newContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: newContext.localizedReason)
|
let success = try await newContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: newContext.localizedReason)
|
||||||
guard success else { return }
|
guard success else { return }
|
||||||
let context = AuthenticationContext(secret: first.secret, context: newContext, requestIDs: Set(requests.map(\.id)))
|
let context = AuthenticationContext(secret: first.secret, context: newContext, requestIDs: Set(requests.map(\.id)))
|
||||||
|
|||||||
@@ -44,6 +44,13 @@ extension SigningRequestTracer {
|
|||||||
|
|
||||||
let pathPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(MAXPATHLEN))
|
let pathPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(MAXPATHLEN))
|
||||||
_ = unsafe proc_pidpath(pid, pathPointer, UInt32(MAXPATHLEN))
|
_ = unsafe proc_pidpath(pid, pathPointer, UInt32(MAXPATHLEN))
|
||||||
|
|
||||||
|
// let bufferLength = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, nil, 0)
|
||||||
|
let x = UnsafeMutablePointer<UInt64>.allocate(capacity: Int(MAXPATHLEN))
|
||||||
|
_ = unsafe proc_pidinfo(pid, PROC_PIDLISTFDS, 0, x, Int32(MAXPATHLEN))
|
||||||
|
let buffer = unsafe UnsafeBufferPointer(start: x, count: Int(MAXPATHLEN)/64)
|
||||||
|
unsafe print(buffer)
|
||||||
|
|
||||||
let path = unsafe String(cString: pathPointer)
|
let path = unsafe String(cString: pathPointer)
|
||||||
var secCode: Unmanaged<SecCode>!
|
var secCode: Unmanaged<SecCode>!
|
||||||
let flags: SecCSFlags = [.considerExpiration, .enforceRevocationChecks]
|
let flags: SecCSFlags = [.considerExpiration, .enforceRevocationChecks]
|
||||||
@@ -80,10 +87,6 @@ extension SigningRequestTracer {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// from libproc.h
|
|
||||||
@_silgen_name("proc_pidpath")
|
|
||||||
@discardableResult func proc_pidpath(_ pid: Int32, _ buffer: UnsafeMutableRawPointer!, _ buffersize: UInt32) -> Int32
|
|
||||||
|
|
||||||
//// from SecTask.h
|
//// from SecTask.h
|
||||||
@_silgen_name("SecCodeCreateWithPID")
|
@_silgen_name("SecCodeCreateWithPID")
|
||||||
@discardableResult func SecCodeCreateWithPID(_: Int32, _: SecCSFlags, _: UnsafeMutablePointer<Unmanaged<SecCode>?>!) -> OSStatus
|
@discardableResult func SecCodeCreateWithPID(_: Int32, _: SecCSFlags, _: UnsafeMutablePointer<Unmanaged<SecCode>?>!) -> OSStatus
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ struct SecretAgent: App {
|
|||||||
private let authenticationHandler = AuthenticationHandler()
|
private let authenticationHandler = AuthenticationHandler()
|
||||||
private let publicKeyFileStoreController = PublicKeyFileStoreController(publicKeysURL: URL.publicKeyDirectory, certificatesURL: URL.certificatesDirectory)
|
private let publicKeyFileStoreController = PublicKeyFileStoreController(publicKeysURL: URL.publicKeyDirectory, certificatesURL: URL.certificatesDirectory)
|
||||||
|
|
||||||
@State var pending: ([[SignatureRequest]], (Set<SignatureRequest>) async throws -> Void)?
|
|
||||||
@Environment(\.openWindow) var openWindow
|
@Environment(\.openWindow) var openWindow
|
||||||
|
|
||||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "App")
|
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "App")
|
||||||
@@ -100,8 +99,7 @@ struct SecretAgent: App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.task {
|
.task {
|
||||||
await authenticationHandler.setBatchAuthHandler { @MainActor pending, authorize in
|
authenticationHandler.setBatchAuthHandler { @MainActor in
|
||||||
self.pending = (pending, authorize)
|
|
||||||
openWindow(id: String(describing: BatchedRequestsView.self))
|
openWindow(id: String(describing: BatchedRequestsView.self))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,10 +127,10 @@ struct SecretAgent: App {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var pendingView: some View {
|
var pendingView: some View {
|
||||||
if let (requests, authorize) = pending {
|
if !authenticationHandler.batchableRequests.isEmpty {
|
||||||
BatchedRequestsView(pending: requests, review: authorize)
|
BatchedRequestsView(authenticationHandler: authenticationHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,23 +2,21 @@ import SwiftUI
|
|||||||
import SecretKit
|
import SecretKit
|
||||||
import SecretAgentKit
|
import SecretAgentKit
|
||||||
import SmartCardSecretKit
|
import SmartCardSecretKit
|
||||||
|
import Common
|
||||||
|
|
||||||
struct BatchedRequestsView: View {
|
struct BatchedRequestsView: View {
|
||||||
|
|
||||||
let pending: [[SignatureRequest]]
|
private let authenticationHandler: any AuthenticationHandlerProtocol
|
||||||
let review: (Set<SignatureRequest>) async throws -> Void
|
|
||||||
|
|
||||||
init(pending: [[SignatureRequest]], review: @escaping (Set<SignatureRequest>) async throws -> Void) {
|
init(authenticationHandler: some AuthenticationHandlerProtocol) {
|
||||||
self.pending = pending
|
self.authenticationHandler = authenticationHandler
|
||||||
self.review = review
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
// .padding()
|
|
||||||
Form {
|
Form {
|
||||||
// Text("Multiple authenticated requests are pending. You can approve them batches, or request they all proceed individually.")
|
Text("Multiple authenticated requests are pending. You can approve them batches, or request they all proceed individually.")
|
||||||
ForEach(Array(pending.enumerated()), id: \.offset) { group in
|
ForEach(Array(authenticationHandler.batchableRequests.enumerated()), id: \.offset) { group in
|
||||||
Section {
|
Section {
|
||||||
ForEach(Array(group.element.enumerated()), id: \.offset) { pending in
|
ForEach(Array(group.element.enumerated()), id: \.offset) { pending in
|
||||||
HStack {
|
HStack {
|
||||||
@@ -31,7 +29,7 @@ struct BatchedRequestsView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
Button("Review") {
|
Button("Review") {
|
||||||
Task {
|
Task {
|
||||||
try? await review([pending.element])
|
try? await authenticationHandler.requestAuthentication(for: [pending.element])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,7 +40,7 @@ struct BatchedRequestsView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
Button("Review All") {
|
Button("Review All") {
|
||||||
Task {
|
Task {
|
||||||
try? await review(Set(group.element))
|
try? await authenticationHandler.requestAuthentication(for: Set(group.element))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -56,3 +54,65 @@ struct BatchedRequestsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct TestHandler: AuthenticationHandlerProtocol {
|
||||||
|
|
||||||
|
var batchableRequests: [[SignatureRequest]] = []
|
||||||
|
|
||||||
|
func requestAuthentication(for requests: Set<SignatureRequest>) async throws {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func persistAuthentication<SecretType>(secret: SecretType, forDuration duration: TimeInterval) async throws where SecretType : Secret {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func setBatchAuthHandler(_ handler: @escaping () async throws -> Void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForAuthentication(for request: SignatureRequest) async throws -> any AuthenticationContextProtocol {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//
|
||||||
|
//#Preview {
|
||||||
|
// ScrollView {
|
||||||
|
// MultilineInfoView(title: "GitHub", subtitle: "Ghostty", image: Image(systemName: "lock"), items: [
|
||||||
|
// "
|
||||||
|
// ])
|
||||||
|
//// Section {
|
||||||
|
//// ForEach(0..<2) { _ in
|
||||||
|
//// VStack(alignment: .leading) {
|
||||||
|
//// Text("Ghostty")
|
||||||
|
//// .font(.headline)
|
||||||
|
//// Text("zsh git zsh")
|
||||||
|
//// .font(.footnote)
|
||||||
|
//// Text("4:05 PM")
|
||||||
|
//// }
|
||||||
|
//// }
|
||||||
|
//// } header: {
|
||||||
|
//// Text("GitHub")
|
||||||
|
//// }
|
||||||
|
//// Section {
|
||||||
|
//// ForEach(0..<2) { _ in
|
||||||
|
//// VStack(alignment: .leading) {
|
||||||
|
//// Text("Ghostty")
|
||||||
|
//// .font(.headline)
|
||||||
|
//// Text("zsh git")
|
||||||
|
//// .font(.footnote.monospaced())
|
||||||
|
//// Text("Git Signature")
|
||||||
|
//// .font(.footnote)
|
||||||
|
//// Text("4:05 PM")
|
||||||
|
//// .font(.caption)
|
||||||
|
//// }
|
||||||
|
//// }
|
||||||
|
//// } header: {
|
||||||
|
//// Text("GitHub Signing Key")
|
||||||
|
//// }
|
||||||
|
// }
|
||||||
|
// .padding()
|
||||||
|
// .formStyle(.grouped)
|
||||||
|
// .frame(minHeight: 700)
|
||||||
|
//}
|
||||||
|
|||||||
@@ -75,7 +75,6 @@
|
|||||||
50E0145E2EDB9CE400B121F1 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 50E0145D2EDB9CE400B121F1 /* Common */; };
|
50E0145E2EDB9CE400B121F1 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 50E0145D2EDB9CE400B121F1 /* Common */; };
|
||||||
50E204E92FA9D12700402380 /* CertificateDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E204E82FA9D12700402380 /* CertificateDetailView.swift */; };
|
50E204E92FA9D12700402380 /* CertificateDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E204E82FA9D12700402380 /* CertificateDetailView.swift */; };
|
||||||
50E204ED2FAA997F00402380 /* CertificateListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E204EC2FAA997F00402380 /* CertificateListItemView.swift */; };
|
50E204ED2FAA997F00402380 /* CertificateListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E204EC2FAA997F00402380 /* CertificateListItemView.swift */; };
|
||||||
50E204EF2FAA9C1400402380 /* MultilineInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E204EE2FAA9C1400402380 /* MultilineInfoView.swift */; };
|
|
||||||
50E2051D2FAAB81C00402380 /* SecretiveCertificateParser.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
50E2051D2FAAB81C00402380 /* SecretiveCertificateParser.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
50E205282FAAB82700402380 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E205242FAAB82700402380 /* main.swift */; };
|
50E205282FAAB82700402380 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E205242FAAB82700402380 /* main.swift */; };
|
||||||
50E2052C2FAAB85000402380 /* SecretiveCertificateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E2052B2FAAB85000402380 /* SecretiveCertificateParser.swift */; };
|
50E2052C2FAAB85000402380 /* SecretiveCertificateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E2052B2FAAB85000402380 /* SecretiveCertificateParser.swift */; };
|
||||||
@@ -276,7 +275,6 @@
|
|||||||
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = "<group>"; };
|
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = "<group>"; };
|
||||||
50E204E82FA9D12700402380 /* CertificateDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateDetailView.swift; sourceTree = "<group>"; };
|
50E204E82FA9D12700402380 /* CertificateDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateDetailView.swift; sourceTree = "<group>"; };
|
||||||
50E204EC2FAA997F00402380 /* CertificateListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateListItemView.swift; sourceTree = "<group>"; };
|
50E204EC2FAA997F00402380 /* CertificateListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateListItemView.swift; sourceTree = "<group>"; };
|
||||||
50E204EE2FAA9C1400402380 /* MultilineInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineInfoView.swift; sourceTree = "<group>"; };
|
|
||||||
50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretiveCertificateParser.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
|
50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretiveCertificateParser.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
50E205232FAAB82700402380 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
50E205232FAAB82700402380 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
50E205242FAAB82700402380 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
50E205242FAAB82700402380 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||||
@@ -404,7 +402,6 @@
|
|||||||
50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */,
|
50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */,
|
||||||
50617D8423FCE48E0099B055 /* ContentView.swift */,
|
50617D8423FCE48E0099B055 /* ContentView.swift */,
|
||||||
5066A6C72516FE6E004B5A36 /* CopyableView.swift */,
|
5066A6C72516FE6E004B5A36 /* CopyableView.swift */,
|
||||||
50E204EE2FAA9C1400402380 /* MultilineInfoView.swift */,
|
|
||||||
50153E1F250AFCB200525160 /* UpdateView.swift */,
|
50153E1F250AFCB200525160 /* UpdateView.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
@@ -814,7 +811,6 @@
|
|||||||
50E204E92FA9D12700402380 /* CertificateDetailView.swift in Sources */,
|
50E204E92FA9D12700402380 /* CertificateDetailView.swift in Sources */,
|
||||||
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
||||||
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */,
|
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */,
|
||||||
50E204EF2FAA9C1400402380 /* MultilineInfoView.swift in Sources */,
|
|
||||||
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
|
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
|
||||||
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */,
|
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */,
|
||||||
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
||||||
@@ -868,7 +864,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
50E205802FAB291E00402380 /* CertificateMigrator.swift in Sources */,
|
50E205802FAB291E00402380 /* CertificateMigrator.swift in Sources */,
|
||||||
50020BB024064869003D4025 /* AppDelegate.swift in Sources */,
|
50020BB024064869003D4025 /* App.swift in Sources */,
|
||||||
5018F54F24064786002EB505 /* Notifier.swift in Sources */,
|
5018F54F24064786002EB505 /* Notifier.swift in Sources */,
|
||||||
503647482F870B7800977A23 /* BatchedRequestsView.swift in Sources */,
|
503647482F870B7800977A23 /* BatchedRequestsView.swift in Sources */,
|
||||||
501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */,
|
501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */,
|
||||||
|
|||||||
@@ -87,6 +87,9 @@
|
|||||||
ReferencedContainer = "container:Secretive.xcodeproj">
|
ReferencedContainer = "container:Secretive.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
|
<MetalAPIValidationSettings
|
||||||
|
isEnabled = "No">
|
||||||
|
</MetalAPIValidationSettings>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
|
|||||||
@@ -28,6 +28,13 @@ struct SecretDetailView<SecretType: Secret>: View {
|
|||||||
image: Image(systemName: "touchid"),
|
image: Image(systemName: "touchid"),
|
||||||
text: keyWriter.openSSHMD5Fingerprint(secret: secret)
|
text: keyWriter.openSSHMD5Fingerprint(secret: secret)
|
||||||
)
|
)
|
||||||
|
Spacer()
|
||||||
|
.frame(height: 20)
|
||||||
|
CopyableView(
|
||||||
|
title: .secretDetailPublicKeyLabel,
|
||||||
|
image: Image(systemName: "key"),
|
||||||
|
text: keyWriter.openSSHString(secret: secret)
|
||||||
|
)
|
||||||
Spacer()
|
Spacer()
|
||||||
.frame(height: 20)
|
.frame(height: 20)
|
||||||
CopyableView(
|
CopyableView(
|
||||||
|
|||||||
Reference in New Issue
Block a user