Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@

[Full Changelog](In progress)

## ⚠️ Breaking Changes ⚠️

### Fxa Client
- Removed the optional `ttl` paramater to `get_access_token`. In practice, no consumers were using this.

## ✨ What's New ✨

### Ads Client
- Add agnostic telemetry support (compatible with Glean)

### Fxa Client
- Added optional `use_cache` paramater to `get_access_token`. Set this to `false` to force
requesting a new token.

# v147.0 (_2025-12-07_)

### Relay
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,17 +299,18 @@ class FxaClient(inner: FirefoxAccount, persistCallback: PersistCallback?) : Auto
* re-login by starting a new OAuth flow.
*
* @param scope Single OAuth scope (no spaces) for which the client wants access
* @param ttl time in seconds for which the token will be valid
* @param useCache set to false to force a new token request. The fetched token will still be
* cached for later `get_access_token` calls.
* @return [AccessTokenInfo] that stores the token, along with its scopes and keys when complete
* @throws FxaException.Network Network error while requesting the access token.
* @throws FxaException.Unauthorized We couldn't provide an access token for this scope.
* @throws FxaException.SyncScopedKeyMissingInServerResponse we received an access token for the
* sync scoped, but the sync key that should accompany it was missing.
*/
fun getAccessToken(scope: String, ttl: Long? = null): AccessTokenInfo {
fun getAccessToken(scope: String, useCache: Boolean = true): AccessTokenInfo {
return withMetrics {
try {
this.inner.getAccessToken(scope, ttl)
this.inner.getAccessToken(scope, useCache)
} finally {
this.tryPersistState()
}
Expand Down
5 changes: 3 additions & 2 deletions components/fxa-client/src/fxa_client.udl
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,8 @@ interface FirefoxAccount {
/// - `scope` - the OAuth scope to be granted by the token.
/// - This must be one of the scopes requested during the signin flow.
/// - Only a single scope is supported; for multiple scopes request multiple tokens.
/// - `ttl` - optionally, the time for which the token should be valid, in seconds.
/// - `use_cache` - optionally set to false to force a new token request. The fetched
/// token will still be cached for later `get_access_token` calls.
///
/// # Notes
///
Expand All @@ -645,7 +646,7 @@ interface FirefoxAccount {
/// before requesting a fresh token.
///
[Throws=FxaError]
AccessTokenInfo get_access_token([ByRef] string scope, optional i64? ttl = null);
AccessTokenInfo get_access_token([ByRef] string scope, optional boolean use_cache = true);

/// Get the session token for the user's account, if one is available.
///
Expand Down
19 changes: 11 additions & 8 deletions components/fxa-client/src/internal/oauth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,21 @@ impl FirefoxAccount {
/// using `begin_oauth_flow`.
///
/// * `scopes` - Space-separated list of requested scopes.
/// * `ttl` - the ttl in seconds of the token requested from the server.
/// * `use_cache` - optionally set to false to force a new token request. The fetched
/// token will still be cached for later `get_access_token` calls.
///
/// **💾 This method may alter the persisted account state.**
pub fn get_access_token(&mut self, scope: &str, ttl: Option<u64>) -> Result<AccessTokenInfo> {
pub fn get_access_token(&mut self, scope: &str, use_cache: bool) -> Result<AccessTokenInfo> {
if scope.contains(' ') {
return Err(Error::MultipleScopesRequested);
}
if let Some(oauth_info) = self.state.get_cached_access_token(scope) {
if oauth_info.expires_at > util::now_secs() + OAUTH_MIN_TIME_LEFT {
// If the cached key is missing the required sync scoped key, try to fetch it again
if oauth_info.check_missing_sync_scoped_key().is_ok() {
return Ok(oauth_info.clone());
if use_cache {
if let Some(oauth_info) = self.state.get_cached_access_token(scope) {
if oauth_info.expires_at > util::now_secs() + OAUTH_MIN_TIME_LEFT {
// If the cached key is missing the required sync scoped key, try to fetch it again
if oauth_info.check_missing_sync_scoped_key().is_ok() {
return Ok(oauth_info.clone());
}
}
}
}
Expand All @@ -59,7 +62,7 @@ impl FirefoxAccount {
self.client.create_access_token_using_refresh_token(
self.state.config(),
&refresh_token.token,
ttl,
None,
&[scope],
)?
} else {
Expand Down
2 changes: 1 addition & 1 deletion components/fxa-client/src/internal/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl FirefoxAccount {
}
etag = Some(cached_profile.etag.clone());
}
let profile_access_token = self.get_access_token(scopes::PROFILE, None)?.token;
let profile_access_token = self.get_access_token(scopes::PROFILE, true)?.token;
match self
.client
.get_profile(self.state.config(), &profile_access_token, etag)?
Expand Down
11 changes: 5 additions & 6 deletions components/fxa-client/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
use crate::{ApiResult, Error, FirefoxAccount};
use error_support::handle_error;
use serde_derive::*;
use std::convert::{TryFrom, TryInto};
use std::convert::TryInto;

impl FirefoxAccount {
/// Get a short-lived OAuth access token for the user's account.
Expand All @@ -37,20 +37,19 @@ impl FirefoxAccount {
/// - `scope` - the OAuth scope to be granted by the token.
/// - This must be one of the scopes requested during the signin flow.
/// - Only a single scope is supported; for multiple scopes request multiple tokens.
/// - `ttl` - optionally, the time for which the token should be valid, in seconds.
/// - `use_cache` - optionally set to false to force a new token request. The fetched
/// token will still be cached for later `get_access_token` calls.
///
/// # Notes
///
/// - If the application receives an authorization error when trying to use the resulting
/// token, it should call [`clear_access_token_cache`](FirefoxAccount::clear_access_token_cache)
/// before requesting a fresh token.
#[handle_error(Error)]
pub fn get_access_token(&self, scope: &str, ttl: Option<i64>) -> ApiResult<AccessTokenInfo> {
// Signedness converstion for Kotlin compatibility :-/
let ttl = ttl.map(|ttl| u64::try_from(ttl).unwrap_or_default());
pub fn get_access_token(&self, scope: &str, use_cache: bool) -> ApiResult<AccessTokenInfo> {
self.internal
.lock()
.get_access_token(scope, ttl)?
.get_access_token(scope, use_cache)?
.try_into()
}

Expand Down
4 changes: 2 additions & 2 deletions examples/cli-support/src/fxa_creds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub fn get_account_and_token(
// TODO: we should probably set a persist callback on acct?
let acct = load_or_create_fxa_creds(cred_file, config.clone(), scopes)?;
// `scope` could be a param, but I can't see it changing.
match acct.get_access_token(SYNC_SCOPE, None) {
match acct.get_access_token(SYNC_SCOPE, true) {
Ok(t) => Ok((acct, t)),
Err(e) => {
match e {
Expand All @@ -101,7 +101,7 @@ pub fn get_account_and_token(
println!("Saw an auth error using stored credentials - attempting to re-authenticate");
println!("If fails, consider deleting {cred_file} to start from scratch");
handle_oauth_flow(cred_file, &acct, scopes)?;
let token = acct.get_access_token(SYNC_SCOPE, None)?;
let token = acct.get_access_token(SYNC_SCOPE, true)?;
Ok((acct, token))
}
_ => Err(e.into()),
Expand Down