Skip to content
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

add mfa section #12466

Merged
merged 54 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
2964dfe
Add PhoneAuth details in Settings view
pragatimodi Jan 29, 2024
c594c19
undo SwiftApplication.plist changes
pragatimodi Jan 29, 2024
354a6ef
undo SwiftApplication.plist changes
pragatimodi Jan 29, 2024
3ab2ac8
undo SwiftApplication.plist changes
pragatimodi Jan 29, 2024
ae27388
clang formatting
pragatimodi Jan 29, 2024
e057b17
game center auth login
pragatimodi Feb 6, 2024
b38a988
lint
pragatimodi Feb 6, 2024
c4d8943
correct method name
pragatimodi Feb 6, 2024
121c656
add game center icon
pragatimodi Feb 6, 2024
e6b298f
add game center account linking
pragatimodi Feb 6, 2024
46e2b62
Merge branch 'game-center' of https://github.com/firebase/firebase-io…
pragatimodi Feb 6, 2024
417168b
lint
pragatimodi Feb 6, 2024
660551c
add app-section
pragatimodi Feb 23, 2024
c704b1a
undo accidental changes
pragatimodi Feb 23, 2024
22272f2
lint fixes
pragatimodi Feb 23, 2024
dd5d5a9
add game center to AuthMenu
pragatimodi Feb 23, 2024
297cb2f
add OOB section
pragatimodi Feb 23, 2024
cd79c97
add verifyClient() method
pragatimodi Feb 24, 2024
63ee2ab
Merge branch 'app-section' of https://github.com/firebase/firebase-io…
pragatimodi Feb 24, 2024
70d6b8d
add oob methods
pragatimodi Feb 28, 2024
4716a45
add oob section methods and Section modifiers
pragatimodi Feb 29, 2024
d8467e4
change ordering
pragatimodi Feb 29, 2024
a071a7c
add mfa section
pragatimodi Mar 4, 2024
d06a7a4
add error prompts
pragatimodi Mar 5, 2024
7bf243d
fix .plist line deletion
pragatimodi Mar 12, 2024
9b5639c
fix unit test
pragatimodi Mar 12, 2024
4f8fbb4
Merge branch 'auth-swift' of https://github.com/firebase/firebase-ios…
pragatimodi Mar 18, 2024
0471e5d
Merge branch 'auth-swift' of https://github.com/firebase/firebase-ios…
pragatimodi Mar 18, 2024
72d213d
Merge branch 'settings-phoneauth' of https://github.com/firebase/fire…
pragatimodi Mar 18, 2024
50d308a
Merge branch 'auth-swift' of https://github.com/firebase/firebase-ios…
pragatimodi Apr 3, 2024
ffcee37
Merge branch 'game-center' of https://github.com/firebase/firebase-io…
pragatimodi Apr 3, 2024
db885d7
style.sh changes
pragatimodi Apr 3, 2024
19b6c57
remove authMenu test
pragatimodi Apr 4, 2024
b55b1f1
style
pragatimodi Apr 4, 2024
7235386
comment out AuthMenu UI test
pragatimodi Apr 4, 2024
1c331eb
cleanup
pragatimodi Apr 4, 2024
8171aca
Merge branch 'game-center' of https://github.com/firebase/firebase-io…
pragatimodi Apr 4, 2024
d736fcb
Merge branch 'app-section' of https://github.com/firebase/firebase-io…
pragatimodi Apr 4, 2024
9645371
Merge branch 'oob-section' of https://github.com/firebase/firebase-io…
pragatimodi Apr 4, 2024
8d2b89e
remove redundant comment
pragatimodi Apr 4, 2024
bd47b2a
oob section changes
pragatimodi Apr 9, 2024
23e7899
lint
pragatimodi Apr 9, 2024
22358a2
undo whitespace changes
pragatimodi Apr 9, 2024
6589e57
Merge branch 'oob-section' of https://github.com/firebase/firebase-io…
pragatimodi Apr 9, 2024
fb323ae
fix bracket
pragatimodi Apr 10, 2024
a8abe96
Merge branch 'oob-section' of https://github.com/firebase/firebase-io…
pragatimodi Apr 10, 2024
f5d8b4f
lint
pragatimodi Apr 10, 2024
71b623c
Merge branches 'mfa-section' and 'oob-section' of https://github.com/…
pragatimodi Apr 10, 2024
3f7d7fb
lint
pragatimodi Apr 10, 2024
3dbe059
fix duplicate declarations due to merge
pragatimodi Apr 10, 2024
72c9c89
apply code review suggestions
pragatimodi Apr 10, 2024
683dd80
fix auth menu items
pragatimodi Apr 15, 2024
562938e
Merge branch 'auth-swift' of https://github.com/firebase/firebase-ios…
pragatimodi May 6, 2024
ebd2a68
lint and undo plist changes
pragatimodi May 6, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ enum AuthMenu: String {
case checkActionCode
case applyActionCode
case verifyPasswordResetCode
case phoneEnroll
case totpEnroll
case multifactorUnenroll

// More intuitively named getter for `rawValue`.
var id: String { rawValue }
Expand Down Expand Up @@ -83,10 +86,13 @@ enum AuthMenu: String {
return "Anonymous Authentication"
case .custom:
return "Custom Auth System"
// Recpatcha
case .initRecaptcha:
return "Initialize reCAPTCHA Enterprise"
// Custom Auth Domain
case .customAuthDomain:
return "Set Custom Auth Domain"
// App Section
case .getToken:
return "Get Token"
case .getTokenForceRefresh:
Expand Down Expand Up @@ -120,6 +126,13 @@ enum AuthMenu: String {
return "Apply Action Code"
case .verifyPasswordResetCode:
return "Verify Password Reset Code"
// Multi factor
case .phoneEnroll:
return "Phone Enroll"
case .totpEnroll:
return "TOTP Enroll"
case .multifactorUnenroll:
return "Multifactor unenroll"
}
}

Expand Down Expand Up @@ -191,6 +204,12 @@ enum AuthMenu: String {
self = .applyActionCode
case "Verify Password Reset Code":
self = .verifyPasswordResetCode
case "Phone Enroll":
self = .phoneEnroll
case "TOTP Enroll":
self = .totpEnroll
case "Multifactor unenroll":
self = .multifactorUnenroll
default:
return nil
}
Expand Down Expand Up @@ -314,9 +333,19 @@ class AuthMenuData: DataSourceProvidable {
return Section(headerDescription: header, items: items)
}

static var multifactorSection: Section {
let header = "Multi Factor"
let items: [Item] = [
Item(title: AuthMenu.phoneEnroll.name),
Item(title: AuthMenu.totpEnroll.name),
Item(title: AuthMenu.multifactorUnenroll.name),
]
return Section(headerDescription: header, items: items)
}

static var sections: [Section] =
[settingsSection, providerSection, emailPasswordSection, otherSection, recaptchaSection,
customAuthDomainSection, appSection, oobSection]
customAuthDomainSection, appSection, oobSection, multifactorSection]

static var authLinkSections: [Section] {
let allItems = AuthMenuData.sections.flatMap { $0.items }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@
<key>CFBundleURLName</key>
<string></string>
<key>CFBundleURLSchemes</key>
<array>
<string>app-1-1085102361755-ios-2b9d4a82e5df229f</string>
<string>app-1-1085102361755-ios-74df231d8a2072e45dca3e</string>
</array>
<array/>
</dict>
</array>
<key>CFBundleVersion</key>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ class AuthViewController: UIViewController, DataSourceProviderDelegate {
func didSelectRowAt(_ indexPath: IndexPath, on tableView: UITableView) {
let item = dataSourceProvider.item(at: indexPath)

let providerName = item.isEditable ? item.detailTitle! : item.title!
guard let providerName = item.title else {
fatalError("Invalid item name")
}

guard let provider = AuthMenu(rawValue: providerName) else {
// The row tapped has no affiliated action.
Expand Down Expand Up @@ -171,6 +173,15 @@ class AuthViewController: UIViewController, DataSourceProviderDelegate {

case .verifyPasswordResetCode:
verifyPasswordResetCode()

case .phoneEnroll:
phoneEnroll()

case .totpEnroll:
totpEnroll()

case .multifactorUnenroll:
mfaUnenroll()
}
}

Expand Down Expand Up @@ -721,6 +732,202 @@ class AuthViewController: UIViewController, DataSourceProviderDelegate {
}
}

private func phoneEnroll() {
guard let user = AppManager.shared.auth().currentUser else {
showAlert(for: "No user logged in!")
print("Error: User must be logged in first.")
return
}

showTextInputPrompt(with: "Phone Number:") { phoneNumber in
user.multiFactor.getSessionWithCompletion { session, error in
guard let session = session else { return }
guard error == nil else {
self.showAlert(for: "Enrollment failed")
print("Multi factor start enroll failed. Error: \(error!)")
return
}

PhoneAuthProvider.provider()
.verifyPhoneNumber(phoneNumber, multiFactorSession: session) { verificationID, error in
guard error == nil else {
self.showAlert(for: "Enrollment failed")
print("Multi factor start enroll failed. Error: \(error!)")
return
}

self.showTextInputPrompt(with: "Verification Code: ") { verificationCode in
let credential = PhoneAuthProvider.provider().credential(
withVerificationID: verificationID!,
verificationCode: verificationCode
)
let assertion = PhoneMultiFactorGenerator.assertion(with: credential)

self.showTextInputPrompt(with: "Display Name:") { displayName in
user.multiFactor.enroll(with: assertion, displayName: displayName) { error in
if let error = error {
self.showAlert(for: "Enrollment failed")
print("Multi factor finalize enroll failed. Error: \(error)")
} else {
self.showAlert(for: "Successfully enrolled: \(displayName)")
print("Multi factor finalize enroll succeeded.")
}
}
}
}
}
}
}
}

private func totpEnroll() {
guard let user = AppManager.shared.auth().currentUser else {
print("Error: User must be logged in first.")
return
}

user.multiFactor.getSessionWithCompletion { session, error in
guard let session = session, error == nil else {
if let error = error {
self.showAlert(for: "Enrollment failed")
print("Multi factor start enroll failed. Error: \(error.localizedDescription)")
} else {
self.showAlert(for: "Enrollment failed")
print("Multi factor start enroll failed with unknown error.")
}
return
}

TOTPMultiFactorGenerator.generateSecret(with: session) { secret, error in
guard let secret = secret, error == nil else {
if let error = error {
self.showAlert(for: "Enrollment failed")
print("Error generating TOTP secret. Error: \(error.localizedDescription)")
} else {
self.showAlert(for: "Enrollment failed")
print("Error generating TOTP secret.")
}
return
}

guard let accountName = user.email, let issuer = Auth.auth().app?.name else {
self.showAlert(for: "Enrollment failed")
print("Multi factor finalize enroll failed. Could not get account details.")
return
}

DispatchQueue.main.async {
let url = secret.generateQRCodeURL(withAccountName: accountName, issuer: issuer)

guard !url.isEmpty else {
self.showAlert(for: "Enrollment failed")
print("Multi factor finalize enroll failed. Could not generate URL.")
return
}

secret.openInOTPApp(withQRCodeURL: url)

self
.showQRCodePromptWithTextInput(with: "Scan this QR code and enter OTP:",
url: url) { oneTimePassword in
guard !oneTimePassword.isEmpty else {
self.showAlert(for: "Display name must not be empty")
print("OTP not entered.")
return
}

let assertion = TOTPMultiFactorGenerator.assertionForEnrollment(
with: secret,
oneTimePassword: oneTimePassword
)

self.showTextInputPrompt(with: "Display Name") { displayName in
guard !displayName.isEmpty else {
self.showAlert(for: "Display name must not be empty")
print("Display name not entered.")
return
}

user.multiFactor.enroll(with: assertion, displayName: displayName) { error in
if let error = error {
self.showAlert(for: "Enrollment failed")
print(
"Multi factor finalize enroll failed. Error: \(error.localizedDescription)"
)
} else {
self.showAlert(for: "Successfully enrolled: \(displayName)")
print("Multi factor finalize enroll succeeded.")
}
}
}
}
}
}
}
}

func mfaUnenroll() {
var displayNames: [String] = []

guard let currentUser = Auth.auth().currentUser else {
print("Error: No current user")
return
}

for factorInfo in currentUser.multiFactor.enrolledFactors {
if let displayName = factorInfo.displayName {
displayNames.append(displayName)
}
}

let alertController = UIAlertController(
title: "Select Multi Factor to Unenroll",
message: nil,
preferredStyle: .actionSheet
)

for displayName in displayNames {
let action = UIAlertAction(title: displayName, style: .default) { _ in
self.unenrollFactor(with: displayName)
}
alertController.addAction(action)
}

let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)

present(alertController, animated: true, completion: nil)
}

private func unenrollFactor(with displayName: String) {
guard let currentUser = Auth.auth().currentUser else {
showAlert(for: "User must be logged in")
print("Error: No current user")
return
}

var factorInfoToUnenroll: MultiFactorInfo?

for factorInfo in currentUser.multiFactor.enrolledFactors {
if factorInfo.displayName == displayName {
factorInfoToUnenroll = factorInfo
break
}
}

if let factorInfo = factorInfoToUnenroll {
currentUser.multiFactor.unenroll(withFactorUID: factorInfo.uid) { error in
if let error = error {
self.showAlert(for: "Failed to unenroll factor: \(displayName)")
print("Multi factor unenroll failed. Error: \(error.localizedDescription)")
} else {
self.showAlert(for: "Successfully unenrolled: \(displayName)")
print("Multi factor unenroll succeeded.")
}
}
}
}

// MARK: - Private Helpers

private func showTextInputPrompt(with message: String, completion: ((String) -> Void)? = nil) {
Expand Down
Loading