Files
Андрей Геращенко f52cdb51bc MALINKA-1095: Password logic refactoring
2023-03-10 11:40:54 +03:00

298 lines
9.3 KiB
Swift

//
// AccountService.swift
// List
//
// Created by Igor Danich on 29.06.2020.
// Copyright © 2020 Igor Danich. All rights reserved.
//
import Foundation
import LocalAuthentication
import Alamofire
import Resolver
import WalletFoundation
import WalletKit
import WalletNetwork
import Combine
// swiftlint:disable:next identifier_name
@discardableResult func Accounts() -> Account.Service { .shared }
extension Notification.Name {
static let didChangeInterface = Notification.Name("Account.Interface.didUpdateTabBar")
}
private let didLoadAppKey = CommonKey("Account.Service.didLoadApp")
private let isPushEnabledKey = CommonKey("Account.Service.isPushEnabled")
private let isBiometricksEnabledKey = CommonKey("Account.Service.isBiometricksEnabled")
private let isAccountMigratedKey = CommonKey("Account.Service.isAccountMigrated")
extension Account {
final class Service: Common.Service.Provider {
let bank: Bank
let messageShelf: MessageBookShelfService
// TODO: - Remove "!"
static var shared: Service!
private var cancellables = Set<AnyCancellable>()
let cityId = 16
var collection: [WalletKit.Wallet] { self.bank.wallets }
var current: WalletKit.Wallet? {
get { self.bank.active }
set {
guard let wallet = newValue else { return }
do {
try self.bank.activate(wallet: wallet)
self.setup()
ResolverScope.userSession.reset()
} catch {}
}
}
var activePublisher: AnyPublisher<WalletKit.Wallet?, Never> { self.bank.activePublisher }
var accountsPublisher: AnyPublisher<[WalletKit.Wallet], Never> { self.bank.walletsPublisher }
lazy var lockedPublisher: AnyPublisher<Bool, Never> = self.lockedSubject.eraseToAnyPublisher()
let quota = Account.Service.CommonResources()
private let lockedSubject = CurrentValueSubject<Bool, Never>(false)
var isLocked: Bool {
get { self.lockedSubject.value }
set { self.lockedSubject.send(newValue) }
}
var hasActiveAccounts: Bool {
let collection = self.collection
guard !collection.isEmpty else { return false }
let filteredAccounts = collection
.filter {
switch $0.state {
case .declined, .pending:
return false
default:
return true
}
}
guard !filteredAccounts.isEmpty else { return false }
return true
}
let contacts = Contacts()
init(using bank: Bank) {
self.bank = bank
self.messageShelf = MessageBookShelfService(with: bank)
if !self.didLoadApp {
self.didLoadApp = true
}
self.setup()
}
func fetchQuota(force: Bool = false) {
self.quota.fetch(using: self.bank.active, force: force)
}
private var didLoadApp: Bool {
get { Settings.shared[didLoadAppKey] }
set { Settings.shared[didLoadAppKey] = newValue }
}
private func setup() {
self.quota.fetch(using: self.bank.active, clear: true)
self.accountsPublisher.sink { [weak self] _ in self?.updatePush() }.store(in: &self.cancellables)
}
}
}
// MARK: - Edit
extension Account.Service {
func addAccounts(purses: [Purse], password: String) throws {
try self.bank.add(using: purses, password: password)
if let wallet = self.collection.last,
let book = self.messageShelf.book(for: wallet) {
book.fetch()
try self.bank.activate(wallet: wallet)
}
}
func remove(wallet: WalletKit.Wallet, isSingle: Bool = true) {
try? self.bank.remove(wallet: wallet)
guard isSingle else { return }
self.selectDefaultAccount()
}
func updateCredentialsIfNeeded(accounts: [ChangeKeyModel], password: String) {
let collection = self.collection
for accountToChange in accounts {
guard let account = collection.first(where: {
$0.name == accountToChange.wallet.name && $0.keyType == accountToChange.wallet.keyType
}) else { continue }
account.updateKeychain(key: accountToChange.key, using: password)
}
}
func migrateChats(accounts: [ChangeKeyModel], password: String) {
for accountToChange in accounts {
guard let oldPrivateKey = Accounts().collection.first(where: { $0.name == accountToChange.wallet.name
&& $0.keyType == accountToChange.keyType })?.privateKey(password: password) else { continue }
guard let msgsService = try? CryptoChat.Service.MsgsHistory(username: accountToChange.wallet.name, encryptionKey: oldPrivateKey) else { continue }
msgsService.copy(username: accountToChange.wallet.name, privateKey: accountToChange.privateKey, publicKey: accountToChange.publicKey)
let hasOwnerAccount = Accounts().collection
.contains(where: { $0.name == accountToChange.wallet.name && $0.keyType == WalletKeyType.owner})
if !hasOwnerAccount {
msgsService.removeDatabase()
}
}
}
func removeAll() {
let wallets = self.bank.wallets
wallets.forEach { self.remove(wallet: $0, isSingle: false) }
}
private func selectDefaultAccount() {
let activeWallets = self.collection.filter { $0.state == .accepted }
if activeWallets.count == 0 {
self.current = self.collection.filter {
switch $0.state {
case .declined, .pending: return false
default: return true
}
}.first
} else {
self.current = activeWallets.first
}
}
}
// MARK: - Remove accounts
extension Account {
static func reset() {
if let service = Self.Service.shared {
service.removeAll()
} else {
Village.reset()
}
}
}
// MARK: - Authorization
extension Account.Service {
var isAuthorized: Bool {
if UserDefaults.standard.bool(forKey: "isPinPwdMode") {
if let pin = Account.Service.Authorize.shared.password,
Self.check(password: pin),
!pin.isEmpty,
pin.count == 4 {
return true
} else {
return false
}
} else {
return true
}
}
static var isPasswordExists: Bool { Village.isPasswordExists }
static var isBiometricksEnabled: Bool {
get { Settings.shared[isBiometricksEnabledKey] }
set { Settings.shared[isBiometricksEnabledKey] = newValue }
}
static var isAccountMigrated: Bool {
get { Settings.shared[isAccountMigratedKey] }
set { Settings.shared[isAccountMigratedKey] = newValue }
}
static var isFaceIdAvailable: Bool { LAContext().biometryType == .faceID }
static var isBiometricksAvailable: Bool {
let context = LAContext()
return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) ? context.biometryType != .none : false
}
static func checkBiometricks(_ completion: @escaping (Bool) -> Void) {
LAContext().evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Enable bimetricks"
) { (success, _) in
Self.isBiometricksEnabled = success
DispatchQueue.main.async { completion(success) }
}
}
static func check(password: String) -> Bool { Village.accept(password: password) }
static func passwordViaBiometrics() -> String? { Village.passwordViaBiometrics() }
func update(password: String, old: String? = nil) throws {
try old >>- { try self.bank.switchPassword(password, old: $0) }
}
func validate(password value: String) -> Bool { value.count >= 4 }
}
// MARK: - Push Notifications
extension Account.Service {
func updatePush(token: String? = nil) {
if let token {
Settings.shared[isPushEnabledKey] = token
self.updatePush()
return
}
guard let pushToken: String = Settings.shared[isPushEnabledKey] else { return }
let record = ApplicationEnvironment.shared().current
let collection = self.collection
Task {
let service = try AccountService(environment: record.networkEnvironment())
do {
try await service.updateNotification(token: pushToken,
accounts: collection.map { $0.name },
language: Common.Model.Language.current.rawValue)
} catch {
print(error)
}
}
}
}