Skip to content

Commit 8808d51

Browse files
[PM-8952] Implemented minimum length for pin (#1522)
1 parent 285aa10 commit 8808d51

File tree

4 files changed

+40
-3
lines changed

4 files changed

+40
-3
lines changed

BitwardenShared/Core/Platform/Utilities/Constants.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ extension Constants {
7070
/// A default value for the minimum number of characters required when creating a password.
7171
static let minimumPasswordCharacters = 12
7272

73+
/// The minimum length when setting a pin.
74+
static let minimumPinLength = 4
75+
7376
/// The minimum number of minutes before allowing the vault to sync again.
7477
static let minimumSyncInterval: TimeInterval = 30 * 60 // 30 minutes
7578

BitwardenShared/UI/Auth/Extensions/Alert+Auth.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import BitwardenKit
12
import Foundation
23

34
// MARK: - Alert+Auth
@@ -322,14 +323,20 @@ extension Alert {
322323
) -> Alert {
323324
Alert(
324325
title: Localizations.enterPIN,
325-
message: settingUp ? Localizations.setPINDescription : Localizations.verifyPIN,
326+
message: settingUp
327+
? Localizations.yourPINMustBeAtLeastXCharactersDescriptionLong(Constants.minimumPinLength)
328+
: Localizations.verifyPIN,
326329
alertActions: [
327330
AlertAction(
328331
title: Localizations.submit,
329332
style: .default,
330333
handler: { _, alertTextFields in
331334
guard let pin = alertTextFields.first(where: { $0.id == "pin" })?.text else { return }
332335
await completion(pin)
336+
},
337+
shouldEnableAction: { textFields in
338+
guard let pin = textFields.first(where: { $0.id == "pin" })?.text else { return false }
339+
return pin.count >= Constants.minimumPinLength
333340
}
334341
),
335342
AlertAction(title: Localizations.cancel, style: .cancel, handler: { _, _ in

BitwardenShared/UI/Auth/Extensions/AlertAuthTests.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import BitwardenKit
12
import XCTest
23

34
@testable import BitwardenShared
@@ -177,7 +178,33 @@ class AlertAuthTests: BitwardenTestCase {
177178
XCTAssertEqual(subject.alertActions.count, 2)
178179
XCTAssertEqual(subject.preferredStyle, .alert)
179180
XCTAssertEqual(subject.title, Localizations.enterPIN)
180-
XCTAssertEqual(subject.message, Localizations.setPINDescription)
181+
XCTAssertEqual(subject.message,
182+
Localizations.yourPINMustBeAtLeastXCharactersDescriptionLong(Constants.minimumPinLength))
183+
}
184+
185+
/// `enterPINCode(completion:settingUp:)` disables the "Submit" button when the text field is empty,
186+
/// and enables it dynamically when the user enters a pin with the minimum length.
187+
@MainActor
188+
func test_enterPINCode_enablesSubmitButtonWhenMinimumLengthPinIsEntered() async throws {
189+
let alert = Alert.enterPINCode(settingUp: true) { _ in }
190+
let controller = alert.createAlertController()
191+
192+
let pinWithMinimumLength = String(repeating: "1", count: Constants.minimumPinLength)
193+
194+
let uiTextField = try XCTUnwrap(controller.textFields?.first)
195+
let submitAction = try XCTUnwrap(
196+
controller.actions.first(where: { $0.title == Localizations.submit })
197+
)
198+
199+
uiTextField.text = String(repeating: "1", count: Constants.minimumPinLength - 1)
200+
alert.alertTextFields.first?.textChanged(in: uiTextField)
201+
202+
XCTAssertFalse(submitAction.isEnabled)
203+
204+
uiTextField.text = pinWithMinimumLength
205+
alert.alertTextFields.first?.textChanged(in: uiTextField)
206+
207+
XCTAssertTrue(submitAction.isEnabled)
181208
}
182209

183210
/// `enterPINCode(completion:settingUp:)` constructs an `Alert`

BitwardenShared/UI/Platform/Application/Support/Localizations/en.lproj/Localizable.strings

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,6 @@
435435
"Unlock" = "Unlock";
436436
"UnlockVault" = "Unlock vault";
437437
"ThirtyMinutes" = "30 minutes";
438-
"SetPINDescription" = "Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application.";
439438
"LoggedInAsOn" = "Logged in as %1$@ on %2$@.";
440439
"VaultLockedMasterPassword" = "Your vault is locked. Verify your master password to continue.";
441440
"VaultLockedPIN" = "Your vault is locked. Verify your PIN code to continue.";
@@ -1167,3 +1166,4 @@
11671166
"ExpiresInXDays" = "Expires in %1$@ days";
11681167
"DateRangeXToY" = "%1$@ to %2$@";
11691168
"SelfHostServerURL" = "Self-host server URL";
1169+
"YourPINMustBeAtLeastXCharactersDescriptionLong" = "Your PIN must be at least %1$@ characters. Your PIN settings will be reset if you ever fully log out of the application.";

0 commit comments

Comments
 (0)