Skip to content

Commit

Permalink
Implement Duress Mode
Browse files Browse the repository at this point in the history
  • Loading branch information
ealymbaev committed Sep 28, 2023
1 parent 6c8cc9e commit 3fb0123
Show file tree
Hide file tree
Showing 32 changed files with 750 additions and 177 deletions.
68 changes: 62 additions & 6 deletions UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

17 changes: 8 additions & 9 deletions UnstoppableWallet/UnstoppableWallet/Core/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,17 @@ class App {
pasteboardManager = PasteboardManager()
reachabilityManager = ReachabilityManager()

biometryManager = BiometryManager(localStorage: StorageKit.LocalStorage.default)
passcodeManager = PasscodeManager(biometryManager: biometryManager, secureStorage: keychainKit.secureStorage)
lockManager = LockManager(passcodeManager: passcodeManager, localStorage: StorageKit.LocalStorage.default, delegate: lockDelegate)
lockoutManager = LockoutManager(secureStorage: keychainKit.secureStorage)

blurManager = BlurManager(lockManager: lockManager)

let accountRecordStorage = AccountRecordStorage(dbPool: dbPool)
let accountStorage = AccountStorage(secureStorage: keychainKit.secureStorage, storage: accountRecordStorage)
let activeAccountStorage = ActiveAccountStorage(dbPool: dbPool)
let accountCachedStorage = AccountCachedStorage(accountStorage: accountStorage, activeAccountStorage: activeAccountStorage)
accountManager = AccountManager(storage: accountCachedStorage)
accountManager = AccountManager(passcodeManager: passcodeManager, accountStorage: accountStorage, activeAccountStorage: activeAccountStorage)
accountRestoreWarningManager = AccountRestoreWarningManager(accountManager: accountManager, localStorage: StorageKit.LocalStorage.default)
accountFactory = AccountFactory(accountManager: accountManager)

Expand Down Expand Up @@ -244,13 +250,6 @@ class App {
let favoriteCoinRecordStorage = FavoriteCoinRecordStorage(dbPool: dbPool)
favoritesManager = FavoritesManager(storage: favoriteCoinRecordStorage)

biometryManager = BiometryManager(localStorage: StorageKit.LocalStorage.default)
passcodeManager = PasscodeManager(biometryManager: biometryManager, secureStorage: keychainKit.secureStorage)
lockManager = LockManager(passcodeManager: passcodeManager, localStorage: StorageKit.LocalStorage.default, delegate: lockDelegate)
lockoutManager = LockoutManager(secureStorage: keychainKit.secureStorage)

blurManager = BlurManager(lockManager: lockManager)

let appVersionRecordStorage = AppVersionRecordStorage(dbPool: dbPool)
let appVersionStorage = AppVersionStorage(storage: appVersionRecordStorage)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import Foundation
import EvmKit
import Foundation

class AccountFactory {
private let accountManager: AccountManager

init(accountManager: AccountManager) {
self.accountManager = accountManager
}

}

extension AccountFactory {

var nextAccountName: String {
let nonWatchAccounts = accountManager.accounts.filter { !$0.watchAccount }
let order = nonWatchAccounts.count + 1
Expand All @@ -22,7 +20,7 @@ extension AccountFactory {
func nextAccountName(cex: Cex) -> String {
let cexAccounts = accountManager.accounts.filter { account in
switch account.type {
case .cex(let cexAccount): return cexAccount.cex == cex
case let .cex(cexAccount): return cexAccount.cex == cex
default: return false
}
}
Expand All @@ -40,22 +38,23 @@ extension AccountFactory {

func account(type: AccountType, origin: AccountOrigin, backedUp: Bool, name: String) -> Account {
Account(
id: UUID().uuidString,
name: name,
type: type,
origin: origin,
backedUp: backedUp
id: UUID().uuidString,
level: accountManager.currentLevel,
name: name,
type: type,
origin: origin,
backedUp: backedUp
)
}

func watchAccount(type: AccountType, name: String) -> Account {
Account(
id: UUID().uuidString,
name: name,
type: type,
origin: .restored,
backedUp: true
id: UUID().uuidString,
level: accountManager.currentLevel,
name: name,
type: type,
origin: .restored,
backedUp: true
)
}

}
107 changes: 94 additions & 13 deletions UnstoppableWallet/UnstoppableWallet/Core/Managers/AccountManager.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import RxSwift
import Combine
import RxRelay
import RxSwift

class AccountManager {
private let passcodeManager: PasscodeManager
private let storage: AccountCachedStorage
private var cancellables = Set<AnyCancellable>()

private let activeAccountRelay = PublishRelay<Account?>()
private let accountsRelay = PublishRelay<[Account]>()
Expand All @@ -12,24 +15,59 @@ class AccountManager {

private var lastCreatedAccount: Account?

init(storage: AccountCachedStorage) {
self.storage = storage
init(passcodeManager: PasscodeManager, accountStorage: AccountStorage, activeAccountStorage: ActiveAccountStorage) {
self.passcodeManager = passcodeManager

storage = AccountCachedStorage(level: passcodeManager.currentPasscodeLevel, accountStorage: accountStorage, activeAccountStorage: activeAccountStorage)

passcodeManager.$currentPasscodeLevel
.sink { [weak self] level in
self?.handle(level: level)
}
.store(in: &cancellables)

passcodeManager.$isDuressPasscodeSet
.sink { [weak self] isSet in
if !isSet {
self?.handleDisableDuress()
}
}
.store(in: &cancellables)
}

private func handle(level: Int) {
storage.set(level: level)

accountsRelay.accept(storage.accounts)
activeAccountRelay.accept(storage.activeAccount)
}

private func handleDisableDuress() {
let currentLevel = passcodeManager.currentPasscodeLevel

for account in storage.accounts {
if account.level > currentLevel {
account.level = currentLevel
storage.save(account: account)
}
}

accountsRelay.accept(storage.accounts)
}

private func clearAccounts(ids: [String]) {
ids.forEach {
storage.delete(accountId: $0)
}

if storage.accounts.isEmpty {
if storage.allAccounts.isEmpty {
accountsLostRelay.accept(true)
}
}

}

extension AccountManager {

var activeAccountObservable: Observable<Account?> {
activeAccountRelay.asObservable()
}
Expand All @@ -50,6 +88,10 @@ extension AccountManager {
accountsLostRelay.asObservable()
}

var currentLevel: Int {
passcodeManager.currentPasscodeLevel
}

var activeAccount: Account? {
storage.activeAccount
}
Expand Down Expand Up @@ -145,21 +187,47 @@ extension AccountManager {
return account
}

func setDuress(accountIds: [String]) {
let currentLevel = passcodeManager.currentPasscodeLevel

for account in storage.accounts {
if accountIds.contains(account.id) {
account.level = currentLevel + 1
storage.save(account: account)
}
}

accountsRelay.accept(storage.accounts)
}
}

class AccountCachedStorage {
private let accountStorage: AccountStorage
private let activeAccountStorage: ActiveAccountStorage

private var _accounts: [String: Account]
private var _allAccounts: [String: Account]

private var level: Int
private var _accounts = [String: Account]()
private var _activeAccount: Account?

init(accountStorage: AccountStorage, activeAccountStorage: ActiveAccountStorage) {
init(level: Int, accountStorage: AccountStorage, activeAccountStorage: ActiveAccountStorage) {
self.level = level
self.accountStorage = accountStorage
self.activeAccountStorage = activeAccountStorage

_accounts = accountStorage.allAccounts.reduce(into: [String: Account]()) { $0[$1.id] = $1 }
_activeAccount = activeAccountStorage.activeAccountId.flatMap { _accounts[$0] }
_allAccounts = accountStorage.allAccounts.reduce(into: [String: Account]()) { $0[$1.id] = $1 }

syncAccounts()
}

private func syncAccounts() {
_accounts = _allAccounts.filter { _, account in account.level >= level }
_activeAccount = activeAccountStorage.activeAccountId(level: level).flatMap { _accounts[$0] }
}

var allAccounts: [Account] {
Array(_allAccounts.values)
}

var accounts: [Account] {
Expand All @@ -174,33 +242,46 @@ class AccountCachedStorage {
accountStorage.lostAccountIds
}

func set(level: Int) {
self.level = level
syncAccounts()
}

func account(id: String) -> Account? {
_accounts[id]
_allAccounts[id]
}

func set(activeAccountId: String?) {
activeAccountStorage.activeAccountId = activeAccountId
activeAccountStorage.save(activeAccountId: activeAccountId, level: level)
_activeAccount = activeAccountId.flatMap { _accounts[$0] }
}

func save(account: Account) {
accountStorage.save(account: account)
_accounts[account.id] = account
_allAccounts[account.id] = account

if account.level >= level {
_accounts[account.id] = account
} else {
_accounts.removeValue(forKey: account.id)
}
}

func delete(account: Account) {
accountStorage.delete(account: account)
_allAccounts.removeValue(forKey: account.id)
_accounts.removeValue(forKey: account.id)
}

func delete(accountId: String) {
accountStorage.delete(accountId: accountId)
_allAccounts.removeValue(forKey: accountId)
_accounts.removeValue(forKey: accountId)
}

func clear() {
accountStorage.clear()
_allAccounts = [:]
_accounts = [:]
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ class PasscodeManager {

private var passcodes = [String]()

@PostPublished private(set) var currentPasscodeLevel: Int
@PostPublished private(set) var isPasscodeSet = false
@PostPublished private(set) var isDuressPasscodeSet = false
@DistinctPublished private(set) var currentPasscodeLevel: Int
@DistinctPublished private(set) var isPasscodeSet = false
@DistinctPublished private(set) var isDuressPasscodeSet = false

init(biometryManager: BiometryManager, secureStorage: ISecureStorage) {
self.biometryManager = biometryManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class AccountStorage {

return Account(
id: id,
level: record.level,
name: record.name,
type: type,
origin: origin,
Expand Down Expand Up @@ -126,6 +127,7 @@ class AccountStorage {

return AccountRecord(
id: id,
level: account.level,
name: account.name,
type: typeName.rawValue,
origin: account.origin.rawValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,18 @@ class ActiveAccountStorage {

extension ActiveAccountStorage {

var activeAccountId: String? {
get {
try! dbPool.read { db in
try ActiveAccount.fetchOne(db)?.accountId
}
func activeAccountId(level: Int) -> String? {
try? dbPool.read { db in
try ActiveAccount.filter(ActiveAccount.Columns.level == level).fetchOne(db)?.accountId
}
set {
_ = try! dbPool.write { db in
if let accountId = newValue {
try ActiveAccount(accountId: accountId).insert(db)
} else {
try ActiveAccount.deleteAll(db)
}
}

func save(activeAccountId: String?, level: Int) {
_ = try? dbPool.write { db in
if let activeAccountId {
try ActiveAccount(level: level, accountId: activeAccountId).insert(db)
} else {
try ActiveAccount.filter(ActiveAccount.Columns.level == level).deleteAll(db)
}
}
}
Expand Down
Loading

0 comments on commit 3fb0123

Please sign in to comment.