Skip to content

Commit d39e415

Browse files
committed
Merge branch 'development' of https://github.com/firebase/FirebaseUI-iOS into ui-upgrades
2 parents e66bb99 + 193656d commit d39e415

File tree

48 files changed

+4023
-432
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4023
-432
lines changed

.github/workflows/swiftui-auth.yml

Lines changed: 139 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ name: SwiftUI Auth
22

33
on:
44
push:
5-
branches: [ main ]
5+
branches: [ main, development ]
66
paths:
77
- '.github/workflows/swiftui-auth.yml'
88
- 'samples/swiftui/**'
99
- 'FirebaseSwiftUI/**'
1010
- 'Package.swift'
1111
pull_request:
12-
branches: [ main ]
12+
branches: [ main, development ]
1313
paths:
1414
- '.github/workflows/swiftui-auth.yml'
1515
- 'samples/swiftui/**'
@@ -22,57 +22,173 @@ permissions:
2222
contents: read
2323

2424
jobs:
25-
swiftui-auth:
25+
# Package Unit Tests (standalone, no emulator needed)
26+
unit-tests:
27+
name: Package Unit Tests
2628
runs-on: macos-15
27-
timeout-minutes: 30
29+
timeout-minutes: 20
2830
steps:
2931
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938
32+
33+
- name: Install xcpretty
34+
run: gem install xcpretty
35+
36+
- name: Select Xcode version
37+
run: sudo xcode-select -switch /Applications/Xcode_16.4.app/Contents/Developer
38+
39+
- name: Run FirebaseSwiftUI Package Unit Tests
40+
run: |
41+
set -o pipefail
42+
xcodebuild test \
43+
-scheme FirebaseUI-Package \
44+
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
45+
-enableCodeCoverage YES \
46+
-resultBundlePath FirebaseSwiftUIPackageTests.xcresult | tee FirebaseSwiftUIPackageTests.log | xcpretty --test --color --simple
47+
48+
- name: Upload test logs
49+
if: failure()
50+
uses: actions/upload-artifact@v4
51+
with:
52+
name: unit-tests-logs
53+
path: FirebaseSwiftUIPackageTests.log
54+
55+
- name: Upload test results
56+
if: failure()
57+
uses: actions/upload-artifact@v4
58+
with:
59+
name: unit-tests-results
60+
path: FirebaseSwiftUIPackageTests.xcresult
61+
62+
# Integration Tests (requires emulator)
63+
integration-tests:
64+
name: Integration Tests
65+
runs-on: macos-15
66+
timeout-minutes: 20
67+
steps:
68+
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938
69+
3070
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a
3171
name: Install Node.js 20
3272
with:
3373
node-version: '20'
74+
3475
- uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b
3576
with:
3677
distribution: 'temurin'
3778
java-version: '17'
79+
3880
- name: Install Firebase
39-
run: |
40-
sudo npm i -g firebase-tools
81+
run: sudo npm i -g firebase-tools
82+
4183
- name: Start Firebase Emulator
4284
run: |
43-
sudo chown -R 501:20 "/Users/runner/.npm" && cd ./samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample && ./start-firebase-emulator.sh
85+
sudo chown -R 501:20 "/Users/runner/.npm"
86+
cd ./samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample
87+
./start-firebase-emulator.sh
88+
4489
- name: Install xcpretty
4590
run: gem install xcpretty
91+
4692
- name: Select Xcode version
47-
run: |
48-
sudo xcode-select -switch /Applications/Xcode_16.3.app/Contents/Developer
49-
- name: Run Integration Tests
93+
run: sudo xcode-select -switch /Applications/Xcode_16.4.app/Contents/Developer
94+
95+
- name: Build for Integration Tests
5096
run: |
5197
cd ./samples/swiftui/FirebaseSwiftUIExample
5298
set -o pipefail
53-
xcodebuild test -scheme FirebaseSwiftUIExampleTests -destination 'platform=iOS Simulator,name=iPhone 16 Pro' -enableCodeCoverage YES -resultBundlePath FirebaseSwiftUIExampleTests.xcresult | tee FirebaseSwiftUIExampleTests.log | xcpretty --test --color --simple
54-
- name: Run View UI Tests
99+
xcodebuild build-for-testing \
100+
-scheme FirebaseSwiftUIExampleTests \
101+
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
102+
-enableCodeCoverage YES | xcpretty --color --simple
103+
104+
- name: Run Integration Tests
55105
run: |
56106
cd ./samples/swiftui/FirebaseSwiftUIExample
57107
set -o pipefail
58-
xcodebuild test -scheme FirebaseSwiftUIExampleUITests -destination 'platform=iOS Simulator,name=iPhone 16 Pro' -enableCodeCoverage YES -resultBundlePath FirebaseSwiftUIExampleUITests.xcresult | tee FirebaseSwiftUIExampleUITests.log | xcpretty --test --color --simple
108+
xcodebuild test-without-building \
109+
-scheme FirebaseSwiftUIExampleTests \
110+
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
111+
-enableCodeCoverage YES \
112+
-resultBundlePath FirebaseSwiftUIExampleTests.xcresult | tee FirebaseSwiftUIExampleTests.log | xcpretty --test --color --simple
113+
59114
- name: Upload test logs
60115
if: failure()
61116
uses: actions/upload-artifact@v4
62117
with:
63-
name: swiftui-auth-test-logs
64-
path: |
65-
samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleTests.log
66-
samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests.log
67-
- name: Upload FirebaseSwiftUIExampleUITests.xcresult bundle
118+
name: integration-tests-logs
119+
path: samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleTests.log
120+
121+
- name: Upload test results
122+
if: failure()
123+
uses: actions/upload-artifact@v4
124+
with:
125+
name: integration-tests-results
126+
path: samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleTests.xcresult
127+
128+
# UI Tests (requires emulator)
129+
ui-tests:
130+
name: UI Tests
131+
runs-on: macos-15
132+
timeout-minutes: 30
133+
steps:
134+
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938
135+
136+
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a
137+
name: Install Node.js 20
138+
with:
139+
node-version: '20'
140+
141+
- uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b
142+
with:
143+
distribution: 'temurin'
144+
java-version: '17'
145+
146+
- name: Install Firebase
147+
run: sudo npm i -g firebase-tools
148+
149+
- name: Start Firebase Emulator
150+
run: |
151+
sudo chown -R 501:20 "/Users/runner/.npm"
152+
cd ./samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample
153+
./start-firebase-emulator.sh
154+
155+
- name: Install xcpretty
156+
run: gem install xcpretty
157+
158+
- name: Select Xcode version
159+
run: sudo xcode-select -switch /Applications/Xcode_16.4.app/Contents/Developer
160+
161+
- name: Build for UI Tests
162+
run: |
163+
cd ./samples/swiftui/FirebaseSwiftUIExample
164+
set -o pipefail
165+
xcodebuild build-for-testing \
166+
-scheme FirebaseSwiftUIExampleUITests \
167+
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
168+
-enableCodeCoverage YES | xcpretty --color --simple
169+
170+
- name: Run UI Tests
171+
run: |
172+
cd ./samples/swiftui/FirebaseSwiftUIExample
173+
set -o pipefail
174+
xcodebuild test-without-building \
175+
-scheme FirebaseSwiftUIExampleUITests \
176+
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
177+
-parallel-testing-enabled YES \
178+
-maximum-concurrent-test-simulator-destinations 2 \
179+
-enableCodeCoverage YES \
180+
-resultBundlePath FirebaseSwiftUIExampleUITests.xcresult | tee FirebaseSwiftUIExampleUITests.log | xcpretty --test --color --simple
181+
182+
- name: Upload test logs
68183
if: failure()
69184
uses: actions/upload-artifact@v4
70185
with:
71-
name: FirebaseSwiftUIExampleUITests.xcresult
72-
path: samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests.xcresult
73-
- name: Upload FirebaseSwiftUIExampleTests.xcresult bundle
186+
name: ui-tests-logs
187+
path: samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests.log
188+
189+
- name: Upload test results
74190
if: failure()
75191
uses: actions/upload-artifact@v4
76192
with:
77-
name: FirebaseSwiftUIExampleTests.xcresult
78-
path: samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleTests.xcresult
193+
name: ui-tests-results
194+
path: samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests.xcresult
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
@preconcurrency import FirebaseAuth
15+
import SwiftUI
16+
17+
public enum SecondFactorType {
18+
case sms
19+
case totp
20+
}
21+
22+
public struct TOTPEnrollmentInfo {
23+
public let sharedSecretKey: String
24+
public let qrCodeURL: URL?
25+
public let accountName: String?
26+
public let issuer: String?
27+
public let verificationStatus: VerificationStatus
28+
29+
public enum VerificationStatus {
30+
case pending
31+
case verified
32+
case failed
33+
}
34+
35+
public init(sharedSecretKey: String,
36+
qrCodeURL: URL? = nil,
37+
accountName: String? = nil,
38+
issuer: String? = nil,
39+
verificationStatus: VerificationStatus = .pending) {
40+
self.sharedSecretKey = sharedSecretKey
41+
self.qrCodeURL = qrCodeURL
42+
self.accountName = accountName
43+
self.issuer = issuer
44+
self.verificationStatus = verificationStatus
45+
}
46+
}
47+
48+
public struct EnrollmentSession {
49+
public let id: String
50+
public let type: SecondFactorType
51+
public let session: MultiFactorSession
52+
public let totpInfo: TOTPEnrollmentInfo?
53+
public let phoneNumber: String?
54+
public let verificationId: String?
55+
public let status: EnrollmentStatus
56+
public let createdAt: Date
57+
public let expiresAt: Date
58+
59+
// Internal handle to finish TOTP
60+
internal let _totpSecret: AnyObject?
61+
62+
public enum EnrollmentStatus {
63+
case initiated
64+
case verificationSent
65+
case verificationPending
66+
case completed
67+
case failed
68+
case expired
69+
}
70+
71+
public init(id: String = UUID().uuidString,
72+
type: SecondFactorType,
73+
session: MultiFactorSession,
74+
totpInfo: TOTPEnrollmentInfo? = nil,
75+
phoneNumber: String? = nil,
76+
verificationId: String? = nil,
77+
status: EnrollmentStatus = .initiated,
78+
createdAt: Date = Date(),
79+
expiresAt: Date = Date().addingTimeInterval(600), // 10 minutes default
80+
_totpSecret: AnyObject? = nil) {
81+
self.id = id
82+
self.type = type
83+
self.session = session
84+
self.totpInfo = totpInfo
85+
self.phoneNumber = phoneNumber
86+
self.verificationId = verificationId
87+
self.status = status
88+
self.createdAt = createdAt
89+
self.expiresAt = expiresAt
90+
self._totpSecret = _totpSecret
91+
}
92+
93+
public var isExpired: Bool {
94+
return Date() > expiresAt
95+
}
96+
97+
public var canProceed: Bool {
98+
return !isExpired &&
99+
(status == .initiated || status == .verificationSent || status == .verificationPending)
100+
}
101+
}
102+
103+
public enum MFAHint {
104+
case phone(displayName: String?, uid: String, phoneNumber: String?)
105+
case totp(displayName: String?, uid: String)
106+
}
107+
108+
public struct MFARequired {
109+
public let hints: [MFAHint]
110+
111+
public init(hints: [MFAHint]) {
112+
self.hints = hints
113+
}
114+
}

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ public enum AuthServiceError: LocalizedError {
3636
case invalidCredentials(String)
3737
case signInFailed(underlying: Error)
3838
case accountMergeConflict(context: AccountMergeConflictContext)
39+
case invalidPhoneAuthenticationArguments(String)
40+
case providerNotFound(String)
41+
case multiFactorAuth(String)
42+
3943

4044
public var errorDescription: String? {
4145
switch self {
@@ -55,6 +59,12 @@ public enum AuthServiceError: LocalizedError {
5559
return "Failed to sign in: \(error.localizedDescription)"
5660
case let .accountMergeConflict(context):
5761
return context.errorDescription
62+
case let .providerNotFound(description):
63+
return description
64+
case let .invalidPhoneAuthenticationArguments(description):
65+
return description
66+
case let .multiFactorAuth(description):
67+
return description
5868
}
5969
}
6070
}

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthConfiguration.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,23 @@ public struct AuthConfiguration {
2525
public let emailLinkSignInActionCodeSettings: ActionCodeSettings?
2626
public let verifyEmailActionCodeSettings: ActionCodeSettings?
2727

28+
// MARK: - MFA Configuration
29+
30+
public let mfaEnabled: Bool
31+
public let allowedSecondFactors: Set<SecondFactorType>
32+
public let mfaIssuer: String
33+
2834
public init(shouldHideCancelButton: Bool = false,
2935
interactiveDismissEnabled: Bool = true,
3036
shouldAutoUpgradeAnonymousUsers: Bool = false,
3137
customStringsBundle: Bundle? = nil,
3238
tosUrl: URL? = nil,
3339
privacyPolicyUrl: URL? = nil,
3440
emailLinkSignInActionCodeSettings: ActionCodeSettings? = nil,
35-
verifyEmailActionCodeSettings: ActionCodeSettings? = nil) {
41+
verifyEmailActionCodeSettings: ActionCodeSettings? = nil,
42+
mfaEnabled: Bool = false,
43+
allowedSecondFactors: Set<SecondFactorType> = [.sms, .totp],
44+
mfaIssuer: String = "Firebase Auth") {
3645
self.shouldHideCancelButton = shouldHideCancelButton
3746
self.interactiveDismissEnabled = interactiveDismissEnabled
3847
self.shouldAutoUpgradeAnonymousUsers = shouldAutoUpgradeAnonymousUsers
@@ -41,5 +50,8 @@ public struct AuthConfiguration {
4150
self.privacyPolicyUrl = privacyPolicyUrl
4251
self.emailLinkSignInActionCodeSettings = emailLinkSignInActionCodeSettings
4352
self.verifyEmailActionCodeSettings = verifyEmailActionCodeSettings
53+
self.mfaEnabled = mfaEnabled
54+
self.allowedSecondFactors = allowedSecondFactors
55+
self.mfaIssuer = mfaIssuer
4456
}
4557
}

0 commit comments

Comments
 (0)