Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors the Multi-Factor Authentication (MFA) management and resolution screens by separating state management from UI layout into reusable screen and content views, such as DefaultMFAManagementContent and DefaultMFAResolutionContent. The review feedback highlights three key areas for improvement: replacing a risky force cast with a conditional cast to prevent runtime crashes, avoiding a SwiftUI anti-pattern by enumerating collections instead of using indices with self as the identifier in ForEach, and ensuring backward compatibility for older iOS versions by explicitly discarding the argument in onChange closures.
| if factor.factorID == PhoneMultiFactorID { | ||
| let phoneInfo = factor as! PhoneMultiFactorInfo | ||
| Text("SMS: \(phoneInfo.phoneNumber)") | ||
| .authFont(.caption) | ||
| .foregroundColor(.secondary) | ||
| } else { |
There was a problem hiding this comment.
Using a force cast (as!) can lead to runtime crashes if the underlying type is not what is expected (e.g., during testing or if SDK behavior changes). It is safer to use a conditional cast (as?) combined with the existing if check.
if factor.factorID == PhoneMultiFactorID, let phoneInfo = factor as? PhoneMultiFactorInfo {
Text("SMS: \(phoneInfo.phoneNumber)")
.authFont(.caption)
.foregroundColor(.secondary)
} else {| ForEach(state.hints.indices, id: \.self) { index in | ||
| let hint = state.hints[index] | ||
| hintSelectionButton(hint: hint, index: index) | ||
| } |
There was a problem hiding this comment.
Using state.hints.indices with id: \.self in a ForEach is a SwiftUI anti-pattern. If the collection changes, the indices can refer to incorrect elements or cause out-of-bounds crashes. Since MFAHint conforms to Hashable, you can safely enumerate the collection and use the element itself as the identifier.
ForEach(Array(state.hints.enumerated()), id: \.element) { index, hint in
hintSelectionButton(hint: hint, index: index)
}| .onChange(of: selectedHintIndex) { | ||
| verificationCode = "" | ||
| totpCode = "" | ||
| verificationId = nil | ||
| errorMessage = nil | ||
| } | ||
| .onChange(of: verificationCode) { errorMessage = nil } | ||
| .onChange(of: totpCode) { errorMessage = nil } |
There was a problem hiding this comment.
In SwiftUI, .onChange(of:action:) with a 0-argument closure was introduced in iOS 17.0. If this library supports older iOS versions (e.g., iOS 14-16), using this signature will cause compilation errors. To maintain backward compatibility, explicitly accept and discard the closure's argument using _ in.
| .onChange(of: selectedHintIndex) { | |
| verificationCode = "" | |
| totpCode = "" | |
| verificationId = nil | |
| errorMessage = nil | |
| } | |
| .onChange(of: verificationCode) { errorMessage = nil } | |
| .onChange(of: totpCode) { errorMessage = nil } | |
| .onChange(of: selectedHintIndex) { _ in | |
| verificationCode = "" | |
| totpCode = "" | |
| verificationId = nil | |
| errorMessage = nil | |
| } | |
| .onChange(of: verificationCode) { _ in errorMessage = nil } | |
| .onChange(of: totpCode) { _ in errorMessage = nil } |
cc01c83 to
ca1c05e
Compare
396b816 to
198f2c8
Compare
5336ee4 to
75d9824
Compare
198f2c8 to
704bd6c
Compare
75d9824 to
ba7ecda
Compare
Add public MFAResolutionScreen and MFAManagementScreen, which own the screen state and actions and pass MFAResolutionContentState / MFAManagementContentState to consumer content. DefaultMFAResolutionContent and DefaultMFAManagementContent hold the stock markup, and MFAResolutionView and MFAManagementView keep their public signatures by rendering them through the screens. MFAManagementScreen keeps the reauthentication modifier outside the content, so the reauth sheet presents for custom content too.
Wrap each MFA screen's content in one container so the reauthentication sheets and lifecycle modifiers attach once, even when the content returns several views or none. Setting selectedHintIndex resets the challenge again on every tap, a code sent for a hint the user switched away from is dropped, cancelling reauthentication no longer sets errorMessage, and refresh clears it.
704bd6c to
6bd36dc
Compare
Adds public
MFAResolutionScreenandMFAManagementScreen, so apps can draw the MFA sign-in and factor management screens themselves while the library keeps sending codes, resolving sign-in, unenrolling, reauthenticating and reporting errors.Changes
MFAResolutionContentStateorMFAManagementContentState:Bindings for input, closures for actions, plusisLoadingand a localizederrorMessage.DefaultMFAResolutionContentandDefaultMFAManagementContenthold the stock markup, andMFAResolutionViewandMFAManagementViewkeep their signatures by rendering them through the screens.MFAManagementScreenkeeps the reauthentication modifier outside the content, so the reauth sheet still presents for custom content.isLoadingnow also covers theAuthServicesigning in, and the actions ignore repeated taps.API Usage
Preview
Default resolution, custom resolution, default management, custom management:
Maintainer note: Fixes internal CPRN-521