Skip to content

[Auth] TOTP support for macOS #15112

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions FirebaseAuth/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
off of the main actor or main thread.
- [fixed] Simplified completion handler memory management in Auth interop
(#14962).
- [added] Added TOTP support for macOS.

# 11.15.0
- [fixed] Fixed `Sendable` warnings introduced in the Xcode 26 beta. (#14996)
Expand Down
2 changes: 1 addition & 1 deletion FirebaseAuth/README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion FirebaseAuth/Sources/ObjC/FIRMultiFactorConstants.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

#import <TargetConditionals.h>
#if TARGET_OS_IOS
#if TARGET_OS_IOS || TARGET_OS_OSX

#import <Foundation/Foundation.h>

Expand Down
15 changes: 7 additions & 8 deletions FirebaseAuth/Sources/Public/FirebaseAuth/FIRMultiFactor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
17 changes: 17 additions & 0 deletions FirebaseAuth/Sources/Public/FirebaseAuth/FIRTOTPSecret.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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/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,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@class FIRTOTPSecret;
1 change: 1 addition & 0 deletions FirebaseAuth/Sources/Public/FirebaseAuth/FirebaseAuth.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@
#import "FIRGoogleAuthProvider.h"
#import "FIRMultiFactor.h"
#import "FIRPhoneAuthProvider.h"
#import "FIRTOTPSecret.h"
#import "FIRTwitterAuthProvider.h"
8 changes: 4 additions & 4 deletions FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@

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 {}

/// 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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import Foundation

#if os(iOS)
#if os(iOS) || os(macOS)

enum SecretOrID {
case secret(TOTPSecret)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
50 changes: 32 additions & 18 deletions FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPSecret.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -57,24 +61,34 @@ 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.
Expand Down
Loading
Loading