Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added change PIN code feature from the settings #3882

Merged
merged 4 commits into from
Jan 11, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Changes to be released in next version
=================================================

✨ Features
*
* Change Pin inside the app (#3881)

🙌 Improvements
* AuthVC: Update SSO button wording.
Expand Down
2 changes: 2 additions & 0 deletions Riot/Assets/en.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1460,6 +1460,7 @@ Tap the + to start adding people.";
"pin_protection_choose_pin" = "Create a PIN for security";
"pin_protection_confirm_pin" = "Confirm your PIN";
"pin_protection_confirm_pin_to_disable" = "Confirm PIN to disable PIN";
"pin_protection_confirm_pin_to_change" = "Confirm PIN to change PIN";
"pin_protection_enter_pin" = "Enter your PIN";
"pin_protection_forgot_pin" = "Forgot PIN";
"pin_protection_reset_alert_title" = "Reset PIN";
Expand All @@ -1472,6 +1473,7 @@ Tap the + to start adding people.";
"pin_protection_settings_section_footer" = "To reset your PIN, you'll need to re-login and create a new one.";
"pin_protection_settings_enabled_forced" = "PIN enabled";
"pin_protection_settings_enable_pin" = "Enable PIN";
"pin_protection_settings_change_pin" = "Change PIN";
"pin_protection_not_allowed_pin" = "For security reasons, this PIN isn’t available. Please try another PIN";
"pin_protection_explanatory" = "Setting up a PIN lets you protect data like messages and contacts, so only you can access them by entering the PIN at the start of the app.";
"pin_protection_kick_user_alert_message" = "Too many errors, you've been logged out";
Expand Down
8 changes: 8 additions & 0 deletions Riot/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2062,6 +2062,10 @@ internal enum VectorL10n {
internal static var pinProtectionConfirmPin: String {
return VectorL10n.tr("Vector", "pin_protection_confirm_pin")
}
/// Confirm PIN to change PIN
internal static var pinProtectionConfirmPinToChange: String {
return VectorL10n.tr("Vector", "pin_protection_confirm_pin_to_change")
}
/// Confirm PIN to disable PIN
internal static var pinProtectionConfirmPinToDisable: String {
return VectorL10n.tr("Vector", "pin_protection_confirm_pin_to_disable")
Expand Down Expand Up @@ -2110,6 +2114,10 @@ internal enum VectorL10n {
internal static var pinProtectionResetAlertTitle: String {
return VectorL10n.tr("Vector", "pin_protection_reset_alert_title")
}
/// Change PIN
internal static var pinProtectionSettingsChangePin: String {
return VectorL10n.tr("Vector", "pin_protection_settings_change_pin")
}
/// Enable PIN
internal static var pinProtectionSettingsEnablePin: String {
return VectorL10n.tr("Vector", "pin_protection_settings_enable_pin")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ final class EnterPinCodeViewController: UIViewController {
self.renderConfirmPinToDisable()
case .inactive:
self.renderInactive()
case .changePin:
self.renderChangePin()
}
}

Expand Down Expand Up @@ -336,6 +338,17 @@ final class EnterPinCodeViewController: UIViewController {
self.explanatoryLabel.isHidden = true
}

private func renderChangePin() {
self.inactiveView.isHidden = true
self.mainStackView.isHidden = false
self.logoImageView.isHidden = true
self.informationLabel.text = VectorL10n.pinProtectionConfirmPinToChange
self.explanatoryLabel.isHidden = true
self.forgotPinButton.isHidden = true
self.bottomView.isHidden = false
self.notAllowedPinView.isHidden = true
}

private func renderPlaceholdersCount(_ count: Int, error: Bool = false) {
UIView.animate(withDuration: 0.3) {
for case let imageView as UIImageView in self.placeholderStackView.arrangedSubviews {
Expand Down
108 changes: 69 additions & 39 deletions Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ final class EnterPinCodeViewModel: EnterPinCodeViewModelType {
private var originalViewMode: SetPinCoordinatorViewMode
private var viewMode: SetPinCoordinatorViewMode

private var initialPin: String = ""
private var firstPin: String = ""
private var currentPin: String = "" {
didSet {
Expand Down Expand Up @@ -116,49 +117,12 @@ final class EnterPinCodeViewModel: EnterPinCodeViewModelType {
switch viewMode {
case .setPin, .setPinAfterLogin, .setPinAfterRegister:
// choosing pin
if firstPin.isEmpty {
// check if this PIN is allowed
if pinCodePreferences.notAllowedPINs.contains(currentPin) {
viewMode = .notAllowedPin
update(viewState: .notAllowedPin)
return
}
// go to next screen
firstPin = currentPin
currentPin.removeAll()
update(viewState: .confirmPin)
} else {
// check first and second pins
if firstPin == currentPin {
// complete with a little delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.coordinatorDelegate?.enterPinCodeViewModel(self, didCompleteWithPin: self.firstPin)
}
} else {
update(viewState: .pinsDontMatch)
}
}
updateAfterPinSet()
case .unlock, .confirmPinToDeactivate:
// unlocking
if currentPin != pinCodePreferences.pin {
// no match
numberOfFailuresDuringEnterPIN += 1
pinCodePreferences.numberOfPinFailures += 1
if viewMode == .unlock && localAuthenticationService.shouldLogOutUser() {
// log out user
self.coordinatorDelegate?.enterPinCodeViewModelDidCompleteWithReset(self, dueToTooManyErrors: true)
return
}
if numberOfFailuresDuringEnterPIN < pinCodePreferences.allowedNumberOfTrialsBeforeAlert {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.viewDelegate?.enterPinCodeViewModel(self, didUpdateViewState: .wrongPin)
self.currentPin.removeAll()
}
} else {
viewDelegate?.enterPinCodeViewModel(self, didUpdateViewState: .wrongPinTooManyTimes)
numberOfFailuresDuringEnterPIN = 0
currentPin.removeAll()
}
updateAfterUnlockFailed()
} else {
// match
// we can use biometrics anymore, if set
Expand All @@ -169,6 +133,23 @@ final class EnterPinCodeViewModel: EnterPinCodeViewModelType {
self.coordinatorDelegate?.enterPinCodeViewModelDidComplete(self)
}
}
case .changePin:
// unlocking
if initialPin.isEmpty && currentPin != pinCodePreferences.pin {
// no match
updateAfterUnlockFailed()
} else {
// match or already unlocked
if initialPin.isEmpty {
// the user can choose a new Pin code
initialPin = currentPin
currentPin.removeAll()
update(viewState: .choosePin)
} else {
// choosing pin
updateAfterPinSet()
}
}
gileluard marked this conversation as resolved.
Show resolved Hide resolved
default:
break
}
Expand All @@ -185,6 +166,8 @@ final class EnterPinCodeViewModel: EnterPinCodeViewModelType {
return .choosePinAfterLogin
case .setPinAfterRegister:
return .choosePinAfterRegister
case .changePin:
return .changePin
default:
return .inactive
}
Expand All @@ -201,6 +184,8 @@ final class EnterPinCodeViewModel: EnterPinCodeViewModelType {
update(viewState: .confirmPinToDisable)
case .inactive:
update(viewState: .inactive)
case .changePin:
update(viewState: .changePin)
default:
break
}
Expand All @@ -209,4 +194,49 @@ final class EnterPinCodeViewModel: EnterPinCodeViewModelType {
private func update(viewState: EnterPinCodeViewState) {
self.viewDelegate?.enterPinCodeViewModel(self, didUpdateViewState: viewState)
}

private func updateAfterUnlockFailed() {
numberOfFailuresDuringEnterPIN += 1
pinCodePreferences.numberOfPinFailures += 1
if viewMode == .unlock && localAuthenticationService.shouldLogOutUser() {
// log out user
self.coordinatorDelegate?.enterPinCodeViewModelDidCompleteWithReset(self, dueToTooManyErrors: true)
return
}
if numberOfFailuresDuringEnterPIN < pinCodePreferences.allowedNumberOfTrialsBeforeAlert {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.viewDelegate?.enterPinCodeViewModel(self, didUpdateViewState: .wrongPin)
self.currentPin.removeAll()
}
} else {
viewDelegate?.enterPinCodeViewModel(self, didUpdateViewState: .wrongPinTooManyTimes)
numberOfFailuresDuringEnterPIN = 0
currentPin.removeAll()
}
}

private func updateAfterPinSet() {
if firstPin.isEmpty {
// check if this PIN is allowed
if pinCodePreferences.notAllowedPINs.contains(currentPin) {
viewMode = .notAllowedPin
update(viewState: .notAllowedPin)
return
}
// go to next screen
firstPin = currentPin
currentPin.removeAll()
update(viewState: .confirmPin)
} else {
// check first and second pins
if firstPin == currentPin {
// complete with a little delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.coordinatorDelegate?.enterPinCodeViewModel(self, didCompleteWithPin: self.firstPin)
}
} else {
update(viewState: .pinsDontMatch)
}
}
gileluard marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ enum EnterPinCodeViewState {
case forgotPin // after pin has been set, user tapped forgot pin
case confirmPinToDisable // after pin has been set, confirm pin to disable pin
case inactive // inactive state, only used when app is not active
case changePin // pin is set, user tapped change pin from settings
}
2 changes: 2 additions & 0 deletions Riot/Modules/SetPinCode/SetPinCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ final class SetPinCoordinator: SetPinCoordinatorType {
return createSetupBiometricsCoordinator()
case .inactive:
return createEnterPinCodeCoordinator()
case .changePin:
return createEnterPinCodeCoordinator()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import Foundation
case setupBiometricsFromSettings
case confirmBiometricsToDeactivate
case inactive
case changePin
}

@objc protocol SetPinCoordinatorBridgePresenterDelegate {
Expand Down
18 changes: 17 additions & 1 deletion Riot/Modules/Settings/Security/SecurityViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
enum {
PIN_CODE_SETTING,
PIN_CODE_DESCRIPTION,
PIN_CODE_CHANGE,
PIN_CODE_BIOMETRICS,
PIN_CODE_COUNT
};
Expand Down Expand Up @@ -315,7 +316,10 @@ - (void)updateSections
// Rows
[pinCodeSection addRowWithTag:PIN_CODE_SETTING];
[pinCodeSection addRowWithTag:PIN_CODE_DESCRIPTION];

if ([PinCodePreferences shared].isPinSet) {
[pinCodeSection addRowWithTag:PIN_CODE_CHANGE];
}

if ([PinCodePreferences shared].isBiometricsAvailable)
{
[pinCodeSection addRowWithTag:PIN_CODE_BIOMETRICS];
Expand Down Expand Up @@ -1293,6 +1297,10 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N
cell = [self descriptionCellForTableView:tableView withText:nil];
}
}
else if (indexPath.row == PIN_CODE_CHANGE)
{
cell = [self buttonCellWithTitle:NSLocalizedStringFromTable(@"pin_protection_settings_change_pin", @"Vector", nil) action:@selector(changePinCode: ) forTableView:tableView atIndexPath:indexPath];
}
else if (indexPath.row == PIN_CODE_BIOMETRICS)
{
MXKTableViewCellWithLabelAndSwitch *switchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
Expand Down Expand Up @@ -1708,6 +1716,14 @@ - (void)enableBiometricsSwitchValueChanged:(UISwitch *)sender
[self.setPinCoordinatorBridgePresenter presentFrom:self animated:YES];
}

- (void)changePinCode:(UIButton *)sender
{
SetPinCoordinatorViewMode viewMode = SetPinCoordinatorViewModeChangePin;
self.setPinCoordinatorBridgePresenter = [[SetPinCoordinatorBridgePresenter alloc] initWithSession:self.mainSession viewMode:viewMode];
self.setPinCoordinatorBridgePresenter.delegate = self;
[self.setPinCoordinatorBridgePresenter presentFrom:self animated:YES];
}

#pragma mark - SettingsKeyBackupTableViewSectionDelegate
#ifdef CROSS_SIGNING_AND_BACKUP_DEV
- (void)settingsKeyBackupTableViewSectionDidUpdate:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection
Expand Down