diff --git a/CHANGELOG.md b/CHANGELOG.md index 7745bff..d7d16e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. Each entry lists the date and the crate versions that were released. +## 2026-05-24 — mqdb-agent 0.8.4 + +### Removed + +- Dead `jwt: String` field on `Session`, `NewSession`, and `SessionRef`. After 0.8.3 wired the JTI directly into the session, no code path read the stored JWT anymore: `handle_logout` uses `session.jti`, `destroy_others_by_canonical_id` returns JTIs, and `handle_ticket` mints fresh ticket JWTs from session claims rather than the stored one. The session-time JWT was never returned to the client either — callback, register, and login all set just the session-id cookie plus a user-info JSON body. Dropped the field and removed `mint_callback_jwt` entirely; its three callers now generate a JTI inline via `JtiRevocationStore::generate_jti()`. `handle_login` no longer needs to construct a `ProviderIdentity` or destructure `email_verified`. No behavior change; the affected types are crate-private. + ## 2026-05-23 — mqdb-agent 0.8.3 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index f11f767..e999ae6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1349,7 +1349,7 @@ dependencies = [ [[package]] name = "mqdb-agent" -version = "0.8.3" +version = "0.8.4" dependencies = [ "arc-swap", "argon2", diff --git a/crates/mqdb-agent/Cargo.toml b/crates/mqdb-agent/Cargo.toml index 7f8213a..927782a 100644 --- a/crates/mqdb-agent/Cargo.toml +++ b/crates/mqdb-agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mqdb-agent" -version = "0.8.3" +version = "0.8.4" edition.workspace = true license = "Apache-2.0" authors.workspace = true diff --git a/crates/mqdb-agent/src/http/handlers.rs b/crates/mqdb-agent/src/http/handlers.rs index 06ffc73..a8ee4ef 100644 --- a/crates/mqdb-agent/src/http/handlers.rs +++ b/crates/mqdb-agent/src/http/handlers.rs @@ -266,11 +266,8 @@ pub async fn handle_callback(state: &ServerState, query: &str) -> HttpResponse { persist_oauth_tokens(state, &link_key, &canonical_id, refresh_token, &identity).await; } - let (jwt, jti) = mint_callback_jwt(state, &canonical_id, &identity); - let Some(session_id) = state.session_store.create(NewSession { - jwt, - jti, + jti: JtiRevocationStore::generate_jti(), canonical_id, provider: provider.to_string(), provider_sub: identity.provider_sub.clone(), @@ -618,33 +615,6 @@ async fn persist_oauth_tokens( } } -fn mint_callback_jwt( - state: &ServerState, - canonical_id: &str, - identity: &ProviderIdentity, -) -> (String, String) { - let now = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map_or(0, |d| d.as_secs()); - - let jti = JtiRevocationStore::generate_jti(); - let claims = json!({ - "sub": canonical_id, - "iss": state.jwt_config.issuer, - "aud": state.jwt_config.audience, - "exp": now + state.jwt_config.expiry_secs, - "iat": now, - "jti": jti, - "email": identity.email, - "name": identity.name, - "picture": identity.picture, - "provider": identity.provider, - "provider_sub": identity.provider_sub, - }); - - (sign_jwt(&claims, &state.jwt_config), jti) -} - pub async fn handle_refresh(state: &ServerState, body: &[u8]) -> HttpResponse { let cors = state.cors_origin.as_deref(); @@ -1741,10 +1711,8 @@ pub async fn handle_register(state: &ServerState, body: &[u8], client_ip: &str) return json_response_with_credentials(500, &json!({"error": "registration failed"}), cors); } - let (jwt, jti) = mint_callback_jwt(state, &canonical_id, &identity); let Some(session_id) = state.session_store.create(NewSession { - jwt, - jti, + jti: JtiRevocationStore::generate_jti(), canonical_id: canonical_id.clone(), provider: "email".to_string(), provider_sub, @@ -1832,7 +1800,7 @@ pub async fn handle_login(state: &ServerState, body: &[u8], client_ip: &str) -> return json_response_with_credentials(500, &json!({"error": "login failed"}), cors); }; - let (display_name, display_email, verified) = if let Some(mut ident) = + let (display_name, display_email) = if let Some(mut ident) = read_entity(&state.mqtt_client, "_identities", canonical_id).await { if let Some(ref crypto) = state.identity_crypto { @@ -1850,30 +1818,15 @@ pub async fn handle_login(state: &ServerState, body: &[u8], client_ip: &str) -> .get("primary_email") .and_then(|v| v.as_str()) .map(String::from); - let ev = ident - .get("email_verified") - .and_then(serde_json::Value::as_bool) - .unwrap_or(false); - (n, e, ev) + (n, e) } else { - (None, Some(email.to_string()), false) + (None, Some(email.to_string())) }; let picture = fetch_picture_from_links(state, canonical_id).await; - let identity = ProviderIdentity { - provider: "email", - provider_sub: canonical_id.to_string(), - email: display_email.clone(), - name: display_name.clone(), - picture: picture.clone(), - email_verified: verified, - }; - let (jwt, jti) = mint_callback_jwt(state, canonical_id, &identity); - let Some(session_id) = state.session_store.create(NewSession { - jwt, - jti, + jti: JtiRevocationStore::generate_jti(), canonical_id: canonical_id.to_string(), provider: "email".to_string(), provider_sub: canonical_id.to_string(), @@ -2833,7 +2786,6 @@ pub async fn handle_dev_login(state: &ServerState, body: &[u8]) -> HttpResponse } let Some(session_id) = state.session_store.create(NewSession { - jwt: String::new(), jti: String::new(), canonical_id: canonical_id.clone(), provider: "dev".to_string(), diff --git a/crates/mqdb-agent/src/http/session_store.rs b/crates/mqdb-agent/src/http/session_store.rs index e52b018..24e23ea 100644 --- a/crates/mqdb-agent/src/http/session_store.rs +++ b/crates/mqdb-agent/src/http/session_store.rs @@ -11,7 +11,6 @@ const SESSION_ID_BYTES: usize = 32; const SESSION_TTL_SECS: u64 = 86400; pub struct Session { - pub jwt: String, pub jti: String, pub canonical_id: String, pub provider: String, @@ -24,7 +23,6 @@ pub struct Session { } pub struct NewSession { - pub jwt: String, pub jti: String, pub canonical_id: String, pub provider: String, @@ -61,7 +59,6 @@ impl SessionStore { let session_id = hex_encode(&bytes); let session = Session { - jwt: new.jwt, jti: new.jti, canonical_id: new.canonical_id, provider: new.provider, @@ -89,7 +86,6 @@ impl SessionStore { } Some(SessionRef { - jwt: session.jwt.clone(), jti: session.jti.clone(), canonical_id: session.canonical_id.clone(), provider: session.provider.clone(), @@ -170,7 +166,6 @@ impl SessionStore { } pub struct SessionRef { - pub jwt: String, pub jti: String, pub canonical_id: String, pub provider: String, @@ -283,7 +278,6 @@ mod tests { let store = SessionStore::new(); let session_id = store .create(NewSession { - jwt: "jwt123".into(), jti: "jti-abc".into(), canonical_id: "550e8400-e29b-41d4-a716-446655440000".into(), provider: "google".into(), @@ -297,7 +291,6 @@ mod tests { assert_eq!(session_id.len(), 64); let session = store.get(&session_id).expect("get should succeed"); - assert_eq!(session.jwt, "jwt123"); assert_eq!(session.jti, "jti-abc"); assert_eq!(session.canonical_id, "550e8400-e29b-41d4-a716-446655440000"); assert_eq!(session.provider, "google"); @@ -310,7 +303,6 @@ mod tests { let store = SessionStore::new(); let session_id = store .create(NewSession { - jwt: "jwt".into(), jti: "jti".into(), canonical_id: "canonical-1".into(), provider: "google".into(), @@ -335,7 +327,6 @@ mod tests { fn make_session(store: &SessionStore, canonical_id: &str, jti: &str) -> String { store .create(NewSession { - jwt: format!("jwt-for-{jti}"), jti: jti.into(), canonical_id: canonical_id.into(), provider: "email".into(), @@ -395,7 +386,6 @@ mod tests { let store = SessionStore::new(); let dev_session = store .create(NewSession { - jwt: String::new(), jti: String::new(), canonical_id: "user-a".into(), provider: "dev".into(),