From bff58b00ec615137516ea4e004a67246e64420c3 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 23 Aug 2025 12:51:38 -0700 Subject: [PATCH] WIP --- Sources/Packages/Localizable.xcstrings | 12 ++++ .../SecretKit/Types/CreationOptions.swift | 2 +- Sources/SecretAgent/AppDelegate.swift | 1 + Sources/Secretive/App.swift | 2 +- Sources/Secretive/Views/ContentView.swift | 2 +- Sources/Secretive/Views/CopyableView.swift | 59 +++++++++++++++---- .../Secretive/Views/CreateSecretView.swift | 52 ++++++++++++++-- Sources/Secretive/Views/EditSecretView.swift | 3 +- .../Secretive/Views/ToolbarButtonStyle.swift | 5 +- 9 files changed, 116 insertions(+), 22 deletions(-) diff --git a/Sources/Packages/Localizable.xcstrings b/Sources/Packages/Localizable.xcstrings index 3b8cdb4..4eedebe 100644 --- a/Sources/Packages/Localizable.xcstrings +++ b/Sources/Packages/Localizable.xcstrings @@ -2179,6 +2179,9 @@ } } } + }, + "Current Biometrics" : { + }, "delete_confirmation_cancel_button" : { "extractionState" : "manual", @@ -2949,6 +2952,9 @@ } } } + }, + "If you change your biometric settings in _any way_, including adding a new fingerprint, this key will no longer be accessible." : { + }, "Key Attribution" : { @@ -3555,6 +3561,9 @@ } } } + }, + "Require authentication with current set of biometrics." : { + }, "secret_detail_md5_fingerprint_label" : { "extractionState" : "manual", @@ -5350,6 +5359,9 @@ } } } + }, + "Test" : { + }, "test@example.com" : { diff --git a/Sources/Packages/Sources/SecretKit/Types/CreationOptions.swift b/Sources/Packages/Sources/SecretKit/Types/CreationOptions.swift index 160f9a8..df9f33a 100644 --- a/Sources/Packages/Sources/SecretKit/Types/CreationOptions.swift +++ b/Sources/Packages/Sources/SecretKit/Types/CreationOptions.swift @@ -17,7 +17,7 @@ public struct Attributes: Sendable, Codable, Hashable { authentication: AuthenticationRequirement = .presenceRequired, publicKeyAttribution: String? = nil ) { - assert(authentication != .unknown, "Secrets cannot be created with an unknown authentication requirement.") +// assert(authentication != .unknown, "Secrets cannot be created with an unknown authentication requirement.") self.keyType = keyType self.authentication = authentication self.publicKeyAttribution = publicKeyAttribution diff --git a/Sources/SecretAgent/AppDelegate.swift b/Sources/SecretAgent/AppDelegate.swift index 6bf0ec2..e11e188 100644 --- a/Sources/SecretAgent/AppDelegate.swift +++ b/Sources/SecretAgent/AppDelegate.swift @@ -13,6 +13,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { @MainActor private let storeList: SecretStoreList = { let list = SecretStoreList() +// list.add(store: SecureEnclave.CryptoKitStore()) list.add(store: SecureEnclave.Store()) list.add(store: SmartCard.Store()) return list diff --git a/Sources/Secretive/App.swift b/Sources/Secretive/App.swift index 56ba6fa..a639f39 100644 --- a/Sources/Secretive/App.swift +++ b/Sources/Secretive/App.swift @@ -1,4 +1,3 @@ -import Cocoa import SwiftUI import SecretKit import SecureEnclaveSecretKit @@ -10,6 +9,7 @@ extension EnvironmentValues { // This is injected through .environment modifier below instead of @Entry for performance reasons (basially, restrictions around init/mainactor causing delay in loading secrets/"empty screen" blip). @MainActor fileprivate static let _secretStoreList: SecretStoreList = { let list = SecretStoreList() + list.add(store: SecureEnclave.Store()) list.add(store: SecureEnclave.CryptoKitStore()) list.add(store: SmartCard.Store()) return list diff --git a/Sources/Secretive/Views/ContentView.swift b/Sources/Secretive/Views/ContentView.swift index e57f5d8..dfc6dff 100644 --- a/Sources/Secretive/Views/ContentView.swift +++ b/Sources/Secretive/Views/ContentView.swift @@ -30,7 +30,7 @@ struct ContentView: View { } .frame(minWidth: 640, minHeight: 320) .toolbar { -// toolbarItem(updateNoticeView, id: "update") + toolbarItem(updateNoticeView, id: "update") toolbarItem(runningOrRunSetupView, id: "setup") toolbarItem(appPathNoticeView, id: "appPath") toolbarItem(newItemView, id: "new") diff --git a/Sources/Secretive/Views/CopyableView.swift b/Sources/Secretive/Views/CopyableView.swift index d1be4be..e766126 100644 --- a/Sources/Secretive/Views/CopyableView.swift +++ b/Sources/Secretive/Views/CopyableView.swift @@ -8,7 +8,9 @@ struct CopyableView: View { var text: String @State private var interactionState: InteractionState = .normal - + @State private var expanded = false + @State private var showExpand = false + var content: some View { VStack(alignment: .leading) { HStack { @@ -31,17 +33,50 @@ struct CopyableView: View { } .padding(EdgeInsets(top: 20, leading: 20, bottom: 10, trailing: 20)) Divider() - Text(text) - .fixedSize(horizontal: false, vertical: true) - .foregroundColor(primaryTextColor) - .padding(EdgeInsets(top: 10, leading: 20, bottom: 20, trailing: 20)) - .multilineTextAlignment(.leading) - .font(.system(.body, design: .monospaced)) + textView + .lineLimit(expanded ? nil : 5) + .overlay { + ViewThatFits { + textView + .hidden() + .onAppear { + showExpand = false + } + Spacer() + .onAppear { + showExpand = true + } + } + } + .overlay(alignment: .bottomTrailing) { + if showExpand { + if #available(macOS 26.0, *) { + Button("Test", systemImage: "rectangle.expand.vertical") { + expanded = true + showExpand = false + } + .labelStyle(.iconOnly) + .buttonBorderShape(.circle) + .buttonStyle(.glass) + .padding() + } else { + } + } + } } ._background(interactionState: interactionState) .frame(minWidth: 150, maxWidth: .infinity) } + var textView: some View { + Text(text) + .fixedSize(horizontal: false, vertical: true) + .foregroundColor(primaryTextColor) + .padding(EdgeInsets(top: 10, leading: 20, bottom: 20, trailing: 20)) + .multilineTextAlignment(.leading) + .font(.system(.body, design: .monospaced)) + } + var body: some View { content .onHover { hovering in @@ -49,10 +84,10 @@ struct CopyableView: View { interactionState = hovering ? .hovering : .normal } } - .onDrag({ - NSItemProvider(item: NSData(data: text.data(using: .utf8)!), typeIdentifier: UTType.utf8PlainText.identifier) - }, preview: { - content + .draggable(text, preview: { + content + .lineLimit(3) + .frame(maxWidth: 300) ._background(interactionState: .dragging) }) .onTapGesture { @@ -170,7 +205,7 @@ struct CopyableView_Previews: PreviewProvider { Group { CopyableView(title: "secret_detail_sha256_fingerprint_label", 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: "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. Long text. Long text. Long text.") .padding() } } diff --git a/Sources/Secretive/Views/CreateSecretView.swift b/Sources/Secretive/Views/CreateSecretView.swift index 66169b4..db20f04 100644 --- a/Sources/Secretive/Views/CreateSecretView.swift +++ b/Sources/Secretive/Views/CreateSecretView.swift @@ -13,7 +13,11 @@ struct CreateSecretView: View { @State var advanced = false private var authenticationOptions: [AuthenticationRequirement] { - [.presenceRequired, .notRequired] + if advanced || authenticationRequirement == .biometryCurrent { + [.presenceRequired, .notRequired, .biometryCurrent] + } else { + [.presenceRequired, .notRequired] + } } var body: some View { @@ -21,11 +25,48 @@ struct CreateSecretView: View { Form { Section { TextField(String(localized: .createSecretNameLabel), text: $name, prompt: Text(.createSecretNamePlaceholder)) - Picker(.createSecretRequireAuthenticationTitle, selection: $authenticationRequirement) { - ForEach(authenticationOptions) { option in - Text(String(describing: option)) + VStack(alignment: .leading, spacing: 10) { + Picker(.createSecretRequireAuthenticationTitle, selection: $authenticationRequirement) { + ForEach(authenticationOptions) { option in + HStack { + switch option { + case .notRequired: + Image(systemName: "bell") + Text(.createSecretNotifyTitle) + case .presenceRequired: + Image(systemName: "lock") + Text(.createSecretRequireAuthenticationTitle) + case .biometryCurrent: + Image(systemName: "lock.trianglebadge.exclamationmark.fill") + Text("Current Biometrics") + case .unknown: + EmptyView() + } + } .tag(option) + } } + Group { + switch authenticationRequirement { + case .notRequired: + Text(.createSecretNotifyDescription) + case .presenceRequired: + Text(.createSecretRequireAuthenticationDescription) + case .biometryCurrent: + Text("Require authentication with current set of biometrics.") + case .unknown: + EmptyView() + } + } + .font(.subheadline) + .foregroundStyle(.secondary) + if authenticationRequirement == .biometryCurrent { + Text("If you change your biometric settings in _any way_, including adding a new fingerprint, this key will no longer be accessible.") + .padding(.horizontal, 10) + .padding(.vertical, 3) + .background(.red.opacity(0.5), in: RoundedRectangle(cornerRadius: 5)) + } + } } if advanced { @@ -48,7 +89,8 @@ struct CreateSecretView: View { VStack(alignment: .leading) { TextField("Key Attribution", text: $keyAttribution, prompt: Text("test@example.com")) Text("This shows at the end of your public key.") - .font(.caption) + .font(.subheadline) + .foregroundStyle(.secondary) } } } diff --git a/Sources/Secretive/Views/EditSecretView.swift b/Sources/Secretive/Views/EditSecretView.swift index 737c666..ab9f8b4 100644 --- a/Sources/Secretive/Views/EditSecretView.swift +++ b/Sources/Secretive/Views/EditSecretView.swift @@ -26,7 +26,8 @@ struct EditSecretView: View { VStack(alignment: .leading) { TextField("Key Attribution", text: $publicKeyAttribution, prompt: Text("test@example.com")) Text("This shows at the end of your public key.") - .font(.caption) + .font(.subheadline) + .foregroundStyle(.secondary) } } } diff --git a/Sources/Secretive/Views/ToolbarButtonStyle.swift b/Sources/Secretive/Views/ToolbarButtonStyle.swift index 7dd1f65..8a35de6 100644 --- a/Sources/Secretive/Views/ToolbarButtonStyle.swift +++ b/Sources/Secretive/Views/ToolbarButtonStyle.swift @@ -31,7 +31,8 @@ struct ToolbarButtonStyle: ButtonStyle { configuration .label .foregroundColor(.white) - .padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8)) + .padding(.vertical, 6) + .padding(.horizontal, 9) .glassEffect(.regular.tint(glassTint), in: .capsule) .onHover { hovering in self.hovering = hovering @@ -41,6 +42,8 @@ struct ToolbarButtonStyle: ButtonStyle { .label .background(colorScheme == .light ? lightColor : darkColor) .foregroundColor(.white) + .padding(.vertical, 6) + .padding(.horizontal, 8) .clipShape(RoundedRectangle(cornerRadius: 5)) .overlay( RoundedRectangle(cornerRadius: 5)