Skip to content

Commit d86b0d1

Browse files
committed
updating changes
1 parent aed4e2e commit d86b0d1

File tree

4 files changed

+78
-75
lines changed

4 files changed

+78
-75
lines changed

FirebaseAuth/Sources/Swift/Auth/Auth.swift

Lines changed: 53 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,31 @@ extension Auth: AuthInterop {
140140
}
141141
}
142142

143+
/// Holds configuration for a R-GCIP tenant.
144+
public struct TenantConfig: Sendable {
145+
public let tenantId: String /// The ID of the tenant.
146+
public let location: String /// The location of the tenant.
147+
148+
/// Initializes a `TenantConfig` instance.
149+
/// - Parameters:
150+
/// - location: The location of the tenant, defaults to "prod-global".
151+
/// - tenantId: The ID of the tenant.
152+
public init(tenantId: String, location: String = "prod-global") {
153+
self.location = location
154+
self.tenantId = tenantId
155+
}
156+
}
157+
158+
/// Holds a Firebase ID token and its expiration.
159+
public struct AuthExchangeToken: Sendable {
160+
public let token: String
161+
public let expirationDate: Date?
162+
init(token: String, expirationDate: Date) {
163+
self.token = token
164+
self.expirationDate = expirationDate
165+
}
166+
}
167+
143168
/// Manages authentication for Firebase apps.
144169
///
145170
/// This class is thread-safe.
@@ -170,16 +195,12 @@ extension Auth: AuthInterop {
170195
/// Gets the `FirebaseApp` object that this auth object is connected to.
171196
@objc public internal(set) weak var app: FirebaseApp?
172197

173-
/// New R-GCIP v2 + BYO-CIAM initializer.
174-
///
175-
/// This initializer allows to create an `Auth` instance with a specific `tenantConfig` for
176-
/// R-GCIP.
198+
/// Gets the auth object for a `FirebaseApp` with an optional `TenantConfig`.
177199
/// - Parameters:
178-
/// - app: The `FirebaseApp` for which to initialize the `Auth` instance.
179-
/// - tenantConfig: The configuration for the tenant, including location and tenant ID.
180-
/// - Returns: An `Auth` instance configured for the specified tenant.
200+
/// - app: The Firebase app instance.
201+
/// - tenantConfig: The optional configuration for the RGCIP.
202+
/// - Returns: The `Auth` instance associated with the given app and tenant config.
181203
public static func auth(app: FirebaseApp, tenantConfig: TenantConfig) -> Auth {
182-
// start from the legacy initializer so we get a fully-formed Auth object
183204
let auth = auth(app: app)
184205
kAuthGlobalWorkQueue.sync {
185206
auth.requestConfiguration.location = tenantConfig.location
@@ -188,31 +209,6 @@ extension Auth: AuthInterop {
188209
return auth
189210
}
190211

191-
/// Holds configuration for a R-GCIP tenant.
192-
public struct TenantConfig: Sendable {
193-
public let tenantId: String /// The ID of the tenant.
194-
public let location: String /// The location of the tenant.
195-
196-
/// Initializes a `TenantConfig` instance.
197-
/// - Parameters:
198-
/// - location: The location of the tenant, defaults to "prod-global".
199-
/// - tenantId: The ID of the tenant.
200-
public init(tenantId: String, location: String = "prod-global") {
201-
self.location = location
202-
self.tenantId = tenantId
203-
}
204-
}
205-
206-
/// Holds a Firebase ID token and its expiration.
207-
public struct AuthExchangeToken: Sendable {
208-
public let token: String
209-
public let expirationDate: Date?
210-
init(token: String, expirationDate: Date) {
211-
self.token = token
212-
self.expirationDate = expirationDate
213-
}
214-
}
215-
216212
/// Synchronously gets the cached current user, or null if there is none.
217213
@objc public var currentUser: User? {
218214
kAuthGlobalWorkQueue.sync {
@@ -2471,47 +2467,44 @@ extension Auth: AuthInterop {
24712467

24722468
@available(iOS 13, *)
24732469
public extension Auth {
2474-
/// Exchanges a third-party OIDC token for a Firebase STS token using a completion handler.
2470+
/// Exchanges a third-party OIDC token for a Firebase STS token.
24752471
///
24762472
/// This method is used in R-GCIP (multi-tenant) environments where the `Auth` instance must
24772473
/// be configured with a `TenantConfig`, including `location` and `tenantId`.
2474+
/// Unlike other sign-in methods, this flow *does not* create or update a `User` object.
24782475
///
24792476
/// - Parameters:
2480-
/// - idToken: The OIDC token received from the third-party Identity Provider (IdP).
2481-
/// - idpConfigId: The identifier of the OIDC provider configuration defined in Firebase.
2477+
/// - request: The ExchangeTokenRequest containing the OIDC token and other parameters.
24822478
/// - completion: A closure that gets called with either an `AuthTokenResult` or an `Error`.
24832479
func exchangeToken(customToken: String,
24842480
idpConfigId: String,
2485-
completion: @escaping (AuthTokenResult?, Error?) -> Void) {
2481+
completion: @escaping (AuthExchangeToken?, Error?) -> Void) {
24862482
// Ensure R-GCIP is configured with location and tenant ID
24872483
guard let _ = requestConfiguration.location,
24882484
let _ = requestConfiguration.tenantId
24892485
else {
2490-
completion(nil, AuthErrorCode.operationNotAllowed)
2486+
Auth.wrapMainAsync(
2487+
callback: completion,
2488+
with: .failure(AuthErrorUtils
2489+
.operationNotAllowedError(message: "R-GCIP is not configured."))
2490+
)
24912491
return
24922492
}
2493-
2494-
// Build the exchange token request
24952493
let request = ExchangeTokenRequest(
24962494
customToken: customToken,
24972495
idpConfigID: idpConfigId,
24982496
config: requestConfiguration
24992497
)
2500-
2501-
// Perform the token exchange asynchronously
25022498
Task {
25032499
do {
25042500
let response = try await backend.call(with: request)
2505-
do {
2506-
// Try to parse the Firebase token response
2507-
do {
2508-
let authTokenResult = try AuthTokenResult.tokenResult(token: response.firebaseToken)
2509-
completion(authTokenResult, nil)
2510-
completion(authTokenResult, nil)
2511-
}
2512-
} catch {
2513-
completion(nil, AuthErrorCode.malformedJWT)
2514-
}
2501+
let authExchangeToken = AuthExchangeToken(
2502+
token: response.firebaseToken,
2503+
expirationDate: response.expirationDate
2504+
)
2505+
Auth.wrapMainAsync(callback: completion, with: .success(authExchangeToken))
2506+
} catch {
2507+
Auth.wrapMainAsync(callback: completion, with: .failure(error))
25152508
}
25162509
}
25172510
}
@@ -2523,32 +2516,31 @@ public extension Auth {
25232516
///
25242517
/// The `Auth` instance must be configured with `TenantConfig` containing `location` and
25252518
/// `tenantId`.
2519+
/// Unlike other sign-in methods, this flow *does not* create or update a `User` object.
25262520
///
25272521
/// - Parameters:
2528-
/// - idToken: The OIDC token received from the third-party Identity Provider (IdP).
2529-
/// - idpConfigId: The identifier of the OIDC provider configuration defined in Firebase.
2522+
/// - request: The ExchangeTokenRequest containing the OIDC token and other parameters.
25302523
/// - Returns: An `AuthTokenResult` containing the Firebase ID token and its expiration details.
25312524
/// - Throws: An error if R-GCIP is not configured, if the network call fails,
25322525
/// or if the token parsing fails.
2533-
func exchangeToken(customToken: String, idpConfigId: String) async throws -> AuthTokenResult {
2526+
func exchangeToken(customToken: String, idpConfigId: String) async throws -> AuthExchangeToken {
25342527
// Ensure R-GCIP is configured with location and tenant ID
25352528
guard let _ = requestConfiguration.location,
25362529
let _ = requestConfiguration.tenantId
25372530
else {
2538-
throw AuthErrorCode.operationNotAllowed
2531+
throw AuthErrorUtils.operationNotAllowedError(message: "R-GCIP is not configured.")
25392532
}
2540-
2541-
// Build the exchange token request
25422533
let request = ExchangeTokenRequest(
25432534
customToken: customToken,
25442535
idpConfigID: idpConfigId,
25452536
config: requestConfiguration
25462537
)
2547-
2548-
// Perform the backend call and return parsed token
25492538
do {
25502539
let response = try await backend.call(with: request)
2551-
return try AuthTokenResult.tokenResult(token: response.firebaseToken)
2540+
return AuthExchangeToken(
2541+
token: response.firebaseToken,
2542+
expirationDate: response.expirationDate
2543+
)
25522544
} catch {
25532545
throw error
25542546
}

FirebaseAuth/Sources/Swift/Backend/AuthRequestConfiguration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ final class AuthRequestConfiguration {
5555
auth: Auth? = nil,
5656
heartbeatLogger: FIRHeartbeatLoggerProtocol? = nil,
5757
appCheck: AppCheckInterop? = nil,
58-
tenantConfig: Auth.TenantConfig? = nil) {
58+
tenantConfig: TenantConfig? = nil) {
5959
self.apiKey = apiKey
6060
self.appID = appID
6161
self.auth = auth

FirebaseAuth/Sources/Swift/Backend/IdentityToolkitRequest.swift

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ private let kRegionalGCIPStagingAPIHost = "staging-identityplatform.sandbox.goog
2828
#else
2929
private var gAPIHost = "www.googleapis.com"
3030
#endif
31+
3132
/// Represents a request to an Identity Toolkit endpoint, routing either to
3233
/// legacy GCIP v1 or regionalized R-GCIP v2 based on presence of tenantID.
3334
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
@@ -74,22 +75,16 @@ class IdentityToolkitRequest {
7475

7576
/// Build the full URL, branching on whether tenantID is set.
7677
func requestURL() -> URL {
77-
guard let auth = _requestConfiguration.auth else {
78-
fatalError("Internal Auth error: missing Auth on requestConfiguration")
79-
}
8078
let protocolScheme: String
8179
let hostPrefix: String
8280
let urlString: String
83-
// R-GCIP v2 if location is non-nil
8481

82+
// R-GCIP v2 if location AND tenantID from requestConfiguration are non-nil.
8583
if let region = _requestConfiguration.location,
86-
let tenant = _requestConfiguration.tenantId,
84+
let tenant = _requestConfiguration.tenantId, // Use tenantId from requestConfiguration
8785
!region.isEmpty,
8886
!tenant.isEmpty {
89-
// Project identifier
90-
guard let project = auth.app?.options.projectID else {
91-
fatalError("Internal Auth error: missing projectID")
92-
}
87+
let projectID = _requestConfiguration.auth?.app?.options.projectID
9388
// Choose emulator, staging, or prod host
9489
if let emu = emulatorHostAndPort {
9590
protocolScheme = kHttpProtocol
@@ -101,10 +96,12 @@ class IdentityToolkitRequest {
10196
protocolScheme = kHttpsProtocol
10297
hostPrefix = kRegionalGCIPAPIHost
10398
}
99+
104100
// Regionalized v2 path
105101
urlString =
106-
"\(protocolScheme)//\(hostPrefix)/v2/projects/\(project)"
102+
"\(protocolScheme)//\(hostPrefix)/v2/projects/\(projectID)"
107103
+ "/locations/\(region)/tenants/\(tenant)/idpConfigs/\(endpoint)?key=\(apiKey)"
104+
108105
} else {
109106
// Legacy GCIP v1 branch
110107
if let emu = emulatorHostAndPort {

FirebaseAuth/Sources/Swift/Backend/RPC/ExchangeTokenRequest.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,27 @@ struct ExchangeTokenRequest: AuthRPCRequest {
2828
typealias Response = ExchangeTokenResponse
2929

3030
/// The OIDC provider's Authorization code or Id Token to exchange.
31-
private let customToken: String
31+
let customToken: String
3232

3333
/// The ExternalUserDirectoryId corresponding to the OIDC custom Token.
34-
private let idpConfigID: String
34+
let idpConfigID: String
3535

3636
/// The configuration for the request, holding API key, tenant, etc.
37-
private let config: AuthRequestConfiguration
37+
let config: AuthRequestConfiguration
38+
39+
var path: String {
40+
guard let region = config.location,
41+
let tenant = config.tenantId,
42+
let project = config.auth?.app?.options.projectID
43+
else {
44+
fatalError(
45+
"exchangeOidcToken requires `auth.location` & `auth.tenantID`"
46+
)
47+
}
48+
_ = "\(region)-identityplatform.googleapis.com"
49+
return "/v2alpha/projects/\(project)/locations/\(region)" +
50+
"/tenants/\(tenant)/idpConfigs/\(idpConfigID):exchangeOidcToken"
51+
}
3852

3953
/// Initializes a new `ExchangeTokenRequest` instance.
4054
///

0 commit comments

Comments
 (0)