feat(session): ensure dpop even with OIDC

This commit is contained in:
Edouard Vanbelle
2026-08-09 05:00:30 +02:00
parent bee856fbd0
commit 10d831b204
7 changed files with 103 additions and 14 deletions
+23
View File
@@ -50,16 +50,38 @@ pub struct SessionSummaryDto {
/// IdP emitted `sid` — otherwise `None`. Useful when an operator is
/// correlating with the upstream IdP's session log.
pub oidc_sid: Option<String>,
/// `true` when this row IS the caller's currently-active session —
/// set by the service layer by comparing the row's `dpop_jkt` with
/// the caller's own bound thumbprint. Lets the admin panel flag
/// "revoking this cuts your own branch" so an admin doesn't
/// accidentally log themselves out. `false` when either side is
/// unbound (can't correlate) or when the jkts don't match.
pub is_current: bool,
}
impl From<Session> for SessionSummaryDto {
fn from(s: Session) -> Self {
Self::from_session(s, None)
}
}
impl SessionSummaryDto {
/// Build the DTO with an optional `caller_jkt` used to compute
/// `is_current`. Pass the admin caller's DPoP thumbprint to have
/// the panel highlight the caller's own row; pass `None` when
/// the caller is unbound (no jkt = no correlation) or from
/// non-admin contexts.
pub fn from_session(s: Session, caller_jkt: Option<&str>) -> Self {
let is_revoked = s.is_revoked();
let is_expired = s.is_expired();
let jkt = s.dpop_jkt().map(|s| s.to_owned());
let dpop_jkt_prefix = jkt
.as_ref()
.map(|t| t.chars().take(8).collect::<String>());
let is_current = match (jkt.as_deref(), caller_jkt) {
(Some(row), Some(caller)) => row == caller,
_ => false,
};
Self {
id: s.id(),
user_id: s.user_id(),
@@ -72,6 +94,7 @@ impl From<Session> for SessionSummaryDto {
is_revoked,
is_active: !is_revoked && !is_expired,
oidc_sid: s.oidc_sid().map(str::to_owned),
is_current,
}
}
}
@@ -2963,6 +2963,7 @@ impl AuthApplicationService {
&self,
authorization: &A,
caller_id: Uuid,
caller_dpop_jkt: Option<&str>,
user_id_filter: Option<Uuid>,
include_revoked: bool,
limit: i64,
@@ -2975,7 +2976,12 @@ impl AuthApplicationService {
.await?;
Ok(sessions
.into_iter()
.map(crate::application::dtos::session_dto::SessionSummaryDto::from)
.map(|s| {
crate::application::dtos::session_dto::SessionSummaryDto::from_session(
s,
caller_dpop_jkt,
)
})
.collect())
}