diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..b9a65a9 Binary files /dev/null and b/.DS_Store differ diff --git a/AutoFill iOS/AutoFill_iOS.entitlements b/AutoFill iOS/AutoFill_iOS.entitlements new file mode 100644 index 0000000..2f61d95 --- /dev/null +++ b/AutoFill iOS/AutoFill_iOS.entitlements @@ -0,0 +1,17 @@ + + + + + com.apple.developer.authentication-services.autofill-credential-provider + + com.apple.security.application-groups + + group.OpenSesame.ethanlipnik + + keychain-access-groups + + $(AppIdentifierPrefix)OpenSesame + $(AppIdentifierPrefix)com.ethanlipnik.OpenSesame + + + diff --git a/AutoFill iOS/Base.lproj/MainInterface.storyboard b/AutoFill iOS/Base.lproj/MainInterface.storyboard new file mode 100644 index 0000000..087369f --- /dev/null +++ b/AutoFill iOS/Base.lproj/MainInterface.storyboard @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutoFill iOS/CredentialProviderViewController.swift b/AutoFill iOS/CredentialProviderViewController.swift new file mode 100644 index 0000000..b280308 --- /dev/null +++ b/AutoFill iOS/CredentialProviderViewController.swift @@ -0,0 +1,107 @@ +// +// CredentialProviderViewController.swift +// AutoFill iOS +// +// Created by Ethan Lipnik on 8/18/21. +// + +import AuthenticationServices +import DomainParser +import KeychainAccess + +class CredentialProviderViewController: ASCredentialProviderViewController { + @IBOutlet weak var tableView: UITableView! + + let viewContext = PersistenceController.shared.container.viewContext + var accounts: [Account] = [] + var allAccounts: [Account] = [] + + /* + Implement this method if your extension supports showing credentials in the QuickType bar. + When the user selects a credential from your app, this method will be called with the + ASPasswordCredentialIdentity your app has previously saved to the ASCredentialIdentityStore. + Provide the password by completing the extension request with the associated ASPasswordCredential. + If using the credential would require showing custom UI for authenticating the user, cancel + the request with error code ASExtensionError.userInteractionRequired. + + override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) { + let databaseIsUnlocked = true + if (databaseIsUnlocked) { + let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") + self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) + } else { + self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue)) + } + } + */ + + /* + Implement this method if provideCredentialWithoutUserInteraction(for:) can fail with + ASExtensionError.userInteractionRequired. In this case, the system may present your extension's + UI and call this method. Show appropriate UI for authenticating the user then provide the password + by completing the extension request with the associated ASPasswordCredential. + + override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) { + } + */ + + override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) { + + } + + @IBAction func cancel(_ sender: AnyObject?) { + self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue)) + } +} + +extension CredentialProviderViewController: UITableViewDelegate, UITableViewDataSource { + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return section == 0 ? nil : "All Accounts" + } + + func numberOfSections(in tableView: UITableView) -> Int { + return accounts.isEmpty ? 1 : 2 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if section == 0 && !accounts.isEmpty { + return accounts.count + } else { + return allAccounts.count + } + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + var account: Account! + if indexPath.section == 0 && !accounts.isEmpty { + account = accounts[indexPath.item] + } else { + account = allAccounts[indexPath.item] + } + + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) + + cell.textLabel?.text = account.domain + cell.detailTextLabel?.text = account.username + + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + var account: Account! + if indexPath.section == 0 && !accounts.isEmpty { + account = accounts[indexPath.item] + } else { + account = allAccounts[indexPath.item] + } + + do { + let decryptedAccount = try decryptedAccount(account) + let passwordCredential = ASPasswordCredential(user: decryptedAccount.username, password: decryptedAccount.password) + self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) + } catch { + self.extensionContext.cancelRequest(withError: error) + } + } +} diff --git a/AutoFill iOS/Info.plist b/AutoFill iOS/Info.plist new file mode 100644 index 0000000..c0339b2 --- /dev/null +++ b/AutoFill iOS/Info.plist @@ -0,0 +1,13 @@ + + + + + NSExtension + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.authentication-services-credential-provider-ui + + + diff --git a/AutoFill macOS/AutoFill_macOS.entitlements b/AutoFill macOS/AutoFill_macOS.entitlements new file mode 100644 index 0000000..789d276 --- /dev/null +++ b/AutoFill macOS/AutoFill_macOS.entitlements @@ -0,0 +1,17 @@ + + + + + com.apple.developer.authentication-services.autofill-credential-provider + + com.apple.security.application-groups + + group.OpenSesame.ethanlipnik + + keychain-access-groups + + $(AppIdentifierPrefix)com.ethanlipnik.OpenSesame + $(AppIdentifierPrefix)OpenSesame + + + diff --git a/AutoFill macOS/Base.lproj/CredentialProviderViewController.xib b/AutoFill macOS/Base.lproj/CredentialProviderViewController.xib new file mode 100644 index 0000000..8aa6166 --- /dev/null +++ b/AutoFill macOS/Base.lproj/CredentialProviderViewController.xib @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutoFill macOS/CredentialProviderViewController.swift b/AutoFill macOS/CredentialProviderViewController.swift new file mode 100644 index 0000000..c73b630 --- /dev/null +++ b/AutoFill macOS/CredentialProviderViewController.swift @@ -0,0 +1,54 @@ +// +// CredentialProviderViewController.swift +// AutoFill macOS +// +// Created by Ethan Lipnik on 8/19/21. +// + +import AuthenticationServices + +class CredentialProviderViewController: ASCredentialProviderViewController { + + let viewContext = PersistenceController.shared.container.viewContext + var accounts: [Account] = [] + var allAccounts: [Account] = [] + + /* + Implement this method if your extension supports showing credentials in the QuickType bar. + When the user selects a credential from your app, this method will be called with the + ASPasswordCredentialIdentity your app has previously saved to the ASCredentialIdentityStore. + Provide the password by completing the extension request with the associated ASPasswordCredential. + If using the credential would require showing custom UI for authenticating the user, cancel + the request with error code ASExtensionError.userInteractionRequired. + + override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) { + let databaseIsUnlocked = true + if (databaseIsUnlocked) { + let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") + self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) + } else { + self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue)) + } + } + */ + + /* + Implement this method if provideCredentialWithoutUserInteraction(for:) can fail with + ASExtensionError.userInteractionRequired. In this case, the system may present your extension's + UI and call this method. Show appropriate UI for authenticating the user then provide the password + by completing the extension request with the associated ASPasswordCredential. + + override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) { + } + */ + + @IBAction func cancel(_ sender: AnyObject?) { + self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue)) + } + + @IBAction func passwordSelected(_ sender: AnyObject?) { + let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") + self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) + } + +} diff --git a/AutoFill macOS/Info.plist b/AutoFill macOS/Info.plist new file mode 100644 index 0000000..9f7104f --- /dev/null +++ b/AutoFill macOS/Info.plist @@ -0,0 +1,18 @@ + + + + + NSExtension + + NSExtensionAttributes + + ASCredentialProviderExtensionShowsConfigurationUI + + + NSExtensionPointIdentifier + com.apple.authentication-services-credential-provider-ui + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).CredentialProviderViewController + + + diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..3919042 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Ethan Lipnik + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/OpenSesame (iOS).entitlements b/OpenSesame (iOS).entitlements new file mode 100644 index 0000000..daa48dd --- /dev/null +++ b/OpenSesame (iOS).entitlements @@ -0,0 +1,27 @@ + + + + + aps-environment + development + com.apple.developer.authentication-services.autofill-credential-provider + + com.apple.developer.icloud-container-identifiers + + iCloud.com.ethanlipnik.OpenSesame + + com.apple.developer.icloud-services + + CloudKit + + com.apple.security.application-groups + + group.OpenSesame.ethanlipnik + + keychain-access-groups + + $(AppIdentifierPrefix)com.ethanlipnik.OpenSesame + $(AppIdentifierPrefix)OpenSesame + + + diff --git a/OpenSesame (macOS).entitlements b/OpenSesame (macOS).entitlements new file mode 100644 index 0000000..ccf2e71 --- /dev/null +++ b/OpenSesame (macOS).entitlements @@ -0,0 +1,35 @@ + + + + + com.apple.developer.aps-environment + development + com.apple.developer.authentication-services.autofill-credential-provider + + com.apple.developer.icloud-container-identifiers + + iCloud.com.ethanlipnik.OpenSesame + + com.apple.developer.icloud-services + + CloudKit + + com.apple.security.app-sandbox + + com.apple.security.application-groups + + group.OpenSesame.ethanlipnik + + com.apple.security.files.user-selected.read-only + + com.apple.security.network.client + + com.apple.security.network.server + + keychain-access-groups + + $(AppIdentifierPrefix)com.ethanlipnik.OpenSesame + $(AppIdentifierPrefix)OpenSesame + + + diff --git a/OpenSesame--iOS--Info.plist b/OpenSesame--iOS--Info.plist new file mode 100644 index 0000000..6dcd08a --- /dev/null +++ b/OpenSesame--iOS--Info.plist @@ -0,0 +1,43 @@ + + + + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + openSesame + + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + otpauth + + + + ITSAppUsesNonExemptEncryption + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UIBackgroundModes + + fetch + remote-notification + + NSLocalNetworkUsageDescription + Easily and securely share your passwords with people nearby. + NSBonjourServices + + _OpenSesame._tcp + + + diff --git a/OpenSesame--macOS--Info.plist b/OpenSesame--macOS--Info.plist new file mode 100644 index 0000000..55faabf --- /dev/null +++ b/OpenSesame--macOS--Info.plist @@ -0,0 +1,33 @@ + + + + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + openSesame + + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + otpauth + + + + NSAppTransportSecurity + + NSBonjourServices + + _OpenSesame._tcp + + NSLocalNetworkUsageDescription + Easily and securely share your passwords with people nearby. + + diff --git a/OpenSesame.xcodeproj/project.pbxproj b/OpenSesame.xcodeproj/project.pbxproj new file mode 100644 index 0000000..7eaf12e --- /dev/null +++ b/OpenSesame.xcodeproj/project.pbxproj @@ -0,0 +1,1748 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + AA05CCB626CD7A0800E27F6D /* FaviconFinder in Frameworks */ = {isa = PBXBuildFile; productRef = AA05CCB526CD7A0800E27F6D /* FaviconFinder */; }; + AA05CCB826CD7A0D00E27F6D /* FaviconFinder in Frameworks */ = {isa = PBXBuildFile; productRef = AA05CCB726CD7A0D00E27F6D /* FaviconFinder */; }; + AA05CCCC26CDA6BE00E27F6D /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = AA05CCCB26CDA6BE00E27F6D /* KeychainAccess */; }; + AA05CCCE26CDA6C300E27F6D /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = AA05CCCD26CDA6C300E27F6D /* KeychainAccess */; }; + AA05CCE126CDDA5200E27F6D /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA1F6A2626CD68D4004E1A81 /* AuthenticationServices.framework */; }; + AA05CCE426CDDA5200E27F6D /* CredentialProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA05CCE326CDDA5200E27F6D /* CredentialProviderViewController.swift */; }; + AA05CCE726CDDA5200E27F6D /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA05CCE526CDDA5200E27F6D /* MainInterface.storyboard */; }; + AA05CCEC26CDDA5200E27F6D /* AutoFill iOS.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = AA05CCE026CDDA5200E27F6D /* AutoFill iOS.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + AA05CCF426CDDBFF00E27F6D /* DomainParser in Frameworks */ = {isa = PBXBuildFile; productRef = AA05CCF326CDDBFF00E27F6D /* DomainParser */; }; + AA05CCF526CDDD6B00E27F6D /* OpenSesame.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = AA8B500326CD641C003C92AE /* OpenSesame.xcdatamodeld */; }; + AA05CCFF26CDDDA300E27F6D /* CryptoSecurityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA05CCFE26CDDD9D00E27F6D /* CryptoSecurityService.swift */; }; + AA05CD0026CDDDA300E27F6D /* CryptoSecurityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA05CCFE26CDDD9D00E27F6D /* CryptoSecurityService.swift */; }; + AA05CD0126CDDDA400E27F6D /* CryptoSecurityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA05CCFE26CDDD9D00E27F6D /* CryptoSecurityService.swift */; }; + AA05CD0226CDDDB300E27F6D /* UserAuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA05CCD426CDB85000E27F6D /* UserAuthenticationService.swift */; }; + AA05CD0326CDDDB400E27F6D /* UserAuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA05CCD426CDB85000E27F6D /* UserAuthenticationService.swift */; }; + AA05CD0426CDDDB400E27F6D /* UserAuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA05CCD426CDB85000E27F6D /* UserAuthenticationService.swift */; }; + AA05CD0626CDE08C00E27F6D /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = AA05CD0526CDE08C00E27F6D /* KeychainAccess */; }; + AA12A79F26D0A5DB000418C7 /* CSV in Frameworks */ = {isa = PBXBuildFile; productRef = AA12A79E26D0A5DB000418C7 /* CSV */; }; + AA12A7A126D0A5E2000418C7 /* CSV in Frameworks */ = {isa = PBXBuildFile; productRef = AA12A7A026D0A5E2000418C7 /* CSV */; }; + AA1F6A2526CD68CF004E1A81 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA1F6A2426CD68CF004E1A81 /* AuthenticationServices.framework */; }; + AA1F6A2726CD68D4004E1A81 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA1F6A2626CD68D4004E1A81 /* AuthenticationServices.framework */; }; + AA1F6A2926CD6993004E1A81 /* VaultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA1F6A2826CD6993004E1A81 /* VaultView.swift */; }; + AA1F6A2A26CD6993004E1A81 /* VaultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA1F6A2826CD6993004E1A81 /* VaultView.swift */; }; + AA32ADAB26D17692006791DA /* DomainParser in Frameworks */ = {isa = PBXBuildFile; productRef = AA32ADAA26D17692006791DA /* DomainParser */; }; + AA32ADAD26D17697006791DA /* DomainParser in Frameworks */ = {isa = PBXBuildFile; productRef = AA32ADAC26D17697006791DA /* DomainParser */; }; + AA340B6C26D2BE33003A2CF9 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8B500726CD641C003C92AE /* Persistence.swift */; }; + AA340B6D26D2BE33003A2CF9 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8B500726CD641C003C92AE /* Persistence.swift */; }; + AA41467726D3451900254812 /* SwiftOTP in Frameworks */ = {isa = PBXBuildFile; productRef = AA41467626D3451900254812 /* SwiftOTP */; }; + AA49EA0426D3EB0100B12838 /* AppDelegate iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA0226D3EB0100B12838 /* AppDelegate iOS.swift */; }; + AA49EA0626D3EB0400B12838 /* AppDelegate macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA0326D3EB0100B12838 /* AppDelegate macOS.swift */; }; + AA49EA0926D3EB1600B12838 /* ContentView+Add.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA0726D3EB1600B12838 /* ContentView+Add.swift */; }; + AA49EA0A26D3EB1600B12838 /* ContentView+Add.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA0726D3EB1600B12838 /* ContentView+Add.swift */; }; + AA49EA0B26D3EB1600B12838 /* ContentView+List.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA0826D3EB1600B12838 /* ContentView+List.swift */; }; + AA49EA0C26D3EB1600B12838 /* ContentView+List.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA0826D3EB1600B12838 /* ContentView+List.swift */; }; + AA49EA1126D3EB2600B12838 /* LockView+CreatePassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA0D26D3EB2500B12838 /* LockView+CreatePassword.swift */; }; + AA49EA1226D3EB2600B12838 /* LockView+CreatePassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA0D26D3EB2500B12838 /* LockView+CreatePassword.swift */; }; + AA49EA1326D3EB2600B12838 /* LockView+Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA0E26D3EB2600B12838 /* LockView+Functions.swift */; }; + AA49EA1426D3EB2600B12838 /* LockView+Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA0E26D3EB2600B12838 /* LockView+Functions.swift */; }; + AA49EA1526D3EB2600B12838 /* LockView+TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA0F26D3EB2600B12838 /* LockView+TextField.swift */; }; + AA49EA1626D3EB2600B12838 /* LockView+TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA0F26D3EB2600B12838 /* LockView+TextField.swift */; }; + AA49EA1726D3EB2600B12838 /* LockView+UnlockButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA1026D3EB2600B12838 /* LockView+UnlockButtons.swift */; }; + AA49EA1826D3EB2600B12838 /* LockView+UnlockButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA1026D3EB2600B12838 /* LockView+UnlockButtons.swift */; }; + AA49EA1A26D3EB4300B12838 /* ImportView+Import.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA1926D3EB4300B12838 /* ImportView+Import.swift */; }; + AA49EA1B26D3EB4300B12838 /* ImportView+Import.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA1926D3EB4300B12838 /* ImportView+Import.swift */; }; + AA49EA1D26D3EB4E00B12838 /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA1C26D3EB4E00B12838 /* ExportView.swift */; }; + AA49EA1E26D3EB4E00B12838 /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA1C26D3EB4E00B12838 /* ExportView.swift */; }; + AA49EA2126D3EB5A00B12838 /* VaultView+Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA1F26D3EB5A00B12838 /* VaultView+Item.swift */; }; + AA49EA2226D3EB5A00B12838 /* VaultView+Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA1F26D3EB5A00B12838 /* VaultView+Item.swift */; }; + AA49EA2326D3EB5A00B12838 /* VaultView+List.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA2026D3EB5A00B12838 /* VaultView+List.swift */; }; + AA49EA2426D3EB5A00B12838 /* VaultView+List.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA2026D3EB5A00B12838 /* VaultView+List.swift */; }; + AA49EA2626D3EB6400B12838 /* NewAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA2526D3EB6400B12838 /* NewAccountView.swift */; }; + AA49EA2726D3EB6400B12838 /* NewAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA2526D3EB6400B12838 /* NewAccountView.swift */; }; + AA49EA2926D3EB7500B12838 /* AccountView+Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA2826D3EB7500B12838 /* AccountView+Content.swift */; }; + AA49EA2A26D3EB7500B12838 /* AccountView+Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA2826D3EB7500B12838 /* AccountView+Content.swift */; }; + AA49EA2E26D3EB8600B12838 /* AccountDetailView+EmailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA2B26D3EB8600B12838 /* AccountDetailView+EmailView.swift */; }; + AA49EA2F26D3EB8600B12838 /* AccountDetailView+EmailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA2B26D3EB8600B12838 /* AccountDetailView+EmailView.swift */; }; + AA49EA3026D3EB8600B12838 /* AccountDetailView+OTPView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA2C26D3EB8600B12838 /* AccountDetailView+OTPView.swift */; }; + AA49EA3126D3EB8600B12838 /* AccountDetailView+OTPView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA2C26D3EB8600B12838 /* AccountDetailView+OTPView.swift */; }; + AA49EA3226D3EB8600B12838 /* AccountDetailView+PasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA2D26D3EB8600B12838 /* AccountDetailView+PasswordView.swift */; }; + AA49EA3326D3EB8600B12838 /* AccountDetailView+PasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA2D26D3EB8600B12838 /* AccountDetailView+PasswordView.swift */; }; + AA49EA3726D3EB9700B12838 /* MultipeerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA3426D3EB9700B12838 /* MultipeerService.swift */; }; + AA49EA3826D3EB9700B12838 /* MultipeerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA3426D3EB9700B12838 /* MultipeerService.swift */; }; + AA49EA3926D3EB9700B12838 /* MultipeerShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA3526D3EB9700B12838 /* MultipeerShareSheet.swift */; }; + AA49EA3A26D3EB9700B12838 /* MultipeerShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA3526D3EB9700B12838 /* MultipeerShareSheet.swift */; }; + AA49EA3B26D3EB9700B12838 /* ShareableAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA3626D3EB9700B12838 /* ShareableAccount.swift */; }; + AA49EA3C26D3EB9700B12838 /* ShareableAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA3626D3EB9700B12838 /* ShareableAccount.swift */; }; + AA49EA3F26D3EBA300B12838 /* Data+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA3D26D3EBA300B12838 /* Data+Misc.swift */; }; + AA49EA4026D3EBA300B12838 /* Data+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA3D26D3EBA300B12838 /* Data+Misc.swift */; }; + AA49EA4126D3EBA300B12838 /* Date+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA3E26D3EBA300B12838 /* Date+Misc.swift */; }; + AA49EA4226D3EBA300B12838 /* Date+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA3E26D3EBA300B12838 /* Date+Misc.swift */; }; + AA49EA4626D3EBCE00B12838 /* Data+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA3D26D3EBA300B12838 /* Data+Misc.swift */; }; + AA49EA4726D3EBCE00B12838 /* Date+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA3E26D3EBA300B12838 /* Date+Misc.swift */; }; + AA49EA4826D3EBCE00B12838 /* Data+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA3D26D3EBA300B12838 /* Data+Misc.swift */; }; + AA49EA4926D3EBCE00B12838 /* Date+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA3E26D3EBA300B12838 /* Date+Misc.swift */; }; + AA49EA4D26D3EBE100B12838 /* SettingsView+GeneralView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA4A26D3EBE100B12838 /* SettingsView+GeneralView.swift */; }; + AA49EA4F26D3EBE100B12838 /* SettingsView+MenuBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA4B26D3EBE100B12838 /* SettingsView+MenuBarView.swift */; }; + AA49EA5026D3EBF300B12838 /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA4326D3EBB000B12838 /* Credentials.swift */; }; + AA49EA5126D3EBF300B12838 /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA49EA4326D3EBB000B12838 /* Credentials.swift */; }; + AA519A5E26CEE5B700C1E354 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA1F6A2626CD68D4004E1A81 /* AuthenticationServices.framework */; }; + AA519A6126CEE5B700C1E354 /* CredentialProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA519A6026CEE5B700C1E354 /* CredentialProviderViewController.swift */; }; + AA519A6426CEE5B700C1E354 /* CredentialProviderViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA519A6226CEE5B700C1E354 /* CredentialProviderViewController.xib */; }; + AA519A6926CEE5B700C1E354 /* AutoFill macOS.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = AA519A5D26CEE5B700C1E354 /* AutoFill macOS.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + AA519A7326CEE6CA00C1E354 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = AA519A7226CEE6CA00C1E354 /* KeychainAccess */; }; + AA519A7526CEE6CA00C1E354 /* DomainParser in Frameworks */ = {isa = PBXBuildFile; productRef = AA519A7426CEE6CA00C1E354 /* DomainParser */; }; + AA519A7826CEE6D900C1E354 /* OpenSesame.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = AA8B500326CD641C003C92AE /* OpenSesame.xcdatamodeld */; }; + AA519A7A26CEE6E500C1E354 /* CryptoSecurityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA05CCFE26CDDD9D00E27F6D /* CryptoSecurityService.swift */; }; + AA519A7B26CEE6E700C1E354 /* UserAuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA05CCD426CDB85000E27F6D /* UserAuthenticationService.swift */; }; + AA519A8426CF2A5000C1E354 /* LockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA519A8026CF283C00C1E354 /* LockView.swift */; }; + AA519A8526CF2A5000C1E354 /* LockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA519A8026CF283C00C1E354 /* LockView.swift */; }; + AA519A8626CF453300C1E354 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA519A7F26CF283C00C1E354 /* AccountView.swift */; }; + AA519A8726CF453300C1E354 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA519A7F26CF283C00C1E354 /* AccountView.swift */; }; + AA519A9126CF4B3800C1E354 /* OTPAuthenticatorService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA519A9026CF4B3200C1E354 /* OTPAuthenticatorService.swift */; }; + AA519A9226CF4B3800C1E354 /* OTPAuthenticatorService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA519A9026CF4B3200C1E354 /* OTPAuthenticatorService.swift */; }; + AA8B501D26CD641D003C92AE /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8B501C26CD641D003C92AE /* Tests_iOS.swift */; }; + AA8B501F26CD641D003C92AE /* Tests_iOSLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8B501E26CD641D003C92AE /* Tests_iOSLaunchTests.swift */; }; + AA8B502926CD641D003C92AE /* Tests_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8B502826CD641D003C92AE /* Tests_macOS.swift */; }; + AA8B502B26CD641D003C92AE /* Tests_macOSLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8B502A26CD641D003C92AE /* Tests_macOSLaunchTests.swift */; }; + AA8B502C26CD641D003C92AE /* OpenSesame.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = AA8B500326CD641C003C92AE /* OpenSesame.xcdatamodeld */; }; + AA8B502D26CD641D003C92AE /* OpenSesame.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = AA8B500326CD641C003C92AE /* OpenSesame.xcdatamodeld */; }; + AA8B502E26CD641D003C92AE /* OpenSesameApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8B500526CD641C003C92AE /* OpenSesameApp.swift */; }; + AA8B502F26CD641D003C92AE /* OpenSesameApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8B500526CD641C003C92AE /* OpenSesameApp.swift */; }; + AA8B503026CD641D003C92AE /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8B500626CD641C003C92AE /* ContentView.swift */; }; + AA8B503126CD641D003C92AE /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8B500626CD641C003C92AE /* ContentView.swift */; }; + AA8B503226CD641D003C92AE /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8B500726CD641C003C92AE /* Persistence.swift */; }; + AA8B503326CD641D003C92AE /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8B500726CD641C003C92AE /* Persistence.swift */; }; + AA8B503426CD641D003C92AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA8B500826CD641D003C92AE /* Assets.xcassets */; }; + AA8B503526CD641D003C92AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA8B500826CD641D003C92AE /* Assets.xcassets */; }; + AA8B504726CD6457003C92AE /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA8B504626CD6457003C92AE /* CloudKit.framework */; }; + AA8B504A26CD6468003C92AE /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA8B504926CD6468003C92AE /* CloudKit.framework */; }; + AA980E3D26D2DF05001CFF4E /* MultipeerKit in Frameworks */ = {isa = PBXBuildFile; productRef = AA980E3C26D2DF05001CFF4E /* MultipeerKit */; }; + AA980E4126D2DF0C001CFF4E /* MultipeerKit in Frameworks */ = {isa = PBXBuildFile; productRef = AA980E4026D2DF0C001CFF4E /* MultipeerKit */; }; + AAD3DB9026D3258900531E92 /* FaviconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DB8F26D3258900531E92 /* FaviconView.swift */; }; + AAD3DB9126D3258900531E92 /* FaviconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DB8F26D3258900531E92 /* FaviconView.swift */; }; + AAD3DBAA26D3351400531E92 /* ImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DB8126D30D3300531E92 /* ImportView.swift */; }; + AAD3DBAB26D3351400531E92 /* ImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DB8126D30D3300531E92 /* ImportView.swift */; }; + AAD3DBC826D33D2500531E92 /* AccountView+AccountDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DBC726D33D1D00531E92 /* AccountView+AccountDetailView.swift */; }; + AAD3DBC926D33D2600531E92 /* AccountView+AccountDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DBC726D33D1D00531E92 /* AccountView+AccountDetailView.swift */; }; + AAD3DBDD26D3417500531E92 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DBDC26D3417500531E92 /* MainView.swift */; }; + AAD3DBDE26D3417500531E92 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DBDC26D3417500531E92 /* MainView.swift */; }; + AAD3DBE226D341D300531E92 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE4A8BC26CDFB3200D1C58E /* SettingsView.swift */; }; + AAD3DBE826D342B800531E92 /* URL+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DBE726D342AB00531E92 /* URL+Misc.swift */; }; + AAD3DBE926D342B800531E92 /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DBE626D342AB00531E92 /* String+Misc.swift */; }; + AAD3DBEA26D342B800531E92 /* URL+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DBE726D342AB00531E92 /* URL+Misc.swift */; }; + AAD3DBEB26D342B800531E92 /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DBE626D342AB00531E92 /* String+Misc.swift */; }; + AAD3DBEC26D342B900531E92 /* URL+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DBE726D342AB00531E92 /* URL+Misc.swift */; }; + AAD3DBED26D342B900531E92 /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DBE626D342AB00531E92 /* String+Misc.swift */; }; + AAD3DBEE26D342B900531E92 /* URL+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DBE726D342AB00531E92 /* URL+Misc.swift */; }; + AAD3DBEF26D342B900531E92 /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3DBE626D342AB00531E92 /* String+Misc.swift */; }; + AAD3DBFC26D3446500531E92 /* SwiftOTP in Frameworks */ = {isa = PBXBuildFile; productRef = AAD3DBFB26D3446500531E92 /* SwiftOTP */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + AA05CCEA26CDDA5200E27F6D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = AA8B4FFE26CD641C003C92AE /* Project object */; + proxyType = 1; + remoteGlobalIDString = AA05CCDF26CDDA5200E27F6D; + remoteInfo = "AutoFill iOS"; + }; + AA519A6726CEE5B700C1E354 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = AA8B4FFE26CD641C003C92AE /* Project object */; + proxyType = 1; + remoteGlobalIDString = AA519A5C26CEE5B700C1E354; + remoteInfo = "AutoFill macOS"; + }; + AA8B501926CD641D003C92AE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = AA8B4FFE26CD641C003C92AE /* Project object */; + proxyType = 1; + remoteGlobalIDString = AA8B500C26CD641D003C92AE; + remoteInfo = "OpenSesame (iOS)"; + }; + AA8B502526CD641D003C92AE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = AA8B4FFE26CD641C003C92AE /* Project object */; + proxyType = 1; + remoteGlobalIDString = AA8B501226CD641D003C92AE; + remoteInfo = "OpenSesame (macOS)"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + AA05CCF026CDDA5200E27F6D /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + AA05CCEC26CDDA5200E27F6D /* AutoFill iOS.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + AA519A6A26CEE5B700C1E354 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + AA519A6926CEE5B700C1E354 /* AutoFill macOS.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + AA980E4526D2DF0C001CFF4E /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + AA980E4D26D2DF75001CFF4E /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + AA05CCD426CDB85000E27F6D /* UserAuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAuthenticationService.swift; sourceTree = ""; }; + AA05CCE026CDDA5200E27F6D /* AutoFill iOS.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "AutoFill iOS.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + AA05CCE326CDDA5200E27F6D /* CredentialProviderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderViewController.swift; sourceTree = ""; }; + AA05CCE626CDDA5200E27F6D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + AA05CCE826CDDA5200E27F6D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AA05CCE926CDDA5200E27F6D /* AutoFill_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AutoFill_iOS.entitlements; sourceTree = ""; }; + AA05CCFE26CDDD9D00E27F6D /* CryptoSecurityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSecurityService.swift; sourceTree = ""; }; + AA1F6A2426CD68CF004E1A81 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; + AA1F6A2626CD68D4004E1A81 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.0.sdk/System/Library/Frameworks/AuthenticationServices.framework; sourceTree = DEVELOPER_DIR; }; + AA1F6A2826CD6993004E1A81 /* VaultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultView.swift; sourceTree = ""; }; + AA49EA0026D3EAF000B12838 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + AA49EA0126D3EAF000B12838 /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; }; + AA49EA0226D3EB0100B12838 /* AppDelegate iOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppDelegate iOS.swift"; sourceTree = ""; }; + AA49EA0326D3EB0100B12838 /* AppDelegate macOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppDelegate macOS.swift"; sourceTree = ""; }; + AA49EA0726D3EB1600B12838 /* ContentView+Add.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ContentView+Add.swift"; sourceTree = ""; }; + AA49EA0826D3EB1600B12838 /* ContentView+List.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ContentView+List.swift"; sourceTree = ""; }; + AA49EA0D26D3EB2500B12838 /* LockView+CreatePassword.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "LockView+CreatePassword.swift"; sourceTree = ""; }; + AA49EA0E26D3EB2600B12838 /* LockView+Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "LockView+Functions.swift"; sourceTree = ""; }; + AA49EA0F26D3EB2600B12838 /* LockView+TextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "LockView+TextField.swift"; sourceTree = ""; }; + AA49EA1026D3EB2600B12838 /* LockView+UnlockButtons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "LockView+UnlockButtons.swift"; sourceTree = ""; }; + AA49EA1926D3EB4300B12838 /* ImportView+Import.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ImportView+Import.swift"; sourceTree = ""; }; + AA49EA1C26D3EB4E00B12838 /* ExportView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportView.swift; sourceTree = ""; }; + AA49EA1F26D3EB5A00B12838 /* VaultView+Item.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "VaultView+Item.swift"; sourceTree = ""; }; + AA49EA2026D3EB5A00B12838 /* VaultView+List.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "VaultView+List.swift"; sourceTree = ""; }; + AA49EA2526D3EB6400B12838 /* NewAccountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewAccountView.swift; sourceTree = ""; }; + AA49EA2826D3EB7500B12838 /* AccountView+Content.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AccountView+Content.swift"; sourceTree = ""; }; + AA49EA2B26D3EB8600B12838 /* AccountDetailView+EmailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AccountDetailView+EmailView.swift"; sourceTree = ""; }; + AA49EA2C26D3EB8600B12838 /* AccountDetailView+OTPView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AccountDetailView+OTPView.swift"; sourceTree = ""; }; + AA49EA2D26D3EB8600B12838 /* AccountDetailView+PasswordView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AccountDetailView+PasswordView.swift"; sourceTree = ""; }; + AA49EA3426D3EB9700B12838 /* MultipeerService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipeerService.swift; sourceTree = ""; }; + AA49EA3526D3EB9700B12838 /* MultipeerShareSheet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipeerShareSheet.swift; sourceTree = ""; }; + AA49EA3626D3EB9700B12838 /* ShareableAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareableAccount.swift; sourceTree = ""; }; + AA49EA3D26D3EBA300B12838 /* Data+Misc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Misc.swift"; sourceTree = ""; }; + AA49EA3E26D3EBA300B12838 /* Date+Misc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Misc.swift"; sourceTree = ""; }; + AA49EA4326D3EBB000B12838 /* Credentials.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = ""; }; + AA49EA4A26D3EBE100B12838 /* SettingsView+GeneralView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SettingsView+GeneralView.swift"; sourceTree = ""; }; + AA49EA4B26D3EBE100B12838 /* SettingsView+MenuBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SettingsView+MenuBarView.swift"; sourceTree = ""; }; + AA519A5D26CEE5B700C1E354 /* AutoFill macOS.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "AutoFill macOS.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + AA519A6026CEE5B700C1E354 /* CredentialProviderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderViewController.swift; sourceTree = ""; }; + AA519A6326CEE5B700C1E354 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/CredentialProviderViewController.xib; sourceTree = ""; }; + AA519A6526CEE5B700C1E354 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AA519A6626CEE5B700C1E354 /* AutoFill_macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AutoFill_macOS.entitlements; sourceTree = ""; }; + AA519A7F26CF283C00C1E354 /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = ""; }; + AA519A8026CF283C00C1E354 /* LockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockView.swift; sourceTree = ""; }; + AA519A9026CF4B3200C1E354 /* OTPAuthenticatorService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTPAuthenticatorService.swift; sourceTree = ""; }; + AA8B500426CD641C003C92AE /* Shared.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Shared.xcdatamodel; sourceTree = ""; }; + AA8B500526CD641C003C92AE /* OpenSesameApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSesameApp.swift; sourceTree = ""; }; + AA8B500626CD641C003C92AE /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + AA8B500726CD641C003C92AE /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; + AA8B500826CD641D003C92AE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + AA8B500D26CD641D003C92AE /* OpenSesame.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenSesame.app; sourceTree = BUILT_PRODUCTS_DIR; }; + AA8B501326CD641D003C92AE /* OpenSesame.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenSesame.app; sourceTree = BUILT_PRODUCTS_DIR; }; + AA8B501826CD641D003C92AE /* Tests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + AA8B501C26CD641D003C92AE /* Tests_iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOS.swift; sourceTree = ""; }; + AA8B501E26CD641D003C92AE /* Tests_iOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOSLaunchTests.swift; sourceTree = ""; }; + AA8B502426CD641D003C92AE /* Tests macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + AA8B502826CD641D003C92AE /* Tests_macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOS.swift; sourceTree = ""; }; + AA8B502A26CD641D003C92AE /* Tests_macOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOSLaunchTests.swift; sourceTree = ""; }; + AA8B504426CD6454003C92AE /* OpenSesame (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "OpenSesame (iOS).entitlements"; sourceTree = ""; }; + AA8B504626CD6457003C92AE /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.0.sdk/System/Library/Frameworks/CloudKit.framework; sourceTree = DEVELOPER_DIR; }; + AA8B504826CD6465003C92AE /* OpenSesame (macOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "OpenSesame (macOS).entitlements"; sourceTree = ""; }; + AA8B504926CD6468003C92AE /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; + AA8B504B26CD646F003C92AE /* OpenSesame--iOS--Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "OpenSesame--iOS--Info.plist"; sourceTree = ""; }; + AA8B504C26CD6596003C92AE /* OpenSesame--macOS--Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "OpenSesame--macOS--Info.plist"; sourceTree = ""; }; + AAD3DB8126D30D3300531E92 /* ImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportView.swift; sourceTree = ""; }; + AAD3DB8F26D3258900531E92 /* FaviconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconView.swift; sourceTree = ""; }; + AAD3DBC726D33D1D00531E92 /* AccountView+AccountDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountView+AccountDetailView.swift"; sourceTree = ""; }; + AAD3DBDC26D3417500531E92 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; + AAD3DBE626D342AB00531E92 /* String+Misc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Misc.swift"; sourceTree = ""; }; + AAD3DBE726D342AB00531E92 /* URL+Misc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Misc.swift"; sourceTree = ""; }; + AAE4A8BC26CDFB3200D1C58E /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + AA05CCDD26CDDA5200E27F6D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AA05CCE126CDDA5200E27F6D /* AuthenticationServices.framework in Frameworks */, + AA05CCF426CDDBFF00E27F6D /* DomainParser in Frameworks */, + AA05CD0626CDE08C00E27F6D /* KeychainAccess in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA519A5A26CEE5B700C1E354 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AA519A7526CEE6CA00C1E354 /* DomainParser in Frameworks */, + AA519A7326CEE6CA00C1E354 /* KeychainAccess in Frameworks */, + AA519A5E26CEE5B700C1E354 /* AuthenticationServices.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA8B500A26CD641D003C92AE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AA32ADAD26D17697006791DA /* DomainParser in Frameworks */, + AA05CCB626CD7A0800E27F6D /* FaviconFinder in Frameworks */, + AA1F6A2726CD68D4004E1A81 /* AuthenticationServices.framework in Frameworks */, + AA8B504726CD6457003C92AE /* CloudKit.framework in Frameworks */, + AA05CCCC26CDA6BE00E27F6D /* KeychainAccess in Frameworks */, + AA12A79F26D0A5DB000418C7 /* CSV in Frameworks */, + AAD3DBFC26D3446500531E92 /* SwiftOTP in Frameworks */, + AA980E3D26D2DF05001CFF4E /* MultipeerKit in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA8B501026CD641D003C92AE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AA32ADAB26D17692006791DA /* DomainParser in Frameworks */, + AA05CCB826CD7A0D00E27F6D /* FaviconFinder in Frameworks */, + AA1F6A2526CD68CF004E1A81 /* AuthenticationServices.framework in Frameworks */, + AA8B504A26CD6468003C92AE /* CloudKit.framework in Frameworks */, + AA05CCCE26CDA6C300E27F6D /* KeychainAccess in Frameworks */, + AA12A7A126D0A5E2000418C7 /* CSV in Frameworks */, + AA41467726D3451900254812 /* SwiftOTP in Frameworks */, + AA980E4126D2DF0C001CFF4E /* MultipeerKit in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA8B501526CD641D003C92AE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA8B502126CD641D003C92AE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + AA02830326D04B13004FCEA7 /* AppDelegate */ = { + isa = PBXGroup; + children = ( + AA49EA0226D3EB0100B12838 /* AppDelegate iOS.swift */, + AA49EA0326D3EB0100B12838 /* AppDelegate macOS.swift */, + ); + path = AppDelegate; + sourceTree = ""; + }; + AA05CCCF26CDB16A00E27F6D /* Vault */ = { + isa = PBXGroup; + children = ( + AA49EA2526D3EB6400B12838 /* NewAccountView.swift */, + AAD3DBB126D3365100531E92 /* VaultView */, + ); + path = Vault; + sourceTree = ""; + }; + AA05CCD326CDB84F00E27F6D /* Services */ = { + isa = PBXGroup; + children = ( + AA519A9026CF4B3200C1E354 /* OTPAuthenticatorService.swift */, + AA05CCD426CDB85000E27F6D /* UserAuthenticationService.swift */, + AA05CCFE26CDDD9D00E27F6D /* CryptoSecurityService.swift */, + ); + path = Services; + sourceTree = ""; + }; + AA05CCE226CDDA5200E27F6D /* AutoFill iOS */ = { + isa = PBXGroup; + children = ( + AA05CCE326CDDA5200E27F6D /* CredentialProviderViewController.swift */, + AA05CCE526CDDA5200E27F6D /* MainInterface.storyboard */, + AA05CCE826CDDA5200E27F6D /* Info.plist */, + AA05CCE926CDDA5200E27F6D /* AutoFill_iOS.entitlements */, + ); + path = "AutoFill iOS"; + sourceTree = ""; + }; + AA519A5F26CEE5B700C1E354 /* AutoFill macOS */ = { + isa = PBXGroup; + children = ( + AA519A6026CEE5B700C1E354 /* CredentialProviderViewController.swift */, + AA519A6226CEE5B700C1E354 /* CredentialProviderViewController.xib */, + AA519A6526CEE5B700C1E354 /* Info.plist */, + AA519A6626CEE5B700C1E354 /* AutoFill_macOS.entitlements */, + ); + path = "AutoFill macOS"; + sourceTree = ""; + }; + AA519A6E26CEE5DC00C1E354 /* AutoFill */ = { + isa = PBXGroup; + children = ( + AA49EA4326D3EBB000B12838 /* Credentials.swift */, + ); + path = AutoFill; + sourceTree = ""; + }; + AA519A7E26CF283C00C1E354 /* Views */ = { + isa = PBXGroup; + children = ( + AAD3DBDC26D3417500531E92 /* MainView.swift */, + AAD3DB8F26D3258900531E92 /* FaviconView.swift */, + AAD3DB9F26D330A400531E92 /* ContentView */, + AAD3DB9226D3265700531E92 /* LockView */, + AAD3DB8026D30D3300531E92 /* ImportExport */, + AA05CCCF26CDB16A00E27F6D /* Vault */, + AAD3DBB726D33B6400531E92 /* AccountView */, + AAE4A8BB26CDFB3200D1C58E /* SettingsView */, + ); + path = Views; + sourceTree = ""; + }; + AA8B4FFD26CD641C003C92AE = { + isa = PBXGroup; + children = ( + AA49EA0126D3EAF000B12838 /* LICENSE.md */, + AA49EA0026D3EAF000B12838 /* README.md */, + AA8B504C26CD6596003C92AE /* OpenSesame--macOS--Info.plist */, + AA8B504B26CD646F003C92AE /* OpenSesame--iOS--Info.plist */, + AA8B504826CD6465003C92AE /* OpenSesame (macOS).entitlements */, + AA8B504426CD6454003C92AE /* OpenSesame (iOS).entitlements */, + AA8B500226CD641C003C92AE /* Shared */, + AA8B501B26CD641D003C92AE /* Tests iOS */, + AA8B502726CD641D003C92AE /* Tests macOS */, + AA05CCE226CDDA5200E27F6D /* AutoFill iOS */, + AA519A5F26CEE5B700C1E354 /* AutoFill macOS */, + AA8B500E26CD641D003C92AE /* Products */, + AA8B504526CD6457003C92AE /* Frameworks */, + ); + sourceTree = ""; + }; + AA8B500226CD641C003C92AE /* Shared */ = { + isa = PBXGroup; + children = ( + AA8B500526CD641C003C92AE /* OpenSesameApp.swift */, + AA02830326D04B13004FCEA7 /* AppDelegate */, + AA519A7E26CF283C00C1E354 /* Views */, + AA980E4626D2DF11001CFF4E /* Multipeer */, + AAD3DBE526D342AB00531E92 /* Extensions */, + AA8B500726CD641C003C92AE /* Persistence.swift */, + AA8B500826CD641D003C92AE /* Assets.xcassets */, + AA8B500326CD641C003C92AE /* OpenSesame.xcdatamodeld */, + AA05CCD326CDB84F00E27F6D /* Services */, + AA519A6E26CEE5DC00C1E354 /* AutoFill */, + ); + path = Shared; + sourceTree = ""; + }; + AA8B500E26CD641D003C92AE /* Products */ = { + isa = PBXGroup; + children = ( + AA8B500D26CD641D003C92AE /* OpenSesame.app */, + AA8B501326CD641D003C92AE /* OpenSesame.app */, + AA8B501826CD641D003C92AE /* Tests iOS.xctest */, + AA8B502426CD641D003C92AE /* Tests macOS.xctest */, + AA05CCE026CDDA5200E27F6D /* AutoFill iOS.appex */, + AA519A5D26CEE5B700C1E354 /* AutoFill macOS.appex */, + ); + name = Products; + sourceTree = ""; + }; + AA8B501B26CD641D003C92AE /* Tests iOS */ = { + isa = PBXGroup; + children = ( + AA8B501C26CD641D003C92AE /* Tests_iOS.swift */, + AA8B501E26CD641D003C92AE /* Tests_iOSLaunchTests.swift */, + ); + path = "Tests iOS"; + sourceTree = ""; + }; + AA8B502726CD641D003C92AE /* Tests macOS */ = { + isa = PBXGroup; + children = ( + AA8B502826CD641D003C92AE /* Tests_macOS.swift */, + AA8B502A26CD641D003C92AE /* Tests_macOSLaunchTests.swift */, + ); + path = "Tests macOS"; + sourceTree = ""; + }; + AA8B504526CD6457003C92AE /* Frameworks */ = { + isa = PBXGroup; + children = ( + AA1F6A2626CD68D4004E1A81 /* AuthenticationServices.framework */, + AA1F6A2426CD68CF004E1A81 /* AuthenticationServices.framework */, + AA8B504926CD6468003C92AE /* CloudKit.framework */, + AA8B504626CD6457003C92AE /* CloudKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + AA980E4626D2DF11001CFF4E /* Multipeer */ = { + isa = PBXGroup; + children = ( + AA49EA3426D3EB9700B12838 /* MultipeerService.swift */, + AA49EA3526D3EB9700B12838 /* MultipeerShareSheet.swift */, + AA49EA3626D3EB9700B12838 /* ShareableAccount.swift */, + ); + path = Multipeer; + sourceTree = ""; + }; + AAD3DB8026D30D3300531E92 /* ImportExport */ = { + isa = PBXGroup; + children = ( + AA49EA1C26D3EB4E00B12838 /* ExportView.swift */, + AAD3DBA626D3347B00531E92 /* ImportView */, + ); + path = ImportExport; + sourceTree = ""; + }; + AAD3DB9226D3265700531E92 /* LockView */ = { + isa = PBXGroup; + children = ( + AA519A8026CF283C00C1E354 /* LockView.swift */, + AA49EA0D26D3EB2500B12838 /* LockView+CreatePassword.swift */, + AA49EA0E26D3EB2600B12838 /* LockView+Functions.swift */, + AA49EA0F26D3EB2600B12838 /* LockView+TextField.swift */, + AA49EA1026D3EB2600B12838 /* LockView+UnlockButtons.swift */, + ); + path = LockView; + sourceTree = ""; + }; + AAD3DB9F26D330A400531E92 /* ContentView */ = { + isa = PBXGroup; + children = ( + AA8B500626CD641C003C92AE /* ContentView.swift */, + AA49EA0726D3EB1600B12838 /* ContentView+Add.swift */, + AA49EA0826D3EB1600B12838 /* ContentView+List.swift */, + ); + path = ContentView; + sourceTree = ""; + }; + AAD3DBA626D3347B00531E92 /* ImportView */ = { + isa = PBXGroup; + children = ( + AAD3DB8126D30D3300531E92 /* ImportView.swift */, + AA49EA1926D3EB4300B12838 /* ImportView+Import.swift */, + ); + path = ImportView; + sourceTree = ""; + }; + AAD3DBB126D3365100531E92 /* VaultView */ = { + isa = PBXGroup; + children = ( + AA1F6A2826CD6993004E1A81 /* VaultView.swift */, + AA49EA1F26D3EB5A00B12838 /* VaultView+Item.swift */, + AA49EA2026D3EB5A00B12838 /* VaultView+List.swift */, + ); + path = VaultView; + sourceTree = ""; + }; + AAD3DBB726D33B6400531E92 /* AccountView */ = { + isa = PBXGroup; + children = ( + AA519A7F26CF283C00C1E354 /* AccountView.swift */, + AA49EA2826D3EB7500B12838 /* AccountView+Content.swift */, + AAD3DBC626D33D1D00531E92 /* AccountDetailView */, + ); + path = AccountView; + sourceTree = ""; + }; + AAD3DBC626D33D1D00531E92 /* AccountDetailView */ = { + isa = PBXGroup; + children = ( + AAD3DBC726D33D1D00531E92 /* AccountView+AccountDetailView.swift */, + AA49EA2B26D3EB8600B12838 /* AccountDetailView+EmailView.swift */, + AA49EA2C26D3EB8600B12838 /* AccountDetailView+OTPView.swift */, + AA49EA2D26D3EB8600B12838 /* AccountDetailView+PasswordView.swift */, + ); + path = AccountDetailView; + sourceTree = ""; + }; + AAD3DBDF26D341BA00531E92 /* macOS */ = { + isa = PBXGroup; + children = ( + AAE4A8BC26CDFB3200D1C58E /* SettingsView.swift */, + AA49EA4A26D3EBE100B12838 /* SettingsView+GeneralView.swift */, + AA49EA4B26D3EBE100B12838 /* SettingsView+MenuBarView.swift */, + ); + path = macOS; + sourceTree = ""; + }; + AAD3DBE526D342AB00531E92 /* Extensions */ = { + isa = PBXGroup; + children = ( + AAD3DBE626D342AB00531E92 /* String+Misc.swift */, + AAD3DBE726D342AB00531E92 /* URL+Misc.swift */, + AA49EA3D26D3EBA300B12838 /* Data+Misc.swift */, + AA49EA3E26D3EBA300B12838 /* Date+Misc.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + AAE4A8BB26CDFB3200D1C58E /* SettingsView */ = { + isa = PBXGroup; + children = ( + AAD3DBDF26D341BA00531E92 /* macOS */, + ); + path = SettingsView; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + AA05CCDF26CDDA5200E27F6D /* AutoFill iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = AA05CCED26CDDA5200E27F6D /* Build configuration list for PBXNativeTarget "AutoFill iOS" */; + buildPhases = ( + AA05CCDC26CDDA5200E27F6D /* Sources */, + AA05CCDD26CDDA5200E27F6D /* Frameworks */, + AA05CCDE26CDDA5200E27F6D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "AutoFill iOS"; + packageProductDependencies = ( + AA05CCF326CDDBFF00E27F6D /* DomainParser */, + AA05CD0526CDE08C00E27F6D /* KeychainAccess */, + ); + productName = "AutoFill iOS"; + productReference = AA05CCE026CDDA5200E27F6D /* AutoFill iOS.appex */; + productType = "com.apple.product-type.app-extension"; + }; + AA519A5C26CEE5B700C1E354 /* AutoFill macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = AA519A6D26CEE5B700C1E354 /* Build configuration list for PBXNativeTarget "AutoFill macOS" */; + buildPhases = ( + AA519A5926CEE5B700C1E354 /* Sources */, + AA519A5A26CEE5B700C1E354 /* Frameworks */, + AA519A5B26CEE5B700C1E354 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "AutoFill macOS"; + packageProductDependencies = ( + AA519A7226CEE6CA00C1E354 /* KeychainAccess */, + AA519A7426CEE6CA00C1E354 /* DomainParser */, + ); + productName = "AutoFill macOS"; + productReference = AA519A5D26CEE5B700C1E354 /* AutoFill macOS.appex */; + productType = "com.apple.product-type.app-extension"; + }; + AA8B500C26CD641D003C92AE /* OpenSesame (iOS) */ = { + isa = PBXNativeTarget; + buildConfigurationList = AA8B503826CD641D003C92AE /* Build configuration list for PBXNativeTarget "OpenSesame (iOS)" */; + buildPhases = ( + AA8B500926CD641D003C92AE /* Sources */, + AA8B500A26CD641D003C92AE /* Frameworks */, + AA8B500B26CD641D003C92AE /* Resources */, + AA05CCF026CDDA5200E27F6D /* Embed App Extensions */, + AA980E4D26D2DF75001CFF4E /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + AA05CCEB26CDDA5200E27F6D /* PBXTargetDependency */, + ); + name = "OpenSesame (iOS)"; + packageProductDependencies = ( + AA05CCB526CD7A0800E27F6D /* FaviconFinder */, + AA05CCCB26CDA6BE00E27F6D /* KeychainAccess */, + AA12A79E26D0A5DB000418C7 /* CSV */, + AA32ADAC26D17697006791DA /* DomainParser */, + AA980E3C26D2DF05001CFF4E /* MultipeerKit */, + AAD3DBFB26D3446500531E92 /* SwiftOTP */, + ); + productName = "OpenSesame (iOS)"; + productReference = AA8B500D26CD641D003C92AE /* OpenSesame.app */; + productType = "com.apple.product-type.application"; + }; + AA8B501226CD641D003C92AE /* OpenSesame (macOS) */ = { + isa = PBXNativeTarget; + buildConfigurationList = AA8B503B26CD641D003C92AE /* Build configuration list for PBXNativeTarget "OpenSesame (macOS)" */; + buildPhases = ( + AA8B500F26CD641D003C92AE /* Sources */, + AA8B501026CD641D003C92AE /* Frameworks */, + AA8B501126CD641D003C92AE /* Resources */, + AA519A6A26CEE5B700C1E354 /* Embed App Extensions */, + AA980E4526D2DF0C001CFF4E /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + AA519A6826CEE5B700C1E354 /* PBXTargetDependency */, + ); + name = "OpenSesame (macOS)"; + packageProductDependencies = ( + AA05CCB726CD7A0D00E27F6D /* FaviconFinder */, + AA05CCCD26CDA6C300E27F6D /* KeychainAccess */, + AA12A7A026D0A5E2000418C7 /* CSV */, + AA32ADAA26D17692006791DA /* DomainParser */, + AA980E4026D2DF0C001CFF4E /* MultipeerKit */, + AA41467626D3451900254812 /* SwiftOTP */, + ); + productName = "OpenSesame (macOS)"; + productReference = AA8B501326CD641D003C92AE /* OpenSesame.app */; + productType = "com.apple.product-type.application"; + }; + AA8B501726CD641D003C92AE /* Tests iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = AA8B503E26CD641D003C92AE /* Build configuration list for PBXNativeTarget "Tests iOS" */; + buildPhases = ( + AA8B501426CD641D003C92AE /* Sources */, + AA8B501526CD641D003C92AE /* Frameworks */, + AA8B501626CD641D003C92AE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + AA8B501A26CD641D003C92AE /* PBXTargetDependency */, + ); + name = "Tests iOS"; + productName = "Tests iOS"; + productReference = AA8B501826CD641D003C92AE /* Tests iOS.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; + AA8B502326CD641D003C92AE /* Tests macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = AA8B504126CD641D003C92AE /* Build configuration list for PBXNativeTarget "Tests macOS" */; + buildPhases = ( + AA8B502026CD641D003C92AE /* Sources */, + AA8B502126CD641D003C92AE /* Frameworks */, + AA8B502226CD641D003C92AE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + AA8B502626CD641D003C92AE /* PBXTargetDependency */, + ); + name = "Tests macOS"; + productName = "Tests macOS"; + productReference = AA8B502426CD641D003C92AE /* Tests macOS.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + AA8B4FFE26CD641C003C92AE /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1300; + LastUpgradeCheck = 1300; + TargetAttributes = { + AA05CCDF26CDDA5200E27F6D = { + CreatedOnToolsVersion = 13.0; + }; + AA519A5C26CEE5B700C1E354 = { + CreatedOnToolsVersion = 13.0; + }; + AA8B500C26CD641D003C92AE = { + CreatedOnToolsVersion = 13.0; + }; + AA8B501226CD641D003C92AE = { + CreatedOnToolsVersion = 13.0; + }; + AA8B501726CD641D003C92AE = { + CreatedOnToolsVersion = 13.0; + TestTargetID = AA8B500C26CD641D003C92AE; + }; + AA8B502326CD641D003C92AE = { + CreatedOnToolsVersion = 13.0; + TestTargetID = AA8B501226CD641D003C92AE; + }; + }; + }; + buildConfigurationList = AA8B500126CD641C003C92AE /* Build configuration list for PBXProject "OpenSesame" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = AA8B4FFD26CD641C003C92AE; + packageReferences = ( + AA05CCB426CD7A0800E27F6D /* XCRemoteSwiftPackageReference "FaviconFinder" */, + AA05CCCA26CDA6BE00E27F6D /* XCRemoteSwiftPackageReference "KeychainAccess" */, + AA05CCF226CDDBFF00E27F6D /* XCRemoteSwiftPackageReference "SwiftDomainParser" */, + AA12A79D26D0A5DB000418C7 /* XCRemoteSwiftPackageReference "CSV.swift" */, + AA980E3B26D2DF05001CFF4E /* XCRemoteSwiftPackageReference "MultipeerKit" */, + AAD3DBFA26D3446500531E92 /* XCRemoteSwiftPackageReference "SwiftOTP" */, + ); + productRefGroup = AA8B500E26CD641D003C92AE /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + AA8B500C26CD641D003C92AE /* OpenSesame (iOS) */, + AA8B501226CD641D003C92AE /* OpenSesame (macOS) */, + AA8B501726CD641D003C92AE /* Tests iOS */, + AA8B502326CD641D003C92AE /* Tests macOS */, + AA05CCDF26CDDA5200E27F6D /* AutoFill iOS */, + AA519A5C26CEE5B700C1E354 /* AutoFill macOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + AA05CCDE26CDDA5200E27F6D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AA05CCE726CDDA5200E27F6D /* MainInterface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA519A5B26CEE5B700C1E354 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AA519A6426CEE5B700C1E354 /* CredentialProviderViewController.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA8B500B26CD641D003C92AE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AA8B503426CD641D003C92AE /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA8B501126CD641D003C92AE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AA8B503526CD641D003C92AE /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA8B501626CD641D003C92AE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA8B502226CD641D003C92AE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + AA05CCDC26CDDA5200E27F6D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AA05CCE426CDDA5200E27F6D /* CredentialProviderViewController.swift in Sources */, + AAD3DBED26D342B900531E92 /* String+Misc.swift in Sources */, + AA49EA5026D3EBF300B12838 /* Credentials.swift in Sources */, + AAD3DBEC26D342B900531E92 /* URL+Misc.swift in Sources */, + AA49EA4626D3EBCE00B12838 /* Data+Misc.swift in Sources */, + AA05CD0426CDDDB400E27F6D /* UserAuthenticationService.swift in Sources */, + AA340B6C26D2BE33003A2CF9 /* Persistence.swift in Sources */, + AA49EA4726D3EBCE00B12838 /* Date+Misc.swift in Sources */, + AA05CCF526CDDD6B00E27F6D /* OpenSesame.xcdatamodeld in Sources */, + AA05CD0126CDDDA400E27F6D /* CryptoSecurityService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA519A5926CEE5B700C1E354 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AA519A7826CEE6D900C1E354 /* OpenSesame.xcdatamodeld in Sources */, + AAD3DBEF26D342B900531E92 /* String+Misc.swift in Sources */, + AA49EA5126D3EBF300B12838 /* Credentials.swift in Sources */, + AAD3DBEE26D342B900531E92 /* URL+Misc.swift in Sources */, + AA49EA4826D3EBCE00B12838 /* Data+Misc.swift in Sources */, + AA519A6126CEE5B700C1E354 /* CredentialProviderViewController.swift in Sources */, + AA340B6D26D2BE33003A2CF9 /* Persistence.swift in Sources */, + AA49EA4926D3EBCE00B12838 /* Date+Misc.swift in Sources */, + AA519A7B26CEE6E700C1E354 /* UserAuthenticationService.swift in Sources */, + AA519A7A26CEE6E500C1E354 /* CryptoSecurityService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA8B500926CD641D003C92AE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AA49EA3226D3EB8600B12838 /* AccountDetailView+PasswordView.swift in Sources */, + AA519A8426CF2A5000C1E354 /* LockView.swift in Sources */, + AAD3DBE826D342B800531E92 /* URL+Misc.swift in Sources */, + AA49EA1126D3EB2600B12838 /* LockView+CreatePassword.swift in Sources */, + AA49EA0926D3EB1600B12838 /* ContentView+Add.swift in Sources */, + AA49EA3B26D3EB9700B12838 /* ShareableAccount.swift in Sources */, + AAD3DBDE26D3417500531E92 /* MainView.swift in Sources */, + AA05CD0226CDDDB300E27F6D /* UserAuthenticationService.swift in Sources */, + AA519A8626CF453300C1E354 /* AccountView.swift in Sources */, + AA49EA1526D3EB2600B12838 /* LockView+TextField.swift in Sources */, + AA49EA2626D3EB6400B12838 /* NewAccountView.swift in Sources */, + AA49EA2126D3EB5A00B12838 /* VaultView+Item.swift in Sources */, + AA05CD0026CDDDA300E27F6D /* CryptoSecurityService.swift in Sources */, + AA49EA3726D3EB9700B12838 /* MultipeerService.swift in Sources */, + AAD3DB9026D3258900531E92 /* FaviconView.swift in Sources */, + AAD3DBC826D33D2500531E92 /* AccountView+AccountDetailView.swift in Sources */, + AA49EA2926D3EB7500B12838 /* AccountView+Content.swift in Sources */, + AAD3DBAA26D3351400531E92 /* ImportView.swift in Sources */, + AA49EA2326D3EB5A00B12838 /* VaultView+List.swift in Sources */, + AA8B502C26CD641D003C92AE /* OpenSesame.xcdatamodeld in Sources */, + AA49EA0B26D3EB1600B12838 /* ContentView+List.swift in Sources */, + AA49EA1A26D3EB4300B12838 /* ImportView+Import.swift in Sources */, + AA49EA3926D3EB9700B12838 /* MultipeerShareSheet.swift in Sources */, + AA49EA3026D3EB8600B12838 /* AccountDetailView+OTPView.swift in Sources */, + AA49EA4126D3EBA300B12838 /* Date+Misc.swift in Sources */, + AA519A9126CF4B3800C1E354 /* OTPAuthenticatorService.swift in Sources */, + AAD3DBE926D342B800531E92 /* String+Misc.swift in Sources */, + AA1F6A2926CD6993004E1A81 /* VaultView.swift in Sources */, + AA49EA3F26D3EBA300B12838 /* Data+Misc.swift in Sources */, + AA49EA1D26D3EB4E00B12838 /* ExportView.swift in Sources */, + AA49EA2E26D3EB8600B12838 /* AccountDetailView+EmailView.swift in Sources */, + AA8B503226CD641D003C92AE /* Persistence.swift in Sources */, + AA8B502E26CD641D003C92AE /* OpenSesameApp.swift in Sources */, + AA8B503026CD641D003C92AE /* ContentView.swift in Sources */, + AA49EA1726D3EB2600B12838 /* LockView+UnlockButtons.swift in Sources */, + AA49EA1326D3EB2600B12838 /* LockView+Functions.swift in Sources */, + AA49EA0426D3EB0100B12838 /* AppDelegate iOS.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA8B500F26CD641D003C92AE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AA519A8726CF453300C1E354 /* AccountView.swift in Sources */, + AAD3DBDD26D3417500531E92 /* MainView.swift in Sources */, + AA519A9226CF4B3800C1E354 /* OTPAuthenticatorService.swift in Sources */, + AA05CD0326CDDDB400E27F6D /* UserAuthenticationService.swift in Sources */, + AAD3DBEA26D342B800531E92 /* URL+Misc.swift in Sources */, + AA05CCFF26CDDDA300E27F6D /* CryptoSecurityService.swift in Sources */, + AAD3DBE226D341D300531E92 /* SettingsView.swift in Sources */, + AAD3DB9126D3258900531E92 /* FaviconView.swift in Sources */, + AA49EA4F26D3EBE100B12838 /* SettingsView+MenuBarView.swift in Sources */, + AA49EA4D26D3EBE100B12838 /* SettingsView+GeneralView.swift in Sources */, + AA49EA1E26D3EB4E00B12838 /* ExportView.swift in Sources */, + AA49EA0A26D3EB1600B12838 /* ContentView+Add.swift in Sources */, + AA49EA1826D3EB2600B12838 /* LockView+UnlockButtons.swift in Sources */, + AA49EA2F26D3EB8600B12838 /* AccountDetailView+EmailView.swift in Sources */, + AAD3DBC926D33D2600531E92 /* AccountView+AccountDetailView.swift in Sources */, + AA49EA3C26D3EB9700B12838 /* ShareableAccount.swift in Sources */, + AA49EA2426D3EB5A00B12838 /* VaultView+List.swift in Sources */, + AA49EA3A26D3EB9700B12838 /* MultipeerShareSheet.swift in Sources */, + AA49EA2A26D3EB7500B12838 /* AccountView+Content.swift in Sources */, + AAD3DBAB26D3351400531E92 /* ImportView.swift in Sources */, + AAD3DBEB26D342B800531E92 /* String+Misc.swift in Sources */, + AA519A8526CF2A5000C1E354 /* LockView.swift in Sources */, + AA49EA0C26D3EB1600B12838 /* ContentView+List.swift in Sources */, + AA49EA3126D3EB8600B12838 /* AccountDetailView+OTPView.swift in Sources */, + AA49EA1226D3EB2600B12838 /* LockView+CreatePassword.swift in Sources */, + AA49EA4026D3EBA300B12838 /* Data+Misc.swift in Sources */, + AA49EA1426D3EB2600B12838 /* LockView+Functions.swift in Sources */, + AA49EA0626D3EB0400B12838 /* AppDelegate macOS.swift in Sources */, + AA8B502D26CD641D003C92AE /* OpenSesame.xcdatamodeld in Sources */, + AA1F6A2A26CD6993004E1A81 /* VaultView.swift in Sources */, + AA49EA1B26D3EB4300B12838 /* ImportView+Import.swift in Sources */, + AA8B503326CD641D003C92AE /* Persistence.swift in Sources */, + AA49EA4226D3EBA300B12838 /* Date+Misc.swift in Sources */, + AA49EA1626D3EB2600B12838 /* LockView+TextField.swift in Sources */, + AA49EA2726D3EB6400B12838 /* NewAccountView.swift in Sources */, + AA49EA3326D3EB8600B12838 /* AccountDetailView+PasswordView.swift in Sources */, + AA49EA2226D3EB5A00B12838 /* VaultView+Item.swift in Sources */, + AA8B502F26CD641D003C92AE /* OpenSesameApp.swift in Sources */, + AA8B503126CD641D003C92AE /* ContentView.swift in Sources */, + AA49EA3826D3EB9700B12838 /* MultipeerService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA8B501426CD641D003C92AE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AA8B501F26CD641D003C92AE /* Tests_iOSLaunchTests.swift in Sources */, + AA8B501D26CD641D003C92AE /* Tests_iOS.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA8B502026CD641D003C92AE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AA8B502B26CD641D003C92AE /* Tests_macOSLaunchTests.swift in Sources */, + AA8B502926CD641D003C92AE /* Tests_macOS.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + AA05CCEB26CDDA5200E27F6D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = AA05CCDF26CDDA5200E27F6D /* AutoFill iOS */; + targetProxy = AA05CCEA26CDDA5200E27F6D /* PBXContainerItemProxy */; + }; + AA519A6826CEE5B700C1E354 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = AA519A5C26CEE5B700C1E354 /* AutoFill macOS */; + targetProxy = AA519A6726CEE5B700C1E354 /* PBXContainerItemProxy */; + }; + AA8B501A26CD641D003C92AE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = AA8B500C26CD641D003C92AE /* OpenSesame (iOS) */; + targetProxy = AA8B501926CD641D003C92AE /* PBXContainerItemProxy */; + }; + AA8B502626CD641D003C92AE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = AA8B501226CD641D003C92AE /* OpenSesame (macOS) */; + targetProxy = AA8B502526CD641D003C92AE /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + AA05CCE526CDDA5200E27F6D /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + AA05CCE626CDDA5200E27F6D /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; + AA519A6226CEE5B700C1E354 /* CredentialProviderViewController.xib */ = { + isa = PBXVariantGroup; + children = ( + AA519A6326CEE5B700C1E354 /* Base */, + ); + name = CredentialProviderViewController.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + AA05CCEE26CDDA5200E27F6D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "AutoFill iOS/AutoFill_iOS.entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 5; + DEVELOPMENT_TEAM = B6QG723P8Z; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "AutoFill iOS/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "AutoFill iOS"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.ethanlipnik.OpenSesame.AutoFill-iOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG EXTENSION"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + AA05CCEF26CDDA5200E27F6D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "AutoFill iOS/AutoFill_iOS.entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 5; + DEVELOPMENT_TEAM = B6QG723P8Z; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "AutoFill iOS/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "AutoFill iOS"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.ethanlipnik.OpenSesame.AutoFill-iOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = EXTENSION; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + AA519A6B26CEE5B700C1E354 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "AutoFill macOS/AutoFill_macOS.entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 5; + DEVELOPMENT_TEAM = B6QG723P8Z; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "AutoFill macOS/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "AutoFill macOS"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.ethanlipnik.OpenSesame.AutoFill-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG EXTENSION"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + AA519A6C26CEE5B700C1E354 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "AutoFill macOS/AutoFill_macOS.entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 5; + DEVELOPMENT_TEAM = B6QG723P8Z; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "AutoFill macOS/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "AutoFill macOS"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.ethanlipnik.OpenSesame.AutoFill-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = EXTENSION; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + AA8B503626CD641D003C92AE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + AA8B503726CD641D003C92AE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + AA8B503926CD641D003C92AE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "OpenSesame (iOS).entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 5; + DEVELOPMENT_TEAM = B6QG723P8Z; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "OpenSesame--iOS--Info.plist"; + INFOPLIST_KEY_NSFaceIDUsageDescription = "View your accounts"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.ethanlipnik.OpenSesame; + PRODUCT_NAME = OpenSesame; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + AA8B503A26CD641D003C92AE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "OpenSesame (iOS).entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 5; + DEVELOPMENT_TEAM = B6QG723P8Z; + ENABLE_PREVIEWS = YES; + GCC_OPTIMIZATION_LEVEL = z; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "OpenSesame--iOS--Info.plist"; + INFOPLIST_KEY_NSFaceIDUsageDescription = "View your accounts"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.ethanlipnik.OpenSesame; + PRODUCT_NAME = OpenSesame; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Osize"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + AA8B503C26CD641D003C92AE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "OpenSesame (macOS).entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 5; + DEVELOPMENT_TEAM = B6QG723P8Z; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SELECTED_FILES = readonly; + GCC_OPTIMIZATION_LEVEL = 3; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "OpenSesame--macOS--Info.plist"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=200 -Xfrontend -warn-long-function-bodies=200 -Onone"; + PRODUCT_BUNDLE_IDENTIFIER = com.ethanlipnik.OpenSesame; + PRODUCT_NAME = OpenSesame; + SDKROOT = macosx; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + AA8B503D26CD641D003C92AE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "OpenSesame (macOS).entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 5; + DEVELOPMENT_TEAM = B6QG723P8Z; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SELECTED_FILES = readonly; + GCC_OPTIMIZATION_LEVEL = z; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "OpenSesame--macOS--Info.plist"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=200 -Xfrontend -warn-long-function-bodies=200"; + PRODUCT_BUNDLE_IDENTIFIER = com.ethanlipnik.OpenSesame; + PRODUCT_NAME = OpenSesame; + SDKROOT = macosx; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Osize"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + AA8B503F26CD641D003C92AE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B6QG723P8Z; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.ethanlipnik.Tests-iOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "OpenSesame (iOS)"; + }; + name = Debug; + }; + AA8B504026CD641D003C92AE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B6QG723P8Z; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.ethanlipnik.Tests-iOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "OpenSesame (iOS)"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + AA8B504226CD641D003C92AE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B6QG723P8Z; + GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.ethanlipnik.Tests-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = "OpenSesame (macOS)"; + }; + name = Debug; + }; + AA8B504326CD641D003C92AE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B6QG723P8Z; + GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.ethanlipnik.Tests-macOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = "OpenSesame (macOS)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + AA05CCED26CDDA5200E27F6D /* Build configuration list for PBXNativeTarget "AutoFill iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AA05CCEE26CDDA5200E27F6D /* Debug */, + AA05CCEF26CDDA5200E27F6D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AA519A6D26CEE5B700C1E354 /* Build configuration list for PBXNativeTarget "AutoFill macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AA519A6B26CEE5B700C1E354 /* Debug */, + AA519A6C26CEE5B700C1E354 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AA8B500126CD641C003C92AE /* Build configuration list for PBXProject "OpenSesame" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AA8B503626CD641D003C92AE /* Debug */, + AA8B503726CD641D003C92AE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AA8B503826CD641D003C92AE /* Build configuration list for PBXNativeTarget "OpenSesame (iOS)" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AA8B503926CD641D003C92AE /* Debug */, + AA8B503A26CD641D003C92AE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AA8B503B26CD641D003C92AE /* Build configuration list for PBXNativeTarget "OpenSesame (macOS)" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AA8B503C26CD641D003C92AE /* Debug */, + AA8B503D26CD641D003C92AE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AA8B503E26CD641D003C92AE /* Build configuration list for PBXNativeTarget "Tests iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AA8B503F26CD641D003C92AE /* Debug */, + AA8B504026CD641D003C92AE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AA8B504126CD641D003C92AE /* Build configuration list for PBXNativeTarget "Tests macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AA8B504226CD641D003C92AE /* Debug */, + AA8B504326CD641D003C92AE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + AA05CCB426CD7A0800E27F6D /* XCRemoteSwiftPackageReference "FaviconFinder" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/will-lumley/FaviconFinder.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 3.0.0; + }; + }; + AA05CCCA26CDA6BE00E27F6D /* XCRemoteSwiftPackageReference "KeychainAccess" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 4.0.0; + }; + }; + AA05CCF226CDDBFF00E27F6D /* XCRemoteSwiftPackageReference "SwiftDomainParser" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Dashlane/SwiftDomainParser.git"; + requirement = { + branch = master; + kind = branch; + }; + }; + AA12A79D26D0A5DB000418C7 /* XCRemoteSwiftPackageReference "CSV.swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/yaslab/CSV.swift.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.0.0; + }; + }; + AA980E3B26D2DF05001CFF4E /* XCRemoteSwiftPackageReference "MultipeerKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/insidegui/MultipeerKit.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.4.0; + }; + }; + AAD3DBFA26D3446500531E92 /* XCRemoteSwiftPackageReference "SwiftOTP" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/EthanLipnik/SwiftOTP.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 3.0.2; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + AA05CCB526CD7A0800E27F6D /* FaviconFinder */ = { + isa = XCSwiftPackageProductDependency; + package = AA05CCB426CD7A0800E27F6D /* XCRemoteSwiftPackageReference "FaviconFinder" */; + productName = FaviconFinder; + }; + AA05CCB726CD7A0D00E27F6D /* FaviconFinder */ = { + isa = XCSwiftPackageProductDependency; + package = AA05CCB426CD7A0800E27F6D /* XCRemoteSwiftPackageReference "FaviconFinder" */; + productName = FaviconFinder; + }; + AA05CCCB26CDA6BE00E27F6D /* KeychainAccess */ = { + isa = XCSwiftPackageProductDependency; + package = AA05CCCA26CDA6BE00E27F6D /* XCRemoteSwiftPackageReference "KeychainAccess" */; + productName = KeychainAccess; + }; + AA05CCCD26CDA6C300E27F6D /* KeychainAccess */ = { + isa = XCSwiftPackageProductDependency; + package = AA05CCCA26CDA6BE00E27F6D /* XCRemoteSwiftPackageReference "KeychainAccess" */; + productName = KeychainAccess; + }; + AA05CCF326CDDBFF00E27F6D /* DomainParser */ = { + isa = XCSwiftPackageProductDependency; + package = AA05CCF226CDDBFF00E27F6D /* XCRemoteSwiftPackageReference "SwiftDomainParser" */; + productName = DomainParser; + }; + AA05CD0526CDE08C00E27F6D /* KeychainAccess */ = { + isa = XCSwiftPackageProductDependency; + package = AA05CCCA26CDA6BE00E27F6D /* XCRemoteSwiftPackageReference "KeychainAccess" */; + productName = KeychainAccess; + }; + AA12A79E26D0A5DB000418C7 /* CSV */ = { + isa = XCSwiftPackageProductDependency; + package = AA12A79D26D0A5DB000418C7 /* XCRemoteSwiftPackageReference "CSV.swift" */; + productName = CSV; + }; + AA12A7A026D0A5E2000418C7 /* CSV */ = { + isa = XCSwiftPackageProductDependency; + package = AA12A79D26D0A5DB000418C7 /* XCRemoteSwiftPackageReference "CSV.swift" */; + productName = CSV; + }; + AA32ADAA26D17692006791DA /* DomainParser */ = { + isa = XCSwiftPackageProductDependency; + package = AA05CCF226CDDBFF00E27F6D /* XCRemoteSwiftPackageReference "SwiftDomainParser" */; + productName = DomainParser; + }; + AA32ADAC26D17697006791DA /* DomainParser */ = { + isa = XCSwiftPackageProductDependency; + package = AA05CCF226CDDBFF00E27F6D /* XCRemoteSwiftPackageReference "SwiftDomainParser" */; + productName = DomainParser; + }; + AA41467626D3451900254812 /* SwiftOTP */ = { + isa = XCSwiftPackageProductDependency; + package = AAD3DBFA26D3446500531E92 /* XCRemoteSwiftPackageReference "SwiftOTP" */; + productName = SwiftOTP; + }; + AA519A7226CEE6CA00C1E354 /* KeychainAccess */ = { + isa = XCSwiftPackageProductDependency; + package = AA05CCCA26CDA6BE00E27F6D /* XCRemoteSwiftPackageReference "KeychainAccess" */; + productName = KeychainAccess; + }; + AA519A7426CEE6CA00C1E354 /* DomainParser */ = { + isa = XCSwiftPackageProductDependency; + package = AA05CCF226CDDBFF00E27F6D /* XCRemoteSwiftPackageReference "SwiftDomainParser" */; + productName = DomainParser; + }; + AA980E3C26D2DF05001CFF4E /* MultipeerKit */ = { + isa = XCSwiftPackageProductDependency; + package = AA980E3B26D2DF05001CFF4E /* XCRemoteSwiftPackageReference "MultipeerKit" */; + productName = MultipeerKit; + }; + AA980E4026D2DF0C001CFF4E /* MultipeerKit */ = { + isa = XCSwiftPackageProductDependency; + package = AA980E3B26D2DF05001CFF4E /* XCRemoteSwiftPackageReference "MultipeerKit" */; + productName = MultipeerKit; + }; + AAD3DBFB26D3446500531E92 /* SwiftOTP */ = { + isa = XCSwiftPackageProductDependency; + package = AAD3DBFA26D3446500531E92 /* XCRemoteSwiftPackageReference "SwiftOTP" */; + productName = SwiftOTP; + }; +/* End XCSwiftPackageProductDependency section */ + +/* Begin XCVersionGroup section */ + AA8B500326CD641C003C92AE /* OpenSesame.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + AA8B500426CD641C003C92AE /* Shared.xcdatamodel */, + ); + currentVersion = AA8B500426CD641C003C92AE /* Shared.xcdatamodel */; + path = OpenSesame.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ + }; + rootObject = AA8B4FFE26CD641C003C92AE /* Project object */; +} diff --git a/OpenSesame.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/OpenSesame.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/OpenSesame.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/OpenSesame.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/OpenSesame.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/OpenSesame.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/OpenSesame.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OpenSesame.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..2029689 --- /dev/null +++ b/OpenSesame.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,70 @@ +{ + "object": { + "pins": [ + { + "package": "CSV.swift", + "repositoryURL": "https://github.com/yaslab/CSV.swift.git", + "state": { + "branch": null, + "revision": "81d2874c51db364d7e1d71b0d99018a294c87ac1", + "version": "2.4.3" + } + }, + { + "package": "FaviconFinder", + "repositoryURL": "https://github.com/will-lumley/FaviconFinder.git", + "state": { + "branch": null, + "revision": "7162634f8b701f21349c42ebf7632720a6ca0987", + "version": "3.1.0" + } + }, + { + "package": "KeychainAccess", + "repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git", + "state": { + "branch": null, + "revision": "84e546727d66f1adc5439debad16270d0fdd04e7", + "version": "4.2.2" + } + }, + { + "package": "MultipeerKit", + "repositoryURL": "https://github.com/insidegui/MultipeerKit.git", + "state": { + "branch": null, + "revision": "ee013fd04f20166ea40ff456a67058c040289c1c", + "version": "0.4.0" + } + }, + { + "package": "DomainParser", + "repositoryURL": "https://github.com/Dashlane/SwiftDomainParser.git", + "state": { + "branch": "master", + "revision": "9ea8c15c666d7fe6ba97f82d52961ac08babf1bd", + "version": null + } + }, + { + "package": "SwiftOTP", + "repositoryURL": "https://github.com/EthanLipnik/SwiftOTP.git", + "state": { + "branch": null, + "revision": "be0c91ba2f197a033a44a6af836bc03eeef77422", + "version": "3.0.2" + } + }, + { + "package": "SwiftSoup", + "repositoryURL": "https://github.com/scinfu/SwiftSoup.git", + "state": { + "branch": null, + "revision": "774dc9c7213085db8aa59595e27c1cd22e428904", + "version": "2.3.2" + } + } + ] + }, + "version": 1 +} diff --git a/OpenSesame.xcodeproj/project.xcworkspace/xcuserdata/ethan.xcuserdatad/UserInterfaceState.xcuserstate b/OpenSesame.xcodeproj/project.xcworkspace/xcuserdata/ethan.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..a346249 Binary files /dev/null and b/OpenSesame.xcodeproj/project.xcworkspace/xcuserdata/ethan.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/OpenSesame.xcodeproj/xcshareddata/xcschemes/AutoFill iOS.xcscheme b/OpenSesame.xcodeproj/xcshareddata/xcschemes/AutoFill iOS.xcscheme new file mode 100644 index 0000000..4224bdd --- /dev/null +++ b/OpenSesame.xcodeproj/xcshareddata/xcschemes/AutoFill iOS.xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenSesame.xcodeproj/xcshareddata/xcschemes/AutoFill macOS.xcscheme b/OpenSesame.xcodeproj/xcshareddata/xcschemes/AutoFill macOS.xcscheme new file mode 100644 index 0000000..9323c3b --- /dev/null +++ b/OpenSesame.xcodeproj/xcshareddata/xcschemes/AutoFill macOS.xcscheme @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenSesame.xcodeproj/xcshareddata/xcschemes/OpenSesame (iOS).xcscheme b/OpenSesame.xcodeproj/xcshareddata/xcschemes/OpenSesame (iOS).xcscheme new file mode 100644 index 0000000..7fc0cdb --- /dev/null +++ b/OpenSesame.xcodeproj/xcshareddata/xcschemes/OpenSesame (iOS).xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenSesame.xcodeproj/xcshareddata/xcschemes/OpenSesame (macOS).xcscheme b/OpenSesame.xcodeproj/xcshareddata/xcschemes/OpenSesame (macOS).xcscheme new file mode 100644 index 0000000..9b5c7b5 --- /dev/null +++ b/OpenSesame.xcodeproj/xcshareddata/xcschemes/OpenSesame (macOS).xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenSesame.xcodeproj/xcuserdata/ethan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/OpenSesame.xcodeproj/xcuserdata/ethan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..79d2bc9 --- /dev/null +++ b/OpenSesame.xcodeproj/xcuserdata/ethan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/OpenSesame.xcodeproj/xcuserdata/ethan.xcuserdatad/xcschemes/OpenSesame (macOS, Marketing).xcscheme b/OpenSesame.xcodeproj/xcuserdata/ethan.xcuserdatad/xcschemes/OpenSesame (macOS, Marketing).xcscheme new file mode 100644 index 0000000..95d971c --- /dev/null +++ b/OpenSesame.xcodeproj/xcuserdata/ethan.xcuserdatad/xcschemes/OpenSesame (macOS, Marketing).xcscheme @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenSesame.xcodeproj/xcuserdata/ethan.xcuserdatad/xcschemes/xcschememanagement.plist b/OpenSesame.xcodeproj/xcuserdata/ethan.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..13561da --- /dev/null +++ b/OpenSesame.xcodeproj/xcuserdata/ethan.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,109 @@ + + + + + SchemeUserState + + AutoFill iOS.xcscheme_^#shared#^_ + + orderHint + 3 + + AutoFill macOS.xcscheme_^#shared#^_ + + orderHint + 2 + + GettingStarted (Playground) 1.xcscheme + + isShown + + orderHint + 8 + + GettingStarted (Playground) 2.xcscheme + + isShown + + orderHint + 9 + + GettingStarted (Playground) 3.xcscheme + + isShown + + orderHint + 8 + + GettingStarted (Playground) 4.xcscheme + + isShown + + orderHint + 9 + + GettingStarted (Playground) 5.xcscheme + + isShown + + orderHint + 10 + + GettingStarted (Playground).xcscheme + + isShown + + orderHint + 7 + + OpenSesame (iOS).xcscheme_^#shared#^_ + + orderHint + 4 + + OpenSesame (macOS).xcscheme_^#shared#^_ + + orderHint + 0 + + OpenSesame (macOS, Marketing).xcscheme + + orderHint + 1 + + + SuppressBuildableAutocreation + + AA05CCDF26CDDA5200E27F6D + + primary + + + AA519A5C26CEE5B700C1E354 + + primary + + + AA8B500C26CD641D003C92AE + + primary + + + AA8B501226CD641D003C92AE + + primary + + + AA8B501726CD641D003C92AE + + primary + + + AA8B502326CD641D003C92AE + + primary + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc79d38 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# OpenSesame + +Native and encrypted password manager for iOS and macOS. + +## What is it? + +OpenSesame is a free and powerful password manager that lets you manage your passwords with ease. + +It syncs with iCloud and encrypts your data to make sure only **you** get access. No more required subscriptions, no more ram hogs, *and no more electron versions.* + +## Features + +- iCloud Syncing +- OTP Auth (Two Factor Authentication) +- Safari Autofill +- On-Device AES encryption +- Biometrics unlock +- Pinned accounts +- Multiple vaults +- Multipeer account sharing +- Import/Export +- Password generator +- CoreData +- 100% SwiftUI +- Native support for macOS and iOS. + +## Planned Features + +- Better password bulk deleting +- Menubar access +- Notes field +- Credit/Debit card support +- Dropbox, Google Drive, personal server support +- OTP Autofill +- OTP QR codes +- Password suggestions (detect if password is secure) +- Compromised/breached password notice +- Chrome & native Windows support + +### Requirements +- Xcode 13+ +- macOS 12+ +- iOS 15+ + +## Why use OpenSesame + +Now more than ever, what was previously amazing native apps have become slow and big web apps. Big apps take more control away from the user and push subscriptions and other payment models without much reason. OpenSesame aims to fix that by giving a free and open source password manager that is native and performant. Still get all the great features of other password managers without all the baggage. + +## License + +OpenSesame is available under the MIT license. See the LICENSE file for more info. + +### Acknowledgements +OpenSesame depends on the following open-source projects: + +* [SwiftOTP](https://github.com/OpenSesameManager/SwiftOTP.git) by [lachlanbell](https://github.com/lachlanbell) ([License](https://github.com/lachlanbell/SwiftOTP/blob/master/LICENSE)) +* [FaviconFinder](https://github.com/OpenSesameManager/SwiftOTP.git) by [will-lumley](https://github.com/will-lumley) ([License](https://github.com/will-lumley/FaviconFinder/blob/main/LICENSE.txt)) +* [KeychainAccess](https://github.com/kishikawakatsumi/KeychainAccess.git) by [kishikawakatsumi](https://github.com/kishikawakatsumi) ([License](https://github.com/kishikawakatsumi/KeychainAccess/blob/master/LICENSE)) +* [DomainParser](https://github.com/Dashlane/SwiftDomainParser.git) by [Dashlane](https://github.com/Dashlane) ([License](https://github.com/Dashlane/SwiftDomainParser/blob/master/LICENSE)) +* [CSV.swift](https://github.com/yaslab/CSV.swift.git) by [yaslab](https://github.com/yaslab) ([License](https://github.com/yaslab/CSV.swift/blob/master/LICENSE)) +* [MultipeerKit](https://github.com/insidegui/MultipeerKit.git) by [insidegui](https://github.com/insidegui) ([License](https://github.com/insidegui/MultipeerKit/blob/main/LICENSE)) diff --git a/Shared/.DS_Store b/Shared/.DS_Store new file mode 100644 index 0000000..b669f6a Binary files /dev/null and b/Shared/.DS_Store differ diff --git a/Shared/AppDelegate/AppDelegate iOS.swift b/Shared/AppDelegate/AppDelegate iOS.swift new file mode 100644 index 0000000..c87dc58 --- /dev/null +++ b/Shared/AppDelegate/AppDelegate iOS.swift @@ -0,0 +1,18 @@ +// +// AppDelegate iOS.swift +// AppDelegate iOS +// +// Created by Ethan Lipnik on 8/20/21. +// + +import Foundation +import UIKit +import CloudKit + +class AppDelegate: NSObject, UIApplicationDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + + application.registerForRemoteNotifications() + return true + } +} diff --git a/Shared/AppDelegate/AppDelegate macOS.swift b/Shared/AppDelegate/AppDelegate macOS.swift new file mode 100644 index 0000000..d0ce2ec --- /dev/null +++ b/Shared/AppDelegate/AppDelegate macOS.swift @@ -0,0 +1,16 @@ +// +// AppDelegate macOS.swift +// AppDelegate macOS +// +// Created by Ethan Lipnik on 8/20/21. +// + +import Foundation +import AppKit +import CloudKit + +class AppDelegate: NSObject, NSApplicationDelegate { + func applicationDidFinishLaunching(_ notification: Notification) { + NSApplication.shared.registerForRemoteNotifications() + } +} diff --git a/Shared/Assets.xcassets/.DS_Store b/Shared/Assets.xcassets/.DS_Store new file mode 100644 index 0000000..a1cece1 Binary files /dev/null and b/Shared/Assets.xcassets/.DS_Store differ diff --git a/Shared/Assets.xcassets/AccentColor.colorset/Contents.json b/Shared/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..3b48415 --- /dev/null +++ b/Shared/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,15 @@ +{ + "colors" : [ + { + "color" : { + "platform" : "universal", + "reference" : "systemTealColor" + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json b/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..21d0590 --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,218 @@ +{ + "images" : [ + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40", + "filename" : "icon-40.png" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x", + "filename" : "icon-40@2x.png" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "filename" : "icon-60@2x.png", + "size" : "60x60" + }, + { + "filename" : "icon-72.png", + "idiom" : "ipad", + "size" : "72x72", + "scale" : "1x" + }, + { + "size" : "72x72", + "idiom" : "ipad", + "scale" : "2x", + "filename" : "icon-72@2x.png" + }, + { + "size" : "76x76", + "scale" : "1x", + "filename" : "icon-76.png", + "idiom" : "ipad" + }, + { + "filename" : "icon-76@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "size" : "50x50", + "filename" : "icon-small-50.png", + "scale" : "1x", + "idiom" : "ipad" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "filename" : "icon-small-50@2x.png", + "size" : "50x50" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "1x", + "filename" : "icon-small.png" + }, + { + "size" : "29x29", + "scale" : "2x", + "idiom" : "iphone", + "filename" : "icon-small@2x.png" + }, + { + "filename" : "icon.png", + "idiom" : "iphone", + "size" : "57x57", + "scale" : "1x" + }, + { + "filename" : "icon@2x.png", + "size" : "57x57", + "idiom" : "iphone", + "scale" : "2x" + }, + { + "scale" : "3x", + "size" : "29x29", + "filename" : "icon-small@3x.png", + "idiom" : "iphone" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "icon-40@3x.png", + "scale" : "3x" + }, + { + "filename" : "icon-60@3x.png", + "scale" : "3x", + "size" : "60x60", + "idiom" : "iphone" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "icon-40@2x.png", + "scale" : "2x" + }, + { + "scale" : "1x", + "size" : "29x29", + "idiom" : "ipad", + "filename" : "icon-small.png" + }, + { + "idiom" : "ipad", + "filename" : "icon-small@2x.png", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x", + "filename" : "icon-83.5@2x.png" + }, + { + "scale" : "2x", + "filename" : "notification-icon@2x.png", + "size" : "20x20", + "idiom" : "iphone" + }, + { + "filename" : "notification-icon@3x.png", + "scale" : "3x", + "size" : "20x20", + "idiom" : "iphone" + }, + { + "filename" : "notification-icon~ipad.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "scale" : "2x", + "idiom" : "ipad", + "filename" : "notification-icon~ipad@2x.png", + "size" : "20x20" + }, + { + "filename" : "ios-marketing.png", + "scale" : "1x", + "idiom" : "ios-marketing", + "size" : "1024x1024" + }, + { + "filename" : "icon_16x16.png", + "scale" : "1x", + "idiom" : "mac", + "size" : "16x16" + }, + { + "size" : "16x16", + "filename" : "icon_16x16@2x.png", + "idiom" : "mac", + "scale" : "2x" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32", + "filename" : "icon_32x32.png" + }, + { + "idiom" : "mac", + "filename" : "icon_32x32@2x.png", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "icon_128x128.png", + "scale" : "1x", + "idiom" : "mac", + "size" : "128x128" + }, + { + "idiom" : "mac", + "filename" : "icon_128x128@2x.png", + "scale" : "2x", + "size" : "128x128" + }, + { + "size" : "256x256", + "scale" : "1x", + "idiom" : "mac", + "filename" : "icon_256x256.png" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "2x", + "filename" : "icon_256x256@2x.png" + }, + { + "size" : "512x512", + "scale" : "1x", + "idiom" : "mac", + "filename" : "icon_512x512.png" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512", + "filename" : "icon_512x512@2x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png new file mode 100644 index 0000000..2e523fc Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png new file mode 100644 index 0000000..c6e9451 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/Icon-76.png b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-76.png new file mode 100644 index 0000000..533741e Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-76.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png new file mode 100644 index 0000000..ab29080 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png new file mode 100644 index 0000000..1207903 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/Icon-Small.png b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-Small.png new file mode 100644 index 0000000..55b2e1f Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-Small.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png new file mode 100644 index 0000000..0f1021c Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png new file mode 100644 index 0000000..9b54f5f Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon-40.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon-40.png new file mode 100644 index 0000000..afd4b6d Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon-40.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png new file mode 100644 index 0000000..38a6b70 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png new file mode 100644 index 0000000..2e523fc Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon-72.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon-72.png new file mode 100644 index 0000000..6c2d265 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon-72.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png new file mode 100644 index 0000000..a088263 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon-small-50.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon-small-50.png new file mode 100644 index 0000000..54fa454 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon-small-50.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon-small-50@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon-small-50@2x.png new file mode 100644 index 0000000..e62b99e Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon-small-50@2x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon.png new file mode 100644 index 0000000..363b167 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon@2x.png new file mode 100644 index 0000000..9a81263 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon@2x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon_128x128.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon_128x128.png new file mode 100644 index 0000000..5165ee5 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon_128x128.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png new file mode 100644 index 0000000..278e30b Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon_16x16.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon_16x16.png new file mode 100644 index 0000000..524a2a0 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon_16x16.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png new file mode 100644 index 0000000..bd2dd15 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon_256x256.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon_256x256.png new file mode 100644 index 0000000..278e30b Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon_256x256.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png new file mode 100644 index 0000000..e5b7a96 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon_32x32.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon_32x32.png new file mode 100644 index 0000000..bd2dd15 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon_32x32.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png new file mode 100644 index 0000000..cd0792d Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon_512x512.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon_512x512.png new file mode 100644 index 0000000..e5b7a96 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon_512x512.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png new file mode 100644 index 0000000..f8d9f31 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/ios-marketing.png b/Shared/Assets.xcassets/AppIcon.appiconset/ios-marketing.png new file mode 100644 index 0000000..c0d13b9 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/ios-marketing.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/notification-icon@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/notification-icon@2x.png new file mode 100644 index 0000000..afd4b6d Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/notification-icon@2x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/notification-icon@3x.png b/Shared/Assets.xcassets/AppIcon.appiconset/notification-icon@3x.png new file mode 100644 index 0000000..18a8897 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/notification-icon@3x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad.png b/Shared/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad.png new file mode 100644 index 0000000..053d492 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad.png differ diff --git a/Shared/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad@2x.png b/Shared/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad@2x.png new file mode 100644 index 0000000..afd4b6d Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad@2x.png differ diff --git a/Shared/Assets.xcassets/Background.colorset/Contents.json b/Shared/Assets.xcassets/Background.colorset/Contents.json new file mode 100644 index 0000000..e8585ca --- /dev/null +++ b/Shared/Assets.xcassets/Background.colorset/Contents.json @@ -0,0 +1,53 @@ +{ + "colors" : [ + { + "color" : { + "platform" : "ios", + "reference" : "systemBackgroundColor" + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "platform" : "ios", + "reference" : "systemBackgroundColor" + }, + "idiom" : "universal" + }, + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "mac" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "platform" : "osx", + "reference" : "windowBackgroundColor" + }, + "idiom" : "mac" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/Contents.json b/Shared/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Shared/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Shared/Assets.xcassets/Icon.imageset/Contents.json b/Shared/Assets.xcassets/Icon.imageset/Contents.json new file mode 100644 index 0000000..bd24203 --- /dev/null +++ b/Shared/Assets.xcassets/Icon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon_512x512.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/Icon.imageset/icon_512x512.png b/Shared/Assets.xcassets/Icon.imageset/icon_512x512.png new file mode 100644 index 0000000..e5b7a96 Binary files /dev/null and b/Shared/Assets.xcassets/Icon.imageset/icon_512x512.png differ diff --git a/Shared/Assets.xcassets/Websites/Apple.imageset/Apple@1x.heic b/Shared/Assets.xcassets/Websites/Apple.imageset/Apple@1x.heic new file mode 100644 index 0000000..09a0634 Binary files /dev/null and b/Shared/Assets.xcassets/Websites/Apple.imageset/Apple@1x.heic differ diff --git a/Shared/Assets.xcassets/Websites/Apple.imageset/Apple@2x.heic b/Shared/Assets.xcassets/Websites/Apple.imageset/Apple@2x.heic new file mode 100644 index 0000000..df3b81d Binary files /dev/null and b/Shared/Assets.xcassets/Websites/Apple.imageset/Apple@2x.heic differ diff --git a/Shared/Assets.xcassets/Websites/Apple.imageset/Apple@3x.heic b/Shared/Assets.xcassets/Websites/Apple.imageset/Apple@3x.heic new file mode 100644 index 0000000..0f84cf8 Binary files /dev/null and b/Shared/Assets.xcassets/Websites/Apple.imageset/Apple@3x.heic differ diff --git a/Shared/Assets.xcassets/Websites/Apple.imageset/Contents.json b/Shared/Assets.xcassets/Websites/Apple.imageset/Contents.json new file mode 100644 index 0000000..83561ff --- /dev/null +++ b/Shared/Assets.xcassets/Websites/Apple.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Apple@1x.heic", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Apple@2x.heic", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Apple@3x.heic", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/Websites/Contents.json b/Shared/Assets.xcassets/Websites/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Shared/Assets.xcassets/Websites/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/Websites/GitHub.imageset/Contents.json b/Shared/Assets.xcassets/Websites/GitHub.imageset/Contents.json new file mode 100644 index 0000000..5589ee0 --- /dev/null +++ b/Shared/Assets.xcassets/Websites/GitHub.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "GitHub@1x.heic", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "GitHub@2x.heic", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "GitHub@3x.heic", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/Websites/GitHub.imageset/GitHub@1x.heic b/Shared/Assets.xcassets/Websites/GitHub.imageset/GitHub@1x.heic new file mode 100644 index 0000000..aea1ebe Binary files /dev/null and b/Shared/Assets.xcassets/Websites/GitHub.imageset/GitHub@1x.heic differ diff --git a/Shared/Assets.xcassets/Websites/GitHub.imageset/GitHub@2x.heic b/Shared/Assets.xcassets/Websites/GitHub.imageset/GitHub@2x.heic new file mode 100644 index 0000000..35f97b7 Binary files /dev/null and b/Shared/Assets.xcassets/Websites/GitHub.imageset/GitHub@2x.heic differ diff --git a/Shared/Assets.xcassets/Websites/GitHub.imageset/GitHub@3x.heic b/Shared/Assets.xcassets/Websites/GitHub.imageset/GitHub@3x.heic new file mode 100644 index 0000000..830e9e6 Binary files /dev/null and b/Shared/Assets.xcassets/Websites/GitHub.imageset/GitHub@3x.heic differ diff --git a/Shared/Assets.xcassets/Websites/Google.imageset/Contents.json b/Shared/Assets.xcassets/Websites/Google.imageset/Contents.json new file mode 100644 index 0000000..08ee021 --- /dev/null +++ b/Shared/Assets.xcassets/Websites/Google.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Google@1x.heic", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Google@2x.heic", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Google@3x.heic", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/Websites/Google.imageset/Google@1x.heic b/Shared/Assets.xcassets/Websites/Google.imageset/Google@1x.heic new file mode 100644 index 0000000..d9e3518 Binary files /dev/null and b/Shared/Assets.xcassets/Websites/Google.imageset/Google@1x.heic differ diff --git a/Shared/Assets.xcassets/Websites/Google.imageset/Google@2x.heic b/Shared/Assets.xcassets/Websites/Google.imageset/Google@2x.heic new file mode 100644 index 0000000..3ed4cf6 Binary files /dev/null and b/Shared/Assets.xcassets/Websites/Google.imageset/Google@2x.heic differ diff --git a/Shared/Assets.xcassets/Websites/Google.imageset/Google@3x.heic b/Shared/Assets.xcassets/Websites/Google.imageset/Google@3x.heic new file mode 100644 index 0000000..0c9506c Binary files /dev/null and b/Shared/Assets.xcassets/Websites/Google.imageset/Google@3x.heic differ diff --git a/Shared/Assets.xcassets/Websites/Twitter.imageset/Contents.json b/Shared/Assets.xcassets/Websites/Twitter.imageset/Contents.json new file mode 100644 index 0000000..9f20f83 --- /dev/null +++ b/Shared/Assets.xcassets/Websites/Twitter.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Twitter@1x.heic", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Twitter@2x.heic", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Twitter@3x.heic", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/Websites/Twitter.imageset/Twitter@1x.heic b/Shared/Assets.xcassets/Websites/Twitter.imageset/Twitter@1x.heic new file mode 100644 index 0000000..cbff004 Binary files /dev/null and b/Shared/Assets.xcassets/Websites/Twitter.imageset/Twitter@1x.heic differ diff --git a/Shared/Assets.xcassets/Websites/Twitter.imageset/Twitter@2x.heic b/Shared/Assets.xcassets/Websites/Twitter.imageset/Twitter@2x.heic new file mode 100644 index 0000000..91532a2 Binary files /dev/null and b/Shared/Assets.xcassets/Websites/Twitter.imageset/Twitter@2x.heic differ diff --git a/Shared/Assets.xcassets/Websites/Twitter.imageset/Twitter@3x.heic b/Shared/Assets.xcassets/Websites/Twitter.imageset/Twitter@3x.heic new file mode 100644 index 0000000..6e9263b Binary files /dev/null and b/Shared/Assets.xcassets/Websites/Twitter.imageset/Twitter@3x.heic differ diff --git a/Shared/AutoFill/.DS_Store b/Shared/AutoFill/.DS_Store new file mode 100644 index 0000000..472b293 Binary files /dev/null and b/Shared/AutoFill/.DS_Store differ diff --git a/Shared/AutoFill/Credentials.swift b/Shared/AutoFill/Credentials.swift new file mode 100644 index 0000000..f769b3d --- /dev/null +++ b/Shared/AutoFill/Credentials.swift @@ -0,0 +1,104 @@ +// +// Credentials.swift +// Credentials +// +// Created by Ethan Lipnik on 8/19/21. +// + +import CoreData +import AuthenticationServices +import DomainParser +import KeychainAccess + +extension CredentialProviderViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + do { + let fetchRequest : NSFetchRequest = Account.fetchRequest() + let fetchedResults = try viewContext.fetch(fetchRequest) + + self.allAccounts = fetchedResults + } catch { + print(error) + } + +#if os(iOS) + tableView.delegate = self + tableView.dataSource = self + tableView.reloadData() +#endif + } + /* + Prepare your UI to list available credentials for the user to choose from. The items in + 'serviceIdentifiers' describe the service the user is logging in to, so your extension can + prioritize the most relevant credentials in the list. + */ + + override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) { + print("Preparing credentials") + do { + let domainParse = try DomainParser() + + let hosts = serviceIdentifiers + .compactMap({ URL(string: $0.identifier)?.host }) + let domains = hosts + .compactMap({ domainParse.parse(host: $0)?.domain?.lowercased() }) + guard let mainDomain = domains.first else { return } + + let fetchRequest : NSFetchRequest = Account.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "website contains[c] %@", mainDomain) + let fetchedResults = try viewContext.fetch(fetchRequest) + + self.accounts = fetchedResults + + print("Accounts", accounts) + +#if os(iOS) + tableView.reloadData() +#endif + } catch { + print(error) + } + } + + func decryptedAccount(_ account: Account) throws -> (username: String, password: String) { +#if targetEnvironment(simulator) + let accessibility: Accessibility = .always +#else + let accessibility: Accessibility = .whenUnlockedThisDeviceOnly +#endif + if let masterPassword = try Keychain(service: "com.ethanlipnik.OpenSesame", accessGroup: "B6QG723P8Z.OpenSesame") + .accessibility(accessibility, authenticationPolicy: .biometryCurrentSet) + .authenticationPrompt("Authenticate to login to view your accounts") + .get("masterPassword") { + + CryptoSecurityService.loadEncryptionKey(masterPassword) + + let decryptPassword = try CryptoSecurityService.decrypt(account.password!, tag: account.encryptionTag!, nonce: account.nonce) + + print("Returned decrypted account") + + return (account.username!, decryptPassword!) + } else { + print("No master password") + + throw CocoaError(.coderValueNotFound) + } + } + + override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) { + + guard let account = allAccounts.first(where: { $0.username == credentialIdentity.user && $0.domain == credentialIdentity.serviceIdentifier.identifier }) else { extensionContext.cancelRequest(withError: CocoaError(.coderValueNotFound)); return } + + do { + let decryptedAccount = try decryptedAccount(account) + let passwordCredential = ASPasswordCredential(user: decryptedAccount.username, password: decryptedAccount.password) + + self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) + } catch { + extensionContext.cancelRequest(withError: error) + } + } +} diff --git a/Shared/Extensions/Data+Misc.swift b/Shared/Extensions/Data+Misc.swift new file mode 100644 index 0000000..2c46812 --- /dev/null +++ b/Shared/Extensions/Data+Misc.swift @@ -0,0 +1,32 @@ +// +// Data+Misc.swift +// Data+Misc +// +// Created by Ethan Lipnik on 8/22/21. +// + +import Foundation + +extension Data { + init?(hexString: String) { + let len = hexString.count / 2 + var data = Data(capacity: len) + var i = hexString.startIndex + for _ in 0.. Date { + let cal = Calendar.current + let startOfMinute = cal.dateInterval(of: .minute, for: self)!.start + var seconds = self.timeIntervalSince(startOfMinute) + + if seconds < 30 { + seconds = 30 + } else { + seconds = 60 + } + + return startOfMinute.addingTimeInterval(seconds) + } +} diff --git a/Shared/Extensions/String+Misc.swift b/Shared/Extensions/String+Misc.swift new file mode 100644 index 0000000..46ff332 --- /dev/null +++ b/Shared/Extensions/String+Misc.swift @@ -0,0 +1,30 @@ +// +// String+Misc.swift +// String+Misc +// +// Created by Ethan Lipnik on 8/22/21. +// + +import Foundation + +extension String { + func capitalizingFirstLetter() -> String { + return prefix(1).uppercased() + self.lowercased().dropFirst() + } + + mutating func capitalizeFirstLetter() { + self = self.capitalizingFirstLetter() + } + + subscript(i: Int) -> String { + return String(self[index(startIndex, offsetBy: i)]) + } + + var asHexString: String { + return self + .unicodeScalars + .filter { $0.isASCII } + .map { String(format: "%X", $0.value) } + .joined() + } +} diff --git a/Shared/Extensions/URL+Misc.swift b/Shared/Extensions/URL+Misc.swift new file mode 100644 index 0000000..4360562 --- /dev/null +++ b/Shared/Extensions/URL+Misc.swift @@ -0,0 +1,20 @@ +// +// URL+Misc.swift +// URL+Misc +// +// Created by Ethan Lipnik on 8/22/21. +// + +import Foundation + +extension URL { + var isValidURL: Bool { + let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) + if let match = detector.firstMatch(in: self.absoluteString, options: [], range: NSRange(location: 0, length: self.absoluteString.utf16.count)) { + // it is a link, if the match covers the whole string + return match.range.length == self.absoluteString.utf16.count + } else { + return false + } + } +} diff --git a/Shared/Multipeer/MultipeerService.swift b/Shared/Multipeer/MultipeerService.swift new file mode 100644 index 0000000..39b9055 --- /dev/null +++ b/Shared/Multipeer/MultipeerService.swift @@ -0,0 +1,60 @@ +// +// MultipeerService.swift +// MultipeerService +// +// Created by Ethan Lipnik on 8/22/21. +// + +import Combine +import MultipeerKit +import CloudKit +import CoreData + +class MultipeerService: ObservableObject { + static let shared = MultipeerService() + + @Published var availablePeers: [Peer] = [] + @Published var invitedPeers: [Peer] = [] + + let transceiver: MultipeerTransceiver + + private init() { + let security = MultipeerConfiguration.Security(identity: nil, encryptionPreference: .required) { peer, data, completion in + print(peer, data as Any) + completion(true) + } + let config = MultipeerConfiguration(serviceType: "OpenSesame", peerName: UUID().uuidString, defaults: UserDefaults.standard, security: security, invitation: .none) + + transceiver = MultipeerTransceiver(configuration: config) + + transceiver.receive(ShareableAccount.self) { payload, sender in + print("Got my thing from \(sender.name)! \(payload)") + +// let viewContext = PersistenceController.shared.container.viewContext +// +//// let account = Account(context: viewContext) +//// account.username = payload.username +//// account.username = payload + } + + self.availablePeers = transceiver.availablePeers + transceiver.availablePeersDidChange = { peers in + self.availablePeers = peers + } + } + + func invite(_ peer: Peer) { + transceiver.invite(peer, with: nil, timeout: 10) { result in + switch result { + case .success(let peer): + self.invitedPeers.append(peer) + case .failure(let error): + print(error) + } + } + } + + deinit { + transceiver.stop() + } +} diff --git a/Shared/Multipeer/MultipeerShareSheet.swift b/Shared/Multipeer/MultipeerShareSheet.swift new file mode 100644 index 0000000..ac8bbcf --- /dev/null +++ b/Shared/Multipeer/MultipeerShareSheet.swift @@ -0,0 +1,94 @@ +// +// MultipeerShareSheet.swift +// MultipeerShareSheet +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI +import MultipeerKit + +struct MultipeerShareSheet: View { + @StateObject var multipeer = MultipeerService.shared + @Environment(\.dismiss) var dismiss + + let account: Account + + var body: some View { + NavigationView { + List(multipeer.availablePeers) { peer in + NavigationLink { + ShareAccountConfirmationView(peer: peer, account: account) + .environmentObject(multipeer) + } label: { + Label(peer.name, systemImage: "person.fill") + } + } + .animation(.default, value: multipeer.availablePeers) + .listStyle(.sidebar) +#if os(iOS) + .toolbar { + ToolbarItem(placement: .navigation) { + Button("Done", action: dismiss.callAsFunction) + } + } + .navigationBarTitleDisplayMode(.inline) +#endif + .navigationTitle("Share Account") + +#if os(macOS) + EmptyView() +#endif + } + } + + struct ShareAccountConfirmationView: View { + @EnvironmentObject var multipeer: MultipeerService + let peer: Peer + let account: Account + @State var inviteAccepted: Bool = false + + @Environment(\.dismiss) var dismiss + + var body: some View { + VStack { + Text("This doesn't function yet") + .foregroundColor(Color.secondary) + if !inviteAccepted { + Text("Inviting...") + .animation(.default, value: inviteAccepted) + } + Text(peer.name) + GroupBox { + Text(account.domain!) + Text(account.username!) + Text(account.password!) + } + + if inviteAccepted { + Button("Share") { + if let decryptedPassword = try? CryptoSecurityService.decrypt(account.password!, tag: account.encryptionTag!, nonce: account.nonce) { + multipeer.transceiver.send(ShareableAccount(domain: account.domain!, dateAdded: account.dateAdded ?? Date(), lastModified: account.lastModified, password: decryptedPassword, username: account.username!, url: account.url!), to: [peer]) + + dismiss.callAsFunction() + } + } + } + } + .onChange(of: multipeer.invitedPeers) { newValue in + if newValue.contains(where: { $0.id == peer.id }) { + inviteAccepted = true + } + } + .task { + self.multipeer.invite(peer) + } + } + } +} + +struct MultipeerShareSheet_Previews: PreviewProvider { + static var previews: some View { + MultipeerShareSheet(account: .init()) + } +} diff --git a/Shared/Multipeer/ShareableAccount.swift b/Shared/Multipeer/ShareableAccount.swift new file mode 100644 index 0000000..8a34ba7 --- /dev/null +++ b/Shared/Multipeer/ShareableAccount.swift @@ -0,0 +1,17 @@ +// +// ShareableAccount.swift +// ShareableAccount +// +// Created by Ethan Lipnik on 8/22/21. +// + +import Foundation + +struct ShareableAccount: Codable { + var domain: String + var dateAdded: Date = Date() + var lastModified: Date? + var password: String + var username: String + var url: String +} diff --git a/Shared/OpenSesame.xcdatamodeld/.xccurrentversion b/Shared/OpenSesame.xcdatamodeld/.xccurrentversion new file mode 100644 index 0000000..775cb51 --- /dev/null +++ b/Shared/OpenSesame.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Shared.xcdatamodel + + diff --git a/Shared/OpenSesame.xcdatamodeld/Shared.xcdatamodel/contents b/Shared/OpenSesame.xcdatamodeld/Shared.xcdatamodel/contents new file mode 100644 index 0000000..cf02e96 --- /dev/null +++ b/Shared/OpenSesame.xcdatamodeld/Shared.xcdatamodel/contents @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Shared/OpenSesameApp.swift b/Shared/OpenSesameApp.swift new file mode 100644 index 0000000..0619062 --- /dev/null +++ b/Shared/OpenSesameApp.swift @@ -0,0 +1,141 @@ +// +// OpenSesameApp.swift +// Shared +// +// Created by Ethan Lipnik on 8/18/21. +// + +import SwiftUI + +@main +struct OpenSesameApp: SwiftUI.App { + // MARK: - App Delegate + /// This is used for registering for remote notifications and any other functions that requires the AppDelegate + #if os(iOS) + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + #else + @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + #endif + + // MARK: - Environment + @Environment(\.scenePhase) var scenePhase + + // MARK: - Services + let multipeer = MultipeerService.shared + let persistenceController = PersistenceController.shared + + // MARK: - Variables + @State var isLocked: Bool = true + @State var shouldHideApp: Bool = false + + @State var isImportingPasswords: Bool = false + @State var shouldExportPasswords: Bool = false + @State var isExportingPasswords: Bool = false + + // MARK: - View + var body: some Scene { + WindowGroup { + // MARK: - MainView + MainView(isLocked: $isLocked, + shouldHideApp: $shouldHideApp, + isImportingPasswords: $isImportingPasswords, + shouldExportPasswords: $shouldExportPasswords, + isExportingPasswords: $isExportingPasswords) + .environment(\.managedObjectContext, persistenceController.container.viewContext) + .handlesExternalEvents(preferring: Set(arrayLiteral: "*"), allowing: Set(arrayLiteral: "*")) + } + .handlesExternalEvents(matching: Set(arrayLiteral: "*")) + .commands { + SidebarCommands() + ToolbarCommands() + + CommandGroup(replacing: .newItem) { + Group { + Link("New Vault...", destination: URL(string: "openSesame://new?type=vault")!) + .keyboardShortcut("n") + Link("New Account...", destination: URL(string: "openSesame://new?type=account")!) + .keyboardShortcut("n", modifiers: [.shift, .command]) + }.disabled(isLocked) + } + + CommandGroup(after: .newItem) { + Divider() + + Button("Unlock with Biometrics...") { + + } + .keyboardShortcut("b", modifiers: [.command, .shift]) + .disabled(!isLocked) + + Button("Lock") { + isLocked = true + } + .keyboardShortcut("l", modifiers: [.command, .shift]) + .disabled(isLocked) + + Divider() + + Group { + Button("Import...") { + isImportingPasswords.toggle() + } + + Menu("Export") { + Button("CSV...") { + shouldExportPasswords = true + } + + Button("JSON...") { + + }.disabled(true) + } + }.disabled(isLocked) + } + } + .onChange(of: isLocked, perform: { isLocked in + if !isLocked { + multipeer.transceiver.resume() + } else { + multipeer.transceiver.stop() + } + }) + .onChange(of: scenePhase) { phase in + withAnimation { + switch phase { + case .active: + print("App is active") + shouldHideApp = false + + if !isLocked { + multipeer.transceiver.resume() + } + break + case .background: + print("App is in background") + isLocked = true + shouldHideApp = true + case .inactive: + shouldHideApp = true + multipeer.transceiver.stop() + @unknown default: + break + } + } + } + + // MARK: - Settings + #warning("Add back settings for macOS") +// #if os(macOS) +// Settings { +// SettingsView() +// .environment(\.realmConfiguration, Realm.Configuration(fileURL: PersistenceController.storeURL)) +// } +// #endif + } + + // MARK: - Init + /// Load the nonce used for encrypting and decrypting in this session. + init() { + CryptoSecurityService.loadNonce() + } +} diff --git a/Shared/Persistence.swift b/Shared/Persistence.swift new file mode 100644 index 0000000..ba00292 --- /dev/null +++ b/Shared/Persistence.swift @@ -0,0 +1,86 @@ +// +// Persistence.swift +// Shared +// +// Created by Ethan Lipnik on 8/18/21. +// + +import Foundation +import CoreData + +struct PersistenceController { + static let shared = PersistenceController() + + static let storeURL = URL.storeURL(for: "group.OpenSesame.ethanlipnik", databaseName: "group.OpenSesame.ethanlipnik") + + static var preview: PersistenceController = { + let result = PersistenceController(inMemory: true) + let viewContext = result.container.viewContext + do { + try viewContext.save() + } catch { + let nsError = error as NSError + fatalError("Unresolved error \(nsError), \(nsError.userInfo)") + } + return result + }() + + let container: NSPersistentCloudKitContainer + + init(inMemory: Bool = false) { + container = NSPersistentCloudKitContainer(name: "OpenSesame") + if inMemory { + container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") + } else { + let storeDescription = NSPersistentStoreDescription(url: PersistenceController.storeURL) + + let cloudkitOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.com.ethanlipnik.OpenSesame") + storeDescription.cloudKitContainerOptions = cloudkitOptions + + let remoteChangeKey = "NSPersistentStoreRemoteChangeNotificationOptionKey" + storeDescription.setOption(true as NSNumber, + forKey: remoteChangeKey) + + storeDescription.setOption(true as NSNumber, + forKey: NSPersistentHistoryTrackingKey) + +#if !os(macOS) + storeDescription.setOption(FileProtectionType.complete as NSObject, forKey: NSPersistentStoreFileProtectionKey) +#endif + + container.persistentStoreDescriptions = [storeDescription] + + print("CoreData location", PersistenceController.storeURL.path) + } + let viewContext = container.viewContext + container.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error as NSError? { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + /* + Typical reasons for an error here include: + * The parent directory does not exist, cannot be created, or disallows writing. + * The persistent store is not accessible, due to permissions or data protection when the device is locked. + * The device is out of space. + * The store could not be migrated to the current model version. + Check the error message to determine what the actual problem was. + */ + fatalError("Unresolved error \(error), \(error.userInfo)") + } else { + viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump + viewContext.automaticallyMergesChangesFromParent = true + + try? viewContext.setQueryGenerationFrom(.current) + } + }) + } +} + +public extension URL { + static func storeURL(for appGroup: String, databaseName: String) -> URL { + guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { + fatalError("Shared file container could not be created.") + } + return fileContainer.appendingPathComponent("\(databaseName).sqlite") + } +} diff --git a/Shared/Services/CryptoSecurityService.swift b/Shared/Services/CryptoSecurityService.swift new file mode 100644 index 0000000..b955b5d --- /dev/null +++ b/Shared/Services/CryptoSecurityService.swift @@ -0,0 +1,99 @@ +// +// CryptoSecurityService.swift +// CryptoSecurityService +// +// Created by Ethan Lipnik on 8/18/21. +// + +import Foundation +import CryptoKit +import KeychainAccess + +class CryptoSecurityService { + // MARK: - Variables + static var encryptionKey: SymmetricKey? + static var nonce: Data? + static var nonceStr: String? + + // MARK: - Functions + static func loadNonce() { + DispatchQueue.global(qos: .userInitiated).async { + let keychain = Keychain(service: "com.ethanlipnik.OpenSesame", accessGroup: "B6QG723P8Z.OpenSesame") + + if let nonce = try? keychain.get("nonce") { + self.nonce = Data(hexString: nonce) + self.nonceStr = nonce + } else { + let nonce = randomString(length: 24).asHexString + try? keychain.set(nonce, key: "nonce") + self.nonce = Data(hexString: nonce) + self.nonceStr = nonce + } + } + } + + static func loadEncryptionKey(hexString: String) { + let key = SymmetricKey(data: Data(hexString: hexString)!) + encryptionKey = key + } + + static func loadEncryptionKey(_ string: String, completion: (() -> Void)? = nil) { + DispatchQueue.global(qos: .userInitiated).async { + let stringData = string.data(using: .utf8) + let base64Encoded = stringData!.base64EncodedData() + let keyHash = SHA256.hash(data: base64Encoded) + let key = SymmetricKey(data: keyHash) + encryptionKey = key + + completion?() + } + } + + static func decrypt(_ string: String, tag: String, nonce nonceStr: String? = nil) throws -> String? { + guard let key = encryptionKey else { throw CocoaError(.coderInvalidValue) } + + var nonce: Data? + + if let str = nonceStr { + nonce = Data(hexString: str) + } else { + nonce = self.nonce + } + + guard let ciphertext = Data(base64Encoded: string) else { throw CocoaError(.coderReadCorrupt) } + let tag = Data(hexString: tag) + + let sealedBox = try AES.GCM.SealedBox(nonce: AES.GCM.Nonce(data: nonce!), + ciphertext: ciphertext, + tag: tag!) + + let decryptedData = try AES.GCM.open(sealedBox, using: key) + + return String(decoding: decryptedData, as: UTF8.self) + } + + static func encrypt(_ string: String) throws -> (value: String, tag: String)? { + guard let key = encryptionKey else { throw CocoaError(.coderValueNotFound) } + + let plainData = string.data(using: .utf8) + let sealedData = try AES.GCM.seal(plainData!, using: key, nonce: AES.GCM.Nonce(data: nonce!)) + + return (sealedData.ciphertext.base64EncodedString(), sealedData.tag.hexadecimal) + } + + static func randomString(length: Int) -> String { + + let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + let len = UInt32(letters.length) + + var randomString = "" + + for _ in 0 ..< length { + let rand = arc4random_uniform(len) + var nextChar = letters.character(at: Int(rand)) + randomString += NSString(characters: &nextChar, length: 1) as String + } + + return randomString + } +} diff --git a/Shared/Services/OTPAuthenticatorService.swift b/Shared/Services/OTPAuthenticatorService.swift new file mode 100644 index 0000000..f84292b --- /dev/null +++ b/Shared/Services/OTPAuthenticatorService.swift @@ -0,0 +1,71 @@ +// +// OTPAuthenticatorService.swift +// OTPAuthenticatorService +// +// Created by Ethan Lipnik on 8/19/21. +// + +import Foundation +import Combine +import SwiftOTP + +class OTPAuthenticatorService: ObservableObject { + // MARK: - Variables + @Published var totp: TOTP? = nil + @Published var verificationCode: String? = nil + @Published var verificationCodeDate: Date? = nil + + var timer: Timer? + + // MARK: - Init + init() { + startTimer() + } + + init(_ url: URL) { + initialize(url) + + startTimer() + } + + init(_ secret: String) { + initialize(secret) + + startTimer() + } + + // MARK: - Functions + private func startTimer() { + timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] _ in + self?.verificationCode = self?.totp?.generate(time: Date()) + self?.verificationCodeDate = Date().nearestThirtySeconds() + }) + + timer?.fire() + } + + private func generateCode(_ date: Date = Date()) { + + } + + func initialize(_ secret: String) { + self.totp = TOTP(secret: base32DecodeToData(secret)!) + + self.verificationCode = totp?.generate(time: Date()) + self.verificationCodeDate = Date().nearestThirtySeconds() + } + + func initialize(_ url: URL) { // otpauth://totp/{Website}:{Username}?secret={Secret}&issuer={Issuer} + let components = URLComponents(url: url, resolvingAgainstBaseURL: false) + guard let secret = components?.queryItems?.first(where: { $0.name == "secret" })?.value else { return } + + initialize(secret) + } + + deinit { + totp = nil + + timer?.invalidate() + timer = nil + } +} diff --git a/Shared/Services/UserAuthenticationService.swift b/Shared/Services/UserAuthenticationService.swift new file mode 100644 index 0000000..adf3bc6 --- /dev/null +++ b/Shared/Services/UserAuthenticationService.swift @@ -0,0 +1,57 @@ +// +// UserAuthenticationService.swift +// UserAuthenticationService +// +// Created by Ethan Lipnik on 8/18/21. +// + +import Combine +import LocalAuthentication + +class UserAuthenticationService: ObservableObject { + + #if os(macOS) + static let BiometricLogin = LAPolicy.deviceOwnerAuthenticationWithBiometricsOrWatch + #else + static let BiometricLogin = LAPolicy.deviceOwnerAuthenticationWithBiometrics + #endif + + static var cancellables = Set() + + static func authenticate(reason: String = "display your password") -> Future { + return Future { promise in + print("Requesting authentication") + let context = LAContext() + var error: NSError? + + func authenticateWithPassword() { + context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, error in + if success { + promise(.success(success)) + } else { + promise(.success(false)) + } + } + } + + // check whether biometric authentication is possible + if context.canEvaluatePolicy(UserAuthenticationService.BiometricLogin, error: &error) { + // it's possible, so go ahead and use it + + context.evaluatePolicy(UserAuthenticationService.BiometricLogin, localizedReason: reason) { success, authenticationError in + // authentication has now completed + DispatchQueue.main.async { + if success { + // authenticated successfully + promise(.success(success)) + } else { + authenticateWithPassword() + } + } + } + } else { + authenticateWithPassword() + } + } + } +} diff --git a/Shared/Views/.DS_Store b/Shared/Views/.DS_Store new file mode 100644 index 0000000..a45d83e Binary files /dev/null and b/Shared/Views/.DS_Store differ diff --git a/Shared/Views/AccountView/.DS_Store b/Shared/Views/AccountView/.DS_Store new file mode 100644 index 0000000..a79d49e Binary files /dev/null and b/Shared/Views/AccountView/.DS_Store differ diff --git a/Shared/Views/AccountView/AccountDetailView/AccountDetailView+EmailView.swift b/Shared/Views/AccountView/AccountDetailView/AccountDetailView+EmailView.swift new file mode 100644 index 0000000..17a91f2 --- /dev/null +++ b/Shared/Views/AccountView/AccountDetailView/AccountDetailView+EmailView.swift @@ -0,0 +1,26 @@ +// +// AccountDetailsView+EmailView.swift +// AccountDetailsView+EmailView +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI + +extension AccountView.AccountDetailsView { + var emailView: some View { + VStack(alignment: .leading) { + Label("Email", systemImage: "person.fill") + .foregroundColor(Color.secondary) + if isEditing { + TextField("Email or Username", text: $newUsername) + .textFieldStyle(.roundedBorder) + } else { + Text(account.username!) + .font(.headline) + .textSelection(.enabled) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + } +} diff --git a/Shared/Views/AccountView/AccountDetailView/AccountDetailView+OTPView.swift b/Shared/Views/AccountView/AccountDetailView/AccountDetailView+OTPView.swift new file mode 100644 index 0000000..5e8190b --- /dev/null +++ b/Shared/Views/AccountView/AccountDetailView/AccountDetailView+OTPView.swift @@ -0,0 +1,61 @@ +// +// AccountDetailView+OTPView.swift +// AccountDetailView+OTPView +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI + +extension AccountView.AccountDetailsView { + var otpView: some View { + Group { + if !(account.otpAuth?.isEmpty ?? true) { + if let code = otpService.verificationCode { + VStack(alignment: .leading) { + Label("Verification Code", systemImage: "ellipsis.rectangle.fill") + .foregroundColor(Color.secondary) + HStack { + Text(code) + .font(.system(.largeTitle, design: .monospaced).bold()) + .textSelection(.enabled) + + if let date = otpService.verificationCodeDate { + Spacer() + Text(date, style: .relative) + .font(.headline) + .foregroundColor(Color.secondary) + } + } + } + } + } else if isEditing { + if !isAddingVerificationCode { + Button("Add Verification Code") { + isAddingVerificationCode = true + } + } else { + TextField("Verification Code URL or Secret", text: $newVerificationURL, onCommit: { + isAddingVerificationCode = false + + account.otpAuth = newVerificationURL + + try? viewContext.save() + + guard !newVerificationURL.isEmpty else { return } + if let url = URL(string: newVerificationURL) { + otpService.initialize(url) + } else { + otpService.initialize(newVerificationURL) + } + }) + .textFieldStyle(.roundedBorder) +#if os(iOS) + .autocapitalization(.none) +#endif + .disableAutocorrection(true) + } + } + } + } +} diff --git a/Shared/Views/AccountView/AccountDetailView/AccountDetailView+PasswordView.swift b/Shared/Views/AccountView/AccountDetailView/AccountDetailView+PasswordView.swift new file mode 100644 index 0000000..ec8de17 --- /dev/null +++ b/Shared/Views/AccountView/AccountDetailView/AccountDetailView+PasswordView.swift @@ -0,0 +1,83 @@ +// +// AccountDetailsView+PasswordView.swift +// AccountDetailsView+PasswordView +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI + +extension AccountView.AccountDetailsView { + var passwordView: some View { + HStack(alignment: .bottom) { + VStack(alignment: .leading) { + Label("Password", systemImage: "key.fill") + .foregroundColor(Color.secondary) + if isEditing { + TextField("Password", text: $newPassword) + .textFieldStyle(.roundedBorder) + } else { + Text(displayedPassword) + .font(.system(.headline, design: .monospaced)) + .blur(radius: isShowingPassword ? 0 : 8) + .animation(.default, value: isShowingPassword) + .onTapGesture { + if !isShowingPassword { + do { + decryptedPassword = try CryptoSecurityService.decrypt(account.password!, tag: account.encryptionTag!, nonce: account.nonce) + + displayedPassword = decryptedPassword ?? displayedPassword + isShowingPassword = true + } catch { + print(error) + +#if os(macOS) + NSAlert(error: error).runModal() +#endif + } + } else { + isShowingPassword.toggle() + decryptedPassword = nil + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { + displayedPassword = CryptoSecurityService.randomString(length: Int(account.passwordLength)) + } + } + } + .onHover { isHovering in +#if os(macOS) + if isHovering { + NSCursor.pointingHand.set() + } else { + NSCursor.arrow.set() + } +#endif + } + } + } + if !isEditing { + Spacer() + Button { + guard let decryptedPassword = decryptedPassword else { return } +#if os(macOS) + let pasteboard = NSPasteboard.general + pasteboard.declareTypes([.string], owner: nil) + pasteboard.setString(decryptedPassword, forType: .string) +#else + let pasteboard = UIPasteboard.general + pasteboard.string = decryptedPassword +#endif + } label: { + Image(systemName: "doc.on.doc.fill") + } + .opacity(isShowingPassword ? 1 : 0) + .blur(radius: isShowingPassword ? 0 : 5) + .animation(.default, value: isShowingPassword) + .allowsHitTesting(isShowingPassword) +#if os(iOS) + .hoverEffect() +#endif + } + } + } +} diff --git a/Shared/Views/AccountView/AccountDetailView/AccountView+AccountDetailView.swift b/Shared/Views/AccountView/AccountDetailView/AccountView+AccountDetailView.swift new file mode 100644 index 0000000..d920988 --- /dev/null +++ b/Shared/Views/AccountView/AccountDetailView/AccountView+AccountDetailView.swift @@ -0,0 +1,93 @@ +// +// AccountView+AccountDetailView.swift +// AccountView+AccountDetailView +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI + +extension AccountView { + struct AccountDetailsView: View { + // MARK: - Environment + @Environment(\.managedObjectContext) var viewContext + + // MARK: - Variables + @StateObject var otpService: OTPAuthenticatorService + + @State var account: Account + @Binding var isEditing: Bool + + @State var isShowingPassword: Bool = false + + @State var newUsername: String = "" + @State var newPassword: String = "" + + @State var decryptedPassword: String? = nil + + @State var isAddingVerificationCode: Bool = false + @State var newVerificationURL: String = "" + + @State var displayedPassword: String = "" + + // MARK: - Init + init(account: Account, isEditing: Binding) { + self.account = account + self._isEditing = isEditing + + if !(account.otpAuth?.isEmpty ?? true) { + if let url = URL(string: account.otpAuth ?? ""), url.isValidURL { + _otpService = StateObject(wrappedValue: OTPAuthenticatorService(url)) + } else { + _otpService = StateObject(wrappedValue: OTPAuthenticatorService(account.otpAuth ?? "")) + } + } else { + _otpService = StateObject(wrappedValue: OTPAuthenticatorService()) + } + } + + // MARK: - View + var body: some View { + GroupBox { + VStack(alignment: .leading, spacing: 10) { + emailView + + passwordView + + otpView + } + .padding(5) + .onChange(of: isEditing) { isEditing in + if isEditing { + do { + decryptedPassword = try CryptoSecurityService.decrypt(account.password!, tag: account.encryptionTag!, nonce: account.nonce) + newUsername = account.username ?? "" + newPassword = decryptedPassword ?? "" + } catch { + print(error) + } + } else { + do { + account.username = newUsername + + let encryptedPassword = try CryptoSecurityService.encrypt(newPassword) + account.passwordLength = Int16(newPassword.count) + account.password = encryptedPassword?.value ?? account.password + account.encryptionTag = encryptedPassword?.tag ?? account.encryptionTag + account.nonce = CryptoSecurityService.nonceStr + + account.lastModified = Date() + + try? viewContext.save() + } catch { + print(error) + } + } + } + .onAppear { + displayedPassword = CryptoSecurityService.randomString(length: Int(account.passwordLength)) + } + } + } + } +} diff --git a/Shared/Views/AccountView/AccountView+Content.swift b/Shared/Views/AccountView/AccountView+Content.swift new file mode 100644 index 0000000..c219201 --- /dev/null +++ b/Shared/Views/AccountView/AccountView+Content.swift @@ -0,0 +1,101 @@ +// +// AccountView+Content.swift +// AccountView+Content +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI + +extension AccountView { + var content: some View { + GroupBox { + VStack(alignment: .leading) { + if let website = account.domain { + HStack(alignment: .top) { + FaviconView(website: website) + .drawingGroup() + .frame(width: 50, height: 50) + VStack(alignment: .leading) { + Text(website) + .font(.headline) + if let dateAdded = account.dateAdded { + Text("Date Added: ") + .foregroundColor(.secondary) + + Text(dateAdded, style: .date) + .foregroundColor(.secondary) + } + + if let lastModified = account.lastModified { + Text("Last Modified: ") + .foregroundColor(.secondary) + + Text(lastModified, style: .date) + .foregroundColor(.secondary) + } + } + Spacer() + Button(isEditing ? "Done" : "Edit") { + if !isEditing { + UserAuthenticationService.authenticate() + .sink { success in + if success { + withAnimation { + isEditing = true + } + } + } + .store(in: &UserAuthenticationService.cancellables) + } else { + withAnimation { + isEditing = false + } + } + } +#if os(iOS) + .hoverEffect() +#endif + } + } + AccountDetailsView(account: account, isEditing: $isEditing) + .padding(.vertical) +#if os(macOS) + Spacer() +#endif + HStack { + if let website = account.domain, let url = URL(string: "https://" + website) { + Link("Go to website", destination: url) +#if os(iOS) + .hoverEffect() +#endif + } + Spacer() +// if isEditing { +// if !isAddingAlternateDomains { +// Button("Alternate Domains") { +// newAlternateDomains = String((account.alternateDomains? +// .map({ String($0) }) ?? []) +// .joined(separator: ",")) +// isAddingAlternateDomains = true +// } +// } else { +// TextField("Alternate Domains", text: $newAlternateDomains, onCommit: { +// isAddingAlternateDomains = false +// account.alternateDomains = newAlternateDomains.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: ",").map({ $0 as NSString }) +// }) +// .textFieldStyle(.roundedBorder) +//#if os(iOS) +// .autocapitalization(.none) +//#endif +// .disableAutocorrection(true) +// } +// } + } + } +#if os(macOS) + .padding() +#endif + } + .padding() + .frame(maxWidth: 600) + } +} diff --git a/Shared/Views/AccountView/AccountView.swift b/Shared/Views/AccountView/AccountView.swift new file mode 100644 index 0000000..0bf11de --- /dev/null +++ b/Shared/Views/AccountView/AccountView.swift @@ -0,0 +1,95 @@ +// +// AccountView.swift +// AccountView +// +// Created by Ethan Lipnik on 8/18/21. +// + +import SwiftUI + +struct AccountView: View { + // MARK: - Environment + @Environment(\.managedObjectContext) private var viewContext + + // MARK: - CoreData + @FetchRequest( + sortDescriptors: [], + animation: .default) + private var accounts: FetchedResults + + // MARK: - Variables + let account: Account + @State var isEditing: Bool = false + @State private var isAddingAlternateDomains: Bool = false + @State private var newAlternateDomains: String = "" + @State private var isSharing: Bool = false + + // MARK: - Init + init(account: Account) { + self.account = account + + let predicate = NSPredicate(format: "domain contains[c] %@", account.domain!) + self._accounts = FetchRequest(sortDescriptors: [], predicate: predicate, animation: .default) + } + + // MARK: - View + var body: some View { +// let otherAccounts = accounts.filter({ $0.username != account.username }).map({ $0 }) + + ScrollView { + content + +// VStack { +// if !otherAccounts.isEmpty { +// Text("Other Accounts") +// .frame(maxWidth: .infinity, alignment: .leading) +// LazyVStack { +// ForEach(otherAccounts) { account in +// NavigationLink { +// AccountView(account: account) +// } label: { +// GroupBox { +// HStack { +// FaviconView(website: "https://" + account.domain) +// .frame(width: 40, height: 40) +// VStack(alignment: .leading) { +// Text(account.domain) +// .bold() +// .frame(maxWidth: .infinity, alignment: .leading) +// Text(account.username) +// .foregroundColor(Color.secondary) +// } +// } +// } +// }.buttonStyle(.plain) +// } +// } +// } +// }.padding() + } + .sheet(isPresented: $isSharing) { + MultipeerShareSheet(account: account) + } +#if os(iOS) + .navigationTitle(account.domain?.capitalizingFirstLetter() ?? "") + .toolbar { + ToolbarItem { + Button { + isSharing.toggle() + } label: { + Label("Share", systemImage: "square.and.arrow.up") + } + + } + } +#else + .frame(minWidth: 300) +#endif + } +} + +struct AccountView_Previews: PreviewProvider { + static var previews: some View { + AccountView(account: .init()) + } +} diff --git a/Shared/Views/ContentView/ContentView+Add.swift b/Shared/Views/ContentView/ContentView+Add.swift new file mode 100644 index 0000000..a817763 --- /dev/null +++ b/Shared/Views/ContentView/ContentView+Add.swift @@ -0,0 +1,29 @@ +// +// ContentView+Add.swift +// ContentView+Add +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI + +extension ContentView { + func addItem() { + withAnimation { + isCreatingNewVault = true + isNewVaultFocused = true + } + } + + func addItem(withName name: String) { + + do { + let vault = Vault(context: viewContext) + vault.name = name + + try viewContext.save() + } catch { + print(error) + } + } +} diff --git a/Shared/Views/ContentView/ContentView+List.swift b/Shared/Views/ContentView/ContentView+List.swift new file mode 100644 index 0000000..fa45579 --- /dev/null +++ b/Shared/Views/ContentView/ContentView+List.swift @@ -0,0 +1,85 @@ +// +// ContentView+List.swift +// ContentView+List +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI + +extension ContentView { + var list: some View { + List { + vaultSection + + if !pinnedAccounts.isEmpty { + pinnedSection + } + } + .listStyle(.sidebar) + } + + private var vaultSection: some View { + Section("Vaults") { + if isCreatingNewVault { + TextField("New Vault", text: $newVaultName, onCommit: { + addItem(withName: newVaultName) + + newVaultName = "" + isNewVaultFocused = false + withAnimation { + isCreatingNewVault = false + } + }) + .textFieldStyle(.roundedBorder) + .focused($isNewVaultFocused) + } + ForEach(vaults) { vault in + NavigationLink(tag: vault, selection: $selectedVault) { + VaultView(vault: vault) + } label: { + Label(vault.name!.capitalized, systemImage: "lock.square.stack.fill") + } + .contextMenu { + Button("Delete", role: .destructive) { + vaultToBeDeleted = vault + shouldDeleteVault.toggle() + } + } + } + .onDelete { indexSet in + vaultToBeDeleted = vaults[indexSet.first!] + shouldDeleteVault.toggle() + } + } + } + + private var pinnedSection: some View { + Section("Pinned") { + ForEach(pinnedAccounts) { account in + NavigationLink { + VaultView(vault: account.vault!, selectedAccount: account) + } label: { + VStack(alignment: .leading) { + Text(account.domain!.capitalizingFirstLetter()) + .bold() + Text(account.username!) + .foregroundColor(Color.secondary) + } + } + .contextMenu { + Button { + account.isPinned = false + + try? viewContext.save() + } label: { + Label("Unpin", systemImage: "pin.slash") + } + + } + }.onDelete { index in + index.map({ pinnedAccounts[$0] }).forEach({ $0.isPinned = false }) + } + } + } +} diff --git a/Shared/Views/ContentView/ContentView.swift b/Shared/Views/ContentView/ContentView.swift new file mode 100644 index 0000000..015913a --- /dev/null +++ b/Shared/Views/ContentView/ContentView.swift @@ -0,0 +1,135 @@ +// +// ContentView.swift +// Shared +// +// Created by Ethan Lipnik on 8/18/21. +// + +import SwiftUI +import CoreData + +struct ContentView: View { + // MARK: - Environment + @Environment(\.managedObjectContext) var viewContext + + // MARK: - CoreData Variables + @FetchRequest( + sortDescriptors: [], + animation: .default) + var vaults: FetchedResults + + @FetchRequest( + sortDescriptors: [], + predicate: NSPredicate(format: "isPinned == %i", 1), + animation: .default) + var pinnedAccounts: FetchedResults + + // MARK: - Variables + @Binding var isLocked: Bool + + @FocusState var isNewVaultFocused: Bool + @State var isCreatingNewVault: Bool = false + @State var newVaultName: String = "" + + @State var selectedVault: Vault? = nil + + @State var shouldDeleteVault: Bool = false + @State var vaultToBeDeleted: Vault? = nil + + // MARK: - View + var body: some View { + NavigationView { + list + .toolbar { + ToolbarItem(placement: ToolbarItemPlacement.navigation) { + Button { + isLocked = true + } label: { + Label("Lock", systemImage: "lock.fill") + } + } +#if os(iOS) + ToolbarItem(placement: .navigationBarTrailing) { + EditButton() + } +#endif + ToolbarItem { + Button(action: addItem) { + Label("Add Item", systemImage: "plus") + } + } + } + .confirmationDialog("Are you sure you want to delete this '\(vaultToBeDeleted?.name?.capitalized ?? "vault")'? You cannot retreive it when it is gone.", isPresented: $shouldDeleteVault) { // COnfirmation dialogue for deleting a vault. + Button("Delete", role: .destructive) { + guard let vault = vaultToBeDeleted else { return } + + deleteItems(offsets: IndexSet([vaults.firstIndex(of: vault)].compactMap({ $0 }))) + + shouldDeleteVault = false + vaultToBeDeleted = nil + } + + Button("Cancel", role: .cancel) { + shouldDeleteVault = false + vaultToBeDeleted = nil + }.keyboardShortcut(.defaultAction) + } + .navigationTitle("OpenSesame") + .onAppear { +#if os(macOS) + selectedVault = vaults.first +#endif + } + +#if os(macOS) // Add empty views for when the NavigationView is empty. + List {}.listStyle(.inset(alternatesRowBackgrounds: true)) + EmptyView() + .frame(minWidth: 300) +#endif + } + // URL Actions for keyboard shortcuts. + .onOpenURL { url in + if let components = URLComponents(url: url, resolvingAgainstBaseURL: false), + let query = components.query, let url = components.string?.replacingOccurrences(of: "?" + query, with: ""), let queryItems = components.queryItems { + if let type = queryItems.first(where: { $0.name == "type" }), type.value == "vault", url == "openSesame://new" { + addItem() + } + } else { + print("Badly formatted URL") + } + } + } + + private func deleteItems(offsets: IndexSet) { + withAnimation { + + let selectedVaults = offsets.map({ vaults[$0] }) + + selectedVaults + .forEach({ $0.accounts? + .compactMap({ $0 as? NSManagedObject }) + .forEach(viewContext.delete) }) + + selectedVaults + .forEach(viewContext.delete) + + vaultToBeDeleted = nil + shouldDeleteVault = false + + do { + try viewContext.save() + } catch { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + let nsError = error as NSError + fatalError("Unresolved error \(nsError), \(nsError.userInfo)") + } + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView(isLocked: .constant(false)) + } +} diff --git a/Shared/Views/FaviconView.swift b/Shared/Views/FaviconView.swift new file mode 100644 index 0000000..44dfb9b --- /dev/null +++ b/Shared/Views/FaviconView.swift @@ -0,0 +1,106 @@ +// +// FaviconView.swift +// FaviconView +// +// Created by Ethan Lipnik on 8/18/21. +// + +import SwiftUI +import FaviconFinder + +struct FaviconView: View { + // MARK: - Variables + let website: String + + static let cache = NSCache() + + @State private var image: FaviconImage? = nil + + // MARK: - View + var body: some View { + Group { + if let existingCompany = websiteToExistingCompany() { + existingCompany + .resizable() + .padding(5) + .background(Color.white) + .cornerRadius(10) + } else if let image = image { + Group { +#if canImport(UIKit) + Image(uiImage: image) + .resizable() +#else + Image(nsImage: image) + .resizable() +#endif + } + .transition(.opacity) + .cornerRadius(8) + .padding(5) + .background(Color.white) + .cornerRadius(10) + } else { + RoundedRectangle(cornerRadius: 10) + .fill(Color.white) + .overlay(Text(website[0].uppercased()) + .font(.system(.largeTitle, design: .rounded).bold())) + .onAppear { + if let cache = FaviconView.cache.object(forKey: website as NSString) { + + self.image = cache + } + } + .task { + if let cache = FaviconView.cache.object(forKey: website as NSString) { + + self.image = cache + + return + } + guard let url = URL(string: "https://" + website) else { return } + FaviconFinder(url: url, preferredType: .html, preferences: [ + FaviconDownloadType.html: FaviconType.appleTouchIcon.rawValue, + FaviconDownloadType.ico: "favicon.ico" + ]).downloadFavicon { result in + switch result { + case .success(let favicon): + withAnimation { + self.image = favicon.image + } + + FaviconView.cache.setObject(favicon.image, forKey: website as NSString) + + case .failure(let error): + print("Error", error, url) + } + } + } + } + } + } + + // MARK: - Local Company Logos + func websiteToExistingCompany() -> Image? { + switch website.lowercased() { + case "apple.com", "apple.co.uk": + return Image("Apple") + case "google.com", "google.co.uk", "goo.gl": + return Image("Google") + case "github.com", "github.co.uk": + return Image("GitHub") + case "twitter.com", "twitter.co.uk", "t.co": + return Image("Twitter") + default: + return nil + } + } +} + +struct FaviconView_Previews: PreviewProvider { + static var previews: some View { + FaviconView(website: "https://Google.com") + .aspectRatio(1/1, contentMode: .fit) + .padding() + } +} diff --git a/Shared/Views/ImportExport/.DS_Store b/Shared/Views/ImportExport/.DS_Store new file mode 100644 index 0000000..2e0bea2 Binary files /dev/null and b/Shared/Views/ImportExport/.DS_Store differ diff --git a/Shared/Views/ImportExport/ExportView.swift b/Shared/Views/ImportExport/ExportView.swift new file mode 100644 index 0000000..79c36dd --- /dev/null +++ b/Shared/Views/ImportExport/ExportView.swift @@ -0,0 +1,68 @@ +// +// ExportView.swift +// ExportView +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI +import CoreData +import CSV + +struct ExportView: View { + // MARK: - Environment + @Environment(\.managedObjectContext) private var viewContext + @Environment(\.dismiss) var dismiss + + // MARK: - CoreData Variables + @FetchRequest( + sortDescriptors: [], + animation: .default) + private var accounts: FetchedResults + + // MARK: - Variables + @State var fileURL: URL? = nil + @State private var isExporting: Bool = false + + // MARK: - View + var body: some View { + Spacer() + .fileMover(isPresented: $isExporting, file: fileURL, onCompletion: { result in + + switch result { + case .success(let url): + try? FileManager.default.moveItem(at: FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].appendingPathComponent("OpenSesame Passwords.csv"), to: url) + case .failure(let error): + print(error) + } + dismiss.callAsFunction() + }) + .onAppear { + let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].appendingPathComponent("OpenSesame Passwords.csv") + let stream = OutputStream(toFileAtPath: url.path, append: false)! + do { + let csv = try CSVWriter(stream: stream) + try csv.write(row: ["Title", "URL", "Username", "Password", "OTPAuth"]) + + for account in accounts { + if let decryptedPassword = try? CryptoSecurityService.decrypt(account.password!, tag: account.encryptionTag!, nonce: account.nonce) { + try csv.write(row: [account.domain ?? "", account.url ?? "", account.username ?? "", decryptedPassword, account.otpAuth ?? ""]) + } + } + + csv.stream.close() + + fileURL = url + isExporting = true + } catch { + dismiss.callAsFunction() + } + } + } +} + +struct ExportView_Previews: PreviewProvider { + static var previews: some View { + ExportView() + } +} diff --git a/Shared/Views/ImportExport/ImportView/ImportView+Import.swift b/Shared/Views/ImportExport/ImportView/ImportView+Import.swift new file mode 100644 index 0000000..d30981c --- /dev/null +++ b/Shared/Views/ImportExport/ImportView/ImportView+Import.swift @@ -0,0 +1,88 @@ +// +// ImportView+Import.swift +// ImportView+Import +// +// Created by Ethan Lipnik on 8/22/21. +// + +import Foundation +import AuthenticationServices +import DomainParser + +extension ImportView { + func importAccounts() { + + guard let selectedVault = vaults[safe: selectedVault] else { + return + } + + isImporting = true + + DispatchQueue.global(qos: .userInitiated).async { + do { + let domainParser = try DomainParser() + + func finalize() { + if self.accountProgress >= accounts.count { + try! viewContext.save() + + // Save credentials for autofill + ASCredentialIdentityStore.shared.getState { state in + if state.isEnabled { + + let domainIdentifers = addedAccounts.map({ ASPasswordCredentialIdentity(serviceIdentifier: ASCredentialServiceIdentifier(identifier: $0.domain!, type: .domain), + user: $0.username!, + recordIdentifier: nil) }) + + + ASCredentialIdentityStore.shared.saveCredentialIdentities(domainIdentifers, completion: {(_,error) -> Void in + print(error?.localizedDescription ?? "No errors in saving credentials") + }) + } + } + + dismiss.callAsFunction() + } + } + + for importedAccount in accounts { + do { + if let url = URL(string: importedAccount.url), let host = url.host, let domain = domainParser.parse(host: host)?.domain?.lowercased(), !addedAccounts.contains(where: { $0.domain == domain && $0.password == importedAccount.password && $0.username == importedAccount.username }) { + + let account = Account(context: viewContext) + account.domain = domain + account.url = url.absoluteString + + account.username = importedAccount.username + account.otpAuth = importedAccount.otpAuth + account.dateAdded = Date() + + guard let (encryptedPassword, encryptedTag) = try CryptoSecurityService.encrypt(importedAccount.password) else { return } + account.password = encryptedPassword + account.encryptionTag = encryptedTag + account.nonce = CryptoSecurityService.nonceStr + account.passwordLength = Int16(importedAccount.password.count) + + selectedVault.addToAccounts(account) + + DispatchQueue.main.async { + self.addedAccounts.append(account) + self.accountProgress += 1 + + finalize() + } + } else { + self.accountProgress += 1 + + finalize() + } + } catch { + print(error) + } + } + } catch { + print(error) + } + } + } +} diff --git a/Shared/Views/ImportExport/ImportView/ImportView.swift b/Shared/Views/ImportExport/ImportView/ImportView.swift new file mode 100644 index 0000000..6a7f069 --- /dev/null +++ b/Shared/Views/ImportExport/ImportView/ImportView.swift @@ -0,0 +1,126 @@ +// +// ImportView.swift +// ImportView +// +// Created by Ethan Lipnik on 8/20/21. +// + +import SwiftUI +import CSV + +struct ImportView: View { + // MARK: - Environment + @Environment(\.managedObjectContext) var viewContext + @Environment(\.dismiss) var dismiss + + // MARK: - CoreData Variables + @FetchRequest( + sortDescriptors: [], + animation: .default) + var vaults: FetchedResults + + // MARK: - Variables + @State var accounts: [ImportedAccount] = [] + @State var isPresentingImporter: Bool = true + + @State var isImporting: Bool = false + @State var accountProgress: Int = 0 + + @State var selectedVault: Int = 0 + + @State var addedAccounts: [Account] = [] + + // MARK: - View + var body: some View { + VStack(spacing: 0) { + /// Should use a table on macOS but performance was unusable with enough data. + // Table(accounts) { + // TableColumn("Name", value: \.name) + // TableColumn("URL", value: \.url) + // TableColumn("Username", value: \.username) + // TableColumn("Password", value: \.password) + // TableColumn("OTP Auth", value: \.otpAuth) + // } + List(accounts) { + Text($0.name) + } +#if os(macOS) + .listStyle(.inset(alternatesRowBackgrounds: true)) +#endif + GroupBox { + HStack { + Button("Cancel", role: .cancel) { + dismiss.callAsFunction() + }.keyboardShortcut(.cancelAction) + + if isImporting { + ProgressView("Progress", value: Double(accountProgress) / Double(accounts.count)) + } else { + Spacer() + } + + Picker("Vault", selection: $selectedVault) { + ForEach(0.. Element? { + return indices.contains(index) ? self[index] : nil + } +} diff --git a/Shared/Views/LockView/LockView+CreatePassword.swift b/Shared/Views/LockView/LockView+CreatePassword.swift new file mode 100644 index 0000000..56308f9 --- /dev/null +++ b/Shared/Views/LockView/LockView+CreatePassword.swift @@ -0,0 +1,38 @@ +// +// LockView+CreatePassword.swift +// LockView+CreatePassword +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI + +extension LockView { + struct CreatePasswordView: View { + @State private var password: String = "" + let completionAction: (String) -> Void + + var body: some View { + VStack { + Text("Welcome to OpenSesame") + .font(.title.bold()) + GroupBox { + TextField("Enter a new master password", text: $password, onCommit: { + completionAction(password) + }) + .font(.system(.body, design: .monospaced)) + .textFieldStyle(.plain) + .disableAutocorrection(true) +#if os(iOS) + .autocapitalization(.none) +#endif + } + Button("Continue") { + completionAction(password) + } + } + .padding() + .interactiveDismissDisabled() + } + } +} diff --git a/Shared/Views/LockView/LockView+Functions.swift b/Shared/Views/LockView/LockView+Functions.swift new file mode 100644 index 0000000..f346a1f --- /dev/null +++ b/Shared/Views/LockView/LockView+Functions.swift @@ -0,0 +1,112 @@ +// +// LockView+Functions.swift +// LockView+Functions +// +// Created by Ethan Lipnik on 8/22/21. +// + +import Foundation +import KeychainAccess + +extension LockView { + // Try to load encryption test from Keychain Access. + func loadEncryptionTest() { + if let encryptionTest = try? Keychain(service: "com.ethanlipnik.OpenSesame", accessGroup: "B6QG723P8Z.OpenSesame") + .synchronizable(true) + .getData("encryptionTest"), + let json = try? JSONSerialization.jsonObject(with: encryptionTest, options: []) as? [String: String], + let test = json["test"], + let tag = json["tag"] { + + self.encryptionTest = (test, tag, json["nonce"]) + } else { + encryptionTestDoesntExist = true + } + } + + // If a master password doesn't exist, then create one with a given password. + func createMasterPassword(_ password: String) { + CryptoSecurityService.loadEncryptionKey(password) { + do { + guard let encryptedTest = try CryptoSecurityService.encrypt(CryptoSecurityService.randomString(length: 32)) else { fatalError() } + + self.encryptionTest = (encryptedTest.value, encryptedTest.tag, CryptoSecurityService.nonceStr) + + let json = ["test": encryptedTest.value, + "tag": encryptedTest.tag, + "nonce": CryptoSecurityService.nonceStr] + + if let jsonData = try? JSONSerialization.data(withJSONObject: json, options: .sortedKeys) { // Save as JSON so that it's easier to retrieve as a single value. + try? Keychain(service: "com.ethanlipnik.OpenSesame", accessGroup: "B6QG723P8Z.OpenSesame") + .synchronizable(true) + .set(jsonData, key: "encryptionTest") + } + + encryptionTestDoesntExist = false + try? updateBiometrics(password) // Try to update biometrics if available. + } catch { + fatalError(error.localizedDescription) + } + } + } + + // Save the master password to Keychain Access with a limited accessibility and requiring biometrics to view. + func updateBiometrics(_ password: String) throws { +#if targetEnvironment(simulator) + let accessibility: Accessibility = .always +#else + let accessibility: Accessibility = .whenUnlockedThisDeviceOnly +#endif + try Keychain(service: "com.ethanlipnik.OpenSesame", accessGroup: "B6QG723P8Z.OpenSesame") + .accessibility(accessibility, authenticationPolicy: .biometryCurrentSet) // If the biometrics change, this will no longer be accessible. + .authenticationPrompt("Authenticate to view your accounts") + .set(password, key: "masterPassword") + + print("Updated biometrics for password", password) + } + + // Test the given password with the saved encryption test to verify it is valid and correct. + func unlock(_ password: String, method: UnlockMethod = .password) { + guard let (test, tag, nonce) = encryptionTest else { encryptionTestDoesntExist = true; return } + + CryptoSecurityService.loadEncryptionKey(password) { + let string = try? CryptoSecurityService.decrypt(test, tag: tag, nonce: nonce) + + if string != nil { // Passed the encryption test and the password is valid. + + if method != .biometrics && biometricsFailed { + try? updateBiometrics(password) // Master password may have changed and biometrics have failed. Try to update them with the correct password. + } else { + print("No need to update biometrics") + } + + // MARK: Why I added a fake login delay + /// Much like how vacuums are designed to be loud and many services have fake loading bars, OpenSesame uses a short login delay so the user feels how secure it is. CryptoKit is so incredibly fast and low level that this would load instantly (much like iCloud Keychain), but this gives it a more solid feel. + /// This may be removed in the future. +#if DEBUG + let loginDelay: Double = 0 // Remove the login delay when debugging +#else + let loginDelay: Double = 1.5 +#endif + isAuthenticating = true + DispatchQueue.main.asyncAfter(deadline: .now() + loginDelay) { + onSuccessfulUnlock() + self.password = "" + + isAuthenticating = false + + attempts = 0 + } + + print("Unlocked") + } else { + attempts += 1 + print("Failed to unlock for the", attempts, "time.", "Password used:", password) + + if method == .biometrics { + biometricsFailed = true + } + } + } + } +} diff --git a/Shared/Views/LockView/LockView+TextField.swift b/Shared/Views/LockView/LockView+TextField.swift new file mode 100644 index 0000000..661d64c --- /dev/null +++ b/Shared/Views/LockView/LockView+TextField.swift @@ -0,0 +1,45 @@ +// +// LockView+TextField.swift +// LockView+TextField +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI +import KeychainAccess + +extension LockView { + var textField: some View { + GroupBox { + HStack { + SecureField("Master password", text: $password, onCommit: { + guard !password.isEmpty else { return } + unlock(password) + }) + .textFieldStyle(.plain) + .frame(maxWidth: 400) +#if os(macOS) // macOS should display differently since a red background looks weird. + if attempts > 0 { + Text(attempts, format: .number) + .bold() + .foregroundColor(Color.red) + .frame(width: 20) + } +#else + ZStack { + RoundedRectangle(cornerRadius: 5).fill(Color.red).aspectRatio(1/1, contentMode: .fit) + Text(attempts, format: .number) + .bold() + .foregroundColor(Color.white) + .animation(.none, value: attempts) + } + .frame(height: 35) + .opacity(attempts > 0 ? 1 : 0) + .blur(radius: attempts > 0 ? 0 : 10) + .animation(.default, value: attempts) +#endif + unlockButtons + } + } + } +} diff --git a/Shared/Views/LockView/LockView+UnlockButtons.swift b/Shared/Views/LockView/LockView+UnlockButtons.swift new file mode 100644 index 0000000..6ed8ae1 --- /dev/null +++ b/Shared/Views/LockView/LockView+UnlockButtons.swift @@ -0,0 +1,54 @@ +// +// LockView+UnlockButtons.swift +// LockView+UnlockButtons +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI +import KeychainAccess + +extension LockView { + var unlockButtons: some View { + Group { + Button { + unlock(password) + } label: { + Image(systemName: "key.fill") +#if os(iOS) + .imageScale(.large) +#endif + } + .keyboardShortcut(.defaultAction) + .disabled(password.isEmpty) + + if canAuthenticateWithBiometrics || biometricsFailed { + Button { + do { +#if targetEnvironment(simulator) + let accessibility: Accessibility = .always +#else + let accessibility: Accessibility = .whenUnlockedThisDeviceOnly +#endif + if let masterPassword = try Keychain(service: "com.ethanlipnik.OpenSesame", accessGroup: "B6QG723P8Z.OpenSesame") + .accessibility(accessibility, authenticationPolicy: .biometryCurrentSet) + .authenticationPrompt("Authenticate to view your accounts") + .get("masterPassword") { + unlock(masterPassword, method: .biometrics) + } else { + biometricsFailed = true + } + } catch { + print(error) + } + } label: { + Image(systemName: "faceid") +#if os(iOS) + .imageScale(.large) +#endif + } + .keyboardShortcut("b", modifiers: [.command, .shift]) + } + } + } +} diff --git a/Shared/Views/LockView/LockView.swift b/Shared/Views/LockView/LockView.swift new file mode 100644 index 0000000..1d3e42b --- /dev/null +++ b/Shared/Views/LockView/LockView.swift @@ -0,0 +1,77 @@ +// +// LockView.swift +// LockView +// +// Created by Ethan Lipnik on 8/18/21. +// + +import SwiftUI +import KeychainAccess +import CloudKit + +struct LockView: View { + // MARK: - Environment + @Environment(\.managedObjectContext) private var viewContext + + // MARK: - Variables + @Binding var isLocked: Bool + let onSuccessfulUnlock: () -> Void + + + @State var password: String = "" + @State var attempts: Int = 0 + + @State var canAuthenticateWithBiometrics: Bool = true + @State var biometricsFailed: Bool = false + + @State var encryptionTestDoesntExist = false + @State var encryptionTest: (test: String, tag: String, nonce: String?)? = nil + + @State var isAuthenticating: Bool = false + + + // MARK: - Variable Types + public enum UnlockMethod { + case biometrics + case password + } + + // MARK: - View + var body: some View { + VStack(spacing: 30) { + Spacer() + Image("Icon") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 250, height: 250) + .animation(.default, value: isLocked) + textField + .blur(radius: isAuthenticating ? 2.5 : 0) + Spacer() + } + .padding() +#if os(macOS) + .frame(minWidth: 500, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity) +#endif + .allowsHitTesting(!isAuthenticating) + .animation(.default, value: isAuthenticating) + .padding() + .navigationTitle("OpenSesame") + + // MARK: - Master Password Creation + .sheet(isPresented: $encryptionTestDoesntExist) { + CreatePasswordView(completionAction: createMasterPassword) + } + .onAppear { + loadEncryptionTest() + } + } +} + +struct LockView_Previews: PreviewProvider { + static var previews: some View { + LockView(isLocked: .constant(true)) { + + } + } +} diff --git a/Shared/Views/MainView.swift b/Shared/Views/MainView.swift new file mode 100644 index 0000000..a3a153d --- /dev/null +++ b/Shared/Views/MainView.swift @@ -0,0 +1,91 @@ +// +// MainView.swift +// MainView +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI + +struct MainView: View { + // MARK: - Environment + @Environment(\.managedObjectContext) private var viewContext + + // MARK: - Variables + @Binding var isLocked: Bool + @Binding var shouldHideApp: Bool + + @Binding var isImportingPasswords: Bool + @Binding var shouldExportPasswords: Bool + @Binding var isExportingPasswords: Bool + + // MARK: - View + var body: some View { + ZStack { + // MARK: - ContentView + /// ContentView is the main page that displays the vaults. Only gets initialized during login to prevent any peeping. + if !isLocked { + ContentView(isLocked: $isLocked) + .environment(\.managedObjectContext, viewContext) + .opacity(isLocked ? 0 : 1) + .blur(radius: isLocked ? 15 : 0) + .transition(.opacity) + .animation(.default, value: isLocked) + } + + // MARK: - LockView + LockView(isLocked: $isLocked) { // Unlock function + isLocked = false + } + .environment(\.managedObjectContext, viewContext) + .opacity(isLocked ? 1 : 0) + .blur(radius: isLocked ? 0 : 25) + .allowsHitTesting(isLocked) // Prevent lock screen from being interacted with even though it's in the foreground. + .animation(.default, value: isLocked) +#if os(macOS) + .toolbar { // LockView only toolbar + ToolbarItem(placement: .primaryAction) { + if isLocked { + Button { + + } label: { + Label("Info", systemImage: "info") + } + } + } + } +#endif + } +#if os(macOS) + .blur(radius: shouldHideApp ? 25 : 0) +#endif + .animation(.default, value: shouldHideApp) + + // MARK: - ImportView + .sheet(isPresented: $isImportingPasswords) { + ImportView() + .environment(\.managedObjectContext, viewContext) + } + + // MARK: - ExportView + .confirmationDialog("You are about to export your passwords unencrypted. Do not share this file!", isPresented: $shouldExportPasswords, actions: { + Button("Export", role: .destructive) { + self.isExportingPasswords = true + } + + Button("Cancel", role: .cancel) { }.keyboardShortcut(.defaultAction) + }) + .sheet(isPresented: $isExportingPasswords, onDismiss: { + shouldExportPasswords = false + }) { + ExportView() + .environment(\.managedObjectContext, viewContext) + } + } +} + +struct MainView_Previews: PreviewProvider { + static var previews: some View { + MainView(isLocked: .constant(true), shouldHideApp: .constant(false), isImportingPasswords: .constant(false), shouldExportPasswords: .constant(false), isExportingPasswords: .constant(false)) + } +} diff --git a/Shared/Views/SettingsView/.DS_Store b/Shared/Views/SettingsView/.DS_Store new file mode 100644 index 0000000..0cc6cc1 Binary files /dev/null and b/Shared/Views/SettingsView/.DS_Store differ diff --git a/Shared/Views/SettingsView/macOS/SettingsView+GeneralView.swift b/Shared/Views/SettingsView/macOS/SettingsView+GeneralView.swift new file mode 100644 index 0000000..0689fe8 --- /dev/null +++ b/Shared/Views/SettingsView/macOS/SettingsView+GeneralView.swift @@ -0,0 +1,86 @@ +// +// SettingsView+GeneralView.swift +// SettingsView+GeneralView +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI +import CoreData +import KeychainAccess + +extension SettingsView { + struct GeneralView: View { + // MARK: - Environment + @Environment(\.managedObjectContext) private var viewContext + + // MARK: - Variables + @State var canUseBiometrics: Bool = true + @State var passwordForgivenessDuration: Int = 0 + + @State var canLoadWebsiteFavicon: Bool = true + @State var colorScheme: Int = 0 + + @State private var newMasterPassword: String = "" + + // MARK: - View + var body: some View { + VStack(spacing: 20) { + authenticationView + appearanceView + } + .padding() + .padding(.vertical) + } + + // MARK: - AppearanceView + var appearanceView: some View { + VStack { + Text("Appearance") + .frame(maxWidth: .infinity, alignment: .leading) + GroupBox { + VStack(alignment: .leading) { + Toggle("Load Website Favicon", isOn: $canLoadWebsiteFavicon) + .frame(maxWidth: .infinity, alignment: .leading) + Divider() + Picker("Color Scheme:", selection: $colorScheme) { + Text("System") + .tag(0) + Text("Light") + .tag(1) + Text("Dark") + .tag(2) + } + .pickerStyle(.radioGroup) + }.padding(5) + } + } + } + + // MARK: - AuthenticationView + var authenticationView: some View { + VStack { + Text("Authentication") + .frame(maxWidth: .infinity, alignment: .leading) + GroupBox { + VStack(alignment: .leading) { + Toggle("Use Biometrics", isOn: $canUseBiometrics) + .frame(maxWidth: .infinity, alignment: .leading) + Divider() + Picker("Require password after:", selection: $passwordForgivenessDuration) { + Text("Immediately") + .tag(0) + Text("15 seconds") + .tag(1) + Text("1 minute") + .tag(2) + Text("5 minutes") + .tag(3) + } + .pickerStyle(.radioGroup) + }.padding(5) + } + } + } + } +} diff --git a/Shared/Views/SettingsView/macOS/SettingsView+MenuBarView.swift b/Shared/Views/SettingsView/macOS/SettingsView+MenuBarView.swift new file mode 100644 index 0000000..74c041b --- /dev/null +++ b/Shared/Views/SettingsView/macOS/SettingsView+MenuBarView.swift @@ -0,0 +1,46 @@ +// +// SettingsView+MenuBarView.swift +// SettingsView+MenuBarView +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI + +extension SettingsView { + struct MenuBarView: View { + // MARK: - Variables + @State var shouldShowInMenuBar: Bool = false + @State var shouldOpenOnStartup: Bool = false + @State var shouldKeepAppInDock: Bool = true + + // MARK: - View + var body: some View { + VStack(spacing: 20) { + systemView + } + .padding() + } + + // MARK: - SystemView + var systemView: some View { + VStack(spacing: 20) { + Toggle("Show in Menu Bar", isOn: $shouldShowInMenuBar) + .frame(maxWidth: .infinity, alignment: .leading) + VStack { + Text("System") + .frame(maxWidth: .infinity, alignment: .leading) + GroupBox { + VStack(alignment: .leading) { + Toggle("Open on Startup", isOn: $shouldOpenOnStartup) + .frame(maxWidth: .infinity, alignment: .leading) + Toggle("Keep app in dock", isOn: $shouldKeepAppInDock) + .frame(maxWidth: .infinity, alignment: .leading) + }.padding(5) + } + } + .disabled(!shouldShowInMenuBar) + } + } + } +} diff --git a/Shared/Views/SettingsView/macOS/SettingsView.swift b/Shared/Views/SettingsView/macOS/SettingsView.swift new file mode 100644 index 0000000..57863ca --- /dev/null +++ b/Shared/Views/SettingsView/macOS/SettingsView.swift @@ -0,0 +1,37 @@ +// +// SettingsView.swift +// SettingsView +// +// Created by Ethan Lipnik on 8/18/21. +// + +import SwiftUI +import KeychainAccess + +struct SettingsView: View { + // MARK: - Environment + @Environment(\.managedObjectContext) private var viewContext + + // MARK: - View + var body: some View { + TabView { + GeneralView() + .padding() + .tabItem { + Label("General", systemImage: "gearshape.fill") + } + MenuBarView() + .padding() + .tabItem { + Label("Menu Bar", systemImage: "menubar.dock.rectangle") + } + } + .frame(maxWidth: 450) + } +} + +struct SettingsView_Previews: PreviewProvider { + static var previews: some View { + SettingsView() + } +} diff --git a/Shared/Views/Vault/.DS_Store b/Shared/Views/Vault/.DS_Store new file mode 100644 index 0000000..c19750c Binary files /dev/null and b/Shared/Views/Vault/.DS_Store differ diff --git a/Shared/Views/Vault/NewAccountView.swift b/Shared/Views/Vault/NewAccountView.swift new file mode 100644 index 0000000..7c5f0d1 --- /dev/null +++ b/Shared/Views/Vault/NewAccountView.swift @@ -0,0 +1,163 @@ +// +// NewAccountView.swift +// NewAccountView +// +// Created by Ethan Lipnik on 8/18/21. +// + +import SwiftUI +import AuthenticationServices +import KeychainAccess + +struct NewAccountView: View { + // MARK: - Environment + @Environment(\.managedObjectContext) private var viewContext + @Environment(\.dismiss) var dismiss + + // MARK: - CoreData + @FetchRequest( + sortDescriptors: [], + animation: .default) + private var vaults: FetchedResults + + // MARK: - Variables + @State private var website: String = "" + @State private var username: String = "" + @State private var password: String = "" + @State var vault: Int = 0 + + // MARK: - View + var body: some View { + VStack { + Text("New Account") + .font(.title.bold()) + .frame(maxWidth: .infinity) + .padding([.top, .horizontal]) + GroupBox { + VStack(spacing: 10) { + VStack(alignment: .leading) { + Label("Website", systemImage: "globe") + .foregroundColor(Color.secondary) + .frame(maxWidth: .infinity, alignment: .leading) + TextField("Website", text: $website) + .textFieldStyle(.roundedBorder) +#if os(iOS) + .keyboardType(.URL) + .textInputAutocapitalization(.none) +#endif + .disableAutocorrection(true) + } + VStack(alignment: .leading) { + Label("Email or Username", systemImage: "person.fill") + .foregroundColor(Color.secondary) + .frame(maxWidth: .infinity, alignment: .leading) + TextField("Email or Username", text: $username) + .textFieldStyle(.roundedBorder) +#if os(iOS) + .keyboardType(.emailAddress) + .autocapitalization(.none) +#endif + .disableAutocorrection(true) + } + + VStack(alignment: .leading) { + Label("Password", systemImage: "key.fill") + .foregroundColor(Color.secondary) + .frame(maxWidth: .infinity, alignment: .leading) + HStack { + TextField("Password", text: $password) + .textFieldStyle(.roundedBorder) +#if os(iOS) + .autocapitalization(.none) +#endif + .disableAutocorrection(true) +#if os(iOS) + Button { + password = Keychain.generatePassword() + } label: { + Image(systemName: "arrow.clockwise") + .imageScale(.large) + } +#endif + } + } + } +#if os(macOS) + .padding(5) +#endif + } + .padding() + Picker("Vault", selection: $vault) { + ForEach(0.. Void in + print(error?.localizedDescription ?? "No errors in saving credentials") + }) + } + } + + dismiss.callAsFunction() + } catch { + print(error) + +#if os(macOS) + NSAlert(error: error).runModal() +#endif + } + } +} + +struct NewAccountView_Previews: PreviewProvider { + static var previews: some View { + NewAccountView() + } +} diff --git a/Shared/Views/Vault/VaultView/VaultView+Item.swift b/Shared/Views/Vault/VaultView/VaultView+Item.swift new file mode 100644 index 0000000..5e0f61f --- /dev/null +++ b/Shared/Views/Vault/VaultView/VaultView+Item.swift @@ -0,0 +1,31 @@ +// +// VaultView+Item.swift +// VaultView+Item +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI + +extension VaultView { + struct ItemView: View { + let account: Account + @Binding var selectedAccount: Account? + + var body: some View { + NavigationLink(tag: account, selection: $selectedAccount) { + AccountView(account: account) + } label: { + VStack(alignment: .leading) { + Text(account.domain!.capitalizingFirstLetter()) + .bold() + .lineLimit(1) + Text(account.username!) + .foregroundColor(Color.secondary) + .lineLimit(1) + .blur(radius: CommandLine.arguments.contains("-marketing") ? 5 : 0) + } + } + } + } +} diff --git a/Shared/Views/Vault/VaultView/VaultView+List.swift b/Shared/Views/Vault/VaultView/VaultView+List.swift new file mode 100644 index 0000000..aafcd58 --- /dev/null +++ b/Shared/Views/Vault/VaultView/VaultView+List.swift @@ -0,0 +1,46 @@ +// +// VaultView+List.swift +// VaultView+List +// +// Created by Ethan Lipnik on 8/22/21. +// + +import SwiftUI + +extension VaultView { + var list: some View { + List { + ForEach(search.isEmpty ? accounts.map({ $0 }) : accounts.filter({ account in + let website = account.domain?.lowercased() ?? "" + let username = account.username?.lowercased() ?? "" + + return website.contains(search.lowercased()) || username.contains(search.lowercased()) + })) { account in + ItemView(account: account, selectedAccount: $selectedAccount) + .contextMenu { + Button { + + account.isPinned.toggle() + + try? viewContext.save() + } label: { + Label(account.isPinned ? "Unpin" : "Pin", systemImage: account.isPinned ? "pin.slash" : "pin") + } + Button("Delete", role: .destructive) { + shouldDeleteAccount.toggle() + } + } + .confirmationDialog("Are you sure you want to delete this account? You cannot retreive it when it is gone.", isPresented: $shouldDeleteAccount) { + Button("Delete", role: .destructive) { +// viewContext.delete(account) + #warning("This will delete the first account") + } + + Button("Cancel", role: .cancel) { + shouldDeleteAccount = false + }.keyboardShortcut(.defaultAction) + } + }.onDelete(perform: deleteItems) + } + } +} diff --git a/Shared/Views/Vault/VaultView/VaultView.swift b/Shared/Views/Vault/VaultView/VaultView.swift new file mode 100644 index 0000000..d03b4fe --- /dev/null +++ b/Shared/Views/Vault/VaultView/VaultView.swift @@ -0,0 +1,120 @@ +// +// VaultView.swift +// VaultView +// +// Created by Ethan Lipnik on 8/18/21. +// + +import SwiftUI +import AuthenticationServices +import Foundation + +struct VaultView: View { + // MARK: - Environment + @Environment(\.managedObjectContext) var viewContext + @Environment(\.colorScheme) var colorScheme + + // MARK: - CoreData Variables + @FetchRequest var accounts: FetchedResults + + // MARK: - Variables + let vault: Vault + + @State var selectedAccount: Account? = nil + + @State var shouldDeleteAccount: Bool = false + + @State var isCreatingNewAccount: Bool = false + @State var search: String = "" + + // MARK: - Init + init(vault: Vault, selectedAccount: Account? = nil) { + self.vault = vault + self._selectedAccount = .init(initialValue: selectedAccount) + + self._accounts = FetchRequest(sortDescriptors: [.init(key: "domain", ascending: true)], + predicate: NSPredicate(format: "vault == %@", vault), animation: .default) + } + + // MARK: - View + var body: some View { + list + .sheet(isPresented: $isCreatingNewAccount) { + NewAccountView(vault: 0) + } + .searchable(text: $search) +#if os(macOS) + .listStyle(.inset(alternatesRowBackgrounds: true)) + .navigationTitle("OpenSesame – " + (vault.name ?? "Unknown vault")) + .frame(minWidth: 200) +#else + .listStyle(.insetGrouped) + .navigationTitle(vault.name!) +#endif + .toolbar { +#if os(iOS) + ToolbarItem(placement: .navigationBarTrailing) { + EditButton() + } +#endif + ToolbarItem { + Button(action: addItem) { + Label("Add Item", systemImage: "plus") + } + } + } + .onOpenURL { url in + if let components = URLComponents(url: url, resolvingAgainstBaseURL: false), + let query = components.query, let url = components.string?.replacingOccurrences(of: "?" + query, with: ""), let queryItems = components.queryItems { + if let type = queryItems.first(where: { $0.name == "type" }), type.value == "account", url == "openSesame://new" { + addItem() + } + } else { + print("Badly formatted URL") + } + } + } + + private func addItem() { + isCreatingNewAccount = true + } + + func deleteItems(offsets: IndexSet) { +#warning("Add delete accounts") + // withAnimation { + // offsets.map { accounts[$0] }.forEach { account in + // + // let domainIdentifer = ASPasswordCredentialIdentity(serviceIdentifier: ASCredentialServiceIdentifier(identifier: account.domain!, type: .domain), + // user: account.username!, + // recordIdentifier: nil) + // + // ASCredentialIdentityStore.shared.removeCredentialIdentities([domainIdentifer]) { success, error in + // if let error = error { + // print("Failed to remove credential", error) + // + //#if os(macOS) + // NSAlert(error: NSError(domain: "Failed to delete credential for autofill: \(error.localizedDescription)", code: 0, userInfo: nil)).runModal() + //#endif + // } + // } + // + // viewContext.delete(account) + // } + // + // do { + // try viewContext.save() + // } catch { + // // Replace this implementation with code to handle the error appropriately. + // // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + // let nsError = error as NSError + // fatalError("Unresolved error \(nsError), \(nsError.userInfo)") + // } + // } + } +} + +struct VaultView_Previews: PreviewProvider { + static var previews: some View { + VaultView(vault: .init()) + } +} diff --git a/Tests iOS/Tests_iOS.swift b/Tests iOS/Tests_iOS.swift new file mode 100644 index 0000000..af22e3c --- /dev/null +++ b/Tests iOS/Tests_iOS.swift @@ -0,0 +1,42 @@ +// +// Tests_iOS.swift +// Tests iOS +// +// Created by Ethan Lipnik on 8/18/21. +// + +import XCTest + +class Tests_iOS: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/Tests iOS/Tests_iOSLaunchTests.swift b/Tests iOS/Tests_iOSLaunchTests.swift new file mode 100644 index 0000000..fd1085d --- /dev/null +++ b/Tests iOS/Tests_iOSLaunchTests.swift @@ -0,0 +1,32 @@ +// +// Tests_iOSLaunchTests.swift +// Tests iOS +// +// Created by Ethan Lipnik on 8/18/21. +// + +import XCTest + +class Tests_iOSLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} diff --git a/Tests macOS/Tests_macOS.swift b/Tests macOS/Tests_macOS.swift new file mode 100644 index 0000000..78875df --- /dev/null +++ b/Tests macOS/Tests_macOS.swift @@ -0,0 +1,42 @@ +// +// Tests_macOS.swift +// Tests macOS +// +// Created by Ethan Lipnik on 8/18/21. +// + +import XCTest + +class Tests_macOS: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/Tests macOS/Tests_macOSLaunchTests.swift b/Tests macOS/Tests_macOSLaunchTests.swift new file mode 100644 index 0000000..7db092c --- /dev/null +++ b/Tests macOS/Tests_macOSLaunchTests.swift @@ -0,0 +1,32 @@ +// +// Tests_macOSLaunchTests.swift +// Tests macOS +// +// Created by Ethan Lipnik on 8/18/21. +// + +import XCTest + +class Tests_macOSLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +}