Skip to content

Commit 9e6c9cb

Browse files
committed
Record the decoded ID token claims on upstream auth sessions
1 parent 2940534 commit 9e6c9cb

File tree

11 files changed

+128
-45
lines changed

11 files changed

+128
-45
lines changed

crates/data-model/src/upstream_oauth2/session.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub enum UpstreamOAuthAuthorizationSessionState {
1919
completed_at: DateTime<Utc>,
2020
link_id: Ulid,
2121
id_token: Option<String>,
22+
id_token_claims: Option<serde_json::Value>,
2223
extra_callback_parameters: Option<serde_json::Value>,
2324
userinfo: Option<serde_json::Value>,
2425
},
@@ -27,6 +28,7 @@ pub enum UpstreamOAuthAuthorizationSessionState {
2728
consumed_at: DateTime<Utc>,
2829
link_id: Ulid,
2930
id_token: Option<String>,
31+
id_token_claims: Option<serde_json::Value>,
3032
extra_callback_parameters: Option<serde_json::Value>,
3133
userinfo: Option<serde_json::Value>,
3234
},
@@ -35,6 +37,7 @@ pub enum UpstreamOAuthAuthorizationSessionState {
3537
consumed_at: Option<DateTime<Utc>>,
3638
unlinked_at: DateTime<Utc>,
3739
id_token: Option<String>,
40+
id_token_claims: Option<serde_json::Value>,
3841
},
3942
}
4043

@@ -52,6 +55,7 @@ impl UpstreamOAuthAuthorizationSessionState {
5255
completed_at: DateTime<Utc>,
5356
link: &UpstreamOAuthLink,
5457
id_token: Option<String>,
58+
id_token_claims: Option<serde_json::Value>,
5559
extra_callback_parameters: Option<serde_json::Value>,
5660
userinfo: Option<serde_json::Value>,
5761
) -> Result<Self, InvalidTransitionError> {
@@ -60,6 +64,7 @@ impl UpstreamOAuthAuthorizationSessionState {
6064
completed_at,
6165
link_id: link.id,
6266
id_token,
67+
id_token_claims,
6368
extra_callback_parameters,
6469
userinfo,
6570
}),
@@ -83,13 +88,15 @@ impl UpstreamOAuthAuthorizationSessionState {
8388
completed_at,
8489
link_id,
8590
id_token,
91+
id_token_claims,
8692
extra_callback_parameters,
8793
userinfo,
8894
} => Ok(Self::Consumed {
8995
completed_at,
9096
link_id,
9197
consumed_at,
9298
id_token,
99+
id_token_claims,
93100
extra_callback_parameters,
94101
userinfo,
95102
}),
@@ -146,6 +153,29 @@ impl UpstreamOAuthAuthorizationSessionState {
146153
}
147154
}
148155

156+
/// Get the ID token claims for the upstream OAuth 2.0 authorization
157+
/// session.
158+
///
159+
/// Returns `None` if the upstream OAuth 2.0 authorization session state is
160+
/// not [`Pending`].
161+
///
162+
/// [`Pending`]: UpstreamOAuthAuthorizationSessionState::Pending
163+
#[must_use]
164+
pub fn id_token_claims(&self) -> Option<&serde_json::Value> {
165+
match self {
166+
Self::Pending => None,
167+
Self::Completed {
168+
id_token_claims, ..
169+
}
170+
| Self::Consumed {
171+
id_token_claims, ..
172+
}
173+
| Self::Unlinked {
174+
id_token_claims, ..
175+
} => id_token_claims.as_ref(),
176+
}
177+
}
178+
149179
/// Get the extra query parameters that were sent to the upstream provider.
150180
///
151181
/// Returns `None` if the upstream OAuth 2.0 authorization session state is
@@ -277,13 +307,15 @@ impl UpstreamOAuthAuthorizationSession {
277307
completed_at: DateTime<Utc>,
278308
link: &UpstreamOAuthLink,
279309
id_token: Option<String>,
310+
id_token_claims: Option<serde_json::Value>,
280311
extra_callback_parameters: Option<serde_json::Value>,
281312
userinfo: Option<serde_json::Value>,
282313
) -> Result<Self, InvalidTransitionError> {
283314
self.state = self.state.complete(
284315
completed_at,
285316
link,
286317
id_token,
318+
id_token_claims,
287319
extra_callback_parameters,
288320
userinfo,
289321
)?;

crates/handlers/src/admin/v1/upstream_oauth_links/delete.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ mod tests {
126126

127127
let session = repo
128128
.upstream_oauth_session()
129-
.complete_with_link(&state.clock, session, &link, None, None, None)
129+
.complete_with_link(&state.clock, session, &link, None, None, None, None)
130130
.await
131131
.unwrap();
132132

crates/handlers/src/upstream_oauth2/callback.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ pub(crate) async fn handler(
312312
.await?;
313313

314314
let mut jwks = None;
315+
let mut id_token_claims = None;
315316

316317
let mut context = AttributeMappingContext::new();
317318
if let Some(id_token) = token_response.id_token.as_ref() {
@@ -337,6 +338,14 @@ pub(crate) async fn handler(
337338

338339
let (_headers, mut claims) = id_token.into_parts();
339340

341+
// Save a copy of the claims for later; the claims extract methods
342+
// remove them from the map, and we want to store the original claims.
343+
// We anyway need this to be a serde_json::Value
344+
id_token_claims = Some(
345+
serde_json::to_value(&claims)
346+
.expect("serializing a HashMap<String, Value> into a Value should never fail"),
347+
);
348+
340349
// Access token hash must match.
341350
mas_jose::claims::AT_HASH
342351
.extract_optional_with_options(
@@ -472,6 +481,7 @@ pub(crate) async fn handler(
472481
session,
473482
&link,
474483
token_response.id_token,
484+
id_token_claims,
475485
params.extra_callback_parameters,
476486
userinfo,
477487
)

crates/handlers/src/upstream_oauth2/link.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,7 @@ mod tests {
934934
..UpstreamOAuthProviderClaimsImports::default()
935935
};
936936

937-
let id_token = serde_json::json!({
937+
let id_token_claims = serde_json::json!({
938938
"preferred_username": "john",
939939
"email": "[email protected]",
940940
"email_verified": true,
@@ -953,7 +953,8 @@ mod tests {
953953
.signing_key_for_alg(&JsonWebSignatureAlg::Rs256)
954954
.unwrap();
955955
let header = JsonWebSignatureHeader::new(JsonWebSignatureAlg::Rs256);
956-
let id_token = Jwt::sign_with_rng(&mut rng, header, id_token, &signer).unwrap();
956+
let id_token =
957+
Jwt::sign_with_rng(&mut rng, header, id_token_claims.clone(), &signer).unwrap();
957958

958959
// Provision a provider and a link
959960
let mut repo = state.repository().await.unwrap();
@@ -1022,6 +1023,7 @@ mod tests {
10221023
session,
10231024
&link,
10241025
Some(id_token.into_string()),
1026+
Some(id_token_claims),
10251027
None,
10261028
None,
10271029
)

crates/storage-pg/.sqlx/query-5f5245ace61b896f92be78ab4fef701b37c9e3c2f4a332f418b9fb2625a0fe3f.json

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 13 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/storage-pg/.sqlx/query-fd8f3e7ff02d4d1f465aad32edcb06a842cabc787279ba7d690f69b59ad3eb50.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Copyright 2025 New Vector Ltd.
2+
--
3+
-- SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
4+
-- Please see LICENSE in the repository root for full details.
5+
6+
-- This is the decoded claims from the ID token stored as JSONB
7+
ALTER TABLE upstream_oauth_authorization_sessions
8+
ADD COLUMN id_token_claims JSONB;

crates/storage-pg/src/upstream_oauth2/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ mod tests {
152152

153153
let session = repo
154154
.upstream_oauth_session()
155-
.complete_with_link(&clock, session, &link, None, None, None)
155+
.complete_with_link(&clock, session, &link, None, None, None, None)
156156
.await
157157
.unwrap();
158158
// Reload the session

0 commit comments

Comments
 (0)