Skip to content

Commit 3f18ba6

Browse files
chore: prepare new release(s)
1 parent 35cb8d3 commit 3f18ba6

12 files changed

Lines changed: 289 additions & 328 deletions

File tree

Cargo.lock

Lines changed: 155 additions & 308 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,25 +93,25 @@ version = '0.27.0'
9393

9494
[workspace.dependencies.qcs-api-client-common]
9595
path = 'qcs-api-client-common'
96-
version = '0.18.2'
96+
version = '0.18.3'
9797

9898
[workspace.dependencies.qcs-api-client-grpc]
9999
path = 'qcs-api-client-grpc'
100-
version = '0.18.2'
100+
version = '0.18.3'
101101

102102
[workspace.dependencies.qcs-api-client-grpc-internal]
103103
path = 'qcs-api-client-grpc-internal'
104104
registry = 'rigetti-cargo'
105-
version = '0.19.2'
105+
version = '0.19.3'
106106

107107
[workspace.dependencies.qcs-api-client-openapi]
108108
path = 'qcs-api-client-openapi/public'
109-
version = '0.19.2'
109+
version = '0.19.3'
110110

111111
[workspace.dependencies.qcs-api-client-openapi-internal]
112112
path = 'qcs-api-client-openapi/internal'
113113
registry = 'rigetti-cargo'
114-
version = '0.19.2'
114+
version = '0.19.3'
115115

116116
[workspace.dependencies.qcs-dependencies-client]
117117
version = '0.3.1'

qcs-api-client-common/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.18.3 (2026-06-30)
2+
3+
### Fixes
4+
5+
- persist refresh token changes
6+
17
## 0.18.2 (2026-06-01)
28

39
### Fixes

qcs-api-client-common/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ name = 'qcs-api-client-common'
88
publish = true
99
readme = 'README.md'
1010
repository = 'https://github.com/rigetti/qcs-api-client-rust'
11-
version = '0.18.2'
11+
version = '0.18.3'
1212

1313
[package.authors]
1414
workspace = true

qcs-api-client-common/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "qcs-api-client-common"
33
requires-python = ">=3.10,<4"
4-
version = "0.18.2"
4+
version = "0.18.3"
55
description = "Contains core QCS client functionality and middleware implementations."
66
readme = "README-py.md"
77
license = { text = "Apache-2.0" }
@@ -144,6 +144,7 @@ disable_error_code = [
144144
]
145145

146146
[tool.pytest.ini_options]
147+
asyncio_mode = "auto"
147148
markers = [
148149
"integration: mark a test as an integration test.",
149150
]

qcs-api-client-common/src/configuration/tokens.rs

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -604,12 +604,15 @@ impl TokenDispatcher {
604604
match Secrets::is_read_only(secrets_path).await {
605605
Ok(true) => Ok(()),
606606
Ok(false) => {
607-
// If the payload is a PkceFlow, write the fresh refresh token if available.
607+
// Persist the fresh refresh token if the grant carries one, so that a rotated
608+
// refresh token isn't lost on the next load. Both the PKCE and refresh-token
609+
// grants can hold a refresh token that the auth server may have rotated.
608610
let refresh_token = match &oauth_session.payload {
609611
OAuthGrant::PkceFlow(payload) => {
610612
payload.refresh_token.as_ref().map(|rt| &rt.refresh_token)
611613
}
612-
_ => None,
614+
OAuthGrant::RefreshToken(payload) => Some(&payload.refresh_token),
615+
OAuthGrant::ExternallyManaged(_) | OAuthGrant::ClientCredentials(_) => None,
613616
};
614617

615618
let now = OffsetDateTime::now_utc();
@@ -1217,6 +1220,102 @@ updated_at = "2024-01-01T00:00:00Z"
12171220
});
12181221
}
12191222

1223+
/// When the auth server rotates the refresh token, a [`OAuthGrant::RefreshToken`] grant should
1224+
/// persist the new refresh token to the secrets file (not just the access token).
1225+
#[test]
1226+
fn test_refresh_token_grant_persists_rotated_refresh_token() {
1227+
let initial_refresh_token = "initial_refresh_token";
1228+
let rotated_refresh_token = "rotated_refresh_token";
1229+
let new_access_token = "new_access_token";
1230+
1231+
figment::Jail::expect_with(|jail| {
1232+
jail.clear_env();
1233+
1234+
let secrets_path = "secrets.toml";
1235+
let initial_secrets_file_contents = format!(
1236+
r#"
1237+
[credentials]
1238+
[credentials.test]
1239+
[credentials.test.token_payload]
1240+
access_token = "initial_access_token"
1241+
refresh_token = "{initial_refresh_token}"
1242+
updated_at = "2024-01-01T00:00:00Z"
1243+
"#
1244+
);
1245+
jail.create_file(secrets_path, &initial_secrets_file_contents)
1246+
.expect("should create test secrets.toml");
1247+
1248+
let rt = tokio::runtime::Runtime::new().unwrap();
1249+
rt.block_on(async {
1250+
let mock_server = MockServer::start_async().await;
1251+
let oidc_mock = mock_server
1252+
.mock_async(|when, then| {
1253+
when.method(GET).path("/.well-known/openid-configuration");
1254+
then.status(200)
1255+
.json_body_obj(&oidc::Discovery::new_for_test(
1256+
mock_server.base_url().parse().unwrap(),
1257+
));
1258+
})
1259+
.await;
1260+
let issuer_mock = mock_server
1261+
.mock_async(|when, then| {
1262+
when.method(POST).path("/v1/token");
1263+
then.status(200).json_body_obj(&RefreshTokenResponse {
1264+
access_token: SecretAccessToken::from(new_access_token),
1265+
refresh_token: Some(SecretRefreshToken::from(rotated_refresh_token)),
1266+
});
1267+
})
1268+
.await;
1269+
1270+
let dispatcher: TokenDispatcher = OAuthSession::from_refresh_token(
1271+
RefreshToken::new(SecretRefreshToken::from(initial_refresh_token)),
1272+
AuthServer {
1273+
client_id: "client_id".to_string(),
1274+
issuer: mock_server.base_url(),
1275+
scopes: None,
1276+
},
1277+
Some(SecretAccessToken::from("initial_access_token")),
1278+
)
1279+
.into();
1280+
1281+
dispatcher
1282+
.refresh(
1283+
&ConfigSource::File {
1284+
settings_path: "".into(),
1285+
secrets_path: secrets_path.into(),
1286+
},
1287+
"test",
1288+
)
1289+
.await
1290+
.expect("refresh should succeed");
1291+
1292+
oidc_mock.assert_async().await;
1293+
issuer_mock.assert_async().await;
1294+
});
1295+
1296+
// The rotated refresh token (and the new access token) should be persisted.
1297+
let payload = Secrets::load_from_path(&secrets_path.into())
1298+
.expect("should load secrets")
1299+
.credentials
1300+
.remove("test")
1301+
.expect("should have test credentials")
1302+
.token_payload
1303+
.expect("should have token payload");
1304+
assert_eq!(
1305+
payload.refresh_token.unwrap(),
1306+
SecretRefreshToken::from(rotated_refresh_token),
1307+
"rotated refresh token should be persisted to the secrets file"
1308+
);
1309+
assert_eq!(
1310+
payload.access_token.unwrap(),
1311+
SecretAccessToken::from(new_access_token),
1312+
"new access token should be persisted to the secrets file"
1313+
);
1314+
1315+
Ok(())
1316+
});
1317+
}
1318+
12201319
#[test]
12211320
fn test_auth_session_debug_fmt() {
12221321
let session = OAuthSession {

qcs-api-client-common/tests_py/test_grpc.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import asyncio
2-
from unittest.mock import MagicMock
1+
from unittest.mock import AsyncMock
32

43
import grpc
54
import pytest
@@ -30,11 +29,8 @@ def make_request() -> Any:
3029

3130
@pytest.fixture
3231
def mock_config(mocker):
33-
mock = mocker.patch.object(ClientConfiguration, "get_bearer_access_token_async", new_callable=MagicMock)
34-
future = asyncio.Future()
35-
future.set_result("mock_config_fixture_token")
36-
mock.return_value = future
37-
32+
mock = mocker.patch.object(ClientConfiguration, "get_bearer_access_token_async", new_callable=AsyncMock)
33+
mock.return_value = "mock_config_fixture_token"
3834
return mock
3935

4036

qcs-api-client-grpc/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.18.3 (2026-06-30)
2+
3+
### Fixes
4+
5+
- persist refresh token changes
6+
17
## 0.18.2 (2026-06-01)
28

39
### Fixes

qcs-api-client-grpc/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ name = 'qcs-api-client-grpc'
88
publish = true
99
readme = 'README.md'
1010
repository = 'https://github.com/rigetti/qcs-api-client-rust'
11-
version = '0.18.2'
11+
version = '0.18.3'
1212

1313
[package.authors]
1414
workspace = true

qcs-api-client-openapi/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.19.3 (2026-06-30)
2+
3+
### Fixes
4+
5+
- persist refresh token changes
6+
17
## 0.19.2 (2026-06-01)
28

39
### Fixes

0 commit comments

Comments
 (0)