From 60ec66ee91e9d0ea8dbc2ebe1049b03f401f14b5 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Thu, 17 Oct 2024 17:55:23 +0200 Subject: [PATCH] [#1375] App launch protected by device authentication method - authentication added to the app start pipeline - it's triggered for all fresh app launches - it's required after 15minutes after cold starts - recognizes available auth method and reflects it in the UI [#1375] App launch protected by device authentication method - final UI for the re-authenticate [#1375] App launch protected by device authentication method - Changelog updated - Texts localized --- CHANGELOG.md | 3 +- modules/Package.swift | 4 +- .../LocalAuthenticationInterface.swift | 8 + .../LocalAuthenticationLiveKey.swift | 23 +++ .../LocalAuthenticationMocks.swift | 6 +- .../LocalAuthenticationTestKey.swift | 3 +- .../Features/Root/RootDestination.swift | 1 + .../Features/Root/RootInitialization.swift | 9 ++ modules/Sources/Features/Root/RootStore.swift | 2 + modules/Sources/Generated/L10n.swift | 10 ++ .../icons/authKey.imageset/Contents.json | 12 ++ .../icons/authKey.imageset/key.png | Bin 0 -> 16422 bytes .../Generated/Resources/Localizable.strings | 6 + .../Sources/Generated/SharedStateKeys.swift | 1 + .../Generated/XCAssets+Generated.swift | 1 + modules/Sources/Models/FeatureFlags.swift | 13 +- .../UIComponents/Overlays/SplashView.swift | 150 ++++++++++++++---- secant.xcodeproj/project.pbxproj | 12 +- 18 files changed, 219 insertions(+), 45 deletions(-) create mode 100644 modules/Sources/Generated/Resources/Assets.xcassets/icons/authKey.imageset/Contents.json create mode 100644 modules/Sources/Generated/Resources/Assets.xcassets/icons/authKey.imageset/key.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 45543e16..8860a83f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ directly impact users rather than highlighting other crucial architectural updat ## [Unreleased] ### Added -- Flexa integrated into Zashi, users can pay with ZEC for Flexa codes. +- Flexa integrated into Zashi, users can pay with ZEC for Flexa codes. +- Authentication for the app launch and cold starts after 15 minutes. ### Fixed - Splash screen animation is blocked by the main thread on iOS 16 and older. diff --git a/modules/Package.swift b/modules/Package.swift index e8041fcf..921077ba 100644 --- a/modules/Package.swift +++ b/modules/Package.swift @@ -820,10 +820,12 @@ let package = Package( "BalanceFormatter", "DerivationTool", "Generated", + "LocalAuthenticationHandler", "NumberFormatter", "SupportDataGenerator", "Utils", - "ZcashSDKEnvironment" + "ZcashSDKEnvironment", + .product(name: "ComposableArchitecture", package: "swift-composable-architecture") ], path: "Sources/UIComponents" ), diff --git a/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationInterface.swift b/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationInterface.swift index f03dcbc7..752550ba 100644 --- a/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationInterface.swift +++ b/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationInterface.swift @@ -16,5 +16,13 @@ extension DependencyValues { @DependencyClient public struct LocalAuthenticationClient { + public enum Method: Equatable { + case faceID + case none + case passcode + case touchID + } + public let authenticate: @Sendable () async -> Bool + public let method: @Sendable () -> Method } diff --git a/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationLiveKey.swift b/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationLiveKey.swift index 548f8a45..4c7959b2 100644 --- a/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationLiveKey.swift +++ b/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationLiveKey.swift @@ -33,6 +33,29 @@ extension LocalAuthenticationClient: DependencyKey { /// Some interruption occurred during the authentication, access to the sensitive content is therefore forbidden return false } + }, + method: { + let context = LAContext() + var error: NSError? + + if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { + switch context.biometryType { + case .faceID: + return .faceID + case .touchID: + return .touchID + case .none, .opticID: + return .none + @unknown default: + return .none + } + } else { + if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) { + return .passcode + } else { + return .none + } + } } ) } diff --git a/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationMocks.swift b/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationMocks.swift index c13a45c1..905014d6 100644 --- a/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationMocks.swift +++ b/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationMocks.swift @@ -7,10 +7,12 @@ extension LocalAuthenticationClient { public static let mockAuthenticationSucceeded = Self( - authenticate: { true } + authenticate: { true }, + method: { .none } ) public static let mockAuthenticationFailed = Self( - authenticate: { false } + authenticate: { false }, + method: { .none } ) } diff --git a/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationTestKey.swift b/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationTestKey.swift index a956795d..86bd3b0a 100644 --- a/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationTestKey.swift +++ b/modules/Sources/Dependencies/LocalAuthenticationHandler/LocalAuthenticationTestKey.swift @@ -10,6 +10,7 @@ import XCTestDynamicOverlay extension LocalAuthenticationClient: TestDependencyKey { public static let testValue = Self( - authenticate: unimplemented("\(Self.self).authenticate", placeholder: false) + authenticate: unimplemented("\(Self.self).authenticate", placeholder: false), + method: unimplemented("\(Self.self).method", placeholder: .none) ) } diff --git a/modules/Sources/Features/Root/RootDestination.swift b/modules/Sources/Features/Root/RootDestination.swift index 7a021dbe..d84160bc 100644 --- a/modules/Sources/Features/Root/RootDestination.swift +++ b/modules/Sources/Features/Root/RootDestination.swift @@ -134,6 +134,7 @@ extension Root { case .splashFinished: state.splashAppeared = true + state.lastAuthenticationTimestamp = Int(Date().timeIntervalSince1970) exchangeRate.refreshExchangeRateUSD() return .none diff --git a/modules/Sources/Features/Root/RootInitialization.swift b/modules/Sources/Features/Root/RootInitialization.swift index 9c6d189f..0edcc562 100644 --- a/modules/Sources/Features/Root/RootInitialization.swift +++ b/modules/Sources/Features/Root/RootInitialization.swift @@ -17,6 +17,7 @@ import Utils extension Root { public enum Constants { static let udIsRestoringWallet = "udIsRestoringWallet" + static let noAuthenticationWithinXMinutes = 15 } public enum InitializationAction: Equatable { @@ -59,6 +60,14 @@ extension Root { ) case .initialization(.appDelegate(.willEnterForeground)): + if state.featureFlags.appLaunchBiometric { + let now = Date() + let before = Date.init(timeIntervalSince1970: TimeInterval(state.lastAuthenticationTimestamp)) + if let xMinutesAgo = Calendar.current.date(byAdding: .minute, value: -Constants.noAuthenticationWithinXMinutes, to: now), + before < xMinutesAgo { + state.splashAppeared = false + } + } state.appStartState = .willEnterForeground if state.isLockedInKeychainUnavailableState || !sdkSynchronizer.latestState().syncStatus.isPrepared { return .send(.initialization(.initialSetups)) diff --git a/modules/Sources/Features/Root/RootStore.swift b/modules/Sources/Features/Root/RootStore.swift index 4f36a700..ec3b3b3f 100644 --- a/modules/Sources/Features/Root/RootStore.swift +++ b/modules/Sources/Features/Root/RootStore.swift @@ -57,8 +57,10 @@ public struct Root { public var deeplinkWarningState: DeeplinkWarning.State = .initial public var destinationState: DestinationState public var exportLogsState: ExportLogs.State + @Shared(.inMemory(.featureFlags)) public var featureFlags: FeatureFlags = .initial public var isLockedInKeychainUnavailableState = false public var isRestoringWallet = false + @Shared(.appStorage(.lastAuthenticationTimestamp)) public var lastAuthenticationTimestamp: Int = 0 public var notEnoughFreeSpaceState: NotEnoughFreeSpace.State public var onboardingState: OnboardingFlow.State public var phraseDisplayState: RecoveryPhraseDisplay.State diff --git a/modules/Sources/Generated/L10n.swift b/modules/Sources/Generated/L10n.swift index 5ac73036..167c2775 100644 --- a/modules/Sources/Generated/L10n.swift +++ b/modules/Sources/Generated/L10n.swift @@ -788,6 +788,16 @@ public enum L10n { } } } + public enum Splash { + /// Tap the face icon to use Face ID and unlock it. + public static let authFaceID = L10n.tr("Localizable", "splash.authFaceID", fallback: "Tap the face icon to use Face ID and unlock it.") + /// Tap the key icon to enter your passcode and unlock it. + public static let authPasscode = L10n.tr("Localizable", "splash.authPasscode", fallback: "Tap the key icon to enter your passcode and unlock it.") + /// Your Zashi account is secured. + public static let authTitle = L10n.tr("Localizable", "splash.authTitle", fallback: "Your Zashi account is secured.") + /// Tap the print icon to use Touch ID and unlock it. + public static let authTouchID = L10n.tr("Localizable", "splash.authTouchID", fallback: "Tap the print icon to use Touch ID and unlock it.") + } public enum SupportData { public enum AppVersionItem { /// App identifier diff --git a/modules/Sources/Generated/Resources/Assets.xcassets/icons/authKey.imageset/Contents.json b/modules/Sources/Generated/Resources/Assets.xcassets/icons/authKey.imageset/Contents.json new file mode 100644 index 00000000..91bdb7c6 --- /dev/null +++ b/modules/Sources/Generated/Resources/Assets.xcassets/icons/authKey.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "key.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/modules/Sources/Generated/Resources/Assets.xcassets/icons/authKey.imageset/key.png b/modules/Sources/Generated/Resources/Assets.xcassets/icons/authKey.imageset/key.png new file mode 100644 index 0000000000000000000000000000000000000000..eda17e1e8e52077e1b99730146228b34053545d4 GIT binary patch literal 16422 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 AqyPW_ literal 0 HcmV?d00001 diff --git a/modules/Sources/Generated/Resources/Localizable.strings b/modules/Sources/Generated/Resources/Localizable.strings index b913ab71..e45d1ba3 100644 --- a/modules/Sources/Generated/Resources/Localizable.strings +++ b/modules/Sources/Generated/Resources/Localizable.strings @@ -390,6 +390,12 @@ Sharing this private data is irrevocable — once you have shared this private d "deeplinkWarning.cta" = "Rescan in Zashi"; "deeplinkWarning.screenTitle" = "Hello!"; +// MARK: - Splash Screen +"splash.authTitle" = "Your Zashi account is secured."; +"splash.authFaceID" = "Tap the face icon to use Face ID and unlock it."; +"splash.authTouchID" = "Tap the print icon to use Touch ID and unlock it."; +"splash.authPasscode" = "Tap the key icon to enter your passcode and unlock it."; + // MARK: - Tooltips "tooltip.exchangeRate.title" = "Exchange rate unavailable"; "tooltip.exchangeRate.desc" = "We tried but we couldn’t refresh the exchange rate for you. Check your connection, relaunch the app, and we’ll try again."; diff --git a/modules/Sources/Generated/SharedStateKeys.swift b/modules/Sources/Generated/SharedStateKeys.swift index b8e77dc8..996febd4 100644 --- a/modules/Sources/Generated/SharedStateKeys.swift +++ b/modules/Sources/Generated/SharedStateKeys.swift @@ -15,4 +15,5 @@ public extension String { static let addressBookContacts = "sharedStateKey_addressBookContacts" static let toast = "sharedStateKey_toast" static let featureFlags = "sharedStateKey_featureFlags" + static let lastAuthenticationTimestamp = "sharedStateKey_lastAuthenticationTimestamp" } diff --git a/modules/Sources/Generated/XCAssets+Generated.swift b/modules/Sources/Generated/XCAssets+Generated.swift index ea39799b..5ce9d8ba 100644 --- a/modules/Sources/Generated/XCAssets+Generated.swift +++ b/modules/Sources/Generated/XCAssets+Generated.swift @@ -54,6 +54,7 @@ public enum Asset { public static let flyReceivedFilled = ImageAsset(name: "flyReceivedFilled") public enum Icons { public static let alertCircle = ImageAsset(name: "alertCircle") + public static let authKey = ImageAsset(name: "authKey") public static let coinsHand = ImageAsset(name: "coinsHand") public static let currencyDollar = ImageAsset(name: "currencyDollar") public static let currencyZec = ImageAsset(name: "currencyZec") diff --git a/modules/Sources/Models/FeatureFlags.swift b/modules/Sources/Models/FeatureFlags.swift index 057c81b2..592948a5 100644 --- a/modules/Sources/Models/FeatureFlags.swift +++ b/modules/Sources/Models/FeatureFlags.swift @@ -7,11 +7,14 @@ public struct FeatureFlags: Equatable { public let flexa: Bool - + public let appLaunchBiometric: Bool + init( - flexa: Bool = false + flexa: Bool = false, + appLaunchBiometric: Bool = false ) { self.flexa = flexa + self.appLaunchBiometric = appLaunchBiometric } } @@ -27,11 +30,13 @@ private extension FeatureFlags { FeatureFlags.disabled #elseif SECANT_TESTNET FeatureFlags( - flexa: false + flexa: false, + appLaunchBiometric: true ) #else FeatureFlags( - flexa: true + flexa: true, + appLaunchBiometric: true ) #endif } diff --git a/modules/Sources/UIComponents/Overlays/SplashView.swift b/modules/Sources/UIComponents/Overlays/SplashView.swift index b8341cfa..b7aca413 100644 --- a/modules/Sources/UIComponents/Overlays/SplashView.swift +++ b/modules/Sources/UIComponents/Overlays/SplashView.swift @@ -7,6 +7,8 @@ import SwiftUI import Generated +import LocalAuthenticationHandler +import ComposableArchitecture final class SplashManager: ObservableObject { struct SplashShape: Shape { @@ -29,8 +31,10 @@ final class SplashManager: ObservableObject { var task: Task<(), Never>? var currentMaxHeight: CGFloat = 0.0 var step: CGFloat = 0.0 + @Published var authenticationDidntSucceed = false @Published var isOn = true let completion: () -> Void + var timer: Timer? init(_ isHidden: Bool, completion: @escaping () -> Void) { self.isHidden = isHidden @@ -39,12 +43,31 @@ final class SplashManager: ObservableObject { if !isHidden { preparePoints() - self.spinTheWheel() + authenticate() + } + } + + func authenticate() { + @Dependency(\.localAuthentication) var localAuthentication + + authenticationDidntSucceed = false + + Task { + if await !localAuthentication.authenticate() { + await self.authenticationFailed() + } else { + await self.spinTheWheel() + } } } - func spinTheWheel() { - Timer.scheduledTimer(withTimeInterval: 1.0 / 60.0, repeats: true) { timer in + @MainActor func authenticationFailed() { + authenticationDidntSucceed = true + } + + @MainActor func spinTheWheel() { + timer?.invalidate() + timer = Timer.scheduledTimer(withTimeInterval: 1.0 / 60.0, repeats: true) { timer in if self.isOn { Task { await self.tick() @@ -125,31 +148,88 @@ final class SplashManager: ObservableObject { struct SplashView: View { @StateObject var splashManager: SplashManager let isHidden: Bool - + var authenticationIcon: Image { + @Dependency(\.localAuthentication) var localAuthentication + + switch localAuthentication.method() { + case .faceID: return Image(systemName: "faceid") + case .touchID: return Image(systemName: "touchid") + case .passcode: return Asset.Assets.Icons.authKey.image + default: return Asset.Assets.Icons.coinsHand.image + } + } + + var authenticationDesc: String { + @Dependency(\.localAuthentication) var localAuthentication + + switch localAuthentication.method() { + case .faceID: return L10n.Splash.authFaceID + case .touchID: return L10n.Splash.authTouchID + case .passcode: return L10n.Splash.authPasscode + default: return "" + } + } + var body: some View { if splashManager.isOn && !isHidden { - GeometryReader { proxy in - Asset.Assets.zashiLogo.image - .zImage(width: 249, height: 321, color: .white) - .scaleEffect(0.35) - .position( - x: proxy.frame(in: .local).midX, - y: proxy.frame(in: .local).midY * 0.5 - ) + ZStack { + GeometryReader { proxy in + Asset.Assets.zashiLogo.image + .zImage(width: 249, height: 321, color: .white) + .scaleEffect(0.35) + .position( + x: proxy.frame(in: .local).midX, + y: proxy.frame(in: .local).midY * 0.5 + ) + + Asset.Assets.splashHi.image + .zImage(width: 246, height: 213, color: .white) + .scaleEffect(0.35) + .position( + x: proxy.frame(in: .local).midX, + y: proxy.frame(in: .local).midY * 0.8 + ) + } + .background(Asset.Colors.splash.color) + .mask { + SplashManager.SplashShape(points: splashManager.points) + } + .ignoresSafeArea() + .onChange(of: isHidden) { value in + if value { + splashManager.preparePoints() + } + } + if splashManager.authenticationDidntSucceed { + VStack(spacing: 0) { + Spacer() + + Button { + splashManager.authenticate() + } label: { + authenticationIcon + .renderingMode(.template) + .resizable() + .frame(width: 64, height: 64) + .foregroundColor(.white) + } - Asset.Assets.splashHi.image - .zImage(width: 246, height: 213, color: .white) - .scaleEffect(0.35) - .position( - x: proxy.frame(in: .local).midX, - y: proxy.frame(in: .local).midY * 0.8 - ) + Text(L10n.Splash.authTitle) + .font(.custom(FontFamily.Inter.semiBold.name, size: 20)) + .foregroundColor(.white) + .multilineTextAlignment(.center) + .padding(.top, 24) + + Text(authenticationDesc) + .font(.custom(FontFamily.Inter.regular.name, size: 14)) + .foregroundColor(.white) + .multilineTextAlignment(.center) + .padding(.top, 8) + } + .padding(.bottom, 120) + .screenHorizontalPadding() + } } - .background(Asset.Colors.splash.color) - .mask { - SplashManager.SplashShape(points: splashManager.points) - } - .ignoresSafeArea() } } } @@ -161,12 +241,22 @@ struct SplashModifier: ViewModifier { func body(content: Content) -> some View { content .overlay { - SplashView( - splashManager: SplashManager(isHidden) { - completion() - }, - isHidden: isHidden - ) + if isHidden { + SplashView( + splashManager: SplashManager(isHidden) { + completion() + }, + isHidden: isHidden + ) + .hidden() + } else { + SplashView( + splashManager: SplashManager(isHidden) { + completion() + }, + isHidden: isHidden + ) + } } } } diff --git a/secant.xcodeproj/project.pbxproj b/secant.xcodeproj/project.pbxproj index 4d27dd64..b935f621 100644 --- a/secant.xcodeproj/project.pbxproj +++ b/secant.xcodeproj/project.pbxproj @@ -2303,7 +2303,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\""; DEVELOPMENT_TEAM = RLPRR8CPQG; @@ -2316,7 +2316,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.4.2; + MARKETING_VERSION = 0.4.3; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "co.electriccoin.secant-testnet"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2334,7 +2334,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\""; DEVELOPMENT_TEAM = RLPRR8CPQG; ENABLE_BITCODE = NO; @@ -2346,7 +2346,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.4.2; + MARKETING_VERSION = 0.4.3; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "co.electriccoin.secant-testnet"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2364,7 +2364,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\""; DEVELOPMENT_TEAM = RLPRR8CPQG; ENABLE_BITCODE = NO; @@ -2376,7 +2376,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.4.2; + MARKETING_VERSION = 0.4.3; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "co.electriccoin.secant-testnet"; PRODUCT_NAME = "$(TARGET_NAME)";