[#1097] Zashi-iOS audit Issue E

- copy with expiry time set draft

[#1097] Zashi-iOS audit Issue E

- copy to pasteboard has been removed from recovery phrase seed completely
- copy seed to pasteboard added to the debug menu, please note, the debug menu will not be in production build, issue #1113
This commit is contained in:
Lukas Korba 2024-03-06 15:22:58 +01:00
parent 6970b6ff60
commit 746b6859a7
11 changed files with 18 additions and 83 deletions

View File

@ -335,7 +335,6 @@ let package = Package(
"MnemonicClient",
"Models",
"NumberFormatter",
"Pasteboard",
"UIComponents",
"Utils",
"WalletStorage",
@ -374,6 +373,7 @@ let package = Package(
"Models",
"NumberFormatter",
"OnboardingFlow",
"Pasteboard",
"ReadTransactionsStorage",
"RecoveryPhraseDisplay",
"RestoreWalletStorage",

View File

@ -8,7 +8,6 @@
import Foundation
import ComposableArchitecture
import Models
import Pasteboard
import WalletStorage
import ZcashLightClientKit
import Utils
@ -22,20 +21,17 @@ public struct RecoveryPhraseDisplay {
@Presents public var alert: AlertState<Action>?
public var phrase: RecoveryPhrase?
public var showBackButton = false
public var showCopyToBufferAlert = false
public var birthday: Birthday?
public var birthdayValue: String?
public init(
phrase: RecoveryPhrase? = nil,
showBackButton: Bool = false,
showCopyToBufferAlert: Bool = false,
birthday: Birthday? = nil,
birthdayValue: String? = nil
) {
self.phrase = phrase
self.showBackButton = showBackButton
self.showCopyToBufferAlert = showCopyToBufferAlert
self.birthday = birthday
self.birthdayValue = birthdayValue
}
@ -43,12 +39,10 @@ public struct RecoveryPhraseDisplay {
public enum Action: Equatable {
case alert(PresentationAction<Action>)
case copyToBufferPressed
case finishedPressed
case onAppear
}
@Dependency(\.pasteboard) var pasteboard
@Dependency(\.walletStorage) var walletStorage
@Dependency(\.numberFormatter) var numberFormatter
@ -81,12 +75,6 @@ public struct RecoveryPhraseDisplay {
case .alert(.dismiss):
state.alert = nil
return .none
case .copyToBufferPressed:
guard let phrase = state.phrase?.toString() else { return .none }
pasteboard.setString(phrase)
state.showCopyToBufferAlert = true
return .none
case .finishedPressed:
return .none

View File

@ -93,16 +93,6 @@ public struct RecoveryPhraseDisplayView: View {
.onAppear { store.send(.onAppear) }
.alert($store.scope(state: \.alert, action: \.alert))
.zashiBack(false, hidden: !store.showBackButton)
.toolbarAction {
Button {
store.send(.copyToBufferPressed)
} label: {
Text(L10n.General.tapToCopy)
.font(.custom(FontFamily.Inter.bold.name, size: 11))
.underline()
.foregroundColor(Asset.Colors.primary.color)
}
}
}
}
.navigationBarBackButtonHidden()
@ -120,7 +110,6 @@ public struct RecoveryPhraseDisplayView: View {
initialState: RecoveryPhraseDisplay.State(
phrase: .placeholder,
showBackButton: true,
showCopyToBufferAlert: false,
birthdayValue: nil
)
) {
@ -135,7 +124,6 @@ public struct RecoveryPhraseDisplayView: View {
extension RecoveryPhraseDisplay.State {
public static let initial = RecoveryPhraseDisplay.State(
phrase: nil,
showCopyToBufferAlert: false,
birthday: nil
)
}

View File

@ -11,6 +11,7 @@ import Foundation
import ZcashLightClientKit
import Generated
import Models
import Pasteboard
/// In this file is a collection of helpers that control all state and action related operations
/// for the `RootReducer` with a connection to the UI navigation.
@ -20,6 +21,7 @@ extension RootReducer {
public indirect enum DebugAction: Equatable {
case cancelRescan
case cantStartSync(ZcashError)
case copySeedToPasteboard
case flagUpdated
case rateTheApp
case rescanBlockchain
@ -83,6 +85,12 @@ extension RootReducer {
}
.cancellable(id: WalletConfigCancelId.timer, cancelInFlight: true)
case .debug(.copySeedToPasteboard):
let storedWallet = try? walletStorage.exportWallet()
guard let phrase = storedWallet?.seedPhrase.value() else { return .none }
pasteboard.setString(phrase.redacted)
return .none
case let .debug(.walletConfigLoaded(walletConfig)):
return Effect.send(.updateStateAfterConfigUpdate(walletConfig))

View File

@ -118,6 +118,7 @@ public struct RootReducer: Reducer {
@Dependency(\.mainQueue) var mainQueue
@Dependency(\.mnemonic) var mnemonic
@Dependency(\.numberFormatter) var numberFormatter
@Dependency(\.pasteboard) var pasteboard
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
@Dependency(\.userStoredPreferences) var userStoredPreferences
@Dependency(\.walletConfigProvider) var walletConfigProvider

View File

@ -180,10 +180,14 @@ private extension RootView {
}
#endif
Button(L10n.Root.Debug.Option.copySeed) {
viewStore.send(.debug(.copySeedToPasteboard))
}
Button(L10n.Root.Debug.Option.rescanBlockchain) {
viewStore.send(.debug(.rescanBlockchain))
}
Button(L10n.Root.Debug.Option.nukeWallet) {
viewStore.send(.initialization(.nukeWalletRequest))
}

View File

@ -189,7 +189,6 @@ extension AdvancedSettingsReducer.State {
phraseDisplayState: RecoveryPhraseDisplay.State(
phrase: nil,
showBackButton: false,
showCopyToBufferAlert: false,
birthday: nil
),
privateDataConsentState: .initial,
@ -208,7 +207,6 @@ extension AdvancedSettingsStore {
initialState: .init(
phraseDisplayState: RecoveryPhraseDisplay.State(
phrase: nil,
showCopyToBufferAlert: false,
birthday: nil
),
privateDataConsentState: .initial,

View File

@ -396,6 +396,8 @@ public enum L10n {
public enum Option {
/// Rate the App
public static let appReview = L10n.tr("Localizable", "root.debug.option.appReview", fallback: "Rate the App")
/// Copy seed to pasteboard
public static let copySeed = L10n.tr("Localizable", "root.debug.option.copySeed", fallback: "Copy seed to pasteboard")
/// Export Logs
public static let exportLogs = L10n.tr("Localizable", "root.debug.option.exportLogs", fallback: "Export Logs")
/// [Be careful] Nuke Wallet

View File

@ -292,6 +292,7 @@ Sharing this private data is irrevocable — once you have shared this private d
"root.debug.navigationTitle" = "Startup";
"root.debug.option.restartApp" = "Restart the App";
"root.debug.option.rescanBlockchain" = "Rescan Blockchain";
"root.debug.option.copySeed" = "Copy seed to pasteboard";
"root.debug.option.nukeWallet" = "[Be careful] Nuke Wallet";
"root.debug.option.exportLogs" = "Export Logs";
"root.debug.option.appReview" = "Rate the App";

View File

@ -31,7 +31,6 @@
9E34519529C4A4BF00177D16 /* AddressDetailsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E207C382966EF87003E2C9B /* AddressDetailsTests.swift */; };
9E34519629C4A4D800177D16 /* secantUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4E7A2526B364180058B01E /* secantUITests.swift */; };
9E34519729C4A51100177D16 /* RecoveryPhraseBackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFE93DE272C6D4B000FCCA5 /* RecoveryPhraseBackupTests.swift */; };
9E34519829C4A51100177D16 /* RecoveryPhraseDisplayReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */; };
9E34519A29C4A52F00177D16 /* BalanceBreakdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E94C61F28AA7DEE008256E9 /* BalanceBreakdownTests.swift */; };
9E34519B29C4A90700177D16 /* DeeplinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAB4675285B5C7C002904A0 /* DeeplinkTests.swift */; };
9E34519C29C4A91A00177D16 /* HomeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E3911382848AD500073DD9A /* HomeTests.swift */; };
@ -107,7 +106,6 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplayReducerTests.swift; sourceTree = "<group>"; };
0D26AF94299E8196005260EE /* secant-mainnet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "secant-mainnet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
0D3B01EB298DAF89007EBCDA /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
0D4E7A0526B364170058B01E /* secant-testnet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "secant-testnet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@ -308,7 +306,6 @@
0DFE93DD272C6D4B000FCCA5 /* BackupFlowTests */ = {
isa = PBXGroup;
children = (
0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */,
0DFE93DE272C6D4B000FCCA5 /* RecoveryPhraseBackupTests.swift */,
);
path = BackupFlowTests;
@ -973,7 +970,6 @@
9E3451AA29C84ED500177D16 /* CurrencySelectionTests.swift in Sources */,
9E3451C529C857E400177D16 /* TransactionListSnapshotTests.swift in Sources */,
9EEB06C62B344F1E00EEE50F /* SyncProgressTests.swift in Sources */,
9E34519829C4A51100177D16 /* RecoveryPhraseDisplayReducerTests.swift in Sources */,
9E34519C29C4A91A00177D16 /* HomeTests.swift in Sources */,
9E1FAFB72AF2C6D40084CA3D /* PrivateDataConsentSnapshotTests.swift in Sources */,
9E3451BB29C857C800177D16 /* HomeSnapshotTests.swift in Sources */,

View File

@ -1,51 +0,0 @@
//
// RecoveryPhraseDisplayStoreTests.swift
// secantTests
//
// Created by Francisco Gindre on 12/8/21.
//
import XCTest
import ComposableArchitecture
import Pasteboard
import Models
import RecoveryPhraseDisplay
@testable import secant_testnet
class RecoveryPhraseDisplayTests: XCTestCase {
@MainActor func testCopyToBuffer() async {
let testPasteboard = PasteboardClient.testPasteboard
let store = TestStore(
initialState: RecoveryPhraseDisplay.test
) {
RecoveryPhraseDisplay()
}
store.dependencies.pasteboard = testPasteboard
await store.send(.copyToBufferPressed) { state in
state.phrase = .placeholder
state.showCopyToBufferAlert = true
}
XCTAssertEqual(
testPasteboard.getString(),
RecoveryPhrase.placeholder.toString()
)
await store.finish()
}
}
private extension RecoveryPhraseDisplay {
static let test = RecoveryPhraseDisplay.State(
phrase: .placeholder,
showCopyToBufferAlert: false
)
static let empty = RecoveryPhraseDisplay.State(
phrase: .initial,
showCopyToBufferAlert: false
)
}