Files
2025-09-15 16:56:27 +02:00

114 lines
3.8 KiB
Swift

// Copyright (c) 2025 Proton Technologies AG
//
// This file is part of Proton Mail.
//
// Proton Mail is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail. If not, see https://www.gnu.org/licenses/.
import Combine
import InboxCore
import proton_app_uniffi
class LockScreenStore: StateStore {
@Published var state: LockScreenState
private let pinVerifier: PINVerifier
private let biometricAuthorizationNotifier: BiometricAuthorizationNotifier
private let signOutService: SignOutService
private let dismissLock: () -> Void
init(
state: LockScreenState,
pinVerifier: PINVerifier,
biometricAuthorizationNotifier: BiometricAuthorizationNotifier,
signOutService: SignOutService,
dismissLock: @escaping () -> Void
) {
self.state = state
self.pinVerifier = pinVerifier
self.signOutService = signOutService
self.biometricAuthorizationNotifier = biometricAuthorizationNotifier
self.dismissLock = dismissLock
}
func handle(action: LockScreenAction) async {
switch action {
case .biometric(let output):
switch output {
case .authenticated:
biometricAuthorizationNotifier.biometricsCheckPassed()
dismissLock()
case .logOut:
await signOutAllAccounts()
}
case .pin(let output):
switch output {
case .logOut:
await signOutAllAccounts()
case .pin(let pin):
await verify(pin: pin)
}
case .pinScreenLoaded:
let pinAttemptsRemaining = await readNumberOfAttempts()
if pinAttemptsRemaining <= 3 {
handlePinAuthenticationError(attemptsLeft: pinAttemptsRemaining)
}
}
}
private func signOutAllAccounts() async {
do {
try await signOutService.signOutAllAccounts()
dismissLock()
} catch {
AppLogger.log(error: error, category: .appSettings)
}
}
private func handlePinAuthenticationError(attemptsLeft: Int) {
switch attemptsLeft {
case 0:
dismissLock()
case 1...3:
state =
state
.copy(\.pinAuthenticationError, to: .attemptsRemaining(attemptsLeft))
default:
state =
state
.copy(\.pinAuthenticationError, to: .custom(L10n.PINLock.invalidPIN.string))
}
}
private func verify(pin: PIN) async {
switch await pinVerifier.verifyPinCode(pin: pin.digits) {
case .ok:
dismissLock()
case .error(let error) where error == .reason(.tooFrequentAttempts):
state = state.copy(\.pinAuthenticationError, to: .tooFrequentAttempts)
case .error:
let pinAttemptsRemaining = await readNumberOfAttempts()
handlePinAuthenticationError(attemptsLeft: pinAttemptsRemaining)
}
}
private func readNumberOfAttempts() async -> Int {
do {
let attempts = try await pinVerifier.remainingPinAttempts().get() ?? 0
return Int(attempts)
} catch {
AppLogger.log(error: error, category: .appSettings)
return 0
}
}
}