Fixes #2470 - Allow verifiying a session through entering the recovery key
This commit is contained in:
committed by
Stefan Ceriu
parent
5f2ba3a1df
commit
59cfb0ff1a
@@ -567,6 +567,7 @@
|
||||
"screen_session_verification_compare_numbers_subtitle" = "Confirm that the numbers below match those shown on your other session.";
|
||||
"screen_session_verification_compare_numbers_title" = "Compare numbers";
|
||||
"screen_session_verification_complete_subtitle" = "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.";
|
||||
"screen_session_verification_enter_recovery_key" = "Enter recovery key";
|
||||
"screen_session_verification_open_existing_session_subtitle" = "Prove it’s you in order to access your encrypted message history.";
|
||||
"screen_session_verification_open_existing_session_title" = "Open an existing session";
|
||||
"screen_session_verification_positive_button_canceled" = "Retry verification";
|
||||
|
||||
@@ -208,7 +208,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
fatalError("The sessionVerificationController should aways be valid at this point")
|
||||
}
|
||||
|
||||
let verificationParameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController)
|
||||
let verificationParameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController,
|
||||
recoveryState: parameters.userSession.sessionSecurityStatePublisher.value.recoveryState)
|
||||
let coordinator = SessionVerificationScreenCoordinator(parameters: verificationParameters)
|
||||
|
||||
coordinator.actions
|
||||
@@ -216,6 +217,9 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .recoveryKey:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
handleAppRoute(.chatBackupSettings, animated: true)
|
||||
case .done:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
break
|
||||
|
||||
case (.roomList, .showLogoutConfirmationScreen, .logoutConfirmationScreen):
|
||||
presentSecureBackupConfirmationScreen()
|
||||
presentSecureBackupLogoutConfirmationScreen()
|
||||
case (.logoutConfirmationScreen, .dismissedLogoutConfirmationScreen, .roomList):
|
||||
break
|
||||
|
||||
@@ -400,7 +400,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
return
|
||||
}
|
||||
|
||||
presentSecureBackupConfirmationScreen()
|
||||
presentSecureBackupLogoutConfirmationScreen()
|
||||
}
|
||||
|
||||
// MARK: Session verification
|
||||
@@ -410,7 +410,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
fatalError("The sessionVerificationController should aways be valid at this point")
|
||||
}
|
||||
|
||||
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController)
|
||||
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController,
|
||||
recoveryState: userSession.sessionSecurityStatePublisher.value.recoveryState)
|
||||
|
||||
let coordinator = SessionVerificationScreenCoordinator(parameters: parameters)
|
||||
|
||||
@@ -419,6 +420,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .recoveryKey:
|
||||
settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
|
||||
case .done:
|
||||
navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
@@ -511,7 +514,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
// MARK: Secure backup confirmation
|
||||
|
||||
private func presentSecureBackupConfirmationScreen() {
|
||||
private func presentSecureBackupLogoutConfirmationScreen() {
|
||||
let coordinator = SecureBackupLogoutConfirmationScreenCoordinator(parameters: .init(secureBackupController: userSession.clientProxy.secureBackupController,
|
||||
networkMonitor: ServiceLocator.shared.networkMonitor))
|
||||
|
||||
|
||||
@@ -1392,6 +1392,8 @@ public enum L10n {
|
||||
public static var screenSessionVerificationCompareNumbersTitle: String { return L10n.tr("Localizable", "screen_session_verification_compare_numbers_title") }
|
||||
/// Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.
|
||||
public static var screenSessionVerificationCompleteSubtitle: String { return L10n.tr("Localizable", "screen_session_verification_complete_subtitle") }
|
||||
/// Enter recovery key
|
||||
public static var screenSessionVerificationEnterRecoveryKey: String { return L10n.tr("Localizable", "screen_session_verification_enter_recovery_key") }
|
||||
/// Prove it’s you in order to access your encrypted message history.
|
||||
public static var screenSessionVerificationOpenExistingSessionSubtitle: String { return L10n.tr("Localizable", "screen_session_verification_open_existing_session_subtitle") }
|
||||
/// Open an existing session
|
||||
|
||||
@@ -198,6 +198,7 @@ enum A11yIdentifiers {
|
||||
let emojiWrapper = "session_verification-emojis"
|
||||
let verificationComplete = "session_verification-verification_complete"
|
||||
let close = "session_verification-close"
|
||||
let enterRecoveryKey = "session_verification-enter_recovery_key"
|
||||
}
|
||||
|
||||
struct SettingsScreen {
|
||||
|
||||
@@ -75,7 +75,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
case (.unverifiedLastSession, .incomplete):
|
||||
state.requiresExtraAccountSetup = true
|
||||
if state.securityBannerMode != .dismissed {
|
||||
state.securityBannerMode = .recoveryKeyConfirmation
|
||||
state.securityBannerMode = .sessionVerification
|
||||
}
|
||||
case (.verified, .disabled):
|
||||
state.requiresExtraAccountSetup = true
|
||||
|
||||
@@ -18,11 +18,13 @@ import Combine
|
||||
import SwiftUI
|
||||
|
||||
enum SessionVerificationScreenCoordinatorAction {
|
||||
case recoveryKey
|
||||
case done
|
||||
}
|
||||
|
||||
struct SessionVerificationScreenCoordinatorParameters {
|
||||
let sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol
|
||||
let recoveryState: SecureBackupRecoveryState
|
||||
}
|
||||
|
||||
final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
|
||||
@@ -36,7 +38,8 @@ final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
|
||||
}
|
||||
|
||||
init(parameters: SessionVerificationScreenCoordinatorParameters) {
|
||||
viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: parameters.sessionVerificationControllerProxy)
|
||||
viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: parameters.sessionVerificationControllerProxy,
|
||||
recoveryState: parameters.recoveryState)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
@@ -47,6 +50,8 @@ final class SessionVerificationScreenCoordinator: CoordinatorProtocol {
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .recoveryKey:
|
||||
actionsSubject.send(.recoveryKey)
|
||||
case .finished:
|
||||
actionsSubject.send(.done)
|
||||
}
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
import Foundation
|
||||
|
||||
enum SessionVerificationScreenViewModelAction {
|
||||
case recoveryKey
|
||||
case finished
|
||||
}
|
||||
|
||||
struct SessionVerificationScreenViewState: BindableState {
|
||||
let showRecoveryOption: Bool
|
||||
|
||||
var verificationState: SessionVerificationScreenStateMachine.State = .initial
|
||||
|
||||
var title: String? {
|
||||
@@ -83,6 +86,7 @@ struct SessionVerificationScreenViewState: BindableState {
|
||||
}
|
||||
|
||||
enum SessionVerificationScreenViewAction {
|
||||
case recoveryKey
|
||||
case requestVerification
|
||||
case startSasVerification
|
||||
case restart
|
||||
|
||||
@@ -31,12 +31,13 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
||||
}
|
||||
|
||||
init(sessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol,
|
||||
initialState: SessionVerificationScreenViewState = SessionVerificationScreenViewState()) {
|
||||
recoveryState: SecureBackupRecoveryState,
|
||||
verificationState: SessionVerificationScreenStateMachine.State = .initial) {
|
||||
self.sessionVerificationControllerProxy = sessionVerificationControllerProxy
|
||||
|
||||
stateMachine = SessionVerificationScreenStateMachine()
|
||||
|
||||
super.init(initialViewState: initialState)
|
||||
super.init(initialViewState: .init(showRecoveryOption: recoveryState == .incomplete, verificationState: verificationState))
|
||||
|
||||
setupStateMachine()
|
||||
|
||||
@@ -70,6 +71,8 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess
|
||||
|
||||
override func process(viewAction: SessionVerificationScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .recoveryKey:
|
||||
actionsSubject.send(.recoveryKey)
|
||||
case .requestVerification:
|
||||
stateMachine.processEvent(.requestVerification)
|
||||
case .startSasVerification:
|
||||
|
||||
@@ -128,12 +128,21 @@ struct SessionVerificationScreen: View {
|
||||
private var actionButtons: some View {
|
||||
switch context.viewState.verificationState {
|
||||
case .initial:
|
||||
Button(L10n.actionStartVerification) {
|
||||
context.send(viewAction: .requestVerification)
|
||||
VStack(spacing: 32) {
|
||||
Button(L10n.actionStartVerification) {
|
||||
context.send(viewAction: .requestVerification)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.requestVerification)
|
||||
|
||||
if context.viewState.showRecoveryOption {
|
||||
Button(L10n.screenSessionVerificationEnterRecoveryKey) {
|
||||
context.send(viewAction: .recoveryKey)
|
||||
}
|
||||
.buttonStyle(.compound(.plain))
|
||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.enterRecoveryKey)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.requestVerification)
|
||||
|
||||
case .cancelled:
|
||||
Button(L10n.actionRetry) {
|
||||
context.send(viewAction: .restart)
|
||||
@@ -235,7 +244,8 @@ struct SessionVerification_Previews: PreviewProvider, TestablePreview {
|
||||
|
||||
static func sessionVerificationScreen(state: SessionVerificationScreenStateMachine.State) -> some View {
|
||||
let viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: SessionVerificationControllerProxyMock.configureMock(),
|
||||
initialState: SessionVerificationScreenViewState(verificationState: state))
|
||||
recoveryState: .incomplete,
|
||||
verificationState: state)
|
||||
|
||||
return SessionVerificationScreen(context: viewModel.context)
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
||||
state.securitySectionMode = .sessionVerification
|
||||
case (.unverifiedLastSession, .incomplete):
|
||||
state.showSecuritySectionBadge = true
|
||||
state.securitySectionMode = .secureBackup
|
||||
state.securitySectionMode = .sessionVerification
|
||||
case (.verified, .disabled):
|
||||
state.showSecuritySectionBadge = true
|
||||
state.securitySectionMode = .secureBackup
|
||||
|
||||
@@ -25,5 +25,5 @@ struct MockUserSession: UserSessionProtocol {
|
||||
let clientProxy: ClientProxyProtocol
|
||||
let mediaProvider: MediaProviderProtocol
|
||||
let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
|
||||
var sessionSecurityStatePublisher: AnyPublisher<SessionSecurityState, Never> = CurrentValueSubject<SessionSecurityState, Never>(.init(verificationState: .verified, recoveryState: .enabled)).eraseToAnyPublisher()
|
||||
var sessionSecurityStatePublisher = CurrentValueSubject<SessionSecurityState, Never>(.init(verificationState: .verified, recoveryState: .enabled)).asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
@@ -51,22 +51,16 @@ class UserSession: UserSessionProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
let sessionSecurityStatePublisher: AnyPublisher<SessionSecurityState, Never>
|
||||
let sessionSecurityStateSubject = CurrentValueSubject<SessionSecurityState, Never>(.init(verificationState: .unknown, recoveryState: .unknown))
|
||||
var sessionSecurityStatePublisher: CurrentValuePublisher<SessionSecurityState, Never> {
|
||||
sessionSecurityStateSubject.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
init(clientProxy: ClientProxyProtocol, mediaProvider: MediaProviderProtocol, voiceMessageMediaManager: VoiceMessageMediaManagerProtocol) {
|
||||
self.clientProxy = clientProxy
|
||||
self.mediaProvider = mediaProvider
|
||||
self.voiceMessageMediaManager = voiceMessageMediaManager
|
||||
|
||||
sessionSecurityStatePublisher = Publishers.CombineLatest(sessionVerificationStateSubject, clientProxy.secureBackupController.recoveryState)
|
||||
.map {
|
||||
MXLog.info("Session security state changed, verificationState: \($0), recoveryState: \($1)")
|
||||
return SessionSecurityState(verificationState: $0, recoveryState: $1)
|
||||
}
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
clientProxy.callbacks
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] callback in
|
||||
@@ -88,6 +82,18 @@ class UserSession: UserSessionProtocol {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Publishers.CombineLatest(sessionVerificationStateSubject, clientProxy.secureBackupController.recoveryState)
|
||||
.map {
|
||||
MXLog.info("Session security state changed, verificationState: \($0), recoveryState: \($1)")
|
||||
return SessionSecurityState(verificationState: $0, recoveryState: $1)
|
||||
}
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] value in
|
||||
self?.sessionSecurityStateSubject.send(value)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@@ -42,7 +42,7 @@ protocol UserSessionProtocol {
|
||||
var mediaProvider: MediaProviderProtocol { get }
|
||||
var voiceMessageMediaManager: VoiceMessageMediaManagerProtocol { get }
|
||||
|
||||
var sessionSecurityStatePublisher: AnyPublisher<SessionSecurityState, Never> { get }
|
||||
var sessionSecurityStatePublisher: CurrentValuePublisher<SessionSecurityState, Never> { get }
|
||||
var sessionVerificationController: SessionVerificationControllerProxyProtocol? { get }
|
||||
|
||||
var callbacks: PassthroughSubject<UserSessionCallback, Never> { get }
|
||||
|
||||
@@ -552,7 +552,8 @@ class MockScreen: Identifiable {
|
||||
return navigationStackCoordinator
|
||||
case .sessionVerification:
|
||||
var sessionVerificationControllerProxy = SessionVerificationControllerProxyMock.configureMock(requestDelay: .seconds(5))
|
||||
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationControllerProxy)
|
||||
let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationControllerProxy,
|
||||
recoveryState: .unknown)
|
||||
return SessionVerificationScreenCoordinator(parameters: parameters)
|
||||
case .userSessionScreen, .userSessionScreenReply, .userSessionScreenRTE:
|
||||
let appSettings: AppSettings = ServiceLocator.shared.settings
|
||||
|
||||
@@ -27,7 +27,8 @@ class SessionVerificationViewModelTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
sessionVerificationController = SessionVerificationControllerProxyMock.configureMock()
|
||||
viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: sessionVerificationController)
|
||||
viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: sessionVerificationController,
|
||||
recoveryState: .incomplete)
|
||||
context = viewModel.context
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d0c8f14034cf3b7ec84ea309e02ef139ae9ef6556137c1c0a4348286faac3a8f
|
||||
size 98718
|
||||
oid sha256:768819e156631bd2188b55376ce95e270a16828b0d70c3f2c3559a979e558d19
|
||||
size 105136
|
||||
|
||||
Reference in New Issue
Block a user