-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Add FirebaseAIConfig support #15099
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
base: main
Are you sure you want to change the base?
Add FirebaseAIConfig support #15099
Changes from all commits
0eeb7a2
6c878ff
e6de038
086e673
4c9d28b
8a06ee5
8659455
56d1f5f
1b6c09c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,48 @@ | ||||||
// Copyright 2025 Google LLC | ||||||
// | ||||||
// 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. | ||||||
|
||||||
/// Configurable options for how App Check is used within a ``FirebaseAI`` instance. | ||||||
/// | ||||||
/// Can be set when creating a ``FirebaseAI.Config``. | ||||||
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) | ||||||
public struct AppCheckOptions: Sendable, Hashable, Equatable { | ||||||
/// Use `limitedUseTokens`, instead of the standard cached tokens, when sending requests | ||||||
/// to the backend. | ||||||
let requireLimitedUseTokens: Bool | ||||||
|
||||||
/// Creates a new ``AppCheckOptions`` value. | ||||||
/// | ||||||
/// - Parameters: | ||||||
/// - requireLimitedUseTokens: When sending tokens to the backend, this option enables | ||||||
/// the usage of App Check's `limitedUseTokens` instead of the standard cached tokens. | ||||||
/// | ||||||
/// A new `limitedUseToken` will be generated for each request; providing a lower attack | ||||||
/// surface for malicious parties to hijack tokens. When used alongside [replay protection](https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection), | ||||||
/// `limitedUseTokens` are also _consumed_ after each request, ensuring they can't be used | ||||||
/// again. | ||||||
/// | ||||||
/// _To prevent breakage, this flag is set to `false` by default._ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "To prevent breakage" of what?
Suggested change
|
||||||
/// | ||||||
/// > Important: Replay protection is not currently supported for the FirebaseAI backend. | ||||||
/// > While this feature is being developed, you can still migrate to using | ||||||
/// > `limitedUseTokens`. Because `limitedUseTokens` are backwards compatable, you can still | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
/// > use them without replay protection. Due to their shorter TTL over standard App Check | ||||||
/// > tokens, they still provide a security benefit. | ||||||
/// > | ||||||
/// > Migrating to `limitedUseTokens` ahead of time will also allow you to enable replay | ||||||
/// > protection down the road (when support is added), without breaking your users. | ||||||
public init(requireLimitedUseTokens: Bool = false) { | ||||||
self.requireLimitedUseTokens = requireLimitedUseTokens | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,9 +30,12 @@ struct GenerativeAIService { | |
|
||
private let urlSession: URLSession | ||
|
||
init(firebaseInfo: FirebaseInfo, urlSession: URLSession) { | ||
private let aiConfig: FirebaseAI.Config | ||
|
||
init(firebaseInfo: FirebaseInfo, urlSession: URLSession, aiConfig: FirebaseAI.Config) { | ||
self.firebaseInfo = firebaseInfo | ||
self.urlSession = urlSession | ||
self.aiConfig = aiConfig | ||
} | ||
|
||
func loadRequest<T: GenerativeAIRequest>(request: T) async throws -> T.Response { | ||
|
@@ -177,7 +180,7 @@ struct GenerativeAIService { | |
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") | ||
|
||
if let appCheck = firebaseInfo.appCheck { | ||
let tokenResult = await appCheck.getToken(forcingRefresh: false) | ||
let tokenResult = await fetchAppCheckToken(appCheck: appCheck) | ||
urlRequest.setValue(tokenResult.token, forHTTPHeaderField: "X-Firebase-AppCheck") | ||
if let error = tokenResult.error { | ||
AILog.error( | ||
|
@@ -207,6 +210,23 @@ struct GenerativeAIService { | |
return urlRequest | ||
} | ||
|
||
private func fetchAppCheckToken(appCheck: AppCheckInterop) async | ||
-> FIRAppCheckTokenResultInterop { | ||
if aiConfig.appCheck.requireLimitedUseTokens { | ||
if let token = await appCheck.getLimitedUseToken?() { | ||
return token | ||
} | ||
|
||
AILog.error( | ||
code: .appCheckTokenFetchFailed, | ||
"Missing getLimitedUseToken() function, but requireLimitedUseTokens was enabled." | ||
) | ||
// falls back to standard token | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we fall back or throw an error here? I feel like throwing an error might make more sense, since the developer is explicitly opting in to using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is a good point. I agree. cc: @andrewheard |
||
} | ||
|
||
return await appCheck.getToken(forcingRefresh: false) | ||
} | ||
|
||
private func httpResponse(urlResponse: URLResponse) throws -> HTTPURLResponse { | ||
// The following condition should always be true: "Whenever you make HTTP URL load requests, any | ||
// response objects you get back from the URLSession, NSURLConnection, or NSURLDownload class | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe
smaller
instead oflower
?