MALINKA-1095: Password logic refactoring
This commit is contained in:
committed by
Jura Shikin
parent
973e1290dc
commit
f52cdb51bc
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// KeychainProtocol.swift
|
||||
//
|
||||
//
|
||||
// Created by Nut.Tech on 16.02.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public typealias Key = CommonKey
|
||||
|
||||
public protocol KeychainProtocol {
|
||||
|
||||
/// Checks if a given common key have a password-saved value
|
||||
/// - Parameter key: Common Key
|
||||
/// - Returns: is value exists
|
||||
func exist(_ key: Key) -> Bool
|
||||
/// Checks if a given common key have a biometric-saved value
|
||||
/// - Parameter key: Common Key
|
||||
/// - Returns: is value exists
|
||||
func bioExist(_ key: Key) -> Bool
|
||||
/// Access to common key value by biometric authenfication
|
||||
subscript(biometric key: Key) -> String? { get }
|
||||
/// Access to common key value by password authenfication
|
||||
subscript(_ key: Key, password password: String) -> String? { get set }
|
||||
}
|
||||
@@ -5,18 +5,15 @@
|
||||
// Created by Juraldinio on 11/27/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LocalAuthentication
|
||||
|
||||
extension String {
|
||||
fileprivate static let password = "PWD"
|
||||
fileprivate static let biometric = "BIO"
|
||||
}
|
||||
final public class WalletKeychain: KeychainProtocol {
|
||||
|
||||
private enum KeychainLocals {
|
||||
public static let password = "PWD"
|
||||
public static let biometric = "BIO"
|
||||
}
|
||||
|
||||
final public class WalletKeychain {
|
||||
|
||||
public typealias Key = CommonKey
|
||||
|
||||
public static let instance = WalletKeychain()
|
||||
|
||||
// MARK: - Init
|
||||
@@ -25,17 +22,17 @@ final public class WalletKeychain {
|
||||
|
||||
// MARK: - Interface
|
||||
|
||||
public func exist(_ key: Key) -> Bool { self.checkProtectedExist(key: key.with(.password)) }
|
||||
public func exist(_ key: Key) -> Bool { self.checkProtectedExist(key: key.with(KeychainLocals.password)) }
|
||||
|
||||
public func bioExist(_ key: Key) -> Bool { self.checkProtectedExist(key: key.with(.biometric) ) }
|
||||
public func bioExist(_ key: Key) -> Bool { self.checkProtectedExist(key: key.with(KeychainLocals.biometric) ) }
|
||||
|
||||
public subscript(biometric key: Key) -> String? {
|
||||
self.loadBiometricProtected(key: key.with(.biometric))
|
||||
self.loadBiometricProtected(key: key.with(KeychainLocals.biometric))
|
||||
.map({ String(data: $0, encoding: .utf8) }) ?? nil
|
||||
}
|
||||
|
||||
public subscript(_ key: Key, password password: String) -> String? {
|
||||
get { loadPassProtected(key: key.with(.password), password: password).map { String(data: $0, encoding: .utf8) } ?? nil }
|
||||
get { loadPassProtected(key: key.with(KeychainLocals.password), password: password).map { String(data: $0, encoding: .utf8) } ?? nil }
|
||||
set { update(key, password: password, newValue: newValue) }
|
||||
}
|
||||
|
||||
@@ -152,12 +149,13 @@ final public class WalletKeychain {
|
||||
// MARK: -
|
||||
|
||||
private func update(_ key: Key, password: String, newValue: String?) {
|
||||
//TODO: Refactor later - updation of biometric entry only when it is available and needed
|
||||
if let value = newValue {
|
||||
setPassProtected(key: key.with(.password), data: value, password: password)
|
||||
setBiometricEntry(key: key.with(.biometric), data: value)
|
||||
setPassProtected(key: key.with(KeychainLocals.password), data: value, password: password)
|
||||
setBiometricEntry(key: key.with(KeychainLocals.biometric), data: value)
|
||||
} else {
|
||||
removeProtected(key: key.with(.password))
|
||||
removeProtected(key: key.with(.biometric))
|
||||
removeProtected(key: key.with(KeychainLocals.password))
|
||||
removeProtected(key: key.with(KeychainLocals.biometric))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,17 @@ import Foundation
|
||||
|
||||
public extension Data {
|
||||
|
||||
func jsonDecoded<T: Decodable>(type: T.Type) -> T? { try? JSONDecoder().decode(type, from: self) }
|
||||
func jsonDecoded<T: Decodable>(type: T.Type, userInfo: [CodingUserInfoKey: Any]? = nil) -> T? {
|
||||
try? self.makeDecoder(userInfo: userInfo).decode(type, from: self)
|
||||
}
|
||||
|
||||
func jsonDecoded<T: Decodable>(type: T.Type) -> [T]? { try? JSONDecoder().decode([T].self, from: self) }
|
||||
func jsonDecoded<T: Decodable>(type: T.Type, userInfo: [CodingUserInfoKey: Any]? = nil) -> [T]? {
|
||||
try? self.makeDecoder(userInfo: userInfo).decode([T].self, from: self)
|
||||
}
|
||||
|
||||
private func makeDecoder(userInfo: [CodingUserInfoKey: Any]?) -> JSONDecoder {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
userInfo >>- { jsonDecoder.userInfo = $0 }
|
||||
return jsonDecoder
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,15 @@ let package = Package(
|
||||
.package(name: "eosswift", path: "../../Vendors/spm/eos-swift"),
|
||||
.package(name: "KeyChainAccess", path: "../../Vendors/spm/KeyChainAccess"),
|
||||
.package(name: "WalletFoundation", path: "../WalletFoundation"),
|
||||
.package(name: "WalletNetwork", path: "../WalletNetwork")
|
||||
.package(name: "WalletNetwork", path: "../WalletNetwork"),
|
||||
.package(name: "CryptoSwift", path: "../../Vendors/spm/CryptoSwift-1.5.1")
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "WalletKit",
|
||||
dependencies: ["eosswift", "KeyChainAccess", "WalletFoundation", "WalletNetwork"],
|
||||
dependencies: ["CryptoSwift", "eosswift", "KeyChainAccess", "WalletFoundation", "WalletNetwork"],
|
||||
path: "./Sources"),
|
||||
.testTarget(
|
||||
name: "WalletKitTests",
|
||||
|
||||
@@ -9,16 +9,9 @@ import Foundation
|
||||
import WalletFoundation
|
||||
import WalletNetwork
|
||||
|
||||
public enum VillageError: Error {
|
||||
case passwordNotMatch
|
||||
}
|
||||
|
||||
final public class Village {
|
||||
|
||||
private enum Constants {
|
||||
static let passwordKey = CommonKey("Account.Service.password")
|
||||
}
|
||||
|
||||
private static var keychainKeeper = PrivacyKeeper(keychain: WalletKeychain.instance)
|
||||
private static var villages = [Village]()
|
||||
|
||||
private let environment: NetworkEnvironment
|
||||
@@ -41,6 +34,7 @@ final public class Village {
|
||||
|
||||
/// Create instance of Village.
|
||||
public static func get(with environment: NetworkEnvironment) -> Village {
|
||||
|
||||
if let village = Self.villages.first(where: { $0.environment.isEquals(other: environment) }) {
|
||||
return village
|
||||
}
|
||||
@@ -99,17 +93,19 @@ final public class Village {
|
||||
}
|
||||
|
||||
/// Create bank that contain Wallets.
|
||||
public func getBank(with password: String, on device: Device) -> Bank {
|
||||
public func getBank(with password: String, on device: Device) throws -> Bank {
|
||||
|
||||
if let bank = self.houses.first(where: { $0.isEquals(password: password, device: device, environment: self.environment) }) {
|
||||
guard Self.keychainKeeper.accept(password: password) else {
|
||||
throw WalletError.passwordNotMatch
|
||||
}
|
||||
|
||||
if let bank = self.houses.first(where: { $0.isEquals(device: device, environment: self.environment, password: password) }) {
|
||||
return bank
|
||||
}
|
||||
|
||||
if Self.password != password {
|
||||
Self.password = password
|
||||
}
|
||||
|
||||
let house = VillageHouse.create(for: password, on: device, environment: self.environment)
|
||||
let house = VillageHouse.create(password: password,
|
||||
keeper: Self.keychainKeeper,
|
||||
on: device,
|
||||
environment: self.environment)
|
||||
self.houses.append(house)
|
||||
|
||||
return house
|
||||
@@ -117,31 +113,24 @@ final public class Village {
|
||||
|
||||
public static func reset() {
|
||||
VillageHouse.removeAllVillagers()
|
||||
Self.password = nil
|
||||
Self.keychainKeeper.reset()
|
||||
}
|
||||
|
||||
public static var isPasswordExists: Bool { WalletKeychain.instance.exist(Constants.passwordKey) }
|
||||
// Keychain static methods
|
||||
|
||||
public static func getPassword(password: String?) -> String? {
|
||||
if let password {
|
||||
return WalletKeychain.instance[Constants.passwordKey, password: password]
|
||||
} else {
|
||||
return WalletKeychain.instance[biometric: Constants.passwordKey]
|
||||
}
|
||||
public static func accept(password: String) -> Bool {
|
||||
Self.keychainKeeper.accept(password: password)
|
||||
}
|
||||
|
||||
// TODO: Remove this function after refactoring passwords
|
||||
public static func updateCommonPassword(password: String, old: String) throws {
|
||||
if WalletKeychain.instance[Constants.passwordKey, password: old] != nil {
|
||||
Self.password = password
|
||||
} else {
|
||||
throw VillageError.passwordNotMatch
|
||||
}
|
||||
public static func passwordViaBiometrics() -> String? {
|
||||
Self.keychainKeeper.passwordByBiometrics()
|
||||
}
|
||||
|
||||
private static var password: String? {
|
||||
get { WalletKeychain.instance[biometric: Constants.passwordKey] }
|
||||
set { WalletKeychain.instance[Constants.passwordKey, password: newValue ?? ""] = newValue }
|
||||
public static var isPasswordExists: Bool {
|
||||
Self.keychainKeeper.isPasswordExists
|
||||
}
|
||||
|
||||
public static func set(password: String, old: String? = nil) throws {
|
||||
try Self.keychainKeeper.update(password: password, old: old)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
//
|
||||
// PrivacyKeeper.swift
|
||||
//
|
||||
//
|
||||
// Created by Nut.Tech on 07.02.2023.
|
||||
//
|
||||
|
||||
import WalletFoundation
|
||||
import Foundation
|
||||
import CryptoSwift
|
||||
|
||||
final class PrivacyKeeper {
|
||||
private typealias CipherData = (key: String, iv: String)
|
||||
private enum Constants {
|
||||
static let oldPasswordKey = CommonKey("PrivacyKeeper.password")
|
||||
static let passwordKey = "PrivacyKeeper.password"
|
||||
static let cipherKey = "PrivacyKeeper.ckey"
|
||||
static let ivKey = "PrivacyKeeper.ivkey"
|
||||
}
|
||||
|
||||
var isPasswordExists: Bool {
|
||||
if let encryptedPasswordKey {
|
||||
return self.keychain.exist(encryptedPasswordKey)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private var encryptedPasswordKey: CommonKey?
|
||||
private var cipherData: CipherData
|
||||
private var keychain: KeychainProtocol
|
||||
|
||||
init(keychain: KeychainProtocol) {
|
||||
self.keychain = keychain
|
||||
self.cipherData = Self.initCipherData()
|
||||
self.encryptedPasswordKey = Self.makeEncryptedPasswordCommonKey(cipherData: self.cipherData)
|
||||
}
|
||||
|
||||
/// Cheks if password correct
|
||||
func accept(password: String) -> Bool {
|
||||
self.getPassword(password) == password
|
||||
}
|
||||
|
||||
/// Get password via biometry
|
||||
func passwordByBiometrics() -> String? {
|
||||
guard let encryptedPasswordKey else { return nil }
|
||||
return self.keychain.bioExist(encryptedPasswordKey) ?
|
||||
self.keychain[biometric: encryptedPasswordKey] : nil
|
||||
}
|
||||
|
||||
/// Get private key from keychain
|
||||
/// - Parameters:
|
||||
/// - key: common key in keychain
|
||||
/// - password: current password
|
||||
/// - Returns: private key (if exists)
|
||||
func privateKey(for key: String, password: String?) -> String? {
|
||||
let pwd = password ?? self.passwordByBiometrics()
|
||||
if let pwd {
|
||||
return self.keychain[self.encryptPK(commonKey: key), password: pwd]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Update password to new one.
|
||||
func update(password: String, old: String? = nil) throws {
|
||||
guard self.isPasswordExists else {
|
||||
try self.setPassword(password)
|
||||
return
|
||||
}
|
||||
guard let old, self.accept(password: old) else {
|
||||
throw WalletError.passwordNotMatch
|
||||
}
|
||||
|
||||
try self.setPassword(password)
|
||||
}
|
||||
|
||||
/// Update private key for current account. If you are using biometrics, pass nil in password.
|
||||
/// - Parameters:
|
||||
/// - privateKey: new private key
|
||||
/// - key: common key in keychain
|
||||
/// - password: current password
|
||||
func update(privateKey: String?, for key: String, password: String?) {
|
||||
let pwd = password ?? self.passwordByBiometrics()
|
||||
guard let pwd, self.accept(password: pwd) else { return }
|
||||
self.keychain[self.encryptPK(commonKey: key), password: pwd] = privateKey
|
||||
}
|
||||
|
||||
/// Migrates pasword and private key storage in keychain to new style
|
||||
/// - Parameters:
|
||||
/// - oldKey: old private key common key for keychain
|
||||
/// - newKey: new private key common key for keychain
|
||||
/// - password: current password
|
||||
func migrate(oldKey: CommonKey, newKey: CommonKey, password: String) {
|
||||
self.migratePassword(password: password)
|
||||
guard self.accept(password: password) else { return }
|
||||
self.migratePrivateKey(oldKey: oldKey, newKey: newKey, password: password)
|
||||
self.migrateToEncrypted(commonKey: newKey, password: password)
|
||||
}
|
||||
|
||||
/// Resets all data by making all old data inaccessible
|
||||
func reset() {
|
||||
self.cipherData = Self.createCipherData()
|
||||
self.encryptedPasswordKey = Self.makeEncryptedPasswordCommonKey(cipherData: self.cipherData)
|
||||
}
|
||||
|
||||
/// Gets password if exists. Pass nil to get biometrics password
|
||||
/// - Parameter password: current password
|
||||
/// - Returns: password string (if exists)
|
||||
private func getPassword(_ password: String? = nil) -> String? {
|
||||
if let password {
|
||||
guard let encryptedPasswordKey else { return nil }
|
||||
return self.keychain[encryptedPasswordKey, password: password]
|
||||
} else {
|
||||
return self.passwordByBiometrics()
|
||||
}
|
||||
}
|
||||
|
||||
/// Set password
|
||||
/// - Parameter password: new password
|
||||
private func setPassword(_ password: String) throws {
|
||||
guard let encryptedPasswordKey else {
|
||||
throw WalletError.passwordKeyNotExist
|
||||
}
|
||||
self.keychain[encryptedPasswordKey, password: password] = password
|
||||
}
|
||||
|
||||
/// Migrate private key in keychain from old to new for old versions
|
||||
/// - Parameters:
|
||||
/// - oldKey: old-style key
|
||||
/// - newKey: new-style key
|
||||
/// - password: current password
|
||||
private func migratePrivateKey(oldKey: CommonKey, newKey: CommonKey, password: String) {
|
||||
guard let privateKey = self.keychain[oldKey, password: password] else { return }
|
||||
self.keychain[oldKey, password: password] = nil
|
||||
self.keychain[newKey, password: password] = privateKey
|
||||
}
|
||||
|
||||
/// Migrate to encrypted keys for keychain
|
||||
/// - Parameters:
|
||||
/// - commonKey: old-style common key, without encryption
|
||||
/// - password: current password
|
||||
private func migrateToEncrypted(commonKey: CommonKey, password: String) {
|
||||
guard let privateKey = self.keychain[commonKey, password: password] else { return }
|
||||
self.keychain[commonKey, password: password] = nil
|
||||
self.keychain[self.encryptPK(commonKey: commonKey.rawValue), password: password] = privateKey
|
||||
}
|
||||
|
||||
/// Migrating from an old keychain password key to a new one
|
||||
/// - Parameter password: current password
|
||||
private func migratePassword(password: String) {
|
||||
guard self.keychain[Constants.oldPasswordKey, password: password] != nil,
|
||||
let encryptedPasswordKey else { return }
|
||||
self.keychain[Constants.oldPasswordKey, password: password] = nil
|
||||
self.keychain[encryptedPasswordKey, password: password] = password
|
||||
}
|
||||
|
||||
/// Makes encrypted password key for keychain
|
||||
private static func makeEncryptedPasswordCommonKey(cipherData: CipherData) -> CommonKey? {
|
||||
guard let encryptedCKeyString = try? Self.aesEncrypt(string: Constants.passwordKey,
|
||||
key: cipherData.key,
|
||||
iv: cipherData.iv)
|
||||
else { return nil }
|
||||
return CommonKey(encryptedCKeyString)
|
||||
}
|
||||
|
||||
/// Encrypt key for use in keychain
|
||||
/// - Parameter commonKey: common key, e.g. "user@wallet.privatekey"
|
||||
/// - Returns: encrypted CommonKey object
|
||||
private func encryptPK(commonKey: String) -> CommonKey {
|
||||
let encryptedCKeyString = try? Self.aesEncrypt(string: commonKey,
|
||||
key: self.cipherData.key,
|
||||
iv: self.cipherData.iv)
|
||||
return CommonKey(encryptedCKeyString ?? commonKey)
|
||||
}
|
||||
|
||||
/// Generates data for encoding keys
|
||||
private static func makeCipherData() -> CipherData {
|
||||
let ckey = UUID().uuidString.replacingOccurrences(of:"-",
|
||||
with: "",
|
||||
options: .literal)
|
||||
let iv = String(UUID().uuidString.replacingOccurrences(of:"-",
|
||||
with: "",
|
||||
options: .literal)
|
||||
.dropLast(16))
|
||||
return CipherData(key: ckey, iv: iv)
|
||||
}
|
||||
|
||||
/// Tries to load cipher data and creates it, if doesn't exist
|
||||
private static func initCipherData() -> CipherData {
|
||||
if let ckey = UserDefaults.standard.string(forKey: Constants.cipherKey),
|
||||
let iv = UserDefaults.standard.string(forKey: Constants.ivKey) {
|
||||
return CipherData(key: ckey, iv: iv)
|
||||
} else {
|
||||
return Self.createCipherData()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new data for encoding keychain keys and saves it to defaults
|
||||
private static func createCipherData() -> CipherData {
|
||||
let cipherData = Self.makeCipherData()
|
||||
UserDefaults.standard.set(cipherData.key, forKey: Constants.cipherKey)
|
||||
UserDefaults.standard.set(cipherData.iv, forKey: Constants.ivKey)
|
||||
return cipherData
|
||||
}
|
||||
|
||||
static func aesEncrypt(string: String, key: String, iv: String) throws -> String {
|
||||
guard let data = string.data(using: .utf8) else {
|
||||
throw WalletError.cannotEncryptNonUTF8Data
|
||||
}
|
||||
let encrypted = try AES(key: Array(key.utf8),
|
||||
blockMode: CBC(iv: Array(iv.utf8)))
|
||||
.encrypt([UInt8](data))
|
||||
let encryptedData = Data(encrypted)
|
||||
return encryptedData.base64EncodedString()
|
||||
}
|
||||
|
||||
static func aesDecrypt(string: String, key: String, iv: String) throws -> String {
|
||||
guard let data = Data(base64Encoded: string) else {
|
||||
throw WalletError.cannotDecryptNonUTF8Data
|
||||
}
|
||||
let decrypted = try AES(key: Array(key.utf8),
|
||||
blockMode: CBC(iv: Array(iv.utf8)))
|
||||
.decrypt([UInt8](data))
|
||||
let decryptedData = Data(decrypted)
|
||||
guard let decryptedString = String(bytes: decryptedData.bytes, encoding: .utf8) else {
|
||||
throw WalletError.cannotDecryptData
|
||||
}
|
||||
return decryptedString
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import WalletFoundation
|
||||
import WalletNetwork
|
||||
|
||||
final class VillageHouse: Bank {
|
||||
|
||||
private enum Constants {
|
||||
static let oldKey = "Account.Service.collection"
|
||||
static let key = "Wallet.bank.service"
|
||||
@@ -24,10 +23,9 @@ final class VillageHouse: Bank {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private var password: String
|
||||
private let device: Device
|
||||
private let environment: NetworkEnvironment
|
||||
|
||||
private let keychainKeeper: PrivacyKeeper
|
||||
private let activeSubject: CurrentValueSubject<Villager?, Never>
|
||||
private let villagersSubject: CurrentValueSubject<[Villager], Never>
|
||||
|
||||
@@ -35,16 +33,17 @@ final class VillageHouse: Bank {
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
private init(password: String, device: Device, environment: NetworkEnvironment) {
|
||||
private init(password: String, keychain: PrivacyKeeper, device: Device, environment: NetworkEnvironment) {
|
||||
|
||||
self.password = password
|
||||
self.device = device
|
||||
self.environment = environment
|
||||
self.keychainKeeper = keychain
|
||||
|
||||
let collection = Self.restore()
|
||||
let collection = Self.restore(keychain: keychain)
|
||||
Self.migrateIfNeeded(collection: collection, password: password)
|
||||
self.villagersSubject = CurrentValueSubject(collection)
|
||||
|
||||
let active = Self.active(in: collection)
|
||||
let active = Self.active(in: collection, keychain: keychain)
|
||||
self.activeSubject = CurrentValueSubject(active)
|
||||
|
||||
self.villagersSubject
|
||||
@@ -52,8 +51,6 @@ final class VillageHouse: Bank {
|
||||
self?.save(villagers: villagers)
|
||||
}
|
||||
.store(in: &self.cancellables)
|
||||
|
||||
self.migrate(using: self.password)
|
||||
}
|
||||
|
||||
// MARK: - Bank
|
||||
@@ -65,8 +62,6 @@ final class VillageHouse: Bank {
|
||||
lazy var activePublisher: AnyPublisher<Wallet?, Never> = self.activeSubject.map { $0 }.eraseToAnyPublisher()
|
||||
|
||||
lazy var walletsPublisher: AnyPublisher<[Wallet], Never> = self.villagersSubject.map { $0 }.eraseToAnyPublisher()
|
||||
|
||||
func accept(password: String) -> Bool { self.password == password }
|
||||
|
||||
func remove(wallet: Wallet) throws {
|
||||
|
||||
@@ -107,31 +102,29 @@ final class VillageHouse: Bank {
|
||||
self.activeSubject.value = villager
|
||||
}
|
||||
|
||||
func add(using walletCase: WalletCase) async throws -> Wallet {
|
||||
let villager = try await Villager.create(walletCase: walletCase, on: self.device, using: self.environment)
|
||||
villager.updatePrivateKey(using: self.password)
|
||||
|
||||
let villagers = self.villagersSubject.value
|
||||
self.villagersSubject.value = villagers + [villager]
|
||||
|
||||
func add(using walletCase: WalletCase, password: String) async throws -> Wallet {
|
||||
let villager = try await Villager.create(walletCase: walletCase,
|
||||
on: self.device,
|
||||
using: self.environment,
|
||||
keychain: self.keychainKeeper)
|
||||
self.add(wallets: [villager], password: password)
|
||||
return villager
|
||||
}
|
||||
|
||||
func add(using purses: [Purse]) throws {
|
||||
func add(using purses: [Purse], password: String) throws {
|
||||
guard self.keychainKeeper.accept(password: password) else {
|
||||
throw WalletError.passwordNotMatch
|
||||
}
|
||||
|
||||
let wallets = purses
|
||||
.filter { $0.bank?.isEquals(other: self) ?? false }
|
||||
.filter { $0.bank?.isEquals(other: self, password: password) ?? false }
|
||||
.filter { purse in
|
||||
!self.wallets.contains(where: { $0.name == purse.name && $0.keyType.rawValue == purse.permission.permName })
|
||||
}
|
||||
.map { Villager.create(purse: $0) }
|
||||
.map { Villager.create(purse: $0,
|
||||
keeper: self.keychainKeeper) }
|
||||
|
||||
wallets
|
||||
.filter { $0.key.privateKey.isExist }
|
||||
.forEach { $0.updatePrivateKey(using: self.password) }
|
||||
|
||||
let villagers = self.villagersSubject.value
|
||||
self.villagersSubject.value = villagers + wallets
|
||||
self.add(wallets: wallets, password: password)
|
||||
}
|
||||
|
||||
func restore(using keys: WalletKey) async throws -> PurseHolder {
|
||||
@@ -176,22 +169,21 @@ final class VillageHouse: Bank {
|
||||
}
|
||||
}
|
||||
|
||||
func isEquals(other: Bank) -> Bool {
|
||||
guard let house = other as? VillageHouse else { return false }
|
||||
return self.password == house.password &&
|
||||
self.device.isEquals(other: house.device) &&
|
||||
self.environment.isEquals(other: house.environment)
|
||||
func accept(password: String) -> Bool {
|
||||
self.keychainKeeper.accept(password: password)
|
||||
}
|
||||
|
||||
func switchPassword(_ password: String, old: String) throws {
|
||||
|
||||
guard self.accept(password: old) else {
|
||||
throw BankError.passwordNotMatch
|
||||
}
|
||||
|
||||
self.password = password
|
||||
|
||||
old >>- { old in self.villagersSubject.value.forEach { $0.update(password: password, old: old) } }
|
||||
func isEquals(other: Bank, password: String) -> Bool {
|
||||
guard let house = other as? VillageHouse else { return false }
|
||||
return self.device.isEquals(other: house.device) &&
|
||||
self.environment.isEquals(other: house.environment) &&
|
||||
self.keychainKeeper.accept(password: password) &&
|
||||
other.accept(password: password)
|
||||
}
|
||||
|
||||
func switchPassword(_ new: String, old: String) throws {
|
||||
try self.keychainKeeper.update(password: new, old: old)
|
||||
self.wallets.forEach { $0.updatePrivateKeyEncryption(password: new, old: old) }
|
||||
}
|
||||
|
||||
func update(_ keyUpdates: [WalletKeyUpdate], using password: String) async throws -> [WalletKeyUpdateResult] {
|
||||
@@ -216,32 +208,48 @@ final class VillageHouse: Bank {
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
func isEquals(password: String, device: Device, environment: NetworkEnvironment) -> Bool {
|
||||
return self.password == password &&
|
||||
self.device.isEquals(other: device) &&
|
||||
self.environment.isEquals(other: environment)
|
||||
func isEquals(device: Device, environment: NetworkEnvironment, password: String) -> Bool {
|
||||
return self.device.isEquals(other: device) &&
|
||||
self.environment.isEquals(other: environment) &&
|
||||
self.accept(password: password)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func add(wallets: [Villager], password: String) {
|
||||
wallets
|
||||
.filter { $0.key.privateKey.isExist }
|
||||
.forEach { $0.updateKeychain(key: $0.key, using: password) }
|
||||
|
||||
let villagers = self.villagersSubject.value
|
||||
self.villagersSubject.value = villagers + wallets
|
||||
}
|
||||
|
||||
private func save(villagers: [Villager]) {
|
||||
Settings.shared[Constants.Keys.collection] = villagers.jsonData()
|
||||
}
|
||||
|
||||
private static func restore() -> [Villager] {
|
||||
private static func restore(keychain: PrivacyKeeper) -> [Villager] {
|
||||
if let data: Data = Settings.shared[Constants.Keys.collection],
|
||||
let villagers: [Villager] = data.jsonDecoded(type: Villager.self) {
|
||||
let userInfoKey = Villager.keychainUserInfoKey,
|
||||
let villagers: [Villager] = data.jsonDecoded(type: Villager.self,
|
||||
userInfo: [userInfoKey: keychain]) {
|
||||
return villagers
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
private func migrate(using password: String) {
|
||||
private static func migrateIfNeeded(collection: [Villager], password: String) {
|
||||
guard collection.count > 0 else { return }
|
||||
collection.forEach { $0.migrate(password: password) }
|
||||
}
|
||||
|
||||
private func migrate() {
|
||||
|
||||
let migrateWallets: [Villager]
|
||||
if let data = UserDefaults.standard.value(forKey: Constants.oldKey) as? Data,
|
||||
let collection = try? JSONDecoder().decode([Villager.OldVillager].self, from: data) {
|
||||
migrateWallets = collection.compactMap { $0.covert(password: password) }
|
||||
migrateWallets = collection.compactMap { $0.covert(keeper: self.keychainKeeper) }
|
||||
} else {
|
||||
migrateWallets = []
|
||||
}
|
||||
@@ -263,14 +271,20 @@ final class VillageHouse: Bank {
|
||||
|
||||
// MARK: - Static
|
||||
|
||||
static func create(for password: String, on device: Device, environment: NetworkEnvironment) -> VillageHouse {
|
||||
VillageHouse(password: password, device: device, environment: environment)
|
||||
public static func create(password: String, keeper: PrivacyKeeper, on device: Device, environment: NetworkEnvironment) -> VillageHouse {
|
||||
VillageHouse(password: password, keychain: keeper, device: device, environment: environment)
|
||||
}
|
||||
|
||||
private static func active(in collection: [Villager]) -> Villager? {
|
||||
public static func removeAllVillagers() {
|
||||
Settings.shared[Constants.Keys.collection] = Data()
|
||||
}
|
||||
|
||||
private static func active(in collection: [Villager], keychain: PrivacyKeeper) -> Villager? {
|
||||
|
||||
if let data: Data = Settings.shared[Constants.Keys.current],
|
||||
let villager: Villager = data.jsonDecoded(type: Villager.self),
|
||||
let userInfoKey = Villager.keychainUserInfoKey,
|
||||
let villager: Villager = data.jsonDecoded(type: Villager.self,
|
||||
userInfo: [userInfoKey: keychain]),
|
||||
collection.contains(where: { $0 == villager }) {
|
||||
return villager
|
||||
}
|
||||
@@ -283,8 +297,4 @@ final class VillageHouse: Bank {
|
||||
|
||||
return collection.first(where: { $0.name == name && $0.keyType == keyType })
|
||||
}
|
||||
|
||||
static func removeAllVillagers() {
|
||||
Settings.shared[Constants.Keys.collection] = Data()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,17 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
|
||||
var publicKey: String
|
||||
var keyType: String
|
||||
|
||||
func covert(password: String) -> Villager? {
|
||||
func covert(keeper: PrivacyKeeper) -> Villager? {
|
||||
guard let keyType = WalletKeyType(rawValue: self.keyType),
|
||||
let key = try? WalletKey.restore(using: self.username, type: keyType, publicKey: self.publicKey, password: password) else {
|
||||
let key = try? WalletKey.restore(using: self.username, type: keyType, publicKey: self.publicKey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Villager(name: self.username, key: key, keyType: keyType, state: .accepted)
|
||||
return Villager(name: self.username,
|
||||
key: key,
|
||||
keyType: keyType,
|
||||
state: .accepted,
|
||||
keychain: keeper)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,17 +39,26 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
private init(walletCase: WalletCase, keyType: WalletKeyType, state: WalletState) {
|
||||
private init(walletCase: WalletCase,
|
||||
keyType: WalletKeyType,
|
||||
state: WalletState,
|
||||
keychain: PrivacyKeeper) {
|
||||
self.name = walletCase.name
|
||||
self.key = walletCase.key
|
||||
self.keyType = keyType
|
||||
self.keychain = keychain
|
||||
self.stateSubject = CurrentValueSubject<WalletState, Never>(state)
|
||||
}
|
||||
|
||||
private init(name: String, key: WalletKey, keyType: WalletKeyType, state: WalletState) {
|
||||
private init(name: String,
|
||||
key: WalletKey,
|
||||
keyType: WalletKeyType,
|
||||
state: WalletState,
|
||||
keychain: PrivacyKeeper) {
|
||||
self.name = name
|
||||
self.key = key
|
||||
self.keyType = keyType
|
||||
self.keychain = keychain
|
||||
self.stateSubject = CurrentValueSubject<WalletState, Never>(state)
|
||||
}
|
||||
|
||||
@@ -62,6 +75,10 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
|
||||
case state
|
||||
}
|
||||
|
||||
static var keychainUserInfoKey: CodingUserInfoKey? {
|
||||
return CodingUserInfoKey(rawValue: "keychain")
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
@@ -77,6 +94,11 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
|
||||
|
||||
let state = try container.decode(WalletState.self, forKey: .state)
|
||||
self.stateSubject = CurrentValueSubject<WalletState, Never>(state)
|
||||
guard let keychainKey = Self.keychainUserInfoKey,
|
||||
let keeper = decoder.userInfo[keychainKey] as? PrivacyKeeper else {
|
||||
throw WalletError.decodingWithoutKeychainContext
|
||||
}
|
||||
self.keychain = keeper
|
||||
|
||||
// If first version we hold key on flat structure!
|
||||
if let key = try? WalletKey(from: decoder) {
|
||||
@@ -84,6 +106,7 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
|
||||
} else {
|
||||
self.key = try container.decode(WalletKey.self, forKey: .key)
|
||||
}
|
||||
|
||||
// TODO: - NEED COMPLETETASK
|
||||
// self.key = try WalletKey.restore(using: self.name, type: self.keyType, publicKey: publicKey, password: "")
|
||||
}
|
||||
@@ -102,6 +125,7 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
|
||||
private(set) var key: WalletKey
|
||||
let keyType: WalletKeyType
|
||||
var state: WalletState { self.stateSubject.value }
|
||||
private let keychain: PrivacyKeeper
|
||||
|
||||
lazy var statePublisher: AnyPublisher<WalletState, Never> = self.stateSubject.eraseToAnyPublisher()
|
||||
|
||||
@@ -143,52 +167,65 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
|
||||
self.stateSubject.value = state
|
||||
}
|
||||
|
||||
func update(key: WalletKey, using passrod: String) -> Wallet {
|
||||
self.key = key
|
||||
self.updatePrivateKey(using: passrod)
|
||||
@discardableResult
|
||||
func updateKeychain(key: WalletKey, using password: String) -> Wallet {
|
||||
if self.keychain.accept(password: password) {
|
||||
self.key = key
|
||||
self.updatePrivateKey(password: password)
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
func privateKey(_ value: String?) -> String? {
|
||||
let privateKey: String?
|
||||
if let password = value {
|
||||
privateKey = WalletKeychain.instance[.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey), password: password]
|
||||
} else {
|
||||
privateKey = WalletKeychain.instance[biometric: .key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)]
|
||||
}
|
||||
return privateKey
|
||||
func privateKey(password: String?) -> String? {
|
||||
guard let password, self.keychain.accept(password: password) else { return nil }
|
||||
let key = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
|
||||
return self.keychain.privateKey(for: key.rawValue, password: password)
|
||||
}
|
||||
|
||||
func updatePrivateKey(using password: String) {
|
||||
private func updatePrivateKey(password: String) {
|
||||
guard let privateKey = self.key.privateKey else { return }
|
||||
WalletKeychain.instance[.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey),
|
||||
password: password] = privateKey
|
||||
let key = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
|
||||
self.keychain.update(privateKey: privateKey, for: key.rawValue, password: password)
|
||||
}
|
||||
|
||||
// TODO: - Need review
|
||||
|
||||
func migrate(password: String) {
|
||||
let privateKey = WalletKeychain.instance[.key(self.name, suffix: .privateKey), password: password]
|
||||
WalletKeychain.instance[.key(self.name, suffix: .privateKey), password: ""] = nil
|
||||
WalletKeychain.instance[.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey), password: password] = privateKey
|
||||
let oldKey = CommonKey.key(self.name, suffix: .privateKey)
|
||||
let newKey = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
|
||||
self.keychain.migrate(oldKey: oldKey, newKey: newKey, password: password)
|
||||
}
|
||||
|
||||
func update(password: String, old: String) {
|
||||
WalletKeychain.instance[.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey),
|
||||
password: password] = self.privateKey(old)
|
||||
/// Updates storage of private key in keychain for new password
|
||||
/// - Parameters:
|
||||
/// - password: new password
|
||||
/// - old: old password (which was stored with private key earlier)
|
||||
func updatePrivateKeyEncryption(password: String, old: String) {
|
||||
let key = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
|
||||
let privateKey = self.privateKey(password: old)
|
||||
self.keychain.update(privateKey: nil, for: key.rawValue, password: old)
|
||||
self.keychain.update(privateKey: privateKey, for: key.rawValue, password: password)
|
||||
}
|
||||
|
||||
func clear() {
|
||||
WalletKeychain.instance[.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey), password: ""] = nil
|
||||
let key = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
|
||||
self.keychain.update(privateKey: nil, for: key.rawValue, password: "")
|
||||
}
|
||||
|
||||
// MARK: - Static
|
||||
|
||||
static func create(purse: Purse) -> Villager {
|
||||
Villager(name: purse.name, key: purse.key, keyType: purse.keyType, state: .accepted)
|
||||
static func create(purse: Purse, keeper: PrivacyKeeper) -> Villager {
|
||||
Villager(name: purse.name,
|
||||
key: purse.key,
|
||||
keyType: purse.keyType,
|
||||
state: .accepted,
|
||||
keychain: keeper)
|
||||
}
|
||||
|
||||
static func create(walletCase: WalletCase, on device: Device, using environment: NetworkEnvironment) async throws -> Villager {
|
||||
static func create(walletCase: WalletCase,
|
||||
on device: Device,
|
||||
using environment: NetworkEnvironment,
|
||||
keychain: PrivacyKeeper) async throws -> Villager {
|
||||
|
||||
let service = AccountService(environment: environment)
|
||||
do {
|
||||
@@ -205,12 +242,18 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
|
||||
case .completed: state = .accepted
|
||||
}
|
||||
|
||||
return Villager(walletCase: walletCase, keyType: .owner, state: state)
|
||||
return Villager(walletCase: walletCase,
|
||||
keyType: .owner,
|
||||
state: state,
|
||||
keychain: keychain)
|
||||
|
||||
} catch let NetworkServiceError.gqlApplication(error) {
|
||||
|
||||
if ApplicationSettings.ignoreCreateAccountSatus {
|
||||
return Villager(walletCase: walletCase, keyType: .owner, state: .creating("HELLO"))
|
||||
return Villager(walletCase: walletCase,
|
||||
keyType: .owner,
|
||||
state: .creating("HELLO"),
|
||||
keychain: keychain)
|
||||
}
|
||||
|
||||
let walletError: WalletError
|
||||
|
||||
@@ -12,7 +12,6 @@ import WalletNetwork
|
||||
public enum BankError: Error {
|
||||
case empty
|
||||
case notOwned
|
||||
case passwordNotMatch
|
||||
}
|
||||
|
||||
/// Bank that contain wallets.
|
||||
@@ -25,14 +24,12 @@ public protocol Bank: AnyObject {
|
||||
var wallets: [Wallet] { get }
|
||||
/// Publisher for observe wallet changes.
|
||||
var walletsPublisher: AnyPublisher<[Wallet], Never> { get }
|
||||
/// Check password
|
||||
func accept(password: String) -> Bool
|
||||
/// Activate wallet
|
||||
func activate(wallet: Wallet?) throws
|
||||
/// Create new wallet in bank.
|
||||
func add(using walletCase: WalletCase) async throws -> Wallet
|
||||
func add(using walletCase: WalletCase, password: String) async throws -> Wallet
|
||||
/// Add Purse to bank with converting to Wallet
|
||||
func add(using purses: [Purse]) throws
|
||||
func add(using purses: [Purse], password: String) throws
|
||||
/// Restore wallet by key.
|
||||
func restore(using keys: WalletKey) async throws -> PurseHolder
|
||||
/// Remove wallet from bank.
|
||||
@@ -42,7 +39,9 @@ public protocol Bank: AnyObject {
|
||||
/// Update WalletKey for Wallet
|
||||
func update(_ keyUpdates: [WalletKeyUpdate], using password: String) async throws -> [WalletKeyUpdateResult]
|
||||
/// Compare two banks
|
||||
func isEquals(other: Bank) -> Bool
|
||||
func isEquals(other: Bank, password: String) -> Bool
|
||||
/// Switch password
|
||||
func switchPassword(_ password: String, old: String) throws
|
||||
/// Check if password valid for this bank
|
||||
func accept(password: String) -> Bool
|
||||
}
|
||||
|
||||
@@ -33,6 +33,18 @@ public enum WalletError: Error {
|
||||
case privateKeyNotExists
|
||||
/// Wallet with such name already exists.
|
||||
case exists
|
||||
/// Input password doesn't match keychain password
|
||||
case passwordNotMatch
|
||||
/// Error creating access key for password in keychain
|
||||
case passwordKeyNotExist
|
||||
/// Failed encryption access key for keychain
|
||||
case cannotEncryptNonUTF8Data
|
||||
/// Failed decryption access key for keychain
|
||||
case cannotDecryptNonUTF8Data
|
||||
/// Common decrypt error
|
||||
case cannotDecryptData
|
||||
/// Trying to decode Wallet-protocol object without keychain data
|
||||
case decodingWithoutKeychainContext
|
||||
}
|
||||
|
||||
/// Wallet state
|
||||
@@ -127,12 +139,13 @@ public protocol Wallet: AnyObject {
|
||||
var keyType: WalletKeyType { get }
|
||||
/// State
|
||||
var state: WalletState { get }
|
||||
|
||||
var statePublisher: AnyPublisher<WalletState, Never> { get }
|
||||
|
||||
@discardableResult
|
||||
func update(key: WalletKey, using passrod: String) -> Wallet
|
||||
|
||||
func privateKey(_ value: String?) -> String?
|
||||
func updateKeychain(key: WalletKey, using password: String) -> Wallet
|
||||
func privateKey(password: String?) -> String?
|
||||
func updatePrivateKeyEncryption(password: String, old: String)
|
||||
|
||||
/// Compare two Wallets
|
||||
func isEquals(other: Wallet) -> Bool
|
||||
|
||||
@@ -135,7 +135,7 @@ public enum WalletKey: Codable, Equatable {
|
||||
|
||||
// MARK: - Internal static
|
||||
|
||||
static func restore(using name: String, type: WalletKeyType, publicKey: String, password: String) throws -> WalletKey {
|
||||
static func restore(using name: String, type: WalletKeyType, publicKey: String) throws -> WalletKey {
|
||||
|
||||
throw WalletKeyError.unlock
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// JSONParsingTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Nut.Tech on 17.02.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import WalletKit
|
||||
|
||||
fileprivate struct User: Decodable {
|
||||
let id: Int
|
||||
let name: String
|
||||
var employer: String?
|
||||
static var employerUserInfoKey: CodingUserInfoKey? {
|
||||
return CodingUserInfoKey(rawValue: "employer")
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case name
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.id = try keyedContainer.decode(Int.self, forKey: .id)
|
||||
self.name = try keyedContainer.decode(String.self, forKey: .name)
|
||||
|
||||
guard let employerKey = Self.employerUserInfoKey,
|
||||
let employer = decoder.userInfo[employerKey] as? String else {
|
||||
return
|
||||
}
|
||||
self.employer = employer
|
||||
}
|
||||
}
|
||||
|
||||
final class JSONParsingTests: XCTestCase {
|
||||
|
||||
private enum Locals {
|
||||
static let username1 = "Username1"
|
||||
static let username2 = "Username2"
|
||||
static let userId1 = 1
|
||||
static let userId2 = 2
|
||||
static let companyName = "Company Name"
|
||||
}
|
||||
|
||||
func testJsonDecoded() {
|
||||
let jsonData = """
|
||||
{"id": \(Locals.userId1), "name": "\(Locals.username1)"}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
guard let user: User = jsonData.jsonDecoded(type: User.self) else {
|
||||
XCTFail("Error decoding data")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(user.id, Locals.userId1)
|
||||
XCTAssertEqual(user.name, Locals.username1)
|
||||
}
|
||||
|
||||
func testJsonDecodedArray() {
|
||||
let jsonData = """
|
||||
[{"id": \(Locals.userId1), "name": "\(Locals.username1)"}, {"id": \(Locals.userId2), "name": "\(Locals.username2)"}]
|
||||
""".data(using: .utf8)!
|
||||
|
||||
guard let users: [User] = jsonData.jsonDecoded(type: User.self) else {
|
||||
XCTFail("Error decoding data")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(users.count, 2)
|
||||
XCTAssertEqual(users[0].id, Locals.userId1)
|
||||
XCTAssertEqual(users[0].name, Locals.username1)
|
||||
XCTAssertEqual(users[1].id, Locals.userId2)
|
||||
XCTAssertEqual(users[1].name, Locals.username2)
|
||||
}
|
||||
|
||||
func testJsonDecodedWithUserInfo() {
|
||||
let jsonData = """
|
||||
{"id": \(Locals.userId1), "name": "\(Locals.username1)"}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
guard let key = CodingUserInfoKey(rawValue: "employer") else {
|
||||
XCTFail("Error creating user key")
|
||||
return
|
||||
}
|
||||
|
||||
let userInfo = [key: Locals.companyName]
|
||||
|
||||
guard let user: User = jsonData.jsonDecoded(type: User.self, userInfo: userInfo) else {
|
||||
XCTFail("Error decoding data")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(user.id, Locals.userId1)
|
||||
XCTAssertEqual(user.name, Locals.username1)
|
||||
XCTAssertEqual(user.employer, Locals.companyName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// KeychainMock.swift
|
||||
//
|
||||
//
|
||||
// Created by NUT.TECH on 16.02.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import WalletFoundation
|
||||
|
||||
final class KeychainMock: KeychainProtocol {
|
||||
|
||||
private enum KeychainMockLocals {
|
||||
static let password = "PWD"
|
||||
static let biometric = "BIO"
|
||||
}
|
||||
|
||||
typealias Value = (value: String, password: String)
|
||||
|
||||
private var storage: [String: Value]
|
||||
|
||||
init(storage: [String: Value]? = nil) {
|
||||
self.storage = storage ?? [:]
|
||||
}
|
||||
|
||||
func exist(_ key: Key) -> Bool {
|
||||
let commonKey = key.with(KeychainMockLocals.password)
|
||||
return self.storage[commonKey.rawValue].isExist
|
||||
}
|
||||
|
||||
func bioExist(_ key: Key) -> Bool {
|
||||
let commonKey = key.with(KeychainMockLocals.biometric)
|
||||
return self.storage[commonKey.rawValue] != nil
|
||||
}
|
||||
|
||||
subscript(biometric key: Key) -> String? {
|
||||
let commonKey = key.with(KeychainMockLocals.biometric).rawValue
|
||||
let object = self.storage[commonKey]
|
||||
return object?.value
|
||||
}
|
||||
|
||||
subscript(key: Key, password password: String) -> String? {
|
||||
get {
|
||||
let commonKey = key.with(KeychainMockLocals.password).rawValue
|
||||
let object = self.storage[commonKey]
|
||||
return object?.password == password ? object?.value : nil
|
||||
}
|
||||
set {
|
||||
self.update(key, password: password, newValue: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
private func update(_ key: Key, password: String, newValue: String?) {
|
||||
if let value = newValue {
|
||||
self.updateStorage(key: key.with(KeychainMockLocals.password), password: password, value: value)
|
||||
self.updateStorage(key: key.with(KeychainMockLocals.biometric), password: password, value: value)
|
||||
} else {
|
||||
self.updateStorage(key: key.with(KeychainMockLocals.password), password: password, value: nil)
|
||||
self.updateStorage(key: key.with(KeychainMockLocals.biometric), password: password, value: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateStorage(key: Key, password: String, value: String?) {
|
||||
let stringKey = key.rawValue
|
||||
if self.storage[stringKey] != nil, self.storage[stringKey]?.password == password {
|
||||
if let value {
|
||||
self.storage[stringKey]?.value = value
|
||||
} else {
|
||||
self.storage.removeValue(forKey: stringKey)
|
||||
}
|
||||
} else if let value {
|
||||
self.storage[stringKey] = Value(value: value, password: password)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// PrivacyKeeperTests.swift
|
||||
//
|
||||
//
|
||||
// Created by user on 16.02.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import WalletKit
|
||||
@testable import WalletFoundation
|
||||
|
||||
final class PrivacyKeeperTests: XCTestCase {
|
||||
|
||||
func testAES() throws {
|
||||
let commonKey = "PrivacyKeeper.password"
|
||||
let encryptedKey = "mdHBVh0lxLT8VBJnL9MYuM/pxWi/QmTCXmW8YZQO8Cs="
|
||||
let key = "96080AE5042241D9BFF30330C9625201"
|
||||
let iv = "7C515B5233314B42"
|
||||
do {
|
||||
let encryptedCommonKey = try PrivacyKeeper.aesEncrypt(string: commonKey, key: key, iv: iv)
|
||||
|
||||
let decryptedCommonKey = try PrivacyKeeper.aesDecrypt(string: encryptedCommonKey, key: key, iv: iv)
|
||||
|
||||
XCTAssertEqual(encryptedCommonKey, encryptedKey, "Incorrect encryption result")
|
||||
XCTAssertEqual(decryptedCommonKey, commonKey, "Incorrect decryption result")
|
||||
} catch {
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func testPassword() {
|
||||
let password = "0000"
|
||||
let keychainMock = KeychainMock()
|
||||
let keeper = PrivacyKeeper(keychain: keychainMock)
|
||||
do {
|
||||
XCTAssertFalse(keeper.isPasswordExists, "Incorrect isPasswordExists result")
|
||||
|
||||
try keeper.update(password: password)
|
||||
XCTAssertTrue(keeper.isPasswordExists, "Incorrect isPasswordExists result")
|
||||
XCTAssertTrue(keeper.accept(password: password), "Incorrect password")
|
||||
|
||||
let newPassword = "1111"
|
||||
try keeper.update(password: newPassword, old: password)
|
||||
XCTAssertTrue(keeper.accept(password: newPassword), "Incorrect password")
|
||||
|
||||
let incorrectPassword = "2222"
|
||||
XCTAssertFalse(keeper.accept(password: incorrectPassword), "Incorrect password check")
|
||||
|
||||
keeper.reset()
|
||||
XCTAssertFalse(keeper.accept(password: newPassword), "Incorrect password reset")
|
||||
XCTAssertFalse(keeper.isPasswordExists, "Incorrect isPasswordExists result")
|
||||
} catch {
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func testPrivateKey() {
|
||||
let password = "0000"
|
||||
let privateKey = "privateKey1111"
|
||||
let commonKey = "accountKey@malinka.privateKey"
|
||||
let keychainMock = KeychainMock()
|
||||
let keeper = PrivacyKeeper(keychain: keychainMock)
|
||||
|
||||
do {
|
||||
try keeper.update(password: password)
|
||||
XCTAssertTrue(keeper.accept(password: password), "Incorrect password")
|
||||
|
||||
keeper.update(privateKey: privateKey,
|
||||
for: commonKey,
|
||||
password: password)
|
||||
let readPrivateKey = keeper.privateKey(for: commonKey, password: password)
|
||||
XCTAssertNotNil(readPrivateKey, "Error setting private key to keychain")
|
||||
XCTAssertEqual(readPrivateKey, privateKey, "Incorrect private key stored in keychain")
|
||||
|
||||
keeper.reset()
|
||||
let nilPrivateKey = keeper.privateKey(for: commonKey, password: password)
|
||||
XCTAssertNil(nilPrivateKey, "Incorrect private key reset")
|
||||
} catch {
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func testMigration() {
|
||||
let password = "0000"
|
||||
let privateKey = "privateKey1111"
|
||||
let privateKey2 = "privateKey2222"
|
||||
let migratedCommonKey = "accountName@owner.privateKey"
|
||||
let keyToMigrate = CommonKey("accountKey2@malinka.privateKey")
|
||||
let oldKey = CommonKey.key("accountName", suffix: "privateKey")
|
||||
let newKey = CommonKey.key("accountName@owner", suffix: "privateKey")
|
||||
let storage = ["accountName.privateKey.PWD": KeychainMock.Value(value: privateKey, password: password),
|
||||
"PrivacyKeeper.password.PWD": KeychainMock.Value(value: password, password: password),
|
||||
"accountKey2@malinka.privateKey.PWD": KeychainMock.Value(value: privateKey2, password: password)]
|
||||
let keychainMock = KeychainMock(storage: storage)
|
||||
let keeper = PrivacyKeeper(keychain: keychainMock)
|
||||
keeper.migrate(oldKey: oldKey, newKey: newKey, password: password)
|
||||
XCTAssertNil(keeper.privateKey(for: oldKey.rawValue, password: password), "Incorrect private key")
|
||||
XCTAssertEqual(keeper.privateKey(for: migratedCommonKey, password: password), privateKey, "Incorrect private key")
|
||||
|
||||
keeper.migrate(oldKey: keyToMigrate, newKey: keyToMigrate, password: password)
|
||||
XCTAssertEqual(keeper.privateKey(for: keyToMigrate.rawValue, password: password), privateKey2, "Incorrect private key")
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,9 @@ final class AccountControllerManual: UIViewController {
|
||||
for (_, accounts) in accountsToAdd {
|
||||
|
||||
do {
|
||||
try Accounts().addAccounts(purses: accounts.map(\.purse))
|
||||
if let password = Account.Service.Authorize.shared.password {
|
||||
try Accounts().addAccounts(purses: accounts.map(\.purse), password: password)
|
||||
}
|
||||
Loader.hide(in: self)
|
||||
} catch {
|
||||
Loader.hide(in: self)
|
||||
|
||||
+7
-9
@@ -73,28 +73,26 @@ final class SettingsViewController: UIViewController {
|
||||
let ctrl = UINavigationController(rootViewController: accountControollerPin)
|
||||
accountControollerPin.type = .old
|
||||
accountControollerPin.viewModel.validatePin = { [weak ctrl, weak self] (pin: String) -> Bool in
|
||||
guard let password = Account.Service.getPassword(password: pin),
|
||||
!password.isEmpty, password == pin, password.count == 4 else {
|
||||
guard Account.Service.check(password: pin), !pin.isEmpty, pin.count == 4 else {
|
||||
Account.Service.Authorize.shared.savePinTry()
|
||||
return false
|
||||
}
|
||||
|
||||
ctrl?.dismiss(animated: false)
|
||||
Account.Service.Authorize.shared.update(password: password)
|
||||
Account.Service.Authorize.shared.update(password: pin)
|
||||
|
||||
let accountControollerPin = WalletPinController.instantiate()
|
||||
let ctrl = UINavigationController(rootViewController: accountControollerPin)
|
||||
accountControollerPin.type = .new
|
||||
accountControollerPin.viewModel.validatePin = { [weak ctrl] (pin: String) -> Bool in
|
||||
// TODO: It seems that there needs to place if Accounts().bank.accept(password:) instead of Account.Service.getPassword(password:), but compromise solution is to refactor it after 1.4.0
|
||||
if let oldPassword = Account.Service.getPassword(password: password) {
|
||||
accountControollerPin.viewModel.validatePin = { [weak ctrl] (newPin: String) -> Bool in
|
||||
if Account.Service.check(password: pin) {
|
||||
do {
|
||||
try Accounts().update(password: pin, old: oldPassword)
|
||||
try Accounts().update(password: newPin, old: pin)
|
||||
} catch {
|
||||
Alert.error(text: L10n.Account.Password.currentWrong)
|
||||
return false
|
||||
}
|
||||
Account.Service.Authorize.shared.update(password: pin)
|
||||
Account.Service.Authorize.shared.update(password: newPin)
|
||||
ctrl?.dismiss(animated: true)
|
||||
return true
|
||||
} else {
|
||||
@@ -143,7 +141,7 @@ final class SettingsViewController: UIViewController {
|
||||
let ctrl = UINavigationController(rootViewController: accountControollerPin)
|
||||
accountControollerPin.type = .new
|
||||
accountControollerPin.viewModel.validatePin = { [weak ctrl] (pin: String) -> Bool in
|
||||
if let oldPassword = Account.Service.getPassword(password: oldPassword) {
|
||||
if Account.Service.check(password: oldPassword) {
|
||||
do {
|
||||
try Accounts().update(password: pin, old: oldPassword)
|
||||
UserDefaults.standard.setValue(!UserDefaults.standard.bool(forKey: "isPinPwdMode"), forKey: "isPinPwdMode")
|
||||
|
||||
@@ -121,9 +121,9 @@ extension Account {
|
||||
// MARK: - Edit
|
||||
extension Account.Service {
|
||||
|
||||
func addAccounts(purses: [Purse]) throws {
|
||||
func addAccounts(purses: [Purse], password: String) throws {
|
||||
|
||||
try self.bank.add(using: purses)
|
||||
try self.bank.add(using: purses, password: password)
|
||||
|
||||
if let wallet = self.collection.last,
|
||||
let book = self.messageShelf.book(for: wallet) {
|
||||
@@ -150,7 +150,7 @@ extension Account.Service {
|
||||
$0.name == accountToChange.wallet.name && $0.keyType == accountToChange.wallet.keyType
|
||||
}) else { continue }
|
||||
|
||||
account.update(key: accountToChange.key, using: password)
|
||||
account.updateKeychain(key: accountToChange.key, using: password)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ extension Account.Service {
|
||||
for accountToChange in accounts {
|
||||
|
||||
guard let oldPrivateKey = Accounts().collection.first(where: { $0.name == accountToChange.wallet.name
|
||||
&& $0.keyType == accountToChange.keyType })?.privateKey(password) else { continue }
|
||||
&& $0.keyType == accountToChange.keyType })?.privateKey(password: password) else { continue }
|
||||
|
||||
guard let msgsService = try? CryptoChat.Service.MsgsHistory(username: accountToChange.wallet.name, encryptionKey: oldPrivateKey) else { continue }
|
||||
|
||||
@@ -215,10 +215,9 @@ extension Account.Service {
|
||||
var isAuthorized: Bool {
|
||||
if UserDefaults.standard.bool(forKey: "isPinPwdMode") {
|
||||
if let pin = Account.Service.Authorize.shared.password,
|
||||
let password = Self.getPassword(password: pin),
|
||||
!password.isEmpty,
|
||||
password == pin,
|
||||
password.count == 4 {
|
||||
Self.check(password: pin),
|
||||
!pin.isEmpty,
|
||||
pin.count == 4 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@@ -228,14 +227,13 @@ extension Account.Service {
|
||||
}
|
||||
}
|
||||
|
||||
static func getPassword(password: String?) -> String? { Village.getPassword(password: password) }
|
||||
static var isPasswordExists: Bool { Village.isPasswordExists }
|
||||
|
||||
static var isBiometricksEnabled: Bool {
|
||||
get { Settings.shared[isBiometricksEnabledKey] }
|
||||
set { Settings.shared[isBiometricksEnabledKey] = newValue }
|
||||
}
|
||||
|
||||
static var isPasswordExists: Bool { Village.isPasswordExists }
|
||||
static var isAccountMigrated: Bool {
|
||||
get { Settings.shared[isAccountMigratedKey] }
|
||||
set { Settings.shared[isAccountMigratedKey] = newValue }
|
||||
@@ -255,14 +253,13 @@ extension Account.Service {
|
||||
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 >>- {
|
||||
//Compromise solution for 1.4.0
|
||||
//TODO: Refactor this later
|
||||
try Village.updateCommonPassword(password: password, old: $0)
|
||||
try self.bank.switchPassword(password, old: $0)
|
||||
}
|
||||
try old >>- { try self.bank.switchPassword(password, old: $0) }
|
||||
}
|
||||
|
||||
func validate(password value: String) -> Bool { value.count >= 4 }
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import Foundation
|
||||
import Combine
|
||||
import WalletFoundation
|
||||
import WalletKit
|
||||
|
||||
private let pinTriesKey = CommonKey("Account.Service.Authorize.pinTries")
|
||||
|
||||
@@ -43,7 +44,12 @@ extension Account.Service {
|
||||
func update(password value: String?, type: UpdateType = .change) {
|
||||
|
||||
switch type {
|
||||
case .change: self.password = value
|
||||
case .change:
|
||||
if self.password == nil, let value {
|
||||
// TODO: Call only once on initialization, not on update! Needs to refactor in AccountServiceAuthorize task
|
||||
try? Village.set(password: value)
|
||||
}
|
||||
self.password = value
|
||||
case .toggle: self.updateAuthSubject.send(())
|
||||
case .both:
|
||||
self.password = value
|
||||
|
||||
@@ -48,7 +48,7 @@ extension Account.Service {
|
||||
_ completion: @escaping Completion) {
|
||||
|
||||
guard let wallet = accounts.first?.wallet,
|
||||
let privateKey = wallet.privateKey(password) else {
|
||||
let privateKey = wallet.privateKey(password: password) else {
|
||||
completion(.success("None"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -54,19 +54,13 @@ final class AccountViewAuthorize: CommonViewCustom {
|
||||
|
||||
static func getPrivateKey() -> String {
|
||||
|
||||
if UserDefaults.standard.bool(forKey: "isPinPwdMode") {
|
||||
if let pin = Account.Service.Authorize.shared.password,
|
||||
let password = Account.Service.getPassword(password: pin),
|
||||
!password.isEmpty,
|
||||
password == pin,
|
||||
password.count == 4,
|
||||
let value = Accounts().current?.privateKey(password) {
|
||||
return value
|
||||
}
|
||||
return ""
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
if UserDefaults.standard.bool(forKey: "isPinPwdMode"),
|
||||
let pin = Account.Service.Authorize.shared.password,
|
||||
Account.Service.check(password: pin),
|
||||
let value = Accounts().current?.privateKey(password: pin) {
|
||||
return value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
static func showGetPassword(in viewController: UIViewController,
|
||||
@@ -92,14 +86,11 @@ final class AccountViewAuthorize: CommonViewCustom {
|
||||
|
||||
if UserDefaults.standard.bool(forKey: "isPinPwdMode") {
|
||||
if let pin = Account.Service.Authorize.shared.password,
|
||||
let password = Account.Service.getPassword(password: pin),
|
||||
!password.isEmpty,
|
||||
password == pin,
|
||||
password.count == 4 {
|
||||
Account.Service.check(password: pin),
|
||||
!pin.isEmpty,
|
||||
pin.count == 4 {
|
||||
|
||||
completion(isPasswordRequired
|
||||
? password
|
||||
: Accounts().current?.privateKey(password) ?? "")
|
||||
completion(isPasswordRequired ? pin : Accounts().current?.privateKey(password: pin) ?? "")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -107,18 +98,15 @@ final class AccountViewAuthorize: CommonViewCustom {
|
||||
let ctrl = UINavigationController(rootViewController: accountControollerPin)
|
||||
accountControollerPin.showBackButton = showPinBackButton
|
||||
accountControollerPin.viewModel.validatePin = { [weak ctrl] (pin: String) -> Bool in
|
||||
if let password = Account.Service.getPassword(password: pin),
|
||||
!password.isEmpty,
|
||||
password == pin,
|
||||
password.count == 4 {
|
||||
if Account.Service.check(password: pin),
|
||||
!pin.isEmpty,
|
||||
pin.count == 4 {
|
||||
|
||||
ctrl?.dismiss(animated: true)
|
||||
|
||||
Account.Service.Authorize.shared.update(password: password, type: .both)
|
||||
Account.Service.Authorize.shared.update(password: pin, type: .both)
|
||||
|
||||
completion(isPasswordRequired
|
||||
? password
|
||||
: Accounts().current?.privateKey(password) ?? "")
|
||||
completion(isPasswordRequired ? pin : Accounts().current?.privateKey(password: pin) ?? "")
|
||||
|
||||
Account.Service.Authorize.shared.clearPinTries()
|
||||
return true
|
||||
@@ -128,16 +116,16 @@ final class AccountViewAuthorize: CommonViewCustom {
|
||||
}
|
||||
}
|
||||
accountControollerPin.viewModel.submitBiometric = { [weak ctrl] in
|
||||
if let password = Account.Service.getPassword(password: nil),
|
||||
!password.isEmpty,
|
||||
password.count == 4 {
|
||||
if let pin = Account.Service.passwordViaBiometrics(),
|
||||
!pin.isEmpty,
|
||||
pin.count == 4 {
|
||||
|
||||
Account.Service.Authorize.shared.update(password: password, type: .both)
|
||||
Account.Service.Authorize.shared.update(password: pin, type: .both)
|
||||
|
||||
ctrl?.dismiss(animated: true) {
|
||||
completion(isPasswordRequired
|
||||
? password
|
||||
: Accounts().current?.privateKey(password) ?? "")
|
||||
? pin
|
||||
: Accounts().current?.privateKey(password: pin) ?? "")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,13 +155,15 @@ final class AccountViewAuthorize: CommonViewCustom {
|
||||
|
||||
@IBAction private func onSubmit(_ : AnyObject?) {
|
||||
if isGetPassword {
|
||||
if let password = Account.Service.getPassword(password: textField.lzText ?? ""), !password.isEmpty {
|
||||
self.didSubmit?(password)
|
||||
if let pin = textField.lzText, Account.Service.check(password: pin), !pin.isEmpty {
|
||||
self.didSubmit?(pin)
|
||||
} else {
|
||||
self.textField.error = L10n.Account.Authorize.error
|
||||
}
|
||||
} else {
|
||||
if let privateKey = Accounts().current?.privateKey(textField.lzText ?? ""), !privateKey.isEmpty {
|
||||
if let pin = textField.lzText,
|
||||
let privateKey = Accounts().current?.privateKey(password: pin),
|
||||
!privateKey.isEmpty {
|
||||
self.didSubmit?(privateKey)
|
||||
} else {
|
||||
self.textField.error = L10n.Account.Authorize.error
|
||||
@@ -183,14 +173,14 @@ final class AccountViewAuthorize: CommonViewCustom {
|
||||
|
||||
@IBAction private func onFaceId(_ : AnyObject?) {
|
||||
if isGetPassword {
|
||||
if let password = Account.Service.getPassword(password: nil),
|
||||
if let password = Account.Service.passwordViaBiometrics(),
|
||||
!password.isEmpty {
|
||||
self.didSubmit?(password)
|
||||
} else {
|
||||
self.textField.error = L10n.Account.Authorize.error
|
||||
}
|
||||
} else {
|
||||
if let privateKey = Accounts().current?.privateKey(nil),
|
||||
if let privateKey = Accounts().current?.privateKey(password: nil),
|
||||
!privateKey.isEmpty {
|
||||
self.didSubmit?(privateKey)
|
||||
} else {
|
||||
|
||||
@@ -77,14 +77,8 @@ final class AccountViewPassword: CommonViewCustom {
|
||||
}
|
||||
|
||||
@IBAction private func onSubmit(_ : AnyObject?) {
|
||||
|
||||
guard let oldPassword = Account.Service.getPassword(password: passwordTxtFld.value) else {
|
||||
Alert.error(text: L10n.Account.Password.currentWrong)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try Accounts().update(password: newPasswordTxtFld.value, old: oldPassword)
|
||||
try Accounts().update(password: newPasswordTxtFld.value, old: passwordTxtFld.value)
|
||||
didSubmit?()
|
||||
} catch {
|
||||
Alert.error(text: L10n.Account.Password.currentWrong)
|
||||
|
||||
@@ -74,5 +74,4 @@ final class AccountViewSettings: CommonViewCustom {
|
||||
@IBAction private func onChangeProtection(_ sender: Any) {
|
||||
self.actionSubject.send(.changeProtection)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import WalletKit
|
||||
|
||||
final class WalletPinController: UIViewController {
|
||||
|
||||
@@ -216,13 +217,14 @@ final class WalletPinController: UIViewController {
|
||||
if repeatPin == Constants.NoPinDigits,
|
||||
pinCount == Constants.maxPinLength {
|
||||
self.headerLabelRepeatSetup()
|
||||
self.repeatPin = pin
|
||||
self.repeatPin = self.pin
|
||||
self.pin = []
|
||||
} else if repeatPin == Constants.maxPinLength,
|
||||
pinCount == Constants.maxPinLength,
|
||||
self.repeatPin == self.pin {
|
||||
Account.Service.Authorize.shared.update(password: self.pin.map { "\($0)" }.joined())
|
||||
successShow()
|
||||
let pinCode = self.pin.map { "\($0)" }.joined()
|
||||
Account.Service.Authorize.shared.update(password: pinCode)
|
||||
self.successShow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ struct CreateWalletService {
|
||||
// MARK: - Methods
|
||||
|
||||
func create(for password: String) async throws -> UpdateWalletStateService {
|
||||
let bank = self.village.getBank(with: password, on: self.device)
|
||||
let wallet = try await bank.add(using: self.walletCase)
|
||||
let bank = try self.village.getBank(with: password, on: self.device)
|
||||
let wallet = try await bank.add(using: self.walletCase, password: password)
|
||||
return UpdateWalletStateService(bank: bank, wallet: wallet)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ final class OnBoardingWalletController: UIViewController {
|
||||
|
||||
guard let device = try? await village.device() else { throw NSError() }
|
||||
|
||||
let bank = village.getBank(with: password, on: device)
|
||||
let bank = try village.getBank(with: password, on: device)
|
||||
Account.Service.shared = Account.Service(using: bank)
|
||||
|
||||
self.loadingGroup.notify(queue: DispatchQueue.main) {
|
||||
|
||||
Reference in New Issue
Block a user