Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] ABW-2121 Save accounts needing recovery in UserDefaults #685

Merged
merged 100 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
8f03e1a
Versioning of SecurityQuestionsFactorSource SealedMnemonic
CyonAlexRDX Aug 14, 2023
5d53cba
wip
CyonAlexRDX Aug 15, 2023
bcb9b3c
Merge branch 'main' into ABW-1674_import_export_profile_as_file_w_enc…
CyonAlexRDX Aug 15, 2023
9a84b39
WIP
CyonAlexRDX Aug 15, 2023
40485fe
WIP
CyonAlexRDX Aug 15, 2023
2cd7e17
WIP
CyonAlexRDX Aug 15, 2023
70fba81
WIP
CyonAlexRDX Aug 16, 2023
1c5d309
WIP
CyonAlexRDX Aug 16, 2023
5eea02a
WIP
CyonAlexRDX Aug 16, 2023
a3b169e
WIP
CyonAlexRDX Aug 16, 2023
195efb3
WIP
CyonAlexRDX Aug 16, 2023
ce2a1b2
WIP
CyonAlexRDX Aug 16, 2023
3bd771e
WIP
CyonAlexRDX Aug 16, 2023
2a44945
WIP
CyonAlexRDX Aug 16, 2023
f5573aa
WIP
CyonAlexRDX Aug 17, 2023
1aac7be
WIP
CyonAlexRDX Aug 17, 2023
e37e9e5
WIP
CyonAlexRDX Aug 17, 2023
4076cc1
WIP
CyonAlexRDX Aug 17, 2023
53e0241
WIP
CyonAlexRDX Aug 17, 2023
1190d78
WIP
CyonAlexRDX Aug 18, 2023
a9357ac
WIP
CyonAlexRDX Aug 18, 2023
8002ac9
WIP
CyonAlexRDX Aug 18, 2023
22d4898
WIP
CyonAlexRDX Aug 18, 2023
0341f99
WIP
CyonAlexRDX Aug 18, 2023
40c6f8d
WIP
CyonAlexRDX Aug 18, 2023
14fb993
WIP
CyonAlexRDX Aug 21, 2023
416aeac
WIP
CyonAlexRDX Aug 21, 2023
5648282
WIP
CyonAlexRDX Aug 21, 2023
61d8d97
WIP
CyonAlexRDX Aug 21, 2023
9c85639
WIP
CyonAlexRDX Aug 22, 2023
6b1b81e
Merge branch 'main' into ABW-1674_import_export_profile_as_file_w_enc…
CyonAlexRDX Aug 22, 2023
559954c
WIP
CyonAlexRDX Aug 22, 2023
94905d2
WIP
CyonAlexRDX Aug 22, 2023
e5ed61a
WIP
CyonAlexRDX Aug 22, 2023
3a8028a
WIP
CyonAlexRDX Aug 22, 2023
4ca3eee
WIP
CyonAlexRDX Aug 22, 2023
6d77cf5
WIP
CyonAlexRDX Aug 22, 2023
8b0f73c
WIP
CyonAlexRDX Aug 22, 2023
49e18c3
WIP
CyonAlexRDX Aug 22, 2023
f313a53
WIP
CyonAlexRDX Aug 22, 2023
d72aada
WIP
CyonAlexRDX Aug 22, 2023
0072337
WIP
CyonAlexRDX Aug 22, 2023
bb484b8
WIP
CyonAlexRDX Aug 22, 2023
b322e35
WIP
CyonAlexRDX Aug 22, 2023
57e4759
WIP
CyonAlexRDX Aug 22, 2023
e3bd635
WIP
CyonAlexRDX Aug 23, 2023
073c292
WIP
CyonAlexRDX Aug 23, 2023
d583ad3
WIP
CyonAlexRDX Aug 23, 2023
e0f0f57
WIP
CyonAlexRDX Aug 23, 2023
ff105eb
WIP
CyonAlexRDX Aug 23, 2023
8ca6aff
WIP
CyonAlexRDX Aug 23, 2023
3db69e6
WIP
CyonAlexRDX Aug 23, 2023
10ac8bf
WIP
CyonAlexRDX Aug 23, 2023
a15cdd5
WIP
CyonAlexRDX Aug 23, 2023
cb28994
WIP
CyonAlexRDX Aug 23, 2023
89d951d
WIP
CyonAlexRDX Aug 23, 2023
2c1bef4
WIP
CyonAlexRDX Aug 24, 2023
77d521b
Review fix
CyonAlexRDX Aug 24, 2023
def2a55
Review fix2
CyonAlexRDX Aug 24, 2023
a25bfa3
Review fix3
CyonAlexRDX Aug 24, 2023
81616ac
not destructive button roles for encrypt dialog
CyonAlexRDX Aug 24, 2023
4b0708b
WIP
CyonAlexRDX Aug 24, 2023
7622f58
Important bug fix for Import Olympia and add methods in userdefaults …
CyonAlexRDX Aug 24, 2023
371aa8f
Merge branch 'main' into ABW-1674_import_export_profile_as_file_w_enc…
CyonAlexRDX Aug 24, 2023
4e6b346
WIP
CyonAlexRDX Aug 24, 2023
49845f1
WIP
CyonAlexRDX Aug 25, 2023
1584e4f
Merge branch 'ABW-1674_import_export_profile_as_file_w_encryption' in…
CyonAlexRDX Aug 25, 2023
8b81655
merge
CyonAlexRDX Aug 25, 2023
1ec156f
WIP
CyonAlexRDX Aug 25, 2023
a690958
WIP
CyonAlexRDX Aug 25, 2023
2013da8
WIP
CyonAlexRDX Aug 25, 2023
b18ef74
WIP
CyonAlexRDX Aug 25, 2023
c4c488d
WIP
CyonAlexRDX Aug 25, 2023
7d5852b
WIP
CyonAlexRDX Aug 25, 2023
ef57969
WIP
CyonAlexRDX Aug 28, 2023
e5af536
WIP
CyonAlexRDX Aug 28, 2023
2c097cf
WIP
CyonAlexRDX Aug 28, 2023
c3ea819
WIP
CyonAlexRDX Aug 28, 2023
3f80392
WIP
CyonAlexRDX Aug 29, 2023
89cda5e
WIP
CyonAlexRDX Aug 29, 2023
431c5e4
WIP
CyonAlexRDX Aug 29, 2023
75d4410
WIP
CyonAlexRDX Aug 29, 2023
3c14dee
WIP
CyonAlexRDX Aug 29, 2023
f3cb6f4
WIP
CyonAlexRDX Aug 29, 2023
31c36ca
WIP
CyonAlexRDX Aug 29, 2023
b1ef3e2
WIP
CyonAlexRDX Aug 29, 2023
2205361
Merge branch 'main' into ABW-2121_accounts_needing_recovery_in_UserDe…
CyonAlexRDX Aug 31, 2023
b500287
display BIP39 Passphrase in readonly mode if it is not empty, always,
CyonAlexRDX Aug 31, 2023
40b162c
Merge branch 'main' into ABW-2121_accounts_needing_recovery_in_UserDe…
CyonAlexRDX Aug 31, 2023
b90f40c
fix bug
CyonAlexRDX Aug 31, 2023
01b19a2
Merge branch 'main' into ABW-2121_accounts_needing_recovery_in_UserDe…
CyonAlexRDX Sep 1, 2023
c737bef
commit package resolved
CyonAlexRDX Sep 1, 2023
7841024
Merge branch 'main' into ABW-2121_accounts_needing_recovery_in_UserDe…
CyonAlexRDX Sep 1, 2023
b75e2a4
WIP
CyonAlexRDX Sep 1, 2023
e5b1f4b
Merge branch 'main' into ABW-2121_accounts_needing_recovery_in_UserDe…
CyonAlexRDX Sep 4, 2023
d26e72b
fix presentation bug
CyonAlexRDX Sep 4, 2023
8c5bc02
update account details UI regarding prompts
CyonAlexRDX Sep 4, 2023
d423010
fix PR comments
CyonAlexRDX Sep 4, 2023
16e55ed
merge
CyonAlexRDX Sep 4, 2023
776a42c
merge
CyonAlexRDX Sep 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ package.addModules([
"AssetTransferFeature",
"AccountPortfoliosClient",
"AssetsFeature",
"ImportMnemonicFeature",
"ProfileBackupsFeature",
"BackupsClient",
],
tests: .yes()
),
Expand All @@ -30,6 +33,7 @@ package.addModules([
dependencies: [
"AccountPortfoliosClient",
"FactorSourcesClient", // check if `device` or `ledger` controlled for security prompting
"AccountDetailsFeature", // "shield buttons"
],
tests: .no
),
Expand Down Expand Up @@ -242,6 +246,8 @@ package.addModules([
"AccountsClient",
"AppPreferencesClient",
"CreateAccountFeature",
"ImportMnemonicFeature",
"ProfileBackupsFeature", // actually only ImportMnemonicsFlowCoodinator, might split it out in future
],
tests: .yes()
),
Expand Down Expand Up @@ -388,26 +394,27 @@ package.addModules([
dependencies: [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sort alphabetically

"AccountsClient",
"AddLedgerFactorSourceFeature",
"ImportOlympiaLedgerAccountsAndFactorSourcesFeature",
"AppPreferencesClient",
"AuthorizedDAppsFeature",
"CacheClient",
"DeviceFactorSourceClient",
"DebugInspectProfileFeature",
"DeviceFactorSourceClient",
"DisplayEntitiesControlledByMnemonicFeature",
"EditPersonaFeature",
"EngineKit",
"FactorSourcesClient", // Check if user has any ledgers
"FaucetClient", // EpochForWhenLastUsedByAccountAddress
"GatewayAPI",
"GatewaySettingsFeature",
"DisplayEntitiesControlledByMnemonicFeature",
"ImportMnemonicFeature",
"FactorSourcesClient", // Check if user has any ledgers
"ImportOlympiaLedgerAccountsAndFactorSourcesFeature",
"ImportLegacyWalletClient",
"P2PLinksFeature",
"PersonasFeature",
"RadixConnectClient",
"ProfileBackupsFeature",
"ScanQRFeature",
"SecurityStructureConfigurationListFeature",
"EditPersonaFeature",
"ProfileBackupsFeature",
"EngineKit",
],
tests: .yes()
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ extension FactorSourcesClient {
public typealias GetFactorSources = @Sendable () async throws -> FactorSources
public typealias FactorSourcesAsyncSequence = @Sendable () async -> AnyAsyncSequence<FactorSources>
public typealias AddPrivateHDFactorSource = @Sendable (AddPrivateHDFactorSourceRequest) async throws -> FactorSourceID
public typealias CheckIfHasOlympiaFactorSourceForAccounts = @Sendable (NonEmpty<OrderedSet<OlympiaAccountToMigrate>>) async -> FactorSourceID.FromHash?
public typealias CheckIfHasOlympiaFactorSourceForAccounts = @Sendable (BIP39.WordCount, NonEmpty<OrderedSet<OlympiaAccountToMigrate>>) async -> FactorSourceID.FromHash?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wordcount is now part of the FactorSourceID so can be used to filter

public typealias SaveFactorSource = @Sendable (FactorSource) async throws -> Void
public typealias UpdateFactorSource = @Sendable (FactorSource) async throws -> Void
public typealias GetSigningFactors = @Sendable (GetSigningFactorsRequest) async throws -> SigningFactors
Expand Down Expand Up @@ -257,10 +257,7 @@ extension MnemonicWithPassphrase {
) throws -> Bool {
let hdRoot = try self.hdRoot()

for account in accounts {
let path = account.0
let publicKey = account.1

for (path, publicKey) in accounts {
let derivedPublicKey: SLIP10.PublicKey = try {
switch publicKey.curve {
case .secp256k1:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extension FactorSourcesClient: TestDependencyKey {
getFactorSources: { throw NoopError() },
factorSourcesAsyncSequence: { AsyncLazySequence([]).eraseToAnyAsyncSequence() },
addPrivateHDFactorSource: { _ in throw NoopError() },
checkIfHasOlympiaFactorSourceForAccounts: { _ in nil },
checkIfHasOlympiaFactorSourceForAccounts: { _, _ in nil },
saveFactorSource: { _ in },
updateFactorSource: { _ in },
getSigningFactors: { _ in throw NoopError() },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,28 +66,38 @@ extension FactorSourcesClient: DependencyKey {

return factorSourceID
},
checkIfHasOlympiaFactorSourceForAccounts: { softwareAccounts -> FactorSourceID.FromHash? in
checkIfHasOlympiaFactorSourceForAccounts: { wordCount, softwareAccounts -> FactorSourceID.FromHash? in
guard softwareAccounts.allSatisfy({ $0.accountType == .software }) else {
assertionFailure("Unexpectedly received hardware account, unable to verify.")
return nil
}
do {
// Might be empty, if it is, we will just return nil (for-loop below not run).
let factorSourceIDs = try await getFactorSources()
let olympiaDeviceFactorSources: [DeviceFactorSource] = try await getFactorSources()
.filter(\.supportsOlympia)
.filter { $0.kind == .device }
.map(\.id)
.compactMap {
guard
let deviceFactorSource = try? $0.extract(as: DeviceFactorSource.self),
deviceFactorSource.hint.mnemonicWordCount == wordCount
else {
return nil
}
return deviceFactorSource
}

let factorSourceIDs = olympiaDeviceFactorSources.map(\.id)

for factorSourceID in factorSourceIDs {
guard let mnemonic = try await secureStorageClient.loadMnemonicByFactorSourceID(factorSourceID, .importOlympiaAccounts) else {
guard let mnemonic = try await secureStorageClient.loadMnemonicByFactorSourceID(factorSourceID.embed(), .importOlympiaAccounts) else {
continue
}
guard try mnemonic.validatePublicKeys(of: softwareAccounts) else {
guard (try? mnemonic.validatePublicKeys(of: softwareAccounts)) == true else {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

important bug fix solving validation of accounts when user has multiple mnemonics

continue
}
// YES Managed to validate all software accounts against existing factor source
loggerGlobal.debug("Existing factor source found for selected Olympia software accounts.")
return factorSourceID.extract(FactorSourceID.FromHash.self)
return factorSourceID
}

return nil // Did not find any Olympia `.device` factor sources
Expand Down
40 changes: 40 additions & 0 deletions Sources/Clients/FaucetClient/FaucetClient+Interface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,43 @@ extension DependencyValues {
set { self[FaucetClient.self] = newValue }
}
}

extension UserDefaultsClient {
public func loadEpochForWhenLastUsedByAccountAddress() -> EpochForWhenLastUsedByAccountAddress {
(try? loadCodable(key: .epochForWhenLastUsedByAccountAddress)) ?? .init()
}

public func saveEpochForWhenLastUsedByAccountAddress(_ value: EpochForWhenLastUsedByAccountAddress) async {
// not important enough to propagate error
try? await save(codable: value, forKey: .epochForWhenLastUsedByAccountAddress)
}
}

// MARK: - EpochForWhenLastUsedByAccountAddress
// internal for tests
public struct EpochForWhenLastUsedByAccountAddress: Codable, Hashable, Sendable {
public struct EpochForAccount: Codable, Sendable, Hashable, Identifiable {
public typealias ID = AccountAddress
public var id: ID { accountAddress }
public let accountAddress: AccountAddress
public var epoch: Epoch
}

public var epochForAccounts: IdentifiedArrayOf<EpochForAccount>
public init(epochForAccounts: IdentifiedArrayOf<EpochForAccount> = .init()) {
self.epochForAccounts = epochForAccounts
}

public mutating func update(epoch: Epoch, for id: AccountAddress) {
if var existing = epochForAccounts[id: id] {
existing.epoch = epoch
epochForAccounts[id: id] = existing
} else {
epochForAccounts.append(.init(accountAddress: id, epoch: epoch))
}
}

public func getEpoch(for accountAddress: AccountAddress) -> Epoch? {
epochForAccounts[id: accountAddress]?.epoch
}
}
57 changes: 1 addition & 56 deletions Sources/Clients/FaucetClient/FaucetClient+Live.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import SubmitTransactionClient
import TransactionClient

let minimumNumberOfEpochsPassedForFaucetToBeReused = 1
// internal for tests
let epochForWhenLastUsedByAccountAddressKey = "faucet.epochForWhenLastUsedByAccountAddressKey"

// MARK: - FaucetClient + DependencyKey
extension FaucetClient: DependencyKey {
Expand Down Expand Up @@ -114,7 +112,7 @@ extension FaucetClient: DependencyKey {
#if DEBUG
let createFungibleToken: CreateFungibleToken = { request in
let networkID = await gatewaysClient.getCurrentNetworkID()
let manifest = try try ManifestBuilder.manifestForCreateFungibleToken(
let manifest = try ManifestBuilder.manifestForCreateFungibleToken(
account: request.recipientAccountAddress,
networkID: networkID
)
Expand Down Expand Up @@ -169,56 +167,3 @@ extension FaucetClient: DependencyKey {
#endif // DEBUG
}()
}

private extension UserDefaultsClient {
func loadEpochForWhenLastUsedByAccountAddress() -> EpochForWhenLastUsedByAccountAddress {
@Dependency(\.jsonDecoder) var jsonDecoder
if
let data = self.dataForKey(epochForWhenLastUsedByAccountAddressKey),
let epochs = try? jsonDecoder().decode(EpochForWhenLastUsedByAccountAddress.self, from: data)
{
return epochs
} else {
return .init()
}
}

func saveEpochForWhenLastUsedByAccountAddress(_ value: EpochForWhenLastUsedByAccountAddress) async {
@Dependency(\.jsonEncoder) var jsonEncoder
do {
let data = try jsonEncoder().encode(value)
await self.setData(data, epochForWhenLastUsedByAccountAddressKey)
} catch {
// Not important enough to throw...
}
}
}

// MARK: - EpochForWhenLastUsedByAccountAddress
// internal for tests
struct EpochForWhenLastUsedByAccountAddress: Codable, Hashable, Sendable {
struct EpochForAccount: Codable, Sendable, Hashable, Identifiable {
typealias ID = AccountAddress
var id: ID { accountAddress }
let accountAddress: AccountAddress
var epoch: Epoch
}

var epochForAccounts: IdentifiedArrayOf<EpochForAccount>
init(epochForAccounts: IdentifiedArrayOf<EpochForAccount> = .init()) {
self.epochForAccounts = epochForAccounts
}

mutating func update(epoch: Epoch, for id: AccountAddress) {
if var existing = epochForAccounts[id: id] {
existing.epoch = epoch
epochForAccounts[id: id] = existing
} else {
epochForAccounts.append(.init(accountAddress: id, epoch: epoch))
}
}

func getEpoch(for accountAddress: AccountAddress) -> Epoch? {
epochForAccounts[id: accountAddress]?.epoch
}
}
16 changes: 7 additions & 9 deletions Sources/Clients/ProfileStore/ProfileStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import SecureStorageClient
/// and if user has enabled: the keychain item syncs to iCloud
/// as well.
///
/// This actor is meant **not** meant to be used directly by the
/// This actor is **not** meant to be used directly by the
/// apps reducers, but rather always indirectly, via the live
/// implementations of a set of clients (dependencies), e.g.:
/// * AccountsClient
Expand Down Expand Up @@ -636,17 +636,15 @@ struct EphemeralProfile: Sendable, Hashable {
}

extension UserDefaultsClient {
static let activeProfileID = "profile.activeProfileID"

func getActiveProfileID() -> ProfileSnapshot.Header.ID? {
stringForKey(Self.activeProfileID).flatMap(UUID.init(uuidString:))
public func getActiveProfileID() -> ProfileSnapshot.Header.ID? {
stringForKey(.activeProfileID).flatMap(UUID.init(uuidString:))
}

func setActiveProfileID(_ id: ProfileSnapshot.Header.UsedDeviceInfo.ID) async {
await setString(id.uuidString, Self.activeProfileID)
public func setActiveProfileID(_ id: ProfileSnapshot.Header.UsedDeviceInfo.ID) async {
await setString(id.uuidString, .activeProfileID)
}

func removeActiveProfileID() async {
await remove(Self.activeProfileID)
public func removeActiveProfileID() async {
await remove(.activeProfileID)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import EngineKit // AccountAddress
import Prelude // UserDefaultsClient
import Profile

extension UserDefaultsClient {
public func addAccountsThatNeedRecovery(accounts new: OrderedSet<AccountAddress>) async throws {
var accounts = getAddressesOfAccountsThatNeedRecovery()
accounts.append(contentsOf: new)
try await save(codable: accounts, forKey: .accountsThatNeedRecovery)
}

public func removeFromListOfAccountsThatNeedRecovery(accounts toRemove: OrderedSet<AccountAddress>) async throws {
var accounts = getAddressesOfAccountsThatNeedRecovery()
accounts.subtract(toRemove)
try await save(codable: accounts, forKey: .accountsThatNeedRecovery)
}

public func getAddressesOfAccountsThatNeedRecovery() -> OrderedSet<AccountAddress> {
(try? loadCodable(key: .accountsThatNeedRecovery)) ?? OrderedSet<AccountAddress>()
}

public func removeAccountsThatNeedRecovery() async {
await setData(nil, .accountsThatNeedRecovery)
}
}

extension UserDefaultsClient {
public func addFactorSourceIDOfBackedUpMnemonic(_ factorSourceID: FactorSourceID.FromHash) async throws {
var ids = getFactorSourceIDOfBackedUpMnemonics()
ids.append(factorSourceID)
try await save(codable: ids, forKey: .mnemonicsUserClaimsToHaveBackedUp)
}

public func getFactorSourceIDOfBackedUpMnemonics() -> OrderedSet<FactorSourceID.FromHash> {
(try? loadCodable(key: .mnemonicsUserClaimsToHaveBackedUp)) ?? OrderedSet<FactorSourceID.FromHash>()
}

public func removeAllFactorSourceIDsOfBackedUpMnemonics() async {
await setData(nil, .mnemonicsUserClaimsToHaveBackedUp)
}
}
Loading
Loading