mirror of
git://jb55.com/damus
synced 2024-10-01 17:30:44 +00:00
Replace Vault dependency with @KeychainStorage property wrapper
Changelog-Changed: replace Vault dependency with @KeychainStorage property wrapper Closes: #1076
This commit is contained in:
parent
27fb4e797d
commit
e4860f3ba8
@ -258,6 +258,8 @@
|
||||
4CFF8F6D29CD022E008DB934 /* WideEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */; };
|
||||
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
|
||||
50088DA129E8271A008A1FDF /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50088DA029E8271A008A1FDF /* WebSocket.swift */; };
|
||||
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */; };
|
||||
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */; };
|
||||
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; };
|
||||
50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B5685229F97CB400A23243 /* CredentialHandler.swift */; };
|
||||
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; };
|
||||
@ -268,7 +270,6 @@
|
||||
643EA5C8296B764E005081BB /* RelayFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643EA5C7296B764E005081BB /* RelayFilterView.swift */; };
|
||||
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
|
||||
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; };
|
||||
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
|
||||
7C0F392F29B57CAF0039859C /* Binding+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0F392E29B57CAF0039859C /* Binding+.swift */; };
|
||||
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C60CAEE298471A1009C80D6 /* CoreSVG.swift */; };
|
||||
7C902AE32981D55B002AB16E /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */; };
|
||||
@ -681,6 +682,8 @@
|
||||
4CFF8F6C29CD022E008DB934 /* WideEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WideEventView.swift; sourceTree = "<group>"; };
|
||||
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
|
||||
50088DA029E8271A008A1FDF /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = "<group>"; };
|
||||
501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = "<group>"; };
|
||||
501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorageTests.swift; sourceTree = "<group>"; };
|
||||
50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; };
|
||||
50B5685229F97CB400A23243 /* CredentialHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialHandler.swift; sourceTree = "<group>"; };
|
||||
5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; };
|
||||
@ -720,7 +723,6 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */,
|
||||
6C7DE41F2955169800E66263 /* Vault in Frameworks */,
|
||||
4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -1035,6 +1037,7 @@
|
||||
4C363AA728297703006E126D /* InsertSort.swift */,
|
||||
4C477C9D282C3A4800033AA3 /* TipCounter.swift */,
|
||||
4C285C8B28398BC6008A31F1 /* Keys.swift */,
|
||||
501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */,
|
||||
4C90BD19283AA67F008EE7EF /* Bech32.swift */,
|
||||
4C216F352870A9A700040376 /* InputDismissKeyboard.swift */,
|
||||
3169CAEC294FCCFC00EE4006 /* Constants.swift */,
|
||||
@ -1271,6 +1274,7 @@
|
||||
3A3040F229A91366008A0F29 /* ProfileViewTests.swift */,
|
||||
3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */,
|
||||
4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */,
|
||||
501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */,
|
||||
);
|
||||
path = damusTests;
|
||||
sourceTree = "<group>";
|
||||
@ -1393,7 +1397,6 @@
|
||||
packageProductDependencies = (
|
||||
4C649880286E0EE300EAE2B3 /* secp256k1 */,
|
||||
4C06670328FC7EC500038D2A /* Kingfisher */,
|
||||
6C7DE41E2955169800E66263 /* Vault */,
|
||||
);
|
||||
productName = damus;
|
||||
productReference = 4CE6DEE327F7A08100C66700 /* damus.app */;
|
||||
@ -1498,7 +1501,6 @@
|
||||
packageReferences = (
|
||||
4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */,
|
||||
4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */,
|
||||
6C7DE41D2955169800E66263 /* XCRemoteSwiftPackageReference "Vault" */,
|
||||
);
|
||||
productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */;
|
||||
projectDirPath = "";
|
||||
@ -1789,6 +1791,7 @@
|
||||
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
|
||||
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */,
|
||||
4C1A9A2129DDD3E100516EAC /* KeySettingsView.swift in Sources */,
|
||||
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */,
|
||||
4C1A9A1D29DDCF9B00516EAC /* NotificationSettingsView.swift in Sources */,
|
||||
4C75EFB528049D790006080F /* Relay.swift in Sources */,
|
||||
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */,
|
||||
@ -1811,6 +1814,7 @@
|
||||
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */,
|
||||
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */,
|
||||
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */,
|
||||
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */,
|
||||
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
|
||||
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */,
|
||||
3A3040EF29A8FEE9008A0F29 /* EventDetailBarTests.swift in Sources */,
|
||||
@ -2307,14 +2311,6 @@
|
||||
revision = 40b4b38b3b1c83f7088c76189a742870e0ca06a9;
|
||||
};
|
||||
};
|
||||
6C7DE41D2955169800E66263 /* XCRemoteSwiftPackageReference "Vault" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/SparrowTek/Vault";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.0.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
@ -2328,11 +2324,6 @@
|
||||
package = 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */;
|
||||
productName = secp256k1;
|
||||
};
|
||||
6C7DE41E2955169800E66263 /* Vault */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 6C7DE41D2955169800E66263 /* XCRemoteSwiftPackageReference "Vault" */;
|
||||
productName = Vault;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 4CE6DEDB27F7A08100C66700 /* Project object */;
|
||||
|
@ -16,15 +16,6 @@
|
||||
"state" : {
|
||||
"revision" : "40b4b38b3b1c83f7088c76189a742870e0ca06a9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "vault",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SparrowTek/Vault",
|
||||
"state" : {
|
||||
"revision" : "87db56c3c8b6421c65b0745f73e08b0dc56f79d4",
|
||||
"version" : "1.0.3"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
|
@ -6,7 +6,6 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Vault
|
||||
import UIKit
|
||||
|
||||
let fallback_zap_amount = 1000
|
||||
@ -160,16 +159,11 @@ class UserSettingsStore: ObservableObject {
|
||||
var deepl_plan: DeepLPlan
|
||||
|
||||
var deepl_api_key: String {
|
||||
didSet {
|
||||
do {
|
||||
if deepl_api_key == "" {
|
||||
try clearDeepLApiKey()
|
||||
} else {
|
||||
try saveDeepLApiKey(deepl_api_key)
|
||||
}
|
||||
} catch {
|
||||
// No-op.
|
||||
}
|
||||
get {
|
||||
return internal_deepl_api_key ?? ""
|
||||
}
|
||||
set {
|
||||
internal_deepl_api_key = newValue == "" ? nil : newValue
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,73 +173,34 @@ class UserSettingsStore: ObservableObject {
|
||||
@Setting(key: "libretranslate_url", default_value: "")
|
||||
var libretranslate_url: String
|
||||
|
||||
@Setting(key: "libretranslate_api_key", default_value: "")
|
||||
var libretranslate_api_key: String {
|
||||
didSet {
|
||||
do {
|
||||
if libretranslate_api_key == "" {
|
||||
try clearLibreTranslateApiKey()
|
||||
} else {
|
||||
try saveLibreTranslateApiKey(libretranslate_api_key)
|
||||
}
|
||||
} catch {
|
||||
// No-op.
|
||||
}
|
||||
get {
|
||||
return internal_libretranslate_api_key ?? ""
|
||||
}
|
||||
set {
|
||||
internal_libretranslate_api_key = newValue == "" ? nil : newValue
|
||||
}
|
||||
}
|
||||
|
||||
@Published var nokyctranslate_api_key: String {
|
||||
didSet {
|
||||
do {
|
||||
if nokyctranslate_api_key == "" {
|
||||
try clearNoKYCTranslateApiKey()
|
||||
} else {
|
||||
try saveNoKYCTranslateApiKey(nokyctranslate_api_key)
|
||||
}
|
||||
} catch {
|
||||
// No-op.
|
||||
}
|
||||
var nokyctranslate_api_key: String {
|
||||
get {
|
||||
return internal_nokyctranslate_api_key ?? ""
|
||||
}
|
||||
set {
|
||||
internal_nokyctranslate_api_key = newValue == "" ? nil : newValue
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
do {
|
||||
deepl_api_key = try Vault.getPrivateKey(keychainConfiguration: DamusDeepLKeychainConfiguration())
|
||||
} catch {
|
||||
deepl_api_key = ""
|
||||
}
|
||||
// These internal keys are necessary because entries in the keychain need to be Optional,
|
||||
// but the translation view needs non-Optional String in order to use them as Bindings.
|
||||
@KeychainStorage(account: "deepl_apikey")
|
||||
var internal_deepl_api_key: String?
|
||||
|
||||
do {
|
||||
nokyctranslate_api_key = try Vault.getPrivateKey(keychainConfiguration: DamusNoKYCTranslateKeychainConfiguration())
|
||||
} catch {
|
||||
nokyctranslate_api_key = ""
|
||||
}
|
||||
@KeychainStorage(account: "nokyctranslate_apikey")
|
||||
var internal_nokyctranslate_api_key: String?
|
||||
|
||||
}
|
||||
|
||||
private func saveLibreTranslateApiKey(_ apiKey: String) throws {
|
||||
try Vault.savePrivateKey(apiKey, keychainConfiguration: DamusLibreTranslateKeychainConfiguration())
|
||||
}
|
||||
|
||||
private func clearLibreTranslateApiKey() throws {
|
||||
try Vault.deletePrivateKey(keychainConfiguration: DamusLibreTranslateKeychainConfiguration())
|
||||
}
|
||||
|
||||
private func saveNoKYCTranslateApiKey(_ apiKey: String) throws {
|
||||
try Vault.savePrivateKey(apiKey, keychainConfiguration: DamusNoKYCTranslateKeychainConfiguration())
|
||||
}
|
||||
|
||||
private func clearNoKYCTranslateApiKey() throws {
|
||||
try Vault.deletePrivateKey(keychainConfiguration: DamusNoKYCTranslateKeychainConfiguration())
|
||||
}
|
||||
|
||||
private func saveDeepLApiKey(_ apiKey: String) throws {
|
||||
try Vault.savePrivateKey(apiKey, keychainConfiguration: DamusDeepLKeychainConfiguration())
|
||||
}
|
||||
|
||||
private func clearDeepLApiKey() throws {
|
||||
try Vault.deletePrivateKey(keychainConfiguration: DamusDeepLKeychainConfiguration())
|
||||
}
|
||||
@KeychainStorage(account: "libretranslate_apikey")
|
||||
var internal_libretranslate_api_key: String?
|
||||
|
||||
var can_translate: Bool {
|
||||
switch translation_service {
|
||||
@ -254,31 +209,13 @@ class UserSettingsStore: ObservableObject {
|
||||
case .libretranslate:
|
||||
return URLComponents(string: libretranslate_url) != nil
|
||||
case .deepl:
|
||||
return deepl_api_key != ""
|
||||
return internal_deepl_api_key != nil
|
||||
case .nokyctranslate:
|
||||
return nokyctranslate_api_key != ""
|
||||
return internal_nokyctranslate_api_key != nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DamusLibreTranslateKeychainConfiguration: KeychainConfiguration {
|
||||
var serviceName = "damus"
|
||||
var accessGroup: String? = nil
|
||||
var accountName = "libretranslate_apikey"
|
||||
}
|
||||
|
||||
struct DamusDeepLKeychainConfiguration: KeychainConfiguration {
|
||||
var serviceName = "damus"
|
||||
var accessGroup: String? = nil
|
||||
var accountName = "deepl_apikey"
|
||||
}
|
||||
|
||||
struct DamusNoKYCTranslateKeychainConfiguration: KeychainConfiguration {
|
||||
var serviceName = "damus"
|
||||
var accessGroup: String? = nil
|
||||
var accountName = "nokyctranslate_apikey"
|
||||
}
|
||||
|
||||
func pk_setting_key(_ pubkey: String, key: String) -> String {
|
||||
return "\(pubkey)_\(key)"
|
||||
}
|
||||
|
73
damus/Util/KeychainStorage.swift
Normal file
73
damus/Util/KeychainStorage.swift
Normal file
@ -0,0 +1,73 @@
|
||||
//
|
||||
// KeychainStorage.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Bryan Montz on 5/2/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
@propertyWrapper struct KeychainStorage {
|
||||
let account: String
|
||||
private let service = "damus"
|
||||
|
||||
var wrappedValue: String? {
|
||||
get {
|
||||
let query = [
|
||||
kSecAttrService: service,
|
||||
kSecAttrAccount: account,
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecReturnData: true,
|
||||
kSecMatchLimit: kSecMatchLimitOne
|
||||
] as [CFString: Any] as CFDictionary
|
||||
|
||||
var result: AnyObject?
|
||||
let status = SecItemCopyMatching(query, &result)
|
||||
|
||||
if status == errSecSuccess, let data = result as? Data {
|
||||
return String(data: data, encoding: .utf8)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
set {
|
||||
if let newValue {
|
||||
let query = [
|
||||
kSecAttrService: service,
|
||||
kSecAttrAccount: account,
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecValueData: newValue.data(using: .utf8) as Any
|
||||
] as [CFString: Any] as CFDictionary
|
||||
|
||||
var status = SecItemAdd(query, nil)
|
||||
|
||||
if status == errSecDuplicateItem {
|
||||
let query = [
|
||||
kSecAttrService: service,
|
||||
kSecAttrAccount: account,
|
||||
kSecClass: kSecClassGenericPassword
|
||||
] as [CFString: Any] as CFDictionary
|
||||
|
||||
let updates = [
|
||||
kSecValueData: newValue.data(using: .utf8) as Any
|
||||
] as CFDictionary
|
||||
|
||||
status = SecItemUpdate(query, updates)
|
||||
}
|
||||
} else {
|
||||
let query = [
|
||||
kSecAttrService: service,
|
||||
kSecAttrAccount: account,
|
||||
kSecClass: kSecClassGenericPassword
|
||||
] as [CFString: Any] as CFDictionary
|
||||
|
||||
_ = SecItemDelete(query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(account: String) {
|
||||
self.account = account
|
||||
}
|
||||
}
|
@ -7,7 +7,6 @@
|
||||
|
||||
import Foundation
|
||||
import secp256k1
|
||||
import Vault
|
||||
|
||||
let PUBKEY_HRP = "npub"
|
||||
let PRIVKEY_HRP = "nsec"
|
||||
@ -44,12 +43,6 @@ enum Bech32Key {
|
||||
case sec(String)
|
||||
}
|
||||
|
||||
struct DamusKeychainConfiguration: KeychainConfiguration {
|
||||
var serviceName = "damus"
|
||||
var accessGroup: String? = nil
|
||||
var accountName = "privkey"
|
||||
}
|
||||
|
||||
func decode_bech32_key(_ key: String) -> Bech32Key? {
|
||||
guard let decoded = try? bech32_decode(key) else {
|
||||
return nil
|
||||
@ -114,12 +107,17 @@ func save_pubkey(pubkey: String) {
|
||||
UserDefaults.standard.set(pubkey, forKey: "pubkey")
|
||||
}
|
||||
|
||||
enum Keys {
|
||||
@KeychainStorage(account: "privkey")
|
||||
static var privkey: String?
|
||||
}
|
||||
|
||||
func save_privkey(privkey: String) throws {
|
||||
try Vault.savePrivateKey(privkey, keychainConfiguration: DamusKeychainConfiguration())
|
||||
Keys.privkey = privkey
|
||||
}
|
||||
|
||||
func clear_saved_privkey() throws {
|
||||
try Vault.deletePrivateKey(keychainConfiguration: DamusKeychainConfiguration())
|
||||
Keys.privkey = nil
|
||||
}
|
||||
|
||||
func clear_saved_pubkey() {
|
||||
@ -154,7 +152,7 @@ func get_saved_pubkey() -> String? {
|
||||
}
|
||||
|
||||
func get_saved_privkey() -> String? {
|
||||
let mkey = try? Vault.getPrivateKey(keychainConfiguration: DamusKeychainConfiguration());
|
||||
let mkey = Keys.privkey
|
||||
return mkey.map { $0.trimmingCharacters(in: .whitespaces) }
|
||||
}
|
||||
|
||||
|
@ -79,9 +79,7 @@ struct TranslationSettingsView: View {
|
||||
if settings.translation_service != .none {
|
||||
Toggle(NSLocalizedString("Automatically translate notes", comment: "Toggle to automatically translate notes."), isOn: $settings.auto_translate)
|
||||
.toggleStyle(.switch)
|
||||
}
|
||||
|
||||
if settings.translation_service != .none {
|
||||
Toggle(NSLocalizedString("Translate DMs", comment: "Toggle to translate direct messages."), isOn: $settings.translate_dms)
|
||||
.toggleStyle(.switch)
|
||||
}
|
||||
@ -92,81 +90,6 @@ struct TranslationSettingsView: View {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
var libretranslate_view: some View {
|
||||
VStack {
|
||||
Picker(NSLocalizedString("Server", comment: "Prompt selection of LibreTranslate server to perform machine translations on notes"), selection: $settings.libretranslate_server) {
|
||||
ForEach(LibreTranslateServer.allCases, id: \.self) { server in
|
||||
Text(server.model.displayName)
|
||||
.tag(server.model.tag)
|
||||
}
|
||||
}
|
||||
|
||||
TextField(NSLocalizedString("URL", comment: "Example URL to LibreTranslate server"), text: $settings.libretranslate_url)
|
||||
.disableAutocorrection(true)
|
||||
.disabled(settings.libretranslate_server != .custom)
|
||||
.autocapitalization(UITextAutocapitalizationType.none)
|
||||
HStack {
|
||||
let libretranslate_api_key_placeholder = NSLocalizedString("API Key (optional)", comment: "Prompt for optional entry of API Key to use translation server.")
|
||||
if show_api_key {
|
||||
TextField(libretranslate_api_key_placeholder, text: $settings.libretranslate_api_key)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(UITextAutocapitalizationType.none)
|
||||
if settings.libretranslate_api_key != "" {
|
||||
Button(NSLocalizedString("Hide API Key", comment: "Button to hide the LibreTranslate server API key.")) {
|
||||
show_api_key = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SecureField(libretranslate_api_key_placeholder, text: $settings.libretranslate_api_key)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(UITextAutocapitalizationType.none)
|
||||
if settings.libretranslate_api_key != "" {
|
||||
Button(NSLocalizedString("Show API Key", comment: "Button to show the LibreTranslate server API key.")) {
|
||||
show_api_key = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var deepl_view: some View {
|
||||
VStack {
|
||||
Picker(NSLocalizedString("Plan", comment: "Prompt selection of DeepL subscription plan to perform machine translations on notes"), selection: $settings.deepl_plan) {
|
||||
ForEach(DeepLPlan.allCases, id: \.self) { server in
|
||||
Text(server.model.displayName)
|
||||
.tag(server.model.tag)
|
||||
}
|
||||
}
|
||||
|
||||
HStack {
|
||||
let deepl_api_key_placeholder = NSLocalizedString("API Key (required)", comment: "Prompt for required entry of API Key to use translation server.")
|
||||
if show_api_key {
|
||||
TextField(deepl_api_key_placeholder, text: $settings.deepl_api_key)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(UITextAutocapitalizationType.none)
|
||||
if settings.deepl_api_key != "" {
|
||||
Button(NSLocalizedString("Hide API Key", comment: "Button to hide the DeepL translation API key.")) {
|
||||
show_api_key = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SecureField(deepl_api_key_placeholder, text: $settings.deepl_api_key)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(UITextAutocapitalizationType.none)
|
||||
if settings.deepl_api_key != "" {
|
||||
Button(NSLocalizedString("Show API Key", comment: "Button to show the DeepL translation API key.")) {
|
||||
show_api_key = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if settings.deepl_api_key == "" {
|
||||
Link(NSLocalizedString("Get API Key", comment: "Button to navigate to DeepL website to get a translation API key."), destination: URL(string: "https://www.deepl.com/pro-api")!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TranslationSettingsView_Previews: PreviewProvider {
|
||||
|
46
damusTests/KeychainStorageTests.swift
Normal file
46
damusTests/KeychainStorageTests.swift
Normal file
@ -0,0 +1,46 @@
|
||||
//
|
||||
// KeychainStorageTests.swift
|
||||
// damusTests
|
||||
//
|
||||
// Created by Bryan Montz on 5/3/23.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import damus
|
||||
import Security
|
||||
|
||||
final class KeychainStorageTests: XCTestCase {
|
||||
@KeychainStorage(account: "test-keyname")
|
||||
var secret: String?
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
secret = nil
|
||||
}
|
||||
|
||||
func testWriteToKeychain() throws {
|
||||
// write a secret to the keychain using the property wrapper's setter
|
||||
secret = "super-secure-key"
|
||||
|
||||
// verify it exists in the keychain using the property wrapper's getter
|
||||
XCTAssertEqual(secret, "super-secure-key")
|
||||
|
||||
// verify it exists in the keychain directly
|
||||
let query = [
|
||||
kSecAttrService: "damus",
|
||||
kSecAttrAccount: "test-keyname",
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecReturnData: true,
|
||||
kSecMatchLimit: kSecMatchLimitOne
|
||||
] as [CFString: Any] as CFDictionary
|
||||
|
||||
var result: AnyObject?
|
||||
let status = SecItemCopyMatching(query, &result)
|
||||
XCTAssertEqual(status, errSecSuccess)
|
||||
|
||||
let data = try XCTUnwrap(result as? Data)
|
||||
let the_secret = String(data: data, encoding: .utf8)
|
||||
|
||||
XCTAssertEqual(the_secret, "super-secure-key")
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user