mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
[Auth] TOTP support for macOS (#15112)
Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com>
This commit is contained in:
1 parent
7f3c935bcf
commit
b7db7b4492
26 files changed
+135
-94
No files matched your search
@@ -1,3 +1,6 @@
|
||||
# Unreleased
|
||||
- [added] Added TOTP support for macOS.
|
||||
|
||||
# 12.1.0
|
||||
- [fixed] Fix a formatting issue with generated TOTP URLs that prevented them
|
||||
from working with the Google Authenticator app. (#15128)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Firebase Auth for iOS
|
||||
# Firebase Auth for iOS and macOS
|
||||
|
||||
Firebase Auth enables apps to easily support multiple authentication options
|
||||
for their end users.
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
#import <TargetConditionals.h>
|
||||
#if TARGET_OS_IOS
|
||||
#if TARGET_OS_IOS || TARGET_OS_OSX
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@@ -22,27 +22,26 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/** @typedef FIRMultiFactorSessionCallback
|
||||
@brief The callback that triggered when a developer calls `getSessionWithCompletion`.
|
||||
This type is available on iOS only.
|
||||
This type is available on iOS and macOS.
|
||||
@param session The multi factor session returned, if any.
|
||||
@param error The error which occurred, if any.
|
||||
*/
|
||||
typedef void (^FIRMultiFactorSessionCallback)(FIRMultiFactorSession *_Nullable session,
|
||||
NSError *_Nullable error)
|
||||
NS_SWIFT_UNAVAILABLE("Use Swift's closure syntax instead.")
|
||||
API_UNAVAILABLE(macos, tvos, watchos);
|
||||
NS_SWIFT_UNAVAILABLE("Use Swift's closure syntax instead.") API_UNAVAILABLE(tvos, watchos);
|
||||
|
||||
/**
|
||||
@brief The string identifier for using phone as a second factor.
|
||||
This constant is available on iOS only.
|
||||
This constant is available on iOS and macOS.
|
||||
*/
|
||||
extern NSString *const _Nonnull FIRPhoneMultiFactorID NS_SWIFT_NAME(PhoneMultiFactorID)
|
||||
API_UNAVAILABLE(macos, tvos, watchos);
|
||||
API_UNAVAILABLE(tvos, watchos);
|
||||
|
||||
/**
|
||||
@brief The string identifier for using TOTP as a second factor.
|
||||
This constant is available on iOS only.
|
||||
This constant is available on iOS and macOS.
|
||||
*/
|
||||
extern NSString *const _Nonnull FIRTOTPMultiFactorID NS_SWIFT_NAME(TOTPMultiFactorID)
|
||||
API_UNAVAILABLE(macos, tvos, watchos);
|
||||
API_UNAVAILABLE(tvos, watchos);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -99,9 +99,7 @@ final class AuthBackend: AuthBackendProtocol {
|
||||
}
|
||||
|
||||
private static func generateMFAError(response: AuthRPCResponse, auth: Auth) -> Error? {
|
||||
#if !os(iOS)
|
||||
return nil
|
||||
#else
|
||||
#if os(iOS) || os(macOS)
|
||||
if let mfaResponse = response as? AuthMFAResponse,
|
||||
mfaResponse.idToken == nil,
|
||||
let enrollments = mfaResponse.mfaInfo {
|
||||
@@ -124,7 +122,9 @@ final class AuthBackend: AuthBackendProtocol {
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
#endif // !os(iOS)
|
||||
#else
|
||||
return nil
|
||||
#endif // os(iOS) || os(macOS)
|
||||
}
|
||||
|
||||
// Check whether or not the successful response is actually the special case phone
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
|
||||
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
|
||||
extension MultiFactor: NSSecureCoding {}
|
||||
@@ -22,7 +22,7 @@ import Foundation
|
||||
/// The interface defining the multi factor related properties and operations pertaining to a
|
||||
/// user.
|
||||
///
|
||||
/// This class is available on iOS only.
|
||||
/// This class is available on iOS and macOS.
|
||||
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
|
||||
@objc(FIRMultiFactor) open class MultiFactor: NSObject {
|
||||
@objc open var enrolledFactors: [MultiFactorInfo]
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
|
||||
/// The base class for asserting ownership of a second factor. This is equivalent to the
|
||||
/// AuthCredential class.
|
||||
///
|
||||
/// This class is available on iOS only.
|
||||
/// This class is available on iOS and macOS.
|
||||
@objc(FIRMultiFactorAssertion) open class MultiFactorAssertion: NSObject {
|
||||
/// The second factor identifier for this opaque object asserting a second factor.
|
||||
@objc open var factorID: String
|
||||
|
||||
@@ -16,12 +16,12 @@ import Foundation
|
||||
|
||||
// TODO(Swift 6 Breaking): Make checked Sendable.
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
extension MultiFactorInfo: NSSecureCoding {}
|
||||
|
||||
/// Safe public structure used to represent a second factor entity from a client perspective.
|
||||
///
|
||||
/// This class is available on iOS only.
|
||||
/// This class is available on iOS and macOS.
|
||||
@objc(FIRMultiFactorInfo) open class MultiFactorInfo: NSObject, @unchecked Sendable {
|
||||
/// The multi-factor enrollment ID.
|
||||
@objc(UID) public let uid: String
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
|
||||
/// The subclass of base class `MultiFactorAssertion`, used to assert ownership of a phone
|
||||
/// second factor.
|
||||
///
|
||||
/// This class is available on iOS only.
|
||||
/// This class is available on iOS and macOS.
|
||||
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
|
||||
@objc(FIRMultiFactorResolver)
|
||||
open class MultiFactorResolver: NSObject {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
|
||||
/// Opaque object that identifies the current session to enroll a second factor or to
|
||||
/// complete sign in when previously enrolled.
|
||||
@@ -23,7 +23,7 @@ import Foundation
|
||||
/// or to complete sign in when previously enrolled. It contains additional context on the
|
||||
/// existing user, notably the confirmation that the user passed the first factor challenge.
|
||||
///
|
||||
/// This class is available on iOS only.
|
||||
/// This class is available on iOS and macOS.
|
||||
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
|
||||
@objc(FIRMultiFactorSession) open class MultiFactorSession: NSObject {
|
||||
/// The ID token for an enroll flow. This has to be retrieved after recent authentication.
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
|
||||
/// The subclass of base class FIRMultiFactorAssertion, used to assert ownership of a phone
|
||||
/// second factor.
|
||||
///
|
||||
/// This class is available on iOS only.
|
||||
/// This class is available on iOS and macOS.
|
||||
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
|
||||
@objc(FIRPhoneMultiFactorAssertion) open class PhoneMultiFactorAssertion: MultiFactorAssertion {
|
||||
var authCredential: PhoneAuthCredential?
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
|
||||
/// The data structure used to help initialize an assertion for a second factor entity to the
|
||||
/// Firebase Auth/CICP server.
|
||||
///
|
||||
/// Depending on the type of second factor, this will help generate the assertion.
|
||||
///
|
||||
/// This class is available on iOS only.
|
||||
/// This class is available on iOS and macOS.
|
||||
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
|
||||
@objc(FIRPhoneMultiFactorGenerator)
|
||||
open class PhoneMultiFactorGenerator: NSObject {
|
||||
|
||||
@@ -16,13 +16,13 @@ import Foundation
|
||||
|
||||
// TODO(Swift 6 Breaking): Make checked Sendable.
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
|
||||
/// Extends the MultiFactorInfo class for phone number second factors.
|
||||
///
|
||||
/// The identifier of this second factor is "phone".
|
||||
///
|
||||
/// This class is available on iOS only.
|
||||
/// This class is available on iOS and macOS.
|
||||
@objc(FIRPhoneMultiFactorInfo) open class PhoneMultiFactorInfo: MultiFactorInfo,
|
||||
@unchecked Sendable {
|
||||
/// The string identifier for using phone as a second factor.
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
|
||||
enum SecretOrID {
|
||||
case secret(TOTPSecret)
|
||||
@@ -24,7 +24,7 @@ import Foundation
|
||||
/// The subclass of base class MultiFactorAssertion, used to assert ownership of a TOTP
|
||||
/// (Time-based One Time Password) second factor.
|
||||
///
|
||||
/// This class is available on iOS only.
|
||||
/// This class is available on iOS and macOS.
|
||||
@objc(FIRTOTPMultiFactorAssertion) open class TOTPMultiFactorAssertion: MultiFactorAssertion {
|
||||
let oneTimePassword: String
|
||||
let secretOrID: SecretOrID
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
|
||||
/// The data structure used to help initialize an assertion for a second factor entity to the
|
||||
/// Firebase Auth/CICP server. Depending on the type of second factor, this will help generate
|
||||
/// the assertion.
|
||||
///
|
||||
/// This class is available on iOS only.
|
||||
/// This class is available on iOS and macOS.
|
||||
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
|
||||
@objc(FIRTOTPMultiFactorGenerator) open class TOTPMultiFactorGenerator: NSObject {
|
||||
/// Creates a TOTP secret as part of enrolling a TOTP second factor. Used for generating a
|
||||
|
||||
@@ -17,13 +17,13 @@ import Foundation
|
||||
// TODO(Swift 6 Breaking): Make checked Sendable. Also, does this need
|
||||
// to be public?
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
|
||||
/// Extends the MultiFactorInfo class for time based one-time password second factors.
|
||||
///
|
||||
/// The identifier of this second factor is "totp".
|
||||
///
|
||||
/// This class is available on iOS only.
|
||||
/// This class is available on iOS and macOS.
|
||||
class TOTPMultiFactorInfo: MultiFactorInfo, @unchecked Sendable {
|
||||
/// Initialize the AuthProtoMFAEnrollment instance with proto.
|
||||
/// - Parameter proto: AuthProtoMFAEnrollment proto object.
|
||||
|
||||
@@ -19,13 +19,17 @@ import Foundation
|
||||
internal import GoogleUtilities_Environment
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#if os(iOS) || os(macOS)
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#elseif os(macOS)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
/// The subclass of base class MultiFactorAssertion, used to assert ownership of a TOTP
|
||||
/// (Time-based One Time Password) second factor.
|
||||
///
|
||||
/// This class is available on iOS only.
|
||||
/// This class is available on iOS and macOS.
|
||||
@objc(FIRTOTPSecret) open class TOTPSecret: NSObject {
|
||||
/// Returns the shared secret key/seed used to generate time-based one-time passwords.
|
||||
@objc open func sharedSecretKey() -> String {
|
||||
@@ -57,24 +61,33 @@ import Foundation
|
||||
@MainActor @objc(openInOTPAppWithQRCodeURL:)
|
||||
open func openInOTPApp(withQRCodeURL qrCodeURL: String) {
|
||||
if GULAppEnvironmentUtil.isAppExtension() {
|
||||
// iOS App extensions should not call [UIApplication sharedApplication], even if
|
||||
// UIApplication responds to it.
|
||||
// App extensions should not call [UIApplication sharedApplication] or [NSWorkspace
|
||||
// sharedWorkspace], even if they respond to it.
|
||||
return
|
||||
}
|
||||
|
||||
// Using reflection here to avoid build errors in extensions.
|
||||
let sel = NSSelectorFromString("sharedApplication")
|
||||
guard UIApplication.responds(to: sel),
|
||||
let rawApplication = UIApplication.perform(sel),
|
||||
let application = rawApplication.takeUnretainedValue() as? UIApplication else {
|
||||
return
|
||||
}
|
||||
if let url = URL(string: qrCodeURL), application.canOpenURL(url) {
|
||||
application.open(url, options: [:], completionHandler: nil)
|
||||
} else {
|
||||
AuthLog.logError(code: "I-AUT000019",
|
||||
message: "URL: \(qrCodeURL) cannot be opened")
|
||||
}
|
||||
#if os(iOS)
|
||||
// Using reflection here to avoid build errors in extensions.
|
||||
let sel = NSSelectorFromString("sharedApplication")
|
||||
guard UIApplication.responds(to: sel),
|
||||
let rawApplication = UIApplication.perform(sel),
|
||||
let application = rawApplication.takeUnretainedValue() as? UIApplication else {
|
||||
return
|
||||
}
|
||||
if let url = URL(string: qrCodeURL), application.canOpenURL(url) {
|
||||
application.open(url, options: [:], completionHandler: nil)
|
||||
} else {
|
||||
AuthLog.logError(code: "I-AUT000019",
|
||||
message: "URL: \(qrCodeURL) cannot be opened")
|
||||
}
|
||||
#elseif os(macOS)
|
||||
if let url = URL(string: qrCodeURL) {
|
||||
NSWorkspace.shared.open(url)
|
||||
} else {
|
||||
AuthLog.logError(code: "I-AUT000019",
|
||||
message: "URL: \(qrCodeURL) cannot be opened")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Shared secret key/seed used for enrolling in TOTP MFA and generating OTPs.
|
||||
|
||||
@@ -58,10 +58,10 @@ extension User: NSSecureCoding {}
|
||||
/// The tenant ID of the current user. `nil` if none is available.
|
||||
@objc public private(set) var tenantID: String?
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
/// Multi factor object associated with the user.
|
||||
///
|
||||
/// This property is available on iOS only.
|
||||
/// This property is available on iOS and macOS.
|
||||
@objc public private(set) var multiFactor: MultiFactor
|
||||
#endif
|
||||
|
||||
@@ -1066,7 +1066,7 @@ extension User: NSSecureCoding {}
|
||||
isEmailVerified = false
|
||||
metadata = UserMetadata(withCreationDate: nil, lastSignInDate: nil)
|
||||
tenantID = nil
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
multiFactor = MultiFactor(withMFAEnrollments: [])
|
||||
#endif
|
||||
uid = ""
|
||||
@@ -1297,7 +1297,7 @@ extension User: NSSecureCoding {}
|
||||
}
|
||||
}
|
||||
providerDataRaw = providerData
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
if let enrollments = user.mfaEnrollments {
|
||||
multiFactor = MultiFactor(withMFAEnrollments: enrollments)
|
||||
}
|
||||
@@ -1718,7 +1718,7 @@ extension User: NSSecureCoding {}
|
||||
coder.encode(auth.requestConfiguration.appID, forKey: kFirebaseAppIDCodingKey)
|
||||
}
|
||||
coder.encode(tokenService, forKey: kTokenServiceCodingKey)
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
coder.encode(multiFactor, forKey: kMultiFactorCodingKey)
|
||||
#endif
|
||||
}
|
||||
@@ -1747,7 +1747,7 @@ extension User: NSSecureCoding {}
|
||||
as? [String: UserInfoImpl]
|
||||
let metadata = coder.decodeObject(of: UserMetadata.self, forKey: kMetadataCodingKey)
|
||||
let tenantID = coder.decodeObject(of: NSString.self, forKey: kTenantIDCodingKey) as? String
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
let multiFactor = coder.decodeObject(of: MultiFactor.self, forKey: kMultiFactorCodingKey)
|
||||
#endif
|
||||
self.tokenService = tokenService
|
||||
@@ -1778,7 +1778,7 @@ extension User: NSSecureCoding {}
|
||||
backend = AuthBackend(rpcIssuer: AuthBackendRPCIssuer())
|
||||
|
||||
userProfileUpdate = UserProfileUpdate()
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
self.multiFactor = multiFactor ?? MultiFactor()
|
||||
super.init()
|
||||
multiFactor?.user = self
|
||||
|
||||
@@ -568,7 +568,7 @@ class AuthErrorUtils {
|
||||
return error(code: .blockingCloudFunctionError, message: errorMessage)
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
static func secondFactorRequiredError(pendingCredential: String?,
|
||||
hints: [MultiFactorInfo],
|
||||
auth: Auth)
|
||||
@@ -581,7 +581,7 @@ class AuthErrorUtils {
|
||||
|
||||
return error(code: .secondFactorRequired, userInfo: userInfo)
|
||||
}
|
||||
#endif // os(iOS)
|
||||
#endif // os(iOS) || os(macOS)
|
||||
|
||||
static func recaptchaSDKNotLinkedError() -> Error {
|
||||
// TODO(ObjC): point the link to GCIP doc once available.
|
||||
|
||||
@@ -372,7 +372,7 @@
|
||||
accessToken:@"token"];
|
||||
}
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#if TARGET_OS_IOS || TARGET_OS_OSX
|
||||
- (void)FIRMultiFactor_h:(FIRMultiFactor *)mf mfa:(FIRMultiFactorAssertion *)mfa {
|
||||
[mf getSessionWithCompletion:^(FIRMultiFactorSession *_Nullable credential,
|
||||
NSError *_Nullable error){
|
||||
@@ -466,7 +466,9 @@
|
||||
- (void)phoneMultiFactorInfo:(FIRPhoneMultiFactorInfo *)info {
|
||||
__unused NSString *s = [info phoneNumber];
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TARGET_OS_IOS || TARGET_OS_OSX
|
||||
- (void)FIRTOTPSecret_h:(FIRTOTPSecret *)secret {
|
||||
NSString *s = [secret sharedSecretKey];
|
||||
s = [secret generateQRCodeURLWithAccountName:@"name" issuer:@"issuer"];
|
||||
@@ -571,7 +573,7 @@
|
||||
b = [user isEmailVerified];
|
||||
__unused NSArray<NSObject<FIRUserInfo> *> *userInfo = [user providerData];
|
||||
__unused FIRUserMetadata *meta = [user metadata];
|
||||
#if TARGET_OS_IOS
|
||||
#if TARGET_OS_IOS || TARGET_OS_OSX
|
||||
__unused FIRMultiFactor *mf = [user multiFactor];
|
||||
#endif
|
||||
NSString *s = [user refreshToken];
|
||||
|
||||
@@ -41,9 +41,11 @@
|
||||
s = FIRGoogleAuthSignInMethod;
|
||||
#if TARGET_OS_IOS
|
||||
s = FIRPhoneMultiFactorID;
|
||||
s = FIRTOTPMultiFactorID;
|
||||
s = FIRPhoneAuthProviderID;
|
||||
s = FIRPhoneAuthSignInMethod;
|
||||
#endif
|
||||
#if TARGET_OS_IOS || TARGET_OS_OSX
|
||||
s = FIRTOTPMultiFactorID;
|
||||
#endif
|
||||
s = FIRTwitterAuthProviderID;
|
||||
s = FIRTwitterAuthSignInMethod;
|
||||
|
||||
@@ -548,7 +548,9 @@ class AuthAPI_hOnlyTests: XCTestCase {
|
||||
func phoneMultiFactorInfo(mfi: PhoneMultiFactorInfo) {
|
||||
let _: String = mfi.phoneNumber
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(iOS) || os(macOS)
|
||||
func FIRTOTPSecret_h(session: MultiFactorSession) async throws {
|
||||
let obj = try await TOTPMultiFactorGenerator.generateSecret(with: session)
|
||||
_ = obj.sharedSecretKey()
|
||||
@@ -638,7 +640,7 @@ class AuthAPI_hOnlyTests: XCTestCase {
|
||||
let _: Bool = user.isEmailVerified
|
||||
let _: [UserInfo] = user.providerData
|
||||
let _: UserMetadata = user.metadata
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
let _: MultiFactor = user.multiFactor
|
||||
#endif
|
||||
if let _: String = user.refreshToken,
|
||||
|
||||
@@ -38,9 +38,10 @@ class SwiftGlobalTests: XCTestCase {
|
||||
let _: String = GitHubAuthSignInMethod
|
||||
let _: String = GoogleAuthProviderID
|
||||
let _: String = GoogleAuthSignInMethod
|
||||
#if os(iOS)
|
||||
let _: String = PhoneMultiFactorID
|
||||
#if os(iOS) || os(macOS)
|
||||
let _: String = TOTPMultiFactorID
|
||||
#endif
|
||||
#if os(iOS)
|
||||
let _: String = PhoneAuthProviderID
|
||||
let _: String = PhoneAuthSignInMethod
|
||||
#endif
|
||||
|
||||
@@ -135,6 +135,27 @@ class UserTests: RPCBaseTests {
|
||||
])
|
||||
#endif
|
||||
|
||||
var mfaInfo: [[AnyHashable: AnyHashable]] = []
|
||||
|
||||
#if os(iOS)
|
||||
mfaInfo.append([
|
||||
"phoneInfo": kPhoneInfo,
|
||||
"mfaEnrollmentId": kEnrollmentID,
|
||||
"displayName": kDisplayName,
|
||||
"enrolledAt": kEnrolledAt,
|
||||
])
|
||||
#endif
|
||||
|
||||
#if os(iOS) || os(macOS)
|
||||
mfaInfo.append([
|
||||
// In practice, this will be an empty dictionary.
|
||||
"totpInfo": [AnyHashable: AnyHashable](),
|
||||
"mfaEnrollmentId": kEnrollmentID,
|
||||
"displayName": kDisplayName,
|
||||
"enrolledAt": kEnrolledAt,
|
||||
])
|
||||
#endif
|
||||
|
||||
rpcIssuer?.fakeGetAccountProviderJSON = [[
|
||||
kProviderUserInfoKey: providerUserInfos,
|
||||
kLocalIDKey: kLocalID,
|
||||
@@ -146,21 +167,7 @@ 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,
|
||||
],
|
||||
[
|
||||
// In practice, this will be an empty dictionary.
|
||||
"totpInfo": [AnyHashable: AnyHashable](),
|
||||
"mfaEnrollmentId": kEnrollmentID,
|
||||
"displayName": kDisplayName,
|
||||
"enrolledAt": kEnrolledAt,
|
||||
] as [AnyHashable: AnyHashable],
|
||||
],
|
||||
"mfaInfo": mfaInfo,
|
||||
]]
|
||||
|
||||
let expectation = self.expectation(description: #function)
|
||||
@@ -247,9 +254,12 @@ class UserTests: RPCBaseTests {
|
||||
var encodedClasses = [User.self, NSDictionary.self, NSURL.self, SecureTokenService.self,
|
||||
UserInfoImpl.self, NSDate.self, UserMetadata.self, NSString.self,
|
||||
NSArray.self]
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(macOS)
|
||||
encodedClasses.append(MultiFactor.self)
|
||||
encodedClasses.append(PhoneMultiFactorInfo.self)
|
||||
encodedClasses.append(TOTPMultiFactorInfo.self)
|
||||
#if os(iOS)
|
||||
encodedClasses.append(PhoneMultiFactorInfo.self)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
let unarchivedUser = try XCTUnwrap(NSKeyedUnarchiver.unarchivedObject(
|
||||
@@ -370,6 +380,17 @@ class UserTests: RPCBaseTests {
|
||||
XCTAssertEqual("\(date)", kEnrolledAtMatch)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
// Verify TOTP MultiFactorInfo properties.
|
||||
let enrolledFactors = try XCTUnwrap(user.multiFactor.enrolledFactors)
|
||||
XCTAssertEqual(enrolledFactors.count, 1)
|
||||
XCTAssertEqual(enrolledFactors[0].factorID, PhoneMultiFactorInfo.TOTPMultiFactorID)
|
||||
XCTAssertEqual(enrolledFactors[0].uid, kEnrollmentID)
|
||||
XCTAssertEqual(enrolledFactors[0].displayName, self.kDisplayName)
|
||||
let date = try XCTUnwrap(enrolledFactors[0].enrollmentDate)
|
||||
XCTAssertEqual("\(date)", kEnrolledAtMatch)
|
||||
#endif
|
||||
} catch {
|
||||
XCTFail("Caught an error in \(#function): \(error)")
|
||||
}
|
||||
|
||||
@@ -12,13 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#if os(iOS) || targetEnvironment(macCatalyst)
|
||||
#if os(iOS) || targetEnvironment(macCatalyst) || os(macOS)
|
||||
|
||||
import Combine
|
||||
import FirebaseAuth
|
||||
|
||||
@available(iOS 13.0, macCatalyst 13.0, *)
|
||||
@available(macOS, unavailable)
|
||||
@available(iOS 13.0, macCatalyst 13.0, macOS 10.15, *)
|
||||
@available(tvOS, unavailable)
|
||||
@available(watchOS, unavailable)
|
||||
public extension MultiFactor {
|
||||
@@ -111,4 +110,4 @@
|
||||
}
|
||||
}
|
||||
|
||||
#endif // os(iOS) || targetEnvironment(macCatalyst)
|
||||
#endif // os(iOS) || targetEnvironment(macCatalyst) || os(macOS)
|
||||
@@ -12,13 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#if os(iOS) || targetEnvironment(macCatalyst)
|
||||
#if os(iOS) || targetEnvironment(macCatalyst) || os(macOS)
|
||||
|
||||
import Combine
|
||||
import FirebaseAuth
|
||||
|
||||
@available(iOS 13.0, macCatalyst 13.0, *)
|
||||
@available(macOS, unavailable)
|
||||
@available(iOS 13.0, macCatalyst 13.0, macOS 10.15, *)
|
||||
@available(tvOS, unavailable)
|
||||
@available(watchOS, unavailable)
|
||||
public extension MultiFactorResolver {
|
||||
@@ -43,4 +42,4 @@
|
||||
}
|
||||
}
|
||||
|
||||
#endif // os(iOS) || targetEnvironment(macCatalyst)
|
||||
#endif // os(iOS) || targetEnvironment(macCatalyst) || os(macOS)
|
||||
Reference in new issue
Block a user