Skip to content

Commit

Permalink
App section (#12420)
Browse files Browse the repository at this point in the history
  • Loading branch information
pragatimodi authored May 6, 2024
1 parent fe9184f commit a6d6884
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,19 @@ enum AuthMenu: String {
case custom
case initRecaptcha
case customAuthDomain

/// More intuitively named getter for `rawValue`.
case getToken
case getTokenForceRefresh
case addAuthStateChangeListener
case removeLastAuthStateChangeListener
case addIdTokenChangeListener
case removeLastIdTokenChangeListener
case verifyClient
case deleteApp

// More intuitively named getter for `rawValue`.
var id: String { rawValue }

/// The UI friendly name of the `AuthMenu`. Used for display.
// The UI friendly name of the `AuthMenu`. Used for display.
var name: String {
switch self {
case .settings:
Expand Down Expand Up @@ -71,11 +79,27 @@ enum AuthMenu: String {
return "Initialize reCAPTCHA Enterprise"
case .customAuthDomain:
return "Set Custom Auth Domain"
case .getToken:
return "Get Token"
case .getTokenForceRefresh:
return "Get Token Force Refresh"
case .addAuthStateChangeListener:
return "Add Auth State Change Listener"
case .removeLastAuthStateChangeListener:
return "Remove Last Auth State Change Listener"
case .addIdTokenChangeListener:
return "Add ID Token Change Listener"
case .removeLastIdTokenChangeListener:
return "Remove Last ID Token Change Listener"
case .verifyClient:
return "Verify Client"
case .deleteApp:
return "Delete App"
}
}

/// Failable initializer to create an `AuthMenu` from it's corresponding `name` value.
/// - Parameter rawValue: String value representing `AuthMenu`'s name or type.
// Failable initializer to create an `AuthMenu` from its corresponding `name` value.
// - Parameter rawValue: String value representing `AuthMenu`'s name or type.
init?(rawValue: String) {
switch rawValue {
case "Settings":
Expand Down Expand Up @@ -110,7 +134,24 @@ enum AuthMenu: String {
self = .initRecaptcha
case "Set Custom Auth Domain":
self = .customAuthDomain
default: return nil
case "Get Token":
self = .getToken
case "Get Token Force Refresh":
self = .getTokenForceRefresh
case "Add Auth State Change Listener":
self = .addAuthStateChangeListener
case "Remove Last Auth State Change Listener":
self = .removeLastAuthStateChangeListener
case "Add ID Token Change Listener":
self = .addIdTokenChangeListener
case "Remove Last ID Token Change Listener":
self = .removeLastIdTokenChangeListener
case "Verify Client":
self = .verifyClient
case "Delete App":
self = .deleteApp
default:
return nil
}
}
}
Expand Down Expand Up @@ -172,9 +213,24 @@ extension AuthMenu: DataSourceProvidable {
return Section(headerDescription: header, items: [item])
}

static var appSection: Section {
let header = "APP"
let items: [Item] = [
Item(title: getToken.name),
Item(title: getTokenForceRefresh.name),
Item(title: addAuthStateChangeListener.name),
Item(title: removeLastAuthStateChangeListener.name),
Item(title: addIdTokenChangeListener.name),
Item(title: removeLastIdTokenChangeListener.name),
Item(title: verifyClient.name),
Item(title: deleteApp.name),
]
return Section(headerDescription: header, items: items)
}

static var sections: [Section] {
[settingsSection, providerSection, emailPasswordSection, otherSection, recaptchaSection,
customAuthDomainSection]
customAuthDomainSection, appSection]
}

static var authLinkSections: [Section] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ extension User: DataSourceProvidable {
// MARK: - UIKit Extensions

public extension UIViewController {
func displayInfo(title: String, message: String, style: UIAlertController.Style) {
let alert = UIAlertController(title: title, message: message, preferredStyle: style)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))

DispatchQueue.main.async { // Ensure UI updates on the main thread
self.present(alert, animated: true, completion: nil)
}
}

func displayError(_ error: Error?, from function: StaticString = #function) {
guard let error = error else { return }
print("ⓧ Error in \(function): \(error.localizedDescription)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

// For Sign in with Facebook
import FBSDKLoginKit
import FirebaseAuth
@testable import FirebaseAuth

// [START auth_import]
import FirebaseCore
Expand All @@ -33,6 +33,8 @@ private let kFacebookAppID = "ENTER APP ID HERE"

class AuthViewController: UIViewController, DataSourceProviderDelegate {
var dataSourceProvider: DataSourceProvider<AuthMenu>!
var authStateDidChangeListeners: [AuthStateDidChangeListenerHandle] = []
var IDTokenDidChangeListeners: [IDTokenDidChangeListenerHandle] = []

override func loadView() {
view = UITableView(frame: .zero, style: .insetGrouped)
Expand Down Expand Up @@ -95,6 +97,30 @@ class AuthViewController: UIViewController, DataSourceProviderDelegate {

case .customAuthDomain:
performCustomAuthDomainFlow()

case .getToken:
getUserTokenResult(force: false)

case .getTokenForceRefresh:
getUserTokenResult(force: true)

case .addAuthStateChangeListener:
addAuthStateListener()

case .removeLastAuthStateChangeListener:
removeAuthStateListener()

case .addIdTokenChangeListener:
addIDTokenListener()

case .removeLastIdTokenChangeListener:
removeIDTokenListener()

case .verifyClient:
verifyClient()

case .deleteApp:
deleteApp()
}
}

Expand Down Expand Up @@ -316,6 +342,142 @@ class AuthViewController: UIViewController, DataSourceProviderDelegate {
present(prompt, animated: true)
}

private func getUserTokenResult(force: Bool) {
guard let currentUser = Auth.auth().currentUser else {
print("Error: No user logged in")
return
}

currentUser.getIDTokenResult(forcingRefresh: force, completion: { tokenResult, error in
if error != nil {
print("Error: Error refreshing token")
return // Handle error case, returning early
}

if let tokenResult = tokenResult, let claims = tokenResult.claims as? [String: Any] {
var message = "Token refresh succeeded\n\n"
for (key, value) in claims {
message += "\(key): \(value)\n"
}
self.displayInfo(title: "Info", message: message, style: .alert)
} else {
print("Error: Unable to access claims.")
}
})
}

private func addAuthStateListener() {
weak var weakSelf = self
let index = authStateDidChangeListeners.count
print("Auth State Did Change Listener #\(index) was added.")
let handle = Auth.auth().addStateDidChangeListener { [weak weakSelf] auth, user in
guard weakSelf != nil else { return }
print("Auth State Did Change Listener #\(index) was invoked on user '\(user?.uid ?? "nil")'")
}
authStateDidChangeListeners.append(handle)
}

private func removeAuthStateListener() {
guard !authStateDidChangeListeners.isEmpty else {
print("No remaining Auth State Did Change Listeners.")
return
}
let index = authStateDidChangeListeners.count - 1
let handle = authStateDidChangeListeners.last!
Auth.auth().removeStateDidChangeListener(handle)
authStateDidChangeListeners.removeLast()
print("Auth State Did Change Listener #\(index) was removed.")
}

private func addIDTokenListener() {
weak var weakSelf = self
let index = IDTokenDidChangeListeners.count
print("ID Token Did Change Listener #\(index) was added.")
let handle = Auth.auth().addIDTokenDidChangeListener { [weak weakSelf] auth, user in
guard weakSelf != nil else { return }
print("ID Token Did Change Listener #\(index) was invoked on user '\(user?.uid ?? "")'.")
}
IDTokenDidChangeListeners.append(handle)
}

func removeIDTokenListener() {
guard !IDTokenDidChangeListeners.isEmpty else {
print("No remaining ID Token Did Change Listeners.")
return
}
let index = IDTokenDidChangeListeners.count - 1
let handle = IDTokenDidChangeListeners.last!
Auth.auth().removeIDTokenDidChangeListener(handle)
IDTokenDidChangeListeners.removeLast()
print("ID Token Did Change Listener #\(index) was removed.")
}

func verifyClient() {
AppManager.shared.auth().tokenManager.getTokenInternal { token, error in
if token == nil {
print("Verify iOS Client failed.")
return
}
let request = VerifyClientRequest(
withAppToken: token?.string,
isSandbox: token?.type == .sandbox,
requestConfiguration: AppManager.shared.auth().requestConfiguration
)

Task {
do {
let verifyResponse = try await AuthBackend.call(with: request)

guard let receipt = verifyResponse.receipt,
let timeoutDate = verifyResponse.suggestedTimeOutDate else {
print("Internal Auth Error: invalid VerifyClientResponse.")
return
}

let timeout = timeoutDate.timeIntervalSinceNow
do {
let credential = await AppManager.shared.auth().appCredentialManager
.didStartVerification(
withReceipt: receipt,
timeout: timeout
)

guard credential.secret != nil else {
print("Failed to receive remote notification to verify App ID.")
return
}

let testPhoneNumber = "+16509964692"
let request = SendVerificationCodeRequest(
phoneNumber: testPhoneNumber,
codeIdentity: CodeIdentity.credential(credential),
requestConfiguration: AppManager.shared.auth().requestConfiguration
)

do {
_ = try await AuthBackend.call(with: request)
print("Verify iOS client succeeded")
} catch {
print("Verify iOS Client failed: \(error.localizedDescription)")
}
}
} catch {
print("Verify iOS Client failed: \(error.localizedDescription)")
}
}
}
}

func deleteApp() {
AppManager.shared.app.delete { success in
if success {
print("App deleted successfully.")
} else {
print("Failed to delete app.")
}
}
}

// MARK: - Private Helpers

private func configureDataSourceProvider() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,15 @@ extension AuthSettings: DataSourceProvidable {

func appCredentialString() -> String {
if let credential = AppManager.shared.auth().appCredentialManager.credential {
let message = "receipt: \(credential.receipt)\nsecret: \(credential.secret)"

showPromptWithTitle("Clear App Credential?", message: message,
showCancelButton: true) { userPressedOK, _ in
if userPressedOK {
AppManager.shared.auth().appCredentialManager.clearCredential()
}
}
}
let truncatedReceipt = truncatedString(string: credential.receipt, length: 13)
let truncatedSecret = truncatedString(string: credential.secret ?? "", length: 13)
return "\(truncatedReceipt)/\(truncatedSecret)"
Expand Down

0 comments on commit a6d6884

Please sign in to comment.