diff --git a/Sources/Packages/Localizable.xcstrings b/Sources/Packages/Localizable.xcstrings index f16555a..78cf6ec 100644 --- a/Sources/Packages/Localizable.xcstrings +++ b/Sources/Packages/Localizable.xcstrings @@ -1,6 +1,9 @@ { "sourceLanguage" : "en", "strings" : { + "Advanced" : { + + }, "agent_not_running_notice_title" : { "extractionState" : "manual", "localizations" : { @@ -1644,72 +1647,72 @@ "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Nom:" + "value" : "Nom" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Name:" + "value" : "Name" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Name:" + "value" : "Name" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Nimi:" + "value" : "Nimi" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nom :" + "value" : "Nom" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nome:" + "value" : "Nome" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "名前:" + "value" : "名前" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "이름:" + "value" : "이름" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nazwa:" + "value" : "Nazwa" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Nome:" + "value" : "Nome" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Название:" + "value" : "Название" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "名称" } } @@ -2260,72 +2263,72 @@ "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Confirma el nom:" + "value" : "Confirma el nom" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Name bestätigen:" + "value" : "Name bestätigen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Confirm Name:" + "value" : "Confirm Name" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Vahvista nimi:" + "value" : "Vahvista nimi" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Confirmer le nom :" + "value" : "Confirmer le nom" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Conferma nome:" + "value" : "Conferma nome" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "名前の確認:" + "value" : "名前の確認" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "확인 이름:" + "value" : "확인 이름" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Powtórz nazwę:" + "value" : "Powtórz nazwę" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Confirmar Nome:" + "value" : "Confirmar Nome" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Подтвердить название:" + "value" : "Подтвердить название" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "确认名称" } } @@ -2946,6 +2949,12 @@ } } } + }, + "Key Attribution" : { + + }, + "Key Type" : { + }, "no_secure_storage_description" : { "extractionState" : "manual", @@ -5323,6 +5332,9 @@ } } } + }, + "test@example.com" : { + }, "unnamed_secret" : { "extractionState" : "manual", diff --git a/Sources/Packages/Sources/SecretKit/Types/CreationOptions.swift b/Sources/Packages/Sources/SecretKit/Types/CreationOptions.swift index 4ce1b3f..2a61ff5 100644 --- a/Sources/Packages/Sources/SecretKit/Types/CreationOptions.swift +++ b/Sources/Packages/Sources/SecretKit/Types/CreationOptions.swift @@ -30,8 +30,8 @@ public struct Attributes: Sendable, Codable { } /// The option specified -public enum AuthenticationRequirement: String, Hashable, Sendable, Codable { - +public enum AuthenticationRequirement: String, Hashable, Sendable, Codable, Identifiable { + /// Authentication is not required for usage. case notRequired @@ -49,4 +49,8 @@ public enum AuthenticationRequirement: String, Hashable, Sendable, Codable { public var required: Bool { self == .presenceRequired || self == .biometryCurrent } + + public var id: AuthenticationRequirement { + self + } } diff --git a/Sources/Secretive/App.swift b/Sources/Secretive/App.swift index 2c1ec9d..56ba6fa 100644 --- a/Sources/Secretive/App.swift +++ b/Sources/Secretive/App.swift @@ -10,7 +10,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/CreateSecretView.swift b/Sources/Secretive/Views/CreateSecretView.swift index 1425415..f0df680 100644 --- a/Sources/Secretive/Views/CreateSecretView.swift +++ b/Sources/Secretive/Views/CreateSecretView.swift @@ -7,244 +7,74 @@ struct CreateSecretView: View { @Binding var showing: Bool @State private var name = "" - @State private var requiresAuthentication = true + @State private var keyAttribution = "" + @State private var authenticationRequirement: AuthenticationRequirement = .presenceRequired + @State private var keyType: KeyType? + @State var advanced = false + + private var authenticationOptions: [AuthenticationRequirement] { + [.presenceRequired, .notRequired] + } var body: some View { - VStack { - HStack { - VStack { - HStack { - Text(.createSecretTitle) - .font(.largeTitle) - Spacer() + VStack(alignment: .trailing) { + Form { + Section { + TextField(String(localized: .createSecretNameLabel), text: $name, prompt: Text(.createSecretNamePlaceholder)) + Picker(.createSecretRequireAuthenticationTitle, selection: $authenticationRequirement) { + ForEach(authenticationOptions) { option in + Text(String(describing: option)) + .tag(option) + } } - HStack { - Text(.createSecretNameLabel) - TextField(String(localized: .createSecretNamePlaceholder), text: $name) - .focusable() + } + if advanced { + Section { + Picker("Key Type", selection: $keyType) { + ForEach(store.supportedKeyTypes, id: \.self) { option in + Text(String(describing: option)) + .tag(option) + } + } + TextField("Key Attribution", text: $keyAttribution, prompt: Text("test@example.com")) } - ThumbnailPickerView(items: [ - ThumbnailPickerView.Item(value: true, name: .createSecretRequireAuthenticationTitle, description: .createSecretRequireAuthenticationDescription, thumbnail: AuthenticationView()), - ThumbnailPickerView.Item(value: false, name: .createSecretNotifyTitle, - description: .createSecretNotifyDescription, - thumbnail: NotificationView()) - ], selection: $requiresAuthentication) } } HStack { + Toggle("Advanced", isOn: $advanced) + .toggleStyle(.button) Spacer() - Button(.createSecretCancelButton) { + Button(.createSecretCancelButton, role: .cancel) { showing = false } - .keyboardShortcut(.cancelAction) Button(.createSecretCreateButton, action: save) .disabled(name.isEmpty) - .keyboardShortcut(.defaultAction) } - }.padding() + .padding() + } + .onAppear { + keyType = store.supportedKeyTypes.first + } + .formStyle(.grouped) } func save() { + let attribution = keyAttribution.isEmpty ? nil : keyAttribution Task { - try! await store.create(name: name, attributes: .init(keyType: .init(algorithm: .ecdsa, size: 256), authentication: .presenceRequired, publicKeyAttribution: nil)) + try! await store.create( + name: name, + attributes: .init( + keyType: keyType!, + authentication: authenticationRequirement, + publicKeyAttribution: attribution + ) + ) showing = false } } } -struct ThumbnailPickerView: View { - - private let items: [Item] - @Binding var selection: ValueType - - init(items: [ThumbnailPickerView.Item], selection: Binding) { - self.items = items - _selection = selection - } - - var body: some View { - HStack(alignment: .top) { - ForEach(items) { item in - VStack(alignment: .leading, spacing: 15) { - item.thumbnail - .frame(height: 200) - .overlay(RoundedRectangle(cornerRadius: 10) - .stroke(lineWidth: item.value == selection ? 15 : 0)) - .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) - .foregroundColor(.accentColor) - VStack(alignment: .leading, spacing: 5) { - Text(item.name) - .bold() - Text(item.description) - .fixedSize(horizontal: false, vertical: true) - } - } - .frame(width: 250) - .onTapGesture { - withAnimation(.spring()) { - selection = item.value - } - } - } - .padding(5) - } - } - +#Preview { + CreateSecretView(store: Preview.StoreModifiable(), showing: .constant(true)) } - -extension ThumbnailPickerView { - - struct Item: Identifiable { - let id = UUID() - let value: InnerValueType - let name: LocalizedStringResource - let description: LocalizedStringResource - let thumbnail: AnyView - - init(value: InnerValueType, name: LocalizedStringResource, description: LocalizedStringResource, thumbnail: ViewType) { - self.value = value - self.name = name - self.description = description - self.thumbnail = AnyView(thumbnail) - } - } - -} - -@MainActor @Observable class SystemBackground { - - static let shared = SystemBackground() - var image: NSImage? - - private init() { - if let mainScreen = NSScreen.main, let imageURL = NSWorkspace.shared.desktopImageURL(for: mainScreen) { - image = NSImage(contentsOf: imageURL) - } else { - image = nil - } - } - -} - -struct SystemBackgroundView: View { - - let anchor: UnitPoint - - var body: some View { - if let image = SystemBackground.shared.image { - Image(nsImage: image) - .resizable() - .scaleEffect(3, anchor: anchor) - .clipped() - .allowsHitTesting(false) - } else { - Rectangle() - .foregroundColor(Color(.systemPurple)) - } - } -} - -struct AuthenticationView: View { - - var body: some View { - ZStack { - SystemBackgroundView(anchor: .center) - GeometryReader { geometry in - VStack { - Image(systemName: "touchid") - .resizable() - .aspectRatio(contentMode: .fit) - .foregroundColor(Color(.systemRed)) - Text(verbatim: "Touch ID Prompt") - .font(.headline) - .foregroundColor(.primary) - .redacted(reason: .placeholder) - VStack { - Text(verbatim: "Touch ID Detail prompt.Detail two.") - .font(.caption2) - .foregroundColor(.primary) - Text(verbatim: "Touch ID Detail prompt.Detail two.") - .font(.caption2) - .foregroundColor(.primary) - } - .redacted(reason: .placeholder) - RoundedRectangle(cornerRadius: 5) - .frame(width: geometry.size.width, height: 20, alignment: .center) - .foregroundColor(.accentColor) - RoundedRectangle(cornerRadius: 5) - .frame(width: geometry.size.width, height: 20, alignment: .center) - .foregroundColor(Color(.unemphasizedSelectedContentBackgroundColor)) - } - } - .padding() - .frame(width: 150) - .background( - RoundedRectangle(cornerRadius: 15) - .foregroundStyle(.ultraThickMaterial) - ) - .padding() - - } - } - -} - -struct NotificationView: View { - - var body: some View { - ZStack { - SystemBackgroundView(anchor: .topTrailing) - VStack { - Rectangle() - .background(Color.clear) - .foregroundStyle(.thinMaterial) - .frame(height: 35) - VStack { - HStack { - Spacer() - HStack { - Image(nsImage: NSApplication.shared.applicationIconImage) - .resizable() - .frame(width: 64, height: 64) - .foregroundColor(.primary) - VStack(alignment: .leading) { - Text(verbatim: "Secretive") - .font(.title) - .foregroundColor(.primary) - Text(verbatim: "Secretive wants to sign") - .font(.body) - .foregroundColor(.primary) - } - }.padding() - .redacted(reason: .placeholder) - .background( - RoundedRectangle(cornerRadius: 15) - .foregroundStyle(.ultraThickMaterial) - ) - } - Spacer() - } - .padding() - } - } - } - -} - -#if DEBUG - -struct CreateSecretView_Previews: PreviewProvider { - - static var previews: some View { - Group { - CreateSecretView(store: Preview.StoreModifiable(), showing: .constant(true)) - AuthenticationView().environment(\.colorScheme, .dark) - AuthenticationView().environment(\.colorScheme, .light) - NotificationView().environment(\.colorScheme, .dark) - NotificationView().environment(\.colorScheme, .light) - } - } -} - -#endif