mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Initial implementation for AppLockSetupFlowCoordinator. (#1949)
Rename AppLockSettingsScreen to AppLockSetupSettingsScreen. Implement forced logout and persist PIN entry attempts.
This commit is contained in:
@@ -102,8 +102,11 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
||||
backgroundTaskService: backgroundTaskService)
|
||||
|
||||
let appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings)
|
||||
let appLockNavigationCoordinator = NavigationRootCoordinator()
|
||||
let appLockFlowUserIndicatorController = UserIndicatorController(rootCoordinator: appLockNavigationCoordinator)
|
||||
appLockFlowCoordinator = AppLockFlowCoordinator(appLockService: appLockService,
|
||||
navigationCoordinator: NavigationRootCoordinator())
|
||||
userIndicatorController: appLockFlowUserIndicatorController,
|
||||
navigationCoordinator: appLockNavigationCoordinator)
|
||||
|
||||
notificationManager = NotificationManager(notificationCenter: UNUserNotificationCenter.current(),
|
||||
appSettings: appSettings)
|
||||
@@ -329,12 +332,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
||||
// We can ignore signOut when already in the process of signing out,
|
||||
// such as the SDK sending an authError due to token invalidation.
|
||||
break
|
||||
case (_, .signOut(let isSoft), .signingOut):
|
||||
case (_, .signOut(let isSoft, _), .signingOut):
|
||||
self.logout(isSoft: isSoft)
|
||||
case (.signingOut, .completedSigningOut, .signedOut):
|
||||
self.presentSplashScreen(isSoftLogout: false)
|
||||
case (.signingOut, .showSoftLogout, .softLogout):
|
||||
self.presentSplashScreen(isSoftLogout: true)
|
||||
case (.signingOut(_, let disableAppLock), .completedSigningOut, .signedOut):
|
||||
self.presentSplashScreen(isSoftLogout: false, disableAppLock: disableAppLock)
|
||||
case (.signingOut(_, let disableAppLock), .showSoftLogout, .softLogout):
|
||||
self.presentSplashScreen(isSoftLogout: true, disableAppLock: disableAppLock)
|
||||
case (.signedIn, .clearCache, .initial):
|
||||
self.clearCache()
|
||||
default:
|
||||
@@ -371,7 +374,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
||||
navigationStackCoordinator: authenticationNavigationStackCoordinator,
|
||||
appSettings: appSettings,
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
appLockService: appLockFlowCoordinator.appLockService)
|
||||
authenticationCoordinator?.delegate = self
|
||||
|
||||
authenticationCoordinator?.start()
|
||||
@@ -410,7 +414,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
||||
stateMachine.processEvent(.createdUserSession)
|
||||
case .clearAllData:
|
||||
self.softLogoutCoordinator = nil
|
||||
stateMachine.processEvent(.signOut(isSoft: false))
|
||||
stateMachine.processEvent(.signOut(isSoft: false, disableAppLock: false))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
@@ -439,7 +443,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
||||
|
||||
switch action {
|
||||
case .logout:
|
||||
stateMachine.processEvent(.signOut(isSoft: false))
|
||||
stateMachine.processEvent(.signOut(isSoft: false, disableAppLock: false))
|
||||
case .clearCache:
|
||||
stateMachine.processEvent(.clearCache)
|
||||
}
|
||||
@@ -512,7 +516,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
||||
notificationManager.setUserSession(nil)
|
||||
}
|
||||
|
||||
private func presentSplashScreen(isSoftLogout: Bool = false) {
|
||||
private func presentSplashScreen(isSoftLogout: Bool = false, disableAppLock: Bool = false) {
|
||||
navigationRootCoordinator.setRootCoordinator(SplashScreenCoordinator())
|
||||
|
||||
if isSoftLogout {
|
||||
@@ -520,6 +524,15 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
||||
} else {
|
||||
startAuthentication()
|
||||
}
|
||||
|
||||
if disableAppLock {
|
||||
Task {
|
||||
// Ensure the navigation stack has settled.
|
||||
try? await Task.sleep(for: .milliseconds(500))
|
||||
appLockFlowCoordinator.appLockService.disable()
|
||||
windowManager.switchToMain()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func configureNotificationManager() {
|
||||
@@ -549,7 +562,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
||||
guard let self else { return }
|
||||
switch callback {
|
||||
case .didReceiveAuthError(let isSoftLogout):
|
||||
stateMachine.processEvent(.signOut(isSoft: isSoftLogout))
|
||||
stateMachine.processEvent(.signOut(isSoft: isSoftLogout, disableAppLock: false))
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -583,6 +596,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
||||
windowManager.switchToAlternate()
|
||||
case .unlockApp:
|
||||
windowManager.switchToMain()
|
||||
case .forceLogout:
|
||||
stateMachine.processEvent(.signOut(isSoft: false, disableAppLock: true))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
@@ -33,7 +33,7 @@ class AppCoordinatorStateMachine {
|
||||
case signedIn
|
||||
|
||||
/// Processing a sign out request
|
||||
case signingOut(isSoft: Bool)
|
||||
case signingOut(isSoft: Bool, disableAppLock: Bool)
|
||||
}
|
||||
|
||||
/// Events that can be triggered on the AppCoordinator state machine
|
||||
@@ -51,7 +51,7 @@ class AppCoordinatorStateMachine {
|
||||
case createdUserSession
|
||||
|
||||
/// Request sign out.
|
||||
case signOut(isSoft: Bool)
|
||||
case signOut(isSoft: Bool, disableAppLock: Bool)
|
||||
/// Request the soft logout screen.
|
||||
case showSoftLogout
|
||||
/// Signing out completed.
|
||||
@@ -80,16 +80,17 @@ class AppCoordinatorStateMachine {
|
||||
stateMachine.addRoutes(event: .createdUserSession, transitions: [.restoringSession => .signedIn])
|
||||
stateMachine.addRoutes(event: .failedRestoringSession, transitions: [.restoringSession => .signedOut])
|
||||
|
||||
stateMachine.addRoutes(event: .completedSigningOut, transitions: [.signingOut(isSoft: false) => .signedOut])
|
||||
stateMachine.addRoutes(event: .showSoftLogout, transitions: [.signingOut(isSoft: true) => .softLogout])
|
||||
stateMachine.addRoutes(event: .completedSigningOut, transitions: [.signingOut(isSoft: false, disableAppLock: false) => .signedOut,
|
||||
.signingOut(isSoft: false, disableAppLock: true) => .signedOut])
|
||||
stateMachine.addRoutes(event: .showSoftLogout, transitions: [.signingOut(isSoft: true, disableAppLock: false) => .softLogout])
|
||||
|
||||
stateMachine.addRoutes(event: .clearCache, transitions: [.signedIn => .initial])
|
||||
|
||||
// Transitions with associated values need to be handled through `addRouteMapping`
|
||||
stateMachine.addRouteMapping { event, fromState, _ in
|
||||
switch (event, fromState) {
|
||||
case (.signOut(let isSoft), _):
|
||||
return .signingOut(isSoft: isSoft)
|
||||
case (.signOut(let isSoft, let disableAppLock), _):
|
||||
return .signingOut(isSoft: isSoft, disableAppLock: disableAppLock)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ final class AppSettings {
|
||||
private enum UserDefaultsKeys: String {
|
||||
case lastVersionLaunched
|
||||
case seenInvites
|
||||
case hasShownWelcomeScreen
|
||||
case appLockNumberOfPINAttempts
|
||||
case appLockNumberOfBiometricAttempts
|
||||
case lastLoginDate
|
||||
case migratedAccounts
|
||||
case timelineStyle
|
||||
@@ -38,7 +41,6 @@ final class AppSettings {
|
||||
case shouldCollapseRoomStateEvents
|
||||
case userSuggestionsEnabled
|
||||
case readReceiptsEnabled
|
||||
case hasShownWelcomeScreen
|
||||
case swiftUITimelineEnabled
|
||||
case voiceMessageEnabled
|
||||
case mentionsEnabled
|
||||
@@ -122,10 +124,18 @@ final class AppSettings {
|
||||
|
||||
// MARK: - Security
|
||||
|
||||
/// The app must be locked with a PIN code as part of the authentication flow.
|
||||
let appLockIsMandatory = false
|
||||
/// The amount of time the app can remain in the background for without requesting the PIN/TouchID/FaceID.
|
||||
let appLockGracePeriod: TimeInterval = 180
|
||||
/// Any codes that the user isn't allowed to use for their PIN.
|
||||
let appLockPINCodeBlockList = ["0000", "1234"]
|
||||
/// The number of attempts the user has made to unlock the app with a PIN code (resets when unlocked).
|
||||
@UserPreference(key: UserDefaultsKeys.appLockNumberOfPINAttempts, defaultValue: 0, storageType: .userDefaults(store))
|
||||
var appLockNumberOfPINAttempts: Int
|
||||
/// The number of attempts the user has made to unlock the app with Touch/Face ID (resets when unlocked).
|
||||
@UserPreference(key: UserDefaultsKeys.appLockNumberOfBiometricAttempts, defaultValue: 0, storageType: .userDefaults(store))
|
||||
var appLockNumberOfBiometricAttempts: Int
|
||||
|
||||
// MARK: - Authentication
|
||||
|
||||
|
||||
Reference in New Issue
Block a user