From cb8305f014c1940af3863d945e9a72a95c535ebe Mon Sep 17 00:00:00 2001 From: Allan Feldman <6374032+a-feld@users.noreply.github.com> Date: Wed, 26 Feb 2025 23:40:07 -0500 Subject: [PATCH 01/10] (Fix) autofill / extensions and restore persistent storage location (#686) --- pass/AppDelegate.swift | 1 - passKit/Controllers/CoreDataStack.swift | 5 ++++- passKitTests/CoreData/CoreDataTestCase.swift | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pass/AppDelegate.swift b/pass/AppDelegate.swift index 6c18228..08e1a7a 100644 --- a/pass/AppDelegate.swift +++ b/pass/AppDelegate.swift @@ -23,7 +23,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { lazy var passcodeLockPresenter = PasscodeLockPresenter(mainWindow: self.window) func application(_: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - PersistenceController.shared.setup() // Override point for customization after application launch. SVProgressHUD.setMinimumSize(CGSize(width: 150, height: 100)) passcodeLockPresenter.present(windowLevel: UIApplication.shared.windows.last?.windowLevel.rawValue) diff --git a/passKit/Controllers/CoreDataStack.swift b/passKit/Controllers/CoreDataStack.swift index 29dbd1f..0452259 100644 --- a/passKit/Controllers/CoreDataStack.swift +++ b/passKit/Controllers/CoreDataStack.swift @@ -25,10 +25,13 @@ public class PersistenceController { description?.shouldInferMappingModelAutomatically = false if isUnitTest { description?.url = URL(fileURLWithPath: "/dev/null") + } else { + description?.url = URL(fileURLWithPath: Globals.dbPath) } + setup() } - public func setup() { + func setup() { container.loadPersistentStores { _, error in if error != nil { self.reinitializePersistentStore() diff --git a/passKitTests/CoreData/CoreDataTestCase.swift b/passKitTests/CoreData/CoreDataTestCase.swift index 8b3958c..fa356bf 100644 --- a/passKitTests/CoreData/CoreDataTestCase.swift +++ b/passKitTests/CoreData/CoreDataTestCase.swift @@ -21,7 +21,6 @@ class CoreDataTestCase: XCTestCase { try super.setUpWithError() controller = PersistenceController(isUnitTest: true) - controller.setup() } override func tearDown() { From 30ff525224d2f6b9e3a87dcb935551e485f3bf82 Mon Sep 17 00:00:00 2001 From: Mingshen Sun Date: Thu, 27 Feb 2025 21:29:08 -0800 Subject: [PATCH 02/10] Update actions to macos-15 --- .github/workflows/deploying.yml | 2 +- .github/workflows/testing.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploying.yml b/.github/workflows/deploying.yml index ed86795..f1b6c11 100644 --- a/.github/workflows/deploying.yml +++ b/.github/workflows/deploying.yml @@ -8,7 +8,7 @@ on: jobs: build: - runs-on: macos-14 + runs-on: macos-15 strategy: matrix: channel: ['beta', 'release'] diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 1058b8f..9582c47 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: testing: - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 From 0f482709aef2f907e9b1652f4ba4427f9a2e5e3f Mon Sep 17 00:00:00 2001 From: Mingshen Sun Date: Fri, 28 Feb 2025 20:24:01 -0800 Subject: [PATCH 03/10] Version bump --- pass.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index 5b14a28..6f91ef7 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -2001,7 +2001,7 @@ HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ""; - MARKETING_VERSION = 0.18.0; + MARKETING_VERSION = 0.19.0; MTL_ENABLE_DEBUG_INFO = NO; OTHER_SWIFT_FLAGS = "-D BETA"; PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforiosbeta; @@ -2627,7 +2627,7 @@ HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ""; - MARKETING_VERSION = 0.18.0; + MARKETING_VERSION = 0.19.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios; @@ -2695,7 +2695,7 @@ HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ""; - MARKETING_VERSION = 0.18.0; + MARKETING_VERSION = 0.19.0; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios; PRODUCT_NAME = Pass; From dd4dd174d49f160126497dc9fe4890c7eaa66b18 Mon Sep 17 00:00:00 2001 From: Mingshen Sun Date: Sun, 2 Mar 2025 11:43:46 -0800 Subject: [PATCH 04/10] Update password in the destination controller when save edit (#688) --- pass/Controllers/EditPasswordTableViewController.swift | 3 +++ pass/Controllers/PasswordDetailTableViewController.swift | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pass/Controllers/EditPasswordTableViewController.swift b/pass/Controllers/EditPasswordTableViewController.swift index fe7b508..d925a1f 100644 --- a/pass/Controllers/EditPasswordTableViewController.swift +++ b/pass/Controllers/EditPasswordTableViewController.swift @@ -28,6 +28,9 @@ class EditPasswordTableViewController: PasswordEditorTableViewController { if password!.plainText != editedPlainText || password!.path != path { password!.updatePassword(name: name, path: path, plainText: editedPlainText) } + if let controller = segue.destination as? PasswordDetailTableViewController { + controller.password = password + } } } } diff --git a/pass/Controllers/PasswordDetailTableViewController.swift b/pass/Controllers/PasswordDetailTableViewController.swift index 99e0a4a..98aa66a 100644 --- a/pass/Controllers/PasswordDetailTableViewController.swift +++ b/pass/Controllers/PasswordDetailTableViewController.swift @@ -21,7 +21,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni } } - private var password: Password? + var password: Password? private var passwordImage: UIImage? private var oneTimePasswordIndexPath: IndexPath? private var shouldPopCurrentView = false From 4288c4e2188f3e8b5cb1bd27c9d504551215e8e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danny=20M=C3=B6sch?= Date: Fri, 4 Apr 2025 19:43:59 +0200 Subject: [PATCH 05/10] Update dependencies (#690) --- pass.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index 6f91ef7..cfb6340 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -1519,7 +1519,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "SWIFT_PACKAGE_DIR=\"${BUILD_DIR%Build/*}SourcePackages/artifacts\"\nSWIFTLINT_CMD=$(ls \"$SWIFT_PACKAGE_DIR\"/swiftlintplugins/SwiftLintBinary/SwiftLintBinary.artifactbundle/swiftlint-*-macos/bin/swiftlint | head -n 1)\n\nSTRICT_OPT=\n\nif [[ \"${CI}\" == \"true\" ]]; then\n echo \"Running in a Continuous Integration environment. Linting is strictly.\"\n STRICT_OPT=--strict\nfi\n\nif [[ \"${CONFIGURATION}\" == \"Release\" ]]; then\n echo \"Running during a release build. Linting is skipped.\"\n exit 0\nfi\n\nif test -f \"$SWIFTLINT_CMD\" 2>&1\nthen\n \"$SWIFTLINT_CMD\" lint --fix\n \"$SWIFTLINT_CMD\" lint $STRICT_OPT\nelse\n echo \"warning: `swiftlint` command not found\"\nfi\n"; + shellScript = "SWIFT_PACKAGE_DIR=\"${BUILD_DIR%Build/*}SourcePackages/artifacts\"\nSWIFTLINT_CMD=$(ls \"$SWIFT_PACKAGE_DIR\"/swiftlintplugins/SwiftLintBinary/SwiftLintBinary.artifactbundle/swiftlint-*-macos/bin/swiftlint | head -n 1)\n\nSTRICT_OPT=\n\nif [[ \"${CI}\" == \"true\" ]]; then\n echo \"Running in a Continuous Integration environment. Linting is strict.\"\n STRICT_OPT=--strict\nfi\n\nif [[ \"${CONFIGURATION}\" == \"Release\" ]]; then\n echo \"Running during a release build. Linting is skipped.\"\n exit 0\nfi\n\nif test -f \"$SWIFTLINT_CMD\" 2>&1\nthen\n \"$SWIFTLINT_CMD\" lint --fix\n \"$SWIFTLINT_CMD\" lint $STRICT_OPT\nelse\n echo \"warning: `swiftlint` command not found.\"\nfi\n"; }; 9A996C4726DDEAF100A4485D /* Remove SPM Duplicate Frameworks */ = { isa = PBXShellScriptBuildPhase; diff --git a/pass.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/pass.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 907df41..d9ae8ff 100644 --- a/pass.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/pass.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -68,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/nicklockwood/SwiftFormat", "state" : { - "revision" : "2d5a2b6bde636c1feae2c852ab9a50f221e98c66", - "version" : "0.55.3" + "revision" : "468a7d32dedc8d352c191594b3b45d9fd8ba291b", + "version" : "0.55.5" } }, { @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SimplyDanny/SwiftLintPlugins.git", "state" : { - "revision" : "fac0c3d3ac69b15ea5382275dbbd5e583a2e05fa", - "version" : "0.58.0" + "revision" : "7a3d77f3dd9f91d5cea138e52c20cfceabf352de", + "version" : "0.58.2" } }, { @@ -93,10 +93,10 @@ { "identity" : "yubikit-ios", "kind" : "remoteSourceControl", - "location" : "https://github.com/Yubico/yubikit-ios", + "location" : "https://github.com/Yubico/yubikit-ios.git", "state" : { - "revision" : "1ea9c97010780499001ac8552d6440235d947686", - "version" : "4.6.0" + "revision" : "2c4206263763743a2f1ca3309b87432588be637f", + "version" : "4.7.0" } } ], From c3e67967cc0344f6729b44bb8db766ad9599d90f Mon Sep 17 00:00:00 2001 From: Mingshen Sun Date: Sun, 8 Jun 2025 16:35:50 -0700 Subject: [PATCH 06/10] Update YubiKit to 4.7.0 --- pass.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index cfb6340..807205a 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -2948,7 +2948,7 @@ repositoryURL = "https://github.com/Yubico/yubikit-ios.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 4.6.0; + minimumVersion = 4.7.0; }; }; /* End XCRemoteSwiftPackageReference section */ From 1a92996a47f4cc27e62c3a9004e1a17fc2c3d87d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 23:28:05 +0200 Subject: [PATCH 07/10] Bump rexml from 3.3.9 to 3.4.2 (#702) --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index a679b11..4554b14 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -186,7 +186,7 @@ GEM mime-types (>= 1.16, < 4.0) netrc (~> 0.8) retriable (3.1.2) - rexml (3.3.9) + rexml (3.4.2) rouge (2.0.7) ruby2_keywords (0.0.5) rubyzip (2.3.2) From 53ae6426bd0a1a8694f052edcdd9795fcfc005ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:22:19 +0100 Subject: [PATCH 08/10] Bump faraday from 1.10.4 to 1.10.5 (#710) Bumps [faraday](https://github.com/lostisland/faraday) from 1.10.4 to 1.10.5. - [Release notes](https://github.com/lostisland/faraday/releases) - [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md) - [Commits](https://github.com/lostisland/faraday/compare/v1.10.4...v1.10.5) --- updated-dependencies: - dependency-name: faraday dependency-version: 1.10.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 4554b14..847d5ee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -39,7 +39,7 @@ GEM dotenv (2.8.1) emoji_regex (3.2.3) excon (0.112.0) - faraday (1.10.4) + faraday (1.10.5) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -55,11 +55,11 @@ GEM faraday (>= 0.8.0) http-cookie (~> 1.0.0) faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) + faraday-em_synchrony (1.0.1) faraday-excon (1.1.0) faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) + faraday-multipart (1.2.0) + multipart-post (~> 2.0) faraday-net_http (1.0.2) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) From 7db85c9939c51e4a2b8e9a35a2af53002f0b5027 Mon Sep 17 00:00:00 2001 From: Lysann Tranvouez Date: Sun, 8 Mar 2026 11:34:23 +0100 Subject: [PATCH 09/10] DO NOT MERGE local development signing fixes --- pass.xcodeproj/project.pbxproj | 136 ++++++++---------- pass/pass.entitlements | 12 +- pass/passBeta.entitlements | 12 +- .../passAutoFillExtension.entitlements | 6 +- .../passBetaAutoFillExtension.entitlements | 6 +- passExtension/passBetaExtension.entitlements | 4 +- passExtension/passExtension.entitlements | 4 +- passKit/Helpers/Globals.swift | 4 +- passShortcuts/passBetaShortcuts.entitlements | 4 +- passShortcuts/passShortcuts.entitlements | 4 +- 10 files changed, 76 insertions(+), 116 deletions(-) diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index 807205a..ed9df0d 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -1292,14 +1292,10 @@ TargetAttributes = { 30A69944240EED5E00B7D967 = { CreatedOnToolsVersion = 11.3; - DevelopmentTeam = 4WDM8E95VU; - ProvisioningStyle = Manual; }; A239F5942158C08B00576CBF = { CreatedOnToolsVersion = 10.0; - DevelopmentTeam = 4WDM8E95VU; LastSwiftMigration = 1020; - ProvisioningStyle = Manual; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { enabled = 1; @@ -1322,9 +1318,7 @@ }; A26700231EEC466A00176B8A = { CreatedOnToolsVersion = 8.3.3; - DevelopmentTeam = 4WDM8E95VU; LastSwiftMigration = 1020; - ProvisioningStyle = Manual; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { enabled = 1; @@ -1336,16 +1330,13 @@ }; DC13B14D1E8640810097803F = { CreatedOnToolsVersion = 8.3; - DevelopmentTeam = 4WDM8E95VU; LastSwiftMigration = 1020; ProvisioningStyle = Automatic; TestTargetID = DC917BD21E2E8231000FDF54; }; DC917BD21E2E8231000FDF54 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = 4WDM8E95VU; LastSwiftMigration = 1020; - ProvisioningStyle = Manual; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { enabled = 1; @@ -1886,11 +1877,10 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = passShortcuts/PassShortcuts.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "$(inherited)"; @@ -1904,9 +1894,9 @@ MARKETING_VERSION = "$(MARKETING_VERSION)"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios.shortcuts; + PRODUCT_BUNDLE_IDENTIFIER = org.lysanntranvouez.passforios.shortcuts; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios.shortcuts"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1922,11 +1912,10 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = passShortcuts/PassShortcuts.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "$(inherited)"; @@ -1939,9 +1928,9 @@ ); MARKETING_VERSION = "$(MARKETING_VERSION)"; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios.shortcuts; + PRODUCT_BUNDLE_IDENTIFIER = org.lysanntranvouez.passforios.shortcuts; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforios.shortcuts"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1980,7 +1969,6 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -2004,7 +1992,7 @@ MARKETING_VERSION = 0.19.0; MTL_ENABLE_DEBUG_INFO = NO; OTHER_SWIFT_FLAGS = "-D BETA"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforiosbeta; + PRODUCT_BUNDLE_IDENTIFIER = org.lysanntranvouez.passforiosbeta; PRODUCT_NAME = "Pass Beta"; SDKROOT = iphoneos; STRIP_INSTALLED_PRODUCT = NO; @@ -2022,11 +2010,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIconBeta; CODE_SIGN_ENTITLEMENTS = pass/passBeta.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; DEFINES_MODULE = NO; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; HEADER_SEARCH_PATHS = "$(inherited)"; @@ -2042,8 +2028,6 @@ OTHER_LDFLAGS = "${inherited}"; OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; - PROVISIONING_PROFILE = "ee6e841d-ef77-4f00-b534-d7f1fd25dc1d"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforiosbeta"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_OBJC_BRIDGING_HEADER = "pass/Helpers/Objective-CBridgingHeader.h"; SWIFT_VERSION = 5.0; @@ -2057,7 +2041,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = passTests/Info.plist; @@ -2086,9 +2070,10 @@ CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = NO; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = passExtension/passBetaExtension.entitlements; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; HEADER_SEARCH_PATHS = "$(inherited)"; @@ -2105,8 +2090,7 @@ OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).find-login-action-extension"; PRODUCT_NAME = passExtension; - PROVISIONING_PROFILE = "cbd86628-6f3e-40f3-b518-20d2330db545"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforiosbeta.find-login-action-extension"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; STRIP_INSTALLED_PRODUCT = NO; SWIFT_VERSION = 5.0; @@ -2159,6 +2143,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; + DEVELOPMENT_TEAM = QLYN3TZMJW; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = passKitTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -2186,10 +2171,10 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = passAutoFillExtension/passBetaAutoFillExtension.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -2204,9 +2189,9 @@ MARKETING_VERSION = "$(MARKETING_VERSION)"; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = "me.mssun.passforiosbeta.auto-fill-credential-extension"; + PRODUCT_BUNDLE_IDENTIFIER = "org.lysanntranvouez.passforiosbeta.auto-fill-credential-extension"; PRODUCT_NAME = passAutoFillExtension; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforiosbeta.auto-fill-credential-extension"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; STRIP_INSTALLED_PRODUCT = NO; SWIFT_VERSION = 5.0; @@ -2223,11 +2208,10 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = passShortcuts/passBetaShortcuts.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "$(inherited)"; @@ -2241,9 +2225,9 @@ MARKETING_VERSION = "$(MARKETING_VERSION)"; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforiosbeta.shortcuts; + PRODUCT_BUNDLE_IDENTIFIER = org.lysanntranvouez.passforiosbeta.shortcuts; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforiosbeta.shortcuts"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2259,10 +2243,10 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = passAutoFillExtension/passAutoFillExtension.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -2277,9 +2261,9 @@ MARKETING_VERSION = "$(MARKETING_VERSION)"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "me.mssun.passforios.auto-fill-credential-extension"; + PRODUCT_BUNDLE_IDENTIFIER = "org.lysanntranvouez.passforios.auto-fill-credential-extension"; PRODUCT_NAME = passAutoFillExtension; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios.auto-fill-credential-extension"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; STRIP_INSTALLED_PRODUCT = NO; SWIFT_VERSION = 5.0; @@ -2296,10 +2280,10 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = passAutoFillExtension/passAutoFillExtension.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -2313,9 +2297,9 @@ ); MARKETING_VERSION = "$(MARKETING_VERSION)"; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "me.mssun.passforios.auto-fill-credential-extension"; + PRODUCT_BUNDLE_IDENTIFIER = "org.lysanntranvouez.passforios.auto-fill-credential-extension"; PRODUCT_NAME = passAutoFillExtension; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforios.auto-fill-credential-extension"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; STRIP_INSTALLED_PRODUCT = NO; SWIFT_VERSION = 5.0; @@ -2405,6 +2389,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; + DEVELOPMENT_TEAM = QLYN3TZMJW; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = passKitTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -2429,6 +2414,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; + DEVELOPMENT_TEAM = QLYN3TZMJW; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = passKitTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -2455,9 +2441,10 @@ CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = NO; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = passExtension/passExtension.entitlements; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; HEADER_SEARCH_PATHS = "$(inherited)"; @@ -2473,8 +2460,7 @@ OTHER_CFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).find-login-action-extension"; PRODUCT_NAME = passExtension; - PROVISIONING_PROFILE = "d25c9029-bca6-4b2d-b04e-4abc9d232740"; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios.find-login-action-extension"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; STRIP_INSTALLED_PRODUCT = NO; SWIFT_VERSION = 5.0; @@ -2491,9 +2477,10 @@ CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = NO; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = passExtension/passExtension.entitlements; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; HEADER_SEARCH_PATHS = "$(inherited)"; @@ -2509,8 +2496,7 @@ OTHER_CFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).find-login-action-extension"; PRODUCT_NAME = passExtension; - PROVISIONING_PROFILE = "cbd86628-6f3e-40f3-b518-20d2330db545"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforios.find-login-action-extension"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; STRIP_INSTALLED_PRODUCT = NO; SWIFT_VERSION = 5.0; @@ -2524,7 +2510,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = passTests/Info.plist; @@ -2549,7 +2535,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = passTests/Info.plist; @@ -2600,7 +2586,6 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = dwarf; @@ -2630,7 +2615,7 @@ MARKETING_VERSION = 0.19.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios; + PRODUCT_BUNDLE_IDENTIFIER = org.lysanntranvouez.passforios; PRODUCT_NAME = Pass; SDKROOT = iphoneos; STRIP_INSTALLED_PRODUCT = NO; @@ -2674,7 +2659,6 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -2697,7 +2681,7 @@ LD_RUNPATH_SEARCH_PATHS = ""; MARKETING_VERSION = 0.19.0; MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios; + PRODUCT_BUNDLE_IDENTIFIER = org.lysanntranvouez.passforios; PRODUCT_NAME = Pass; SDKROOT = iphoneos; STRIP_INSTALLED_PRODUCT = NO; @@ -2715,11 +2699,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = pass/pass.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; DEFINES_MODULE = NO; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; HEADER_SEARCH_PATHS = "$(inherited)"; @@ -2734,8 +2717,7 @@ OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "${inherited}"; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; - PROVISIONING_PROFILE = "3c4f599a-ce77-4184-b4c4-edebf09cba3b"; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios"; + PROVISIONING_PROFILE_SPECIFIER = ""; STRIP_INSTALLED_PRODUCT = NO; SWIFT_OBJC_BRIDGING_HEADER = "pass/Helpers/Objective-CBridgingHeader.h"; SWIFT_VERSION = 5.0; @@ -2748,11 +2730,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = pass/pass.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; DEFINES_MODULE = NO; - DEVELOPMENT_TEAM = 4WDM8E95VU; + DEVELOPMENT_TEAM = QLYN3TZMJW; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; HEADER_SEARCH_PATHS = "$(inherited)"; @@ -2768,8 +2749,7 @@ OTHER_LDFLAGS = "${inherited}"; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; - PROVISIONING_PROFILE = "ee6e841d-ef77-4f00-b534-d7f1fd25dc1d"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforios"; + PROVISIONING_PROFILE_SPECIFIER = ""; STRIP_INSTALLED_PRODUCT = NO; SWIFT_OBJC_BRIDGING_HEADER = "pass/Helpers/Objective-CBridgingHeader.h"; SWIFT_VERSION = 5.0; diff --git a/pass/pass.entitlements b/pass/pass.entitlements index 26deb49..a844039 100644 --- a/pass/pass.entitlements +++ b/pass/pass.entitlements @@ -2,21 +2,13 @@ - com.apple.developer.authentication-services.autofill-credential-provider - - com.apple.developer.nfc.readersession.formats - - TAG - - com.apple.developer.siri - com.apple.security.application-groups - group.me.mssun.passforios + group.org.lysanntranvouez.passforios keychain-access-groups - $(AppIdentifierPrefix)group.me.mssun.passforios + $(AppIdentifierPrefix)group.org.lysanntranvouez.passforios diff --git a/pass/passBeta.entitlements b/pass/passBeta.entitlements index 1a9efa6..b6ffc4a 100644 --- a/pass/passBeta.entitlements +++ b/pass/passBeta.entitlements @@ -2,21 +2,13 @@ - com.apple.developer.authentication-services.autofill-credential-provider - - com.apple.developer.nfc.readersession.formats - - TAG - - com.apple.developer.siri - com.apple.security.application-groups - group.me.mssun.passforiosbeta + group.org.lysanntranvouez.passforiosbeta keychain-access-groups - $(AppIdentifierPrefix)group.me.mssun.passforiosbeta + $(AppIdentifierPrefix)group.org.lysanntranvouez.passforiosbeta diff --git a/passAutoFillExtension/passAutoFillExtension.entitlements b/passAutoFillExtension/passAutoFillExtension.entitlements index d58dedc..a844039 100644 --- a/passAutoFillExtension/passAutoFillExtension.entitlements +++ b/passAutoFillExtension/passAutoFillExtension.entitlements @@ -2,15 +2,13 @@ - com.apple.developer.authentication-services.autofill-credential-provider - com.apple.security.application-groups - group.me.mssun.passforios + group.org.lysanntranvouez.passforios keychain-access-groups - $(AppIdentifierPrefix)group.me.mssun.passforios + $(AppIdentifierPrefix)group.org.lysanntranvouez.passforios diff --git a/passAutoFillExtension/passBetaAutoFillExtension.entitlements b/passAutoFillExtension/passBetaAutoFillExtension.entitlements index 272d9c8..b6ffc4a 100644 --- a/passAutoFillExtension/passBetaAutoFillExtension.entitlements +++ b/passAutoFillExtension/passBetaAutoFillExtension.entitlements @@ -2,15 +2,13 @@ - com.apple.developer.authentication-services.autofill-credential-provider - com.apple.security.application-groups - group.me.mssun.passforiosbeta + group.org.lysanntranvouez.passforiosbeta keychain-access-groups - $(AppIdentifierPrefix)group.me.mssun.passforiosbeta + $(AppIdentifierPrefix)group.org.lysanntranvouez.passforiosbeta diff --git a/passExtension/passBetaExtension.entitlements b/passExtension/passBetaExtension.entitlements index 9bcfd12..b6ffc4a 100644 --- a/passExtension/passBetaExtension.entitlements +++ b/passExtension/passBetaExtension.entitlements @@ -4,11 +4,11 @@ com.apple.security.application-groups - group.me.mssun.passforiosbeta + group.org.lysanntranvouez.passforiosbeta keychain-access-groups - $(AppIdentifierPrefix)group.me.mssun.passforiosbeta + $(AppIdentifierPrefix)group.org.lysanntranvouez.passforiosbeta diff --git a/passExtension/passExtension.entitlements b/passExtension/passExtension.entitlements index 13c5967..a844039 100644 --- a/passExtension/passExtension.entitlements +++ b/passExtension/passExtension.entitlements @@ -4,11 +4,11 @@ com.apple.security.application-groups - group.me.mssun.passforios + group.org.lysanntranvouez.passforios keychain-access-groups - $(AppIdentifierPrefix)group.me.mssun.passforios + $(AppIdentifierPrefix)group.org.lysanntranvouez.passforios diff --git a/passKit/Helpers/Globals.swift b/passKit/Helpers/Globals.swift index a9242c2..2135dde 100644 --- a/passKit/Helpers/Globals.swift +++ b/passKit/Helpers/Globals.swift @@ -12,9 +12,9 @@ import UIKit public final class Globals { public static let bundleIdentifier: String = { #if BETA - return "me.mssun.passforiosbeta" + return "org.lysanntranvouez.passforiosbeta" #else - return "me.mssun.passforios" + return "org.lysanntranvouez.passforios" #endif }() diff --git a/passShortcuts/passBetaShortcuts.entitlements b/passShortcuts/passBetaShortcuts.entitlements index 9bcfd12..b6ffc4a 100644 --- a/passShortcuts/passBetaShortcuts.entitlements +++ b/passShortcuts/passBetaShortcuts.entitlements @@ -4,11 +4,11 @@ com.apple.security.application-groups - group.me.mssun.passforiosbeta + group.org.lysanntranvouez.passforiosbeta keychain-access-groups - $(AppIdentifierPrefix)group.me.mssun.passforiosbeta + $(AppIdentifierPrefix)group.org.lysanntranvouez.passforiosbeta diff --git a/passShortcuts/passShortcuts.entitlements b/passShortcuts/passShortcuts.entitlements index 13c5967..a844039 100644 --- a/passShortcuts/passShortcuts.entitlements +++ b/passShortcuts/passShortcuts.entitlements @@ -4,11 +4,11 @@ com.apple.security.application-groups - group.me.mssun.passforios + group.org.lysanntranvouez.passforios keychain-access-groups - $(AppIdentifierPrefix)group.me.mssun.passforios + $(AppIdentifierPrefix)group.org.lysanntranvouez.passforios From 1f94712a62f2fd6adb27bbf3351156c2283ad407 Mon Sep 17 00:00:00 2001 From: Lysann Tranvouez Date: Sun, 8 Mar 2026 21:09:00 +0100 Subject: [PATCH 10/10] feature implementation plans --- plans/01-improve-test-coverage-plan.md | 98 +++++ plans/02-multi-recipient-encryption-plan.md | 101 +++++ plans/03-multi-store-plan.md | 423 ++++++++++++++++++++ 3 files changed, 622 insertions(+) create mode 100644 plans/01-improve-test-coverage-plan.md create mode 100644 plans/02-multi-recipient-encryption-plan.md create mode 100644 plans/03-multi-store-plan.md diff --git a/plans/01-improve-test-coverage-plan.md b/plans/01-improve-test-coverage-plan.md new file mode 100644 index 0000000..d9f8efe --- /dev/null +++ b/plans/01-improve-test-coverage-plan.md @@ -0,0 +1,98 @@ +# Improve Test Coverage Plan + +## Motivation + +The passKit codebase has ~100 test methods but critical components that will be heavily refactored (for multi-store support and other changes) have little or no test coverage. Adding regression tests now prevents silent breakage during future work. + +This is standalone — it should be done before any other refactoring. + +--- + +## Current Test Coverage + +### Well-tested areas +- Password parsing (`Password`, `Parser`, `AdditionField`, OTP, `TokenBuilder`) — ~40 tests +- `PGPAgent` — 8 tests covering multiple key types, error cases, passphrase handling +- `PasswordGenerator` — 8 tests +- `GitRepository` — 8 tests (uses real temp git repos on disk) +- `GitCredential` — 6 tests (SSH test is skipped/"failed in CI") +- `PasswordEntity` Core Data operations — 6 tests (uses in-memory store via `CoreDataTestCase`) +- `KeyFileManager` — 7 tests +- `QRKeyScanner` — 6 tests +- String/Array extensions — 6 tests + +### Critical gaps (zero tests) + +| Component | Notes | +|-----------|-------| +| **`PasswordStore`** (36 methods) | Only 1 integration test that clones from GitHub. No unit tests for pull, push, add, delete, edit, decrypt, encrypt, reset, erase, eraseStoreData, deleteCoreData, fetchPasswordEntityCoreData, initPasswordEntityCoreData. | +| **`AppKeychain`** | Zero tests. Only exercised indirectly via `DictBasedKeychain` mock. | +| **`PersistenceController` / Core Data stack** | Only the `isUnitTest: true` path is exercised. No tests for `reinitializePersistentStore`, `deletePersistentStore`, error recovery. | +| **Services** (`PasswordDecryptor`, `PasswordEncryptor`, `PasswordManager`, `PasswordNavigationDataSource`) | Zero tests. Core business logic that ties `PasswordStore` + `PGPAgent` together. | +| **All view controllers (28+)** | Zero tests. No UI test target exists. | +| **AutoFill / Share / Shortcuts extensions** | Zero tests. No test targets for extensions. | +| **`PasscodeLock`** | Zero tests. Security-critical. | + +### Test infrastructure that already exists +- `CoreDataTestCase` — base class with in-memory `PersistenceController` (reusable) +- `DictBasedKeychain` — in-memory `KeyStore` mock (reusable) +- `TestPGPKeys` — PGP key fixtures for RSA2048, RSA4096, ED25519, NISTP384, multi-key sets + +--- + +## Implementation + +### 1. `PasswordStore` unit tests (highest priority) + +The single existing test (`testCloneAndDecryptMultiKeys`) depends on network access. Add offline unit tests using a local git repo fixture: + +- **Setup/teardown**: Create a temp directory, `git init`, add `.gpg-id` + encrypted `.gpg` files, so tests don't need network. +- **Test `initPasswordEntityCoreData`**: Clone a local fixture repo → verify correct `PasswordEntity` tree in Core Data (names, paths, directories, parent-child relationships). +- **Test `deleteCoreData`**: Populate, then delete, verify empty. +- **Test `eraseStoreData`**: Verify repo directory deleted, Core Data cleared, git handle nil'd. +- **Test `erase`**: Verify full cleanup (keychain, defaults, passcode, PGP state). +- **Test `fetchPasswordEntityCoreData`**: Verify fetch with parent filter, withDir filter. +- **Test encrypt → save → decrypt round-trip**: Using `DictBasedKeychain` + test PGP keys + local repo. +- **Test `add` / `delete` / `edit`**: Verify filesystem + Core Data + git commit. +- **Test `reset`**: Verify Core Data rebuilt to match filesystem after git reset. + +### 2. `PasswordEntity` relationship tests + +Extend `PasswordEntityTest` (already uses `CoreDataTestCase`): + +- **Test `initPasswordEntityCoreData` BFS walk**: Create a temp directory tree with `.gpg` files, call the static method, verify entity tree matches filesystem. +- **Test that `.gpg` extension is stripped** from names but non-`.gpg` files keep their names. +- **Test hidden files are skipped**. +- **Test empty directories**. + +### 3. `AppKeychain` tests + +Basic tests against the real Keychain API (or a test wrapper): + +- **Test `add` / `get` / `removeContent`** round-trip. +- **Test `removeAllContent`**. +- **Test `contains`**. +- **Test `removeAllContent(withPrefix:)`** — this method already exists and will be useful for per-store cleanup. + +### 4. `PersistenceController` tests + +- **Test `reinitializePersistentStore`** — verify existing data is gone after reinit. +- **Test model loading** — verify the `.momd` loads correctly. + +### 5. Test infrastructure: local git repo fixture builder + +A helper that creates a temp git repo with configurable `.gpg-id`, encrypted `.gpg` files, and directory structure. Replaces the current network-dependent clone in `PasswordStoreTest`. + +--- + +## Implementation Order + +All steps are independent and can be done in parallel: + +| Step | Description | +|------|-------------| +| 1 | `PasswordStore` unit tests (offline, local git fixture) | +| 2 | `PasswordEntity` BFS walk + relationship tests | +| 3 | `AppKeychain` tests | +| 4 | `PersistenceController` tests | +| 5 | Local git repo fixture builder (prerequisite for step 1) | diff --git a/plans/02-multi-recipient-encryption-plan.md b/plans/02-multi-recipient-encryption-plan.md new file mode 100644 index 0000000..d2b3b87 --- /dev/null +++ b/plans/02-multi-recipient-encryption-plan.md @@ -0,0 +1,101 @@ +# Multi-Recipient Encryption Plan + +## Concept + +The `pass` password store format supports encrypting each password to multiple PGP keys via `.gpg-id` files (one key ID per line). This enables sharing a store with other users — each person imports the same git repository but decrypts with their own private key. When adding or editing a password, it must be encrypted to **all** key IDs listed in `.gpg-id`. + +The app currently has a setting (`isEnableGPGIDOn`) that reads `.gpg-id` for per-directory key selection, but it only supports a single key ID. This plan fixes every layer to support multiple recipients. + +This is standalone — it can be implemented before or after multi-store support. + +--- + +## Current State + +The codebase does **not** support encrypting to multiple public keys. Every layer assumes a single recipient: + +| Layer | Current state | What needs to change | +|-------|--------------|---------------------| +| `.gpg-id` file format | Supports multiple key IDs (one per line) | No change needed | +| `findGPGID(from:)` | Returns the **entire file as one trimmed string** — does not split by newline | Split by newline, return `[String]` | +| `PGPInterface.encrypt()` | Signature: `encrypt(plainData:keyID:)` — singular `keyID: String?` | Add `encrypt(plainData:keyIDs:[String])` or change `keyID` to `keyIDs: [String]?` | +| `GopenPGPInterface` | Creates a `CryptoKeyRing` with **one** public key | Add all recipient public keys to the keyring before encrypting | +| `ObjectivePGPInterface` | Passes `keyring.keys` (all keys, including private) — accidentally multi-recipient but not intentionally | Filter to only the specified public keys, pass those to `ObjectivePGP.encrypt()` | +| `PGPAgent.encrypt()` | Routes to a single key via `keyID: String` | Accept `[String]` and pass through to the interface | +| `PasswordStore.encrypt()` | Calls `findGPGID()` for a single key ID string | Call the updated `findGPGID()`, pass the key ID array | + +--- + +## Implementation + +### 1. `findGPGID(from:) -> [String]` + +Split file contents by newline, trim each line, filter empty lines. Return array of key IDs. Callers that only need a single key (e.g. for decryption routing) can use `.first`. + +### 2. `PGPInterface` protocol + +Change `encrypt(plainData:keyID:)` to `encrypt(plainData:keyIDs:)` where `keyIDs: [String]?`. When `nil`, encrypt to the first/default key (backward compatible). + +### 3. `GopenPGPInterface.encrypt()` + +Look up all keys matching the `keyIDs` array from `publicKeys`. Add each to the `CryptoKeyRing` (GopenPGP's `CryptoKeyRing` supports multiple keys via `add()`). Encrypt with the multi-key ring. + +### 4. `ObjectivePGPInterface.encrypt()` + +Filter `keyring.keys` to only the public keys matching the requested `keyIDs`. Pass the filtered array to `ObjectivePGP.encrypt()`. + +### 5. `PGPAgent.encrypt()` + +Update both overloads to accept `keyIDs: [String]?` and pass through to the interface. + +### 6. `PasswordStore.encrypt()` + +Call updated `findGPGID()`, pass the array to `PGPAgent`. + +--- + +## Public Key Management + +When a store lists multiple key IDs in `.gpg-id`, the user needs the public keys of all recipients. The user's own private key is sufficient for decryption (since the message is encrypted to all recipients), but all public keys are needed for re-encryption when editing. + +Options: + +- Import additional public keys (alongside the user's own key pair) +- Or fetch them from a keyserver (out of scope for initial implementation) +- The PGP key import flow should allow importing multiple public keys +- `PGPAgent.initKeys()` already supports loading multiple keys from a single armored blob (both `GopenPGPInterface` and `ObjectivePGPInterface` parse multi-key armored input) + +--- + +## Implementation Order + +| Step | Description | Depends On | +|------|-------------|------------| +| 1 | `findGPGID` returns `[String]` + update callers | — | +| 2 | `PGPInterface` protocol change (`keyIDs: [String]?`) | — | +| 3 | `GopenPGPInterface` multi-key encryption | Step 2 | +| 4 | `ObjectivePGPInterface` multi-key encryption | Step 2 | +| 5 | `PGPAgent` updated overloads | Steps 2-4 | +| 6 | `PasswordStore.encrypt()` uses `[String]` from `findGPGID` | Steps 1+5 | +| T | Tests (see testing section) | Steps 1-6 | + +--- + +## Testing + +### Pre-work: existing encryption tests + +The `PGPAgentTest` already covers single-key encrypt/decrypt with multiple key types. These serve as the regression baseline. + +### Multi-recipient encryption tests + +- **Test `findGPGID` with multi-line `.gpg-id`**: File with two key IDs on separate lines → returns `[String]` with both. +- **Test `findGPGID` with single-line `.gpg-id`**: Backward compatible → returns `[String]` with one element. +- **Test `findGPGID` with empty lines and whitespace**: Trims and filters correctly. +- **Test `GopenPGPInterface.encrypt` with multiple keys**: Encrypt with two public keys → decrypt succeeds with either private key. +- **Test `ObjectivePGPInterface.encrypt` with multiple keys**: Same as above. +- **Test `PGPAgent.encrypt` with `keyIDs` array**: Routes through correctly to the interface. +- **Test round-trip**: Encrypt with key IDs `[A, B]` → user with private key A can decrypt, user with private key B can decrypt. +- **Test encrypt with single keyID still works**: Backward compatibility — `keyIDs: ["X"]` behaves like the old `keyID: "X"`. +- **Test encrypt with unknown keyID in list**: If one of the key IDs is not in the keyring, appropriate error is thrown. +- **Test multi-key public key import**: Import an armored blob containing multiple public keys → all are available for encryption. diff --git a/plans/03-multi-store-plan.md b/plans/03-multi-store-plan.md new file mode 100644 index 0000000..11541b2 --- /dev/null +++ b/plans/03-multi-store-plan.md @@ -0,0 +1,423 @@ +# Multi-Store Support — Implementation Plan + +## Concept + +Each **store** is an independent password repository with its own git remote, credentials, branch, and (optionally) its own PGP key pair. Users can enable/disable individual stores for the password list and separately for AutoFill. Stores can be shared between users who each decrypt with their own key (leveraging the existing `.gpg-id` per-directory mechanism from `pass`). + +--- + +## Phase 1: Improve Test Coverage Before Refactoring + +See [01-improve-test-coverage-plan.md](01-improve-test-coverage-plan.md). This is standalone and should be done before any refactoring to catch regressions. + +--- + +## Phase 2: Data Model — `StoreConfiguration` + +Create a new persistent model for store definitions. This is the foundation everything else builds on. + +### 2.1 Define `StoreConfiguration` as a Core Data entity + +→ Testing: [T1 — `StoreConfiguration` entity tests](#t1-storeconfiguration-entity-tests) + +Add a `StoreConfiguration` entity to the existing Core Data model (`pass.xcdatamodeld`), with attributes: + +- `id: UUID` — unique identifier +- `name: String` — display name (e.g. "Personal", "Work") +- `gitURL: URI` (stored as String) +- `gitBranchName: String` +- `gitAuthenticationMethod: String` (raw value of `GitAuthenticationMethod`) +- `gitUsername: String` +- `pgpKeySource: String?` (raw value of `KeySource`) +- `isVisibleInPasswords: Bool` — shown in the password list +- `isVisibleInAutoFill: Bool` — shown in AutoFill +- `sortOrder: Int16` — for user-defined ordering +- `lastSyncedTime: Date?` + +Relationship: `passwords` → to-many `PasswordEntity` (inverse: `store`; cascade delete rule — deleting a store removes all its password entities). + +Using Core Data instead of a separate JSON file because: +- The Core Data stack already exists and is shared across all targets via the app group +- The `StoreConfiguration` ↔ `PasswordEntity` relationship gives referential integrity and cascade deletes for free +- No second persistence mechanism to maintain +- Built-in concurrency/conflict handling + +### 2.2 Define `StoreConfigurationManager` + +→ Testing: [T1 — `StoreConfiguration` entity tests](#t1-storeconfiguration-entity-tests), [T3 — `PasswordStoreManager` tests](#t3-passwordstoremanager-tests) + +Manages the list of stores via Core Data. Provides CRUD, reordering, and lookup by ID. Observable (via `NotificationCenter` or Combine) so UI updates when stores change. + +### 2.3 Migration from single-store + +→ Testing: [T2 — Migration tests](#t2-migration-tests) + +On first launch after upgrade, create a single `StoreConfiguration` from the current `Defaults.*` values and keychain entries. Assign all existing `PasswordEntity` rows to this store. Existing users see no change. + +This is a Core Data model version migration: add the `StoreConfiguration` entity, add the `store` relationship to `PasswordEntity`, and populate it in a post-migration step. + +### 2.4 Per-store secrets + +→ Testing: [T5 — Per-store keychain namespace tests](#t5-per-store-keychain-namespace-tests) + +Per-store secrets go in the keychain with namespaced keys: + +- `"{storeID}.gitPassword"`, `"{storeID}.gitSSHPrivateKeyPassphrase"`, `"{storeID}.sshPrivateKey"` +- `"{storeID}.pgpPublicKey"`, `"{storeID}.pgpPrivateKey"` +- The existing `"pgpKeyPassphrase-{keyID}"` scheme already works across stores since it's keyed by PGP key ID. + +--- + +## Phase 3: De-singleton the Backend + +The most invasive but essential change. Requires careful sequencing. + +### 3.1 Parameterize `Globals` paths + +Add a method to compute the per-store repository directory: + +- `repositoryURL(for storeID: UUID) -> URL` — e.g. `Library/password-stores/{storeID}/` + +The database path (`dbPath`) stays single since we use one Core Data database with a relationship. + +### 3.2 Make `PasswordStore` non-singleton + +→ Testing: [T3 — `PasswordStoreManager` tests](#t3-passwordstoremanager-tests), [T4 — Per-store `PasswordStore` tests](#t4-per-store-passwordstore-tests) + +Convert to a class that takes a `StoreConfiguration` at init: + +- Each instance owns its own `storeURL`, `gitRepository`, `context` +- Inject `StoreConfiguration` (for git URL, branch, credentials) and a `PGPAgent` instance +- Keep a **`PasswordStoreManager`** that holds all active `PasswordStore` instances (keyed by store ID), lazily creating them +- `PasswordStoreManager` replaces all `PasswordStore.shared` call sites + +### 3.3 Core Data: `PasswordEntity` ↔ `StoreConfiguration` relationship + +→ Testing: [T1 — `StoreConfiguration` entity tests](#t1-storeconfiguration-entity-tests), [T6 — `PasswordEntity` fetch filtering tests](#t6-passwordentity-fetch-filtering-tests) + +Add a `store` relationship (to-one) on `PasswordEntity` pointing to `StoreConfiguration` (inverse: `passwords`, to-many, cascade delete). This replaces the need for a separate `storeID` UUID attribute — the relationship provides referential integrity and cascade deletes. + +All `PasswordEntity` fetch requests must be updated to filter by store (or by set of visible stores for the password list / AutoFill). The `initPasswordEntityCoreData(url:in:)` method already takes a URL parameter; pass the per-store URL and set the `store` relationship on each created entity. + +### 3.4 Make `PGPAgent` per-store + +→ Testing: [T4 — Per-store `PasswordStore` tests](#t4-per-store-passwordstore-tests) (encrypt/decrypt with per-store keys) + +Remove the singleton. `PasswordStore` instances each hold an optional `PGPAgent`. Stores sharing the same PGP key pair just load the same keychain entries. Stores using different keys load different ones. The `KeyStore` protocol already supports this — just pass different key names. + +### 3.5 Make `GitCredential` per-store + +→ Testing: [T5 — Per-store keychain namespace tests](#t5-per-store-keychain-namespace-tests) + +Already not a singleton, just reads from `Defaults`. Change it to read from `StoreConfiguration` + namespaced keychain keys instead. + +--- + +## Phase 4: Settings UI — Store Management + +### 4.1 New "Stores" settings section + +Replace the current single "Password Repository" and "PGP Key" rows with a section listing all configured stores, plus an "Add Store" button: + +- Each store row shows: name, git host, sync status indicator +- Tapping a store opens `StoreSettingsTableViewController` +- Swipe-to-delete removes a store (with confirmation) +- Drag-to-reorder for sort order + +### 4.2 `StoreSettingsTableViewController` + +Per-store settings screen: + +- Store name (editable text field) +- **Repository section**: Git URL, branch, username, auth method (reuse existing `GitRepositorySettingsTableViewController` logic, but scoped to this store's config) +- **PGP Key section**: Same import options as today but scoped to this store's keychain namespace. Add an option "Use same key as [other store]" for convenience. +- **Visibility section**: Two toggles — "Show in Passwords", "Show in AutoFill" +- **Sync section**: Last synced time, manual sync button +- **Danger zone**: Delete store (see §4.4 for full cleanup steps) + +### 4.3 Migrate existing settings screens + +`GitRepositorySettingsTableViewController`, `PGPKeyArmorImportTableViewController`, etc. currently read/write global `Defaults`. Refactor them to accept a `StoreConfiguration` and read/write to that store's Core Data entity and namespaced keychain keys instead. + +### 4.4 Store lifecycle: adding a store + +→ Testing: [T7 — Store lifecycle integration tests](#t7-store-lifecycle-integration-tests) + +Currently, configuring git settings triggers a clone immediately (`GitRepositorySettingsTableViewController.save()` → `cloneAndSegueIfSuccess()`), and the clone rebuilds Core Data from the filesystem. The multi-store equivalent: + +1. User taps "Add Store" → presented with `StoreSettingsTableViewController` +2. User fills in store name, git URL, branch, username, auth method +3. User imports PGP keys (public + private) for this store +4. User taps "Save" → creates a `StoreConfiguration` entity in Core Data +5. Clone is triggered for this store: + - Compute per-store repo directory: `Library/password-stores/{storeID}/` + - Call `PasswordStore.cloneRepository()` scoped to that directory + - On success: BFS-walk the cloned repo, create `PasswordEntity` rows linked to this `StoreConfiguration` via the `store` relationship + - On success: validate `.gpg-id` exists (warn if missing, since decryption will fail) + - On failure: delete the `StoreConfiguration` entity (cascade deletes any partial `PasswordEntity` rows), clean up the repo directory, remove keychain entries for this store ID +6. Post `.passwordStoreUpdated` notification so the password list refreshes + +### 4.5 Store lifecycle: removing a store + +→ Testing: [T7 — Store lifecycle integration tests](#t7-store-lifecycle-integration-tests) + +Currently `erase()` nukes everything globally. Per-store removal must be scoped: + +1. User confirms deletion (destructive action sheet) +2. Cleanup steps: + - Delete the repo directory: `Library/password-stores/{storeID}/` (rm -rf) + - Delete `StoreConfiguration` entity from Core Data → cascade-deletes all linked `PasswordEntity` rows automatically + - Remove namespaced keychain entries: `"{storeID}.gitPassword"`, `"{storeID}.gitSSHPrivateKeyPassphrase"`, `"{storeID}.sshPrivateKey"`, `"{storeID}.pgpPublicKey"`, `"{storeID}.pgpPrivateKey"` + - Drop the in-memory `PasswordStore` instance from `PasswordStoreManager` + - Post `.passwordStoreUpdated` so the password list refreshes +3. PGP key passphrase entries (`"pgpKeyPassphrase-{keyID}"`) may be shared with other stores using the same key — only remove if no other store references that key ID + +### 4.6 Store lifecycle: re-cloning / changing git URL + +→ Testing: [T7 — Store lifecycle integration tests](#t7-store-lifecycle-integration-tests) + +When the user changes the git URL or branch of an existing store (equivalent to today's "overwrite" flow): + +1. Delete the existing repo directory for this store +2. Delete all `PasswordEntity` rows linked to this `StoreConfiguration` (but keep the `StoreConfiguration` entity itself) +3. Clone the new repo into the store's directory +4. Rebuild `PasswordEntity` rows from the new clone, linked to the same `StoreConfiguration` +5. Clear and re-prompt for git credentials + +### 4.7 Global "Erase all data" + +→ Testing: [T7 — Store lifecycle integration tests](#t7-store-lifecycle-integration-tests) (test global erase) + +The existing "Erase Password Store Data" action in Advanced Settings should: + +1. Delete all `StoreConfiguration` entities (cascade-deletes all `PasswordEntity` rows) +2. Delete all repo directories under `Library/password-stores/` +3. Remove all keychain entries (`AppKeychain.shared.removeAllContent()`) +4. Clear all UserDefaults (`Defaults.removeAll()`) +5. Clear passcode, uninit all PGP agents, drop all `PasswordStore` instances +6. Post `.passwordStoreErased` + +--- + +## Phase 5: Password List UI — Multi-Store Browsing + +### 5.1 Unified password list + +`PasswordNavigationViewController` should show passwords from all visible stores together: + +- **Folder mode**: Add a top-level grouping by store name, then the folder hierarchy within each store. The store name row could have a distinct style (e.g. bold, with a colored dot or icon). +- **Flat mode**: Show all passwords from all visible stores. Subtitle or accessory showing which store each password belongs to. +- **Search**: Searches across all visible stores simultaneously. Results annotated with store name. + +### 5.2 Password detail + +`PasswordDetailTableViewController` needs to know which store a password belongs to (to decrypt with the right `PGPAgent` and write changes back to the right repo). Pass the store context through from the list. + +### 5.3 Add password flow + +`AddPasswordTableViewController` needs a store picker if multiple stores are visible. Default to a "primary" store or the last-used one. + +### 5.4 Sync + +→ Testing: [T9 — Sync tests](#t9-sync-tests) + +Pull-to-refresh in the password list syncs all visible stores (sequentially or in parallel). Show per-store sync status. Allow syncing individual stores from their settings or via long-press. + +--- + +## Phase 6: AutoFill Extension + +### 6.1 Multi-store AutoFill + +→ Testing: [T8 — AutoFill multi-store tests](#t8-autofill-multi-store-tests) + +`CredentialProviderViewController`: + +- Fetch passwords from all stores where `isVisibleInAutoFill == true` +- The "Suggested" section should search across all AutoFill-visible stores +- Each password entry carries its store context for decryption +- No store picker needed — just include all enabled stores transparently +- Consider showing store name in the cell subtitle for disambiguation + +### 6.2 QuickType integration + +→ Testing: [T8 — AutoFill multi-store tests](#t8-autofill-multi-store-tests) (store ID in `recordIdentifier`) + +`provideCredentialWithoutUserInteraction` needs to try the right store's PGP agent for decryption. Since it gets a `credentialIdentity` (which contains a `recordIdentifier` = password path), the path must now encode or be mappable to a store ID. + +--- + +## Phase 7: Extensions & Shortcuts + +### 7.1 passExtension (share extension) + +Same multi-store search as AutoFill. Minor. + +### 7.2 Shortcuts + +`SyncRepositoryIntentHandler`: + +- Add a store parameter to the intent (optional — if nil, sync all stores) +- Register each store as a Shortcut parameter option +- Support "Sync All" and "Sync [store name]" + +--- + +## Phase 8: Multi-Recipient Encryption + +See [02-multi-recipient-encryption-plan.md](02-multi-recipient-encryption-plan.md). This is standalone and can be implemented before or after multi-store support. In a multi-store context, `isEnableGPGIDOn` becomes a per-store setting. + +--- + +## Implementation Order + +| Step | Phase | Description | Depends On | +|------|-------|-------------|------------| +| 1 | 1 | Improve test coverage (see [separate plan](01-improve-test-coverage-plan.md)) | — | +| 2a | 2 | `StoreConfiguration` Core Data entity + relationship to `PasswordEntity` + model migration | Phase 1 | +| 2b | 2 | `StoreConfigurationManager` + single-store migration from existing Defaults/keychain | Step 2a | +| 2t | T | Tests: `StoreConfiguration` CRUD, cascade delete, migration (T1, T2) | Steps 2a+2b | +| 3a | 3 | Parameterize `Globals` paths (per-store repo directory) | Step 2a | +| 3b | 3 | Namespace keychain keys per store | Step 2a | +| 3bt | T | Tests: per-store keychain namespace (T5) | Step 3b | +| 3c | 3 | De-singleton `PGPAgent` | Steps 2a+3a+3b | +| 3d | 3 | De-singleton `PasswordStore` → `PasswordStoreManager` | Steps 2b-3c | +| 3dt | T | Tests: `PasswordStoreManager`, per-store `PasswordStore`, entity filtering (T3, T4, T6) | Step 3d | +| 3e | 3 | Per-store `GitCredential` | Steps 3b+3d | +| 3f | 3 | Store lifecycle: add/clone, remove/cleanup, re-clone, global erase | Steps 3d+3e | +| 3ft | T | Tests: store lifecycle integration (T7) | Step 3f | +| 4a | 4 | Store management UI (add/edit/delete/reorder) | Step 3f | +| 4b | 4 | Migrate existing settings screens to per-store | Step 4a | +| 5a | 5 | Multi-store password list | Step 3d | +| 5b | 5 | Multi-store add/edit/detail | Step 5a | +| 5c | 5 | Multi-store sync | Steps 3e+5a | +| 5ct | T | Tests: sync (T9) | Step 5c | +| 6a | 6 | Multi-store AutoFill | Step 3d | +| 6t | T | Tests: AutoFill multi-store (T8) | Step 6a | +| 7a | 7 | Multi-store Shortcuts | Step 3d | +| 8a | 8 | Multi-recipient encryption (see [separate plan](02-multi-recipient-encryption-plan.md)) | Step 3d | + +--- + +## Testing Plan + +For baseline test coverage of existing code, see [01-improve-test-coverage-plan.md](01-improve-test-coverage-plan.md). + +### Testing new multi-store code + +#### T1: `StoreConfiguration` entity tests + +- **Test CRUD**: Create, read, update, delete `StoreConfiguration` entities. +- **Test cascade delete**: Delete a `StoreConfiguration` → verify all linked `PasswordEntity` rows are deleted. +- **Test relationship integrity**: Create `PasswordEntity` rows linked to a store → verify fetching by store returns the right entities. +- **Test `StoreConfigurationManager`**: Create, list, reorder, delete stores via the manager. + +#### T2: Migration tests + +- **Test fresh install**: No existing data → no `StoreConfiguration` created, app works. +- **Test upgrade migration from single-store**: + 1. Set up a pre-migration Core Data database (using the old model version) with `PasswordEntity` rows, populate `Defaults` with git URL/branch/username, and populate keychain with PGP + SSH keys. + 2. Run the migration. + 3. Verify: one `StoreConfiguration` exists with values from Defaults, all `PasswordEntity` rows are linked to it, keychain entries are namespaced under the new store's ID. +- **Test idempotency**: Running migration twice doesn't create duplicate stores. +- **Test migration with empty repo** (no passwords, just settings): Still creates a `StoreConfiguration`. + +#### T3: `PasswordStoreManager` tests + +- **Test store lookup by ID**. +- **Test lazy instantiation**: Requesting a store creates `PasswordStore` on demand. +- **Test listing visible stores** (filtered by `isVisibleInPasswords` / `isVisibleInAutoFill`). +- **Test adding/removing stores updates the manager**. + +#### T4: Per-store `PasswordStore` tests + +- **Test clone scoped to per-store directory**: Clone into `Library/password-stores/{storeID}/`, verify `PasswordEntity` rows are linked to the right `StoreConfiguration`. +- **Test two stores independently**: Clone two different repos, verify each store's entities are separate, deleting one doesn't affect the other. +- **Test `eraseStoreData` scoped to one store**: Only that store's directory and entities are deleted. +- **Test encrypt/decrypt with per-store PGP keys**: Store A uses key pair X, store B uses key pair Y, each can only decrypt its own passwords. +- **Test store sharing one PGP key pair**: Two stores referencing the same keychain entries both decrypt correctly. + +#### T5: Per-store keychain namespace tests + +- **Test namespaced keys don't collide**: Store A's `"{A}.gitPassword"` and store B's `"{B}.gitPassword"` are independent. +- **Test `removeAllContent(withPrefix:)`**: Removing store A's keys doesn't affect store B's. +- **Test `pgpKeyPassphrase-{keyID}`** shared across stores using the same key. + +#### T6: `PasswordEntity` fetch filtering tests + +- **Test `fetchAll` filtered by one store**. +- **Test `fetchAll` filtered by multiple visible stores** (the AutoFill / password list scenario). +- **Test `fetchUnsynced` filtered by store**. +- **Test search across multiple stores**. + +#### T7: Store lifecycle integration tests + +- **Test add store flow**: Create config → clone → BFS walk → entities linked → notification posted. +- **Test remove store flow**: Delete config → cascade deletes entities → repo directory removed → keychain cleaned → notification posted. +- **Test re-clone flow**: Change git URL → old entities deleted → new clone → new entities → same `StoreConfiguration`. +- **Test global erase**: Multiple stores → all gone. +- **Test clone failure cleanup**: Clone fails → `StoreConfiguration` deleted → no orphan entities or directories. + +#### T8: AutoFill multi-store tests + +- **Test credential listing from multiple stores**: Entries from all AutoFill-visible stores appear. +- **Test store ID encoded in `recordIdentifier`**: Can map a credential identity back to the correct store for decryption. +- **Test filtering**: Only `isVisibleInAutoFill == true` stores appear. + +#### T9: Sync tests + +- **Test pull updates one store's entities without affecting others**. +- **Test sync-all triggers pull for each visible store**. + +### Test infrastructure additions needed + +- **Multi-store `CoreDataTestCase`**: Extend `CoreDataTestCase` to support the new model version with `StoreConfiguration`. Provide a helper to create a `StoreConfiguration` + linked entities in one call. +- **Pre-migration database fixture**: A snapshot of the old Core Data model (without `StoreConfiguration`) to use in migration tests. Can be a `.sqlite` file committed to the test bundle. + +--- + +## Risks & Considerations + +- **Data migration**: Existing users must be migrated seamlessly. The migration (steps 2a-2b) should be idempotent and tested thoroughly. +- **Core Data migration**: Adding the `StoreConfiguration` entity and the `store` relationship on `PasswordEntity` requires a lightweight migration (new entity + new optional relationship). The post-migration step creates a default `StoreConfiguration` from existing Defaults and assigns all existing `PasswordEntity` rows to it. +- **Memory**: Multiple `PasswordStore` instances each holding a `GTRepository` and `PGPAgent` — lazy instantiation is important. Only active/visible stores should be loaded. +- **Concurrency**: Git operations (pull/push) across multiple stores should not block each other. Use per-store serial queues. +- **AutoFill performance**: The extension has strict memory limits (~30MB). Loading all stores' Core Data is fine (single DB), but loading multiple PGP agents may be expensive. Decrypt lazily, only when the user selects a password. +- **Backward compatibility**: Older versions won't understand the new data layout. Consider a one-way migration flag. + +--- + +## Context + +### Prompt + +I want to add support for several separate password repositories, each with a unique repository connection (url, authnetication), and potentially separate encryption/decryption keys. + +Another GUI app that supports this is QtPass. There is information about this its readme: https://raw.githubusercontent.com/IJHack/QtPass/refs/heads/main/README.md +It calls it "profiles". I would probably call it "stores". + +I want to be able to configure which stores are enabled when I view the list, and separately also for the autofill feature. + +It should be possible to share a store with another user (who would be using a separate key on their end). + +Make a plan for what needs to be done to support this in this application. + +### Key Architecture Facts +- `PasswordStore.shared` singleton referenced from ~20+ call sites (app, AutoFill, passExtension, Shortcuts) +- `PGPAgent.shared` singleton holds single key pair +- `Globals` has all paths as `static let` (single repo, single DB, single key paths) +- `DefaultsKeys` — all git/PGP settings single-valued in shared UserDefaults +- `AppKeychain.shared` — flat keys, no per-store namespace +- Core Data: single `PasswordEntity` entity, no store discriminator, single SQLite DB +- `PersistenceController.shared` — single NSPersistentContainer +- UI: UITabBarController with 2 tabs (Passwords, Settings). Passwords tab uses PasswordNavigationViewController +- AutoFill: CredentialProviderViewController uses PasswordStore.shared directly +- App group + keychain group shared across all targets +- `.gpg-id` per-directory key selection already exists (closest to multi-key concept) +- QtPass calls them "profiles" — each can have different git repo and GPG key + +### User Requirements +- Multiple password stores, each with unique repo connection (URL, auth) and potentially separate PGP keys +- Call them "stores" (not profiles) +- Configure which stores are visible in password list vs AutoFill separately +- Support sharing a store with another user (who uses a different key)