Repository: firebase/firebase-ios-sdk (language: cpp)

Issue:
Critical bug FirebaseAuth.TOTPMultiFactorInfo in v11.0.0 - v11.1.0
### Description

I get this error when I login with TOTP -> 

`Class 'FirebaseAuth.TOTPMultiFactorInfo' has a superclass that supports secure coding, but 'FirebaseAuth.TOTPMultiFactorInfo' overrides -initWithCoder: and does not override +supportsSecureCoding. The class must implement +supportsSecureCoding and return YES to verify that its implementation of -initWithCoder: is secure coding compliant.`

I tested it in versions 11.0.0 - 11.1.0 and I use Google Authenticator. I had to downgrade to 10.29.0 in order to login with TOTP.

### Reproducing the issue

Install v11.0.0 and sign in with TOTPMultiFactorInfo (Authenticator app). 

### Firebase SDK Version

11.0.0-11.1.0

### Xcode Version

15.4

### Installation Method

CocoaPods

### Firebase Product(s)

Authentication

### Targeted Platforms

iOS

### Relevant Log Output

```shell
Class 'FirebaseAuth.TOTPMultiFactorInfo' has a superclass that supports secure coding, but 'FirebaseAuth.TOTPMultiFactorInfo' overrides -initWithCoder: and does not override +supportsSecureCoding. The class must implement +supportsSecureCoding and return YES to verify that its implementation of -initWithCoder: is secure coding compliant.
```

### If using CocoaPods, the project's Podfile.lock

<!--- Look below for instructions on how to share your Podfile.lock. --->

<details>
<summary>Expand <code>Podfile.lock</code> snippet</summary>
<br>

```yml

- Firebase/AnalyticsWithoutAdIdSupport (11.0.0):
    - Firebase/CoreOnly
    - FirebaseAnalytics/WithoutAdIdSupport (~> 11.0.0)
  - Firebase/CoreOnly (11.0.0):
    - FirebaseCore (= 11.0.0)
  - FirebaseABTesting (11.1.0):
    - FirebaseCore (~> 11.0)
  - FirebaseAnalytics/WithoutAdIdSupport (11.0.0):
    - FirebaseCore (~> 11.0)
    - FirebaseInstallations (~> 11.0)
    - GoogleAppMeasurement/WithoutAdIdSupport (= 11.0.0)
    - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
    - GoogleUtilities/MethodSwizzler (~> 8.0)
    - GoogleUtilities/Network (~> 8.0)
    - "GoogleUtilities/NSData+zlib (~> 8.0)"
    - nanopb (~> 3.30910.0)
  - FirebaseAppCheckInterop (11.1.0)
  - FirebaseAuth (11.0.0):
    - FirebaseAppCheckInterop (~> 11.0)
    - FirebaseAuthInterop (~> 11.0)
    - FirebaseCore (~> 11.0)
    - FirebaseCoreExtension (~> 11.0)
    - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
    - GoogleUtilities/Environment (~> 8.0)
    - GTMSessionFetcher/Core (~> 3.4)
    - RecaptchaInterop (~> 100.0)
  - FirebaseAuthInterop (11.1.0)
  - FirebaseCore (11.0.0):
    - FirebaseCoreInternal (~> 11.0)
    - GoogleUtilities/Environment (~> 8.0)
    - GoogleUtilities/Logger (~> 8.0)
  - FirebaseCoreExtension (11.1.0):
    - FirebaseCore (~> 11.0)
  - FirebaseCoreInternal (11.1.0):
    - "GoogleUtilities/NSData+zlib (~> 8.0)"
  - FirebaseCrashlytics (11.0.0):
    - FirebaseCore (~> 11.0)
    - FirebaseInstallations (~> 11.0)
    - FirebaseRemoteConfigInterop (~> 11.0)
    - FirebaseSessions (~> 11.0)
    - GoogleDataTransport (~> 10.0)
    - GoogleUtilities/Environment (~> 8.0)
    - nanopb (~> 3.30910.0)
    - PromisesObjC (~> 2.4)
  - FirebaseInstallations (11.1.0):
    - FirebaseCore (~> 11.0)
    - GoogleUtilities/Environment (~> 8.0)
    - GoogleUtilities/UserDefaults (~> 8.0)
    - PromisesObjC (~> 2.4)
  - FirebaseMessaging (11.0.0):
    - FirebaseCore (~> 11.0)
    - FirebaseInstallations (~> 11.0)
    - GoogleDataTransport (~> 10.0)
    - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
    - GoogleUtilities/Environment (~> 8.0)
    - GoogleUtilities/Reachability (~> 8.0)
    - GoogleUtilities/UserDefaults (~> 8.0)
    - nanopb (~> 3.30910.0)
  - FirebasePerformance (11.0.0):
    - FirebaseCore (~> 11.0)
    - FirebaseInstallations (~> 11.0)
    - FirebaseRemoteConfig (~> 11.0)
    - FirebaseSessions (~> 11.0)
    - GoogleDataTransport (~> 10.0)
    - GoogleUtilities/Environment (~> 8.0)
    - GoogleUtilities/MethodSwizzler (~> 8.0)
    - GoogleUtilities/UserDefaults (~> 8.0)
    - nanopb (~> 3.30910.0)
  - FirebaseRemoteConfig (11.1.0):
    - FirebaseABTesting (~> 11.0)
    - FirebaseCore (~> 11.0)
    - FirebaseInstallations (~> 11.0)
    - FirebaseRemoteConfigInterop (~> 11.0)
    - FirebaseSharedSwift (~> 11.0)
    - GoogleUtilities/Environment (~> 8.0)
    - "GoogleUtilities/NSData+zlib (~> 8.0)"
  - FirebaseRemoteConfigInterop (11.1.0)
  - FirebaseSessions (11.1.0):
    - FirebaseCore (~> 11.0)
    - FirebaseCoreExtension (~> 11.0)
    - FirebaseInstallations (~> 11.0)
    - GoogleDataTransport (~> 10.0)
    - GoogleUtilities/Environment (~> 8.0)
    - GoogleUtilities/UserDefaults (~> 8.0)
    - nanopb (~> 3.30910.0)
    - PromisesSwift (~> 2.1)
  - FirebaseSharedSwift (11.1.0)

```

</details>



Discussion / hints:
I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

Fix patch:
diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md
index 96771bafd05..81388ecef86 100644
--- a/FirebaseAuth/CHANGELOG.md
+++ b/FirebaseAuth/CHANGELOG.md
@@ -7,6 +7,9 @@
   in methods and a `nil` error. In such cases, an empty array is instead
   returned with the `nil` error. (#13550)
 - [Fixed] Fixed user session persistence in multi tenant projects. Introduced in 11.0.0. (#13565)
+- [Fixed] Fixed encoding crash that occurs when using TOTP multi-factor
+  authentication. Note that this fix will not be in the 11.2.0 zip and Carthage
+  distributions, but will be included from 11.3.0 onwards. (#13591)
 
 # 11.1.0
 - [fixed] Fixed `Swift.error` conformance for `AuthErrorCode`. (#13430)
diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift
index e742cf8cf77..494252ac56e 100644
--- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift
+++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift
@@ -16,6 +16,8 @@ import Foundation
 
 class AuthProtoMFAEnrollment: NSObject, AuthProto {
   let phoneInfo: String?
+  // In practice, this will be an empty dictionary. The presence of which
+  // indicates TOTP MFA enrollment rather than phone MFA enrollment.
   let totpInfo: NSObject?
   let mfaEnrollmentID: String?
   let displayName: String?
diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift
index 2273b2aea9e..b1c3f7b7d95 100644
--- a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift
+++ b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift
@@ -22,19 +22,18 @@ import Foundation
   ///
   /// This class is available on iOS only.
   class TOTPMultiFactorInfo: MultiFactorInfo {
-    /// This is the totp info for the second factor.
-    let totpInfo: NSObject?
-
     /// Initialize the AuthProtoMFAEnrollment instance with proto.
     /// - Parameter proto: AuthProtoMFAEnrollment proto object.
     init(proto: AuthProtoMFAEnrollment) {
-      totpInfo = proto.totpInfo
       super.init(proto: proto, factorID: PhoneMultiFactorInfo.TOTPMultiFactorID)
     }
 
-    @available(*, unavailable)
     required init?(coder: NSCoder) {
-      fatalError("init(coder:) has not been implemented")
+      super.init(coder: coder)
+    }
+
+    override class var supportsSecureCoding: Bool {
+      super.supportsSecureCoding
     }
   }
 #endif
diff --git a/FirebaseAuth/Tests/Unit/UserTests.swift b/FirebaseAuth/Tests/Unit/UserTests.swift
index 619efac790a..77c1449792f 100644
--- a/FirebaseAuth/Tests/Unit/UserTests.swift
+++ b/FirebaseAuth/Tests/Unit/UserTests.swift
@@ -141,12 +141,21 @@ class UserTests: RPCBaseTests {
       "phoneNumber": kPhoneNumber,
       "createdAt": String(Int(kCreationDateTimeIntervalInSeconds) * 1000), // to nanoseconds
       "lastLoginAt": String(Int(kLastSignInDateTimeIntervalInSeconds) * 1000),
-      "mfaInfo": [[
-        "phoneInfo": kPhoneInfo,
-        "mfaEnrollmentId": kEnrollmentID,
-        "displayName": kDisplayName,
-        "enrolledAt": kEnrolledAt,
-      ]],
+      "mfaInfo": [
+        [
+          "phoneInfo": kPhoneInfo,
+          "mfaEnrollmentId": kEnrollmentID,
+          "displayName": kDisplayName,
+          "enrolledAt": kEnrolledAt,
+        ],
+        [
+          // In practice, this will be an empty dictionary.
+          "totpInfo": [AnyHashable: AnyHashable](),
+          "mfaEnrollmentId": kEnrollmentID,
+          "displayName": kDisplayName,
+          "enrolledAt": kEnrolledAt,
+        ] as [AnyHashable: AnyHashable],
+      ],
     ]]
 
     let expectation = self.expectation(description: #function)
@@ -346,11 +355,15 @@ class UserTests: RPCBaseTests {
 
           // Verify MultiFactorInfo properties.
           let enrolledFactors = try XCTUnwrap(user.multiFactor.enrolledFactors)
+          XCTAssertEqual(enrolledFactors.count, 2)
           XCTAssertEqual(enrolledFactors[0].factorID, PhoneMultiFactorInfo.PhoneMultiFactorID)
-          XCTAssertEqual(enrolledFactors[0].uid, kEnrollmentID)
-          XCTAssertEqual(enrolledFactors[0].displayName, self.kDisplayName)
-          let date = try XCTUnwrap(enrolledFactors[0].enrollmentDate)
-          XCTAssertEqual("\(date)", kEnrolledAtMatch)
+          XCTAssertEqual(enrolledFactors[1].factorID, PhoneMultiFactorInfo.TOTPMultiFactorID)
+          for enrolledFactor in enrolledFactors {
+            XCTAssertEqual(enrolledFactor.uid, kEnrollmentID)
+            XCTAssertEqual(enrolledFactor.displayName, self.kDisplayName)
+            let date = try XCTUnwrap(enrolledFactor.enrollmentDate)
+            XCTAssertEqual("\(date)", kEnrolledAtMatch)
+          }
         #endif
       } catch {
         XCTFail("Caught an error in \(#function): \(error)")


Test patch: