mirror of
https://github.com/bitwarden/ios.git
synced 2025-12-11 04:34:55 -06:00
BIT-1720: logout user when password changed (#470)
This commit is contained in:
parent
6b44a67af1
commit
5ecddeec07
@ -39,6 +39,10 @@ protocol NotificationService {
|
||||
/// The delegate to handle login request actions originating from notifications.
|
||||
///
|
||||
protocol NotificationServiceDelegate: AnyObject {
|
||||
/// Users are logged out, route to landing page.
|
||||
///
|
||||
func routeToLanding() async
|
||||
|
||||
/// Show the login request.
|
||||
///
|
||||
/// - Parameter loginRequest: The login request.
|
||||
@ -68,6 +72,9 @@ class DefaultNotificationService: NotificationService {
|
||||
/// The service used by the application to manage the app's ID.
|
||||
private let appIdService: AppIdService
|
||||
|
||||
/// The repository used by the application to manage auth data for the UI layer.
|
||||
private let authRepository: AuthRepository
|
||||
|
||||
/// The service used by the application to handle authentication tasks.
|
||||
private let authService: AuthService
|
||||
|
||||
@ -89,6 +96,7 @@ class DefaultNotificationService: NotificationService {
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - appIdService: The service used by the application to manage the app's ID.
|
||||
/// - authRepository: The repository used by the application to manage auth data for the UI layer.
|
||||
/// - authService: The service used by the application to handle authentication tasks.
|
||||
/// - errorReporter: The service used by the application to report non-fatal errors.
|
||||
/// - notificationAPIService: The API service used to make notification requests.
|
||||
@ -96,6 +104,7 @@ class DefaultNotificationService: NotificationService {
|
||||
/// - syncService: The service used to handle syncing vault data with the API.
|
||||
init(
|
||||
appIdService: AppIdService,
|
||||
authRepository: AuthRepository,
|
||||
authService: AuthService,
|
||||
errorReporter: ErrorReporter,
|
||||
notificationAPIService: NotificationAPIService,
|
||||
@ -103,6 +112,7 @@ class DefaultNotificationService: NotificationService {
|
||||
syncService: SyncService
|
||||
) {
|
||||
self.appIdService = appIdService
|
||||
self.authRepository = authRepository
|
||||
self.authService = authService
|
||||
self.errorReporter = errorReporter
|
||||
self.notificationAPIService = notificationAPIService
|
||||
@ -185,8 +195,12 @@ class DefaultNotificationService: NotificationService {
|
||||
case .syncOrgKeys:
|
||||
try await syncService.fetchSync(forceSync: true)
|
||||
case .logOut:
|
||||
// no-op
|
||||
break
|
||||
guard let data: UserNotification = notificationData.data() else { return }
|
||||
try await authRepository.logout(userId: data.userId)
|
||||
// Only route to landing page if the current active user was logged out.
|
||||
if data.userId == userId {
|
||||
await delegate?.routeToLanding()
|
||||
}
|
||||
case .syncSendCreate,
|
||||
.syncSendUpdate:
|
||||
if let data: SyncSendNotification = notificationData.data(), data.userId == userId {
|
||||
|
||||
@ -6,6 +6,7 @@ class NotificationServiceTests: BitwardenTestCase { // swiftlint:disable:this ty
|
||||
// MARK: Properties
|
||||
|
||||
var appSettingsStore: MockAppSettingsStore!
|
||||
var authRepository: MockAuthRepository!
|
||||
var authService: MockAuthService!
|
||||
var client: MockHTTPClient!
|
||||
var delegate: MockNotificationServiceDelegate!
|
||||
@ -21,6 +22,7 @@ class NotificationServiceTests: BitwardenTestCase { // swiftlint:disable:this ty
|
||||
super.setUp()
|
||||
|
||||
appSettingsStore = MockAppSettingsStore()
|
||||
authRepository = MockAuthRepository()
|
||||
authService = MockAuthService()
|
||||
client = MockHTTPClient()
|
||||
delegate = MockNotificationServiceDelegate()
|
||||
@ -31,6 +33,7 @@ class NotificationServiceTests: BitwardenTestCase { // swiftlint:disable:this ty
|
||||
|
||||
subject = DefaultNotificationService(
|
||||
appIdService: AppIdService(appSettingStore: appSettingsStore),
|
||||
authRepository: authRepository,
|
||||
authService: authService,
|
||||
errorReporter: errorReporter,
|
||||
notificationAPIService: notificationAPIService,
|
||||
@ -428,6 +431,54 @@ class NotificationServiceTests: BitwardenTestCase { // swiftlint:disable:this ty
|
||||
XCTAssertEqual(delegate.showLoginRequestRequest, .fixture())
|
||||
}
|
||||
|
||||
/// `messageReceived(_:notificationDismissed:notificationTapped:)` handles logout requests and will not route
|
||||
/// to the landing screen if the logged-out account was not the currently active account.
|
||||
func test_messageReceived_logout_nonActiveUser() async throws {
|
||||
// Set up the mock data.
|
||||
stateService.setIsAuthenticated()
|
||||
let activeAccount: Account = .fixture()
|
||||
let nonActiveAccount: Account = .fixture(profile: .fixture(userId: "b245a33f"))
|
||||
stateService.accounts = [activeAccount, nonActiveAccount]
|
||||
|
||||
let message: [AnyHashable: Any] = [
|
||||
"aps": [
|
||||
"data": [
|
||||
"type": NotificationType.logOut.rawValue,
|
||||
"payload": "{\"UserId\":\"\(nonActiveAccount.profile.userId)\"}",
|
||||
],
|
||||
],
|
||||
]
|
||||
|
||||
// Test.
|
||||
await subject.messageReceived(message, notificationDismissed: nil, notificationTapped: nil)
|
||||
XCTAssertEqual(authRepository.logoutUserId, nonActiveAccount.profile.userId)
|
||||
XCTAssertFalse(delegate.routeToLandingCalled)
|
||||
}
|
||||
|
||||
/// `messageReceived(_:notificationDismissed:notificationTapped:)` handles logout requests and will route
|
||||
/// to the landing screen if the logged-out account was the currently active account.
|
||||
func test_messageReceived_logout_activeUser() async throws {
|
||||
// Set up the mock data.
|
||||
stateService.setIsAuthenticated()
|
||||
let activeAccount: Account = .fixture()
|
||||
let nonActiveAccount: Account = .fixture(profile: .fixture(userId: "b245a33f"))
|
||||
stateService.accounts = [activeAccount, nonActiveAccount]
|
||||
|
||||
let message: [AnyHashable: Any] = [
|
||||
"aps": [
|
||||
"data": [
|
||||
"type": NotificationType.logOut.rawValue,
|
||||
"payload": "{\"UserId\":\"\(activeAccount.profile.userId)\"}",
|
||||
],
|
||||
],
|
||||
]
|
||||
|
||||
// Test.
|
||||
await subject.messageReceived(message, notificationDismissed: nil, notificationTapped: nil)
|
||||
XCTAssertEqual(authRepository.logoutUserId, activeAccount.profile.userId)
|
||||
XCTAssertTrue(delegate.routeToLandingCalled)
|
||||
}
|
||||
|
||||
/// `messageReceived(_:notificationDismissed:notificationTapped:)` handles notifications being dismissed.
|
||||
func test_messageReceived_notificationDismissed() async throws {
|
||||
// Set up the mock data.
|
||||
@ -500,12 +551,18 @@ class NotificationServiceTests: BitwardenTestCase { // swiftlint:disable:this ty
|
||||
// MARK: - MockNotificationServiceDelegate
|
||||
|
||||
class MockNotificationServiceDelegate: NotificationServiceDelegate {
|
||||
var routeToLandingCalled: Bool = false
|
||||
|
||||
var showLoginRequestRequest: LoginRequest?
|
||||
|
||||
var switchAccountsAccount: Account?
|
||||
var switchAccountsLoginRequest: LoginRequest?
|
||||
var switchAccountsShowAlert: Bool?
|
||||
|
||||
func routeToLanding() async {
|
||||
routeToLandingCalled = true
|
||||
}
|
||||
|
||||
func showLoginRequest(_ loginRequest: LoginRequest) {
|
||||
showLoginRequestRequest = loginRequest
|
||||
}
|
||||
|
||||
@ -331,15 +331,6 @@ public class ServiceContainer: Services { // swiftlint:disable:this type_body_le
|
||||
systemDevice: UIDevice.current
|
||||
)
|
||||
|
||||
let notificationService = DefaultNotificationService(
|
||||
appIdService: appIdService,
|
||||
authService: authService,
|
||||
errorReporter: errorReporter,
|
||||
notificationAPIService: apiService,
|
||||
stateService: stateService,
|
||||
syncService: syncService
|
||||
)
|
||||
|
||||
let authRepository = DefaultAuthRepository(
|
||||
accountAPIService: apiService,
|
||||
authService: authService,
|
||||
@ -354,6 +345,16 @@ public class ServiceContainer: Services { // swiftlint:disable:this type_body_le
|
||||
vaultTimeoutService: vaultTimeoutService
|
||||
)
|
||||
|
||||
let notificationService = DefaultNotificationService(
|
||||
appIdService: appIdService,
|
||||
authRepository: authRepository,
|
||||
authService: authService,
|
||||
errorReporter: errorReporter,
|
||||
notificationAPIService: apiService,
|
||||
stateService: stateService,
|
||||
syncService: syncService
|
||||
)
|
||||
|
||||
let generatorRepository = DefaultGeneratorRepository(
|
||||
clientGenerators: clientService.clientGenerator(),
|
||||
clientVaultService: clientService.clientVault(),
|
||||
|
||||
@ -139,6 +139,12 @@ public class AppProcessor {
|
||||
}
|
||||
|
||||
extension AppProcessor: NotificationServiceDelegate {
|
||||
/// Users are logged out, route to landing page.
|
||||
///
|
||||
func routeToLanding() async {
|
||||
coordinator?.navigate(to: .auth(.landing))
|
||||
}
|
||||
|
||||
/// Show the login request.
|
||||
///
|
||||
/// - Parameter loginRequest: The login request.
|
||||
|
||||
@ -112,6 +112,12 @@ class AppProcessorTests: BitwardenTestCase {
|
||||
XCTAssertEqual(notificationService.messageReceivedMessage?.keys.first, "knock knock")
|
||||
}
|
||||
|
||||
/// `routeToLanding(_:)` navigates to show the landing view.
|
||||
func test_routeToLanding() async {
|
||||
await subject.routeToLanding()
|
||||
XCTAssertEqual(coordinator.routes.last, .auth(.landing))
|
||||
}
|
||||
|
||||
/// Upon a session timeout on app foreground, send the user to the `.didTimeout` route.
|
||||
func test_shouldSessionTimeout_navigateTo_didTimeout() throws {
|
||||
let rootNavigator = MockRootNavigator()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user