mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-07-22 16:02:45 +00:00
auth: newtype jti claims, sessions, & store
Lewis: May this revision serve well! <lu5a@proton.me>
This commit is contained in:
@@ -14,7 +14,7 @@ use tranquil_pds::auth::NormalizedLoginIdentifier;
|
||||
use tranquil_pds::auth::{ServiceTokenVerifier, generate_app_password, is_service_token};
|
||||
use tranquil_pds::rate_limit::{AccountCreationLimit, PasswordResetLimit, RateLimited};
|
||||
use tranquil_pds::state::AppState;
|
||||
use tranquil_pds::types::{Did, Handle, PlainPassword};
|
||||
use tranquil_pds::types::{Did, Handle, Jti, Nsid, PlainPassword};
|
||||
use tranquil_pds::validation::validate_password;
|
||||
|
||||
fn generate_setup_token() -> String {
|
||||
@@ -368,7 +368,7 @@ pub async fn create_passkey_account(
|
||||
let access_jwt = if byod_auth.is_some() {
|
||||
match tranquil_pds::auth::create_access_token_with_metadata(&did, &secret_key_bytes) {
|
||||
Ok(token_meta) => {
|
||||
let refresh_jti = uuid::Uuid::new_v4().to_string();
|
||||
let refresh_jti = Jti::from(uuid::Uuid::new_v4().to_string());
|
||||
let refresh_expires = chrono::Utc::now() + chrono::Duration::hours(24);
|
||||
let session_data = tranquil_db_traits::SessionTokenCreate {
|
||||
did: did_typed.clone(),
|
||||
|
||||
@@ -8,6 +8,7 @@ use chrono::{DateTime, Duration, Utc};
|
||||
use hmac::{Hmac, Mac};
|
||||
use k256::ecdsa::{Signature, SigningKey, signature::Signer};
|
||||
use sha2::Sha256;
|
||||
use tranquil_types::{Did, Jti, Nsid};
|
||||
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
@@ -83,7 +84,7 @@ pub fn create_access_token_with_jti(
|
||||
scopes: Option<&str>,
|
||||
controller_did: Option<&str>,
|
||||
hostname: Option<&str>,
|
||||
jti: &str,
|
||||
jti: &Jti,
|
||||
expires_at: DateTime<Utc>,
|
||||
) -> Result<String> {
|
||||
let scope = scopes.unwrap_or(TokenScope::Access.as_str());
|
||||
@@ -94,7 +95,7 @@ pub fn create_access_token_with_jti(
|
||||
TokenType::Access,
|
||||
key_bytes,
|
||||
expires_at,
|
||||
jti.to_string(),
|
||||
jti.clone(),
|
||||
act,
|
||||
hostname,
|
||||
)?
|
||||
@@ -106,7 +107,7 @@ pub fn create_access_token_with_jti(
|
||||
pub fn create_refresh_token_with_jti(
|
||||
did: &str,
|
||||
key_bytes: &[u8],
|
||||
jti: &str,
|
||||
jti: &Jti,
|
||||
expires_at: DateTime<Utc>,
|
||||
) -> Result<String> {
|
||||
Ok(create_signed_token_pinned(
|
||||
@@ -115,7 +116,7 @@ pub fn create_refresh_token_with_jti(
|
||||
TokenType::Refresh,
|
||||
key_bytes,
|
||||
expires_at,
|
||||
jti.to_string(),
|
||||
jti.clone(),
|
||||
None,
|
||||
None,
|
||||
)?
|
||||
@@ -142,8 +143,8 @@ pub fn create_service_token(
|
||||
exp: expiration,
|
||||
iat: Utc::now().timestamp(),
|
||||
scope: None,
|
||||
lxm: lxm.map(ToOwned::to_owned),
|
||||
jti: uuid::Uuid::new_v4().to_string(),
|
||||
lxm: lxm.cloned(),
|
||||
jti: Jti::new(uuid::Uuid::new_v4().to_string()),
|
||||
act: None,
|
||||
};
|
||||
|
||||
@@ -173,7 +174,7 @@ fn create_signed_token_with_act(
|
||||
let expires_at = Utc::now()
|
||||
.checked_add_signed(duration)
|
||||
.expect("valid timestamp");
|
||||
let jti = uuid::Uuid::new_v4().to_string();
|
||||
let jti = Jti::new(uuid::Uuid::new_v4().to_string());
|
||||
create_signed_token_pinned(did, scope, typ, key_bytes, expires_at, jti, act, hostname)
|
||||
}
|
||||
|
||||
@@ -184,7 +185,7 @@ fn create_signed_token_pinned(
|
||||
typ: TokenType,
|
||||
key_bytes: &[u8],
|
||||
expires_at: DateTime<Utc>,
|
||||
jti: String,
|
||||
jti: Jti,
|
||||
act: Option<ActClaim>,
|
||||
hostname: Option<&str>,
|
||||
) -> Result<TokenWithMetadata> {
|
||||
@@ -294,8 +295,8 @@ pub fn create_service_token_hs256(
|
||||
exp: expiration,
|
||||
iat: Utc::now().timestamp(),
|
||||
scope: None,
|
||||
lxm: Some(lxm.to_string()),
|
||||
jti: uuid::Uuid::new_v4().to_string(),
|
||||
lxm: Some(lxm.clone()),
|
||||
jti: Jti::new(uuid::Uuid::new_v4().to_string()),
|
||||
act: None,
|
||||
};
|
||||
|
||||
@@ -314,7 +315,7 @@ fn create_hs256_token_with_metadata(
|
||||
.expect("valid timestamp");
|
||||
|
||||
let expiration = expires_at.timestamp();
|
||||
let jti = uuid::Uuid::new_v4().to_string();
|
||||
let jti = Jti::new(uuid::Uuid::new_v4().to_string());
|
||||
|
||||
let claims = Claims {
|
||||
iss: did.to_owned(),
|
||||
|
||||
@@ -2,6 +2,7 @@ use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize, de, ser};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use tranquil_types::{Did, Jti, Nsid};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TokenType {
|
||||
@@ -217,7 +218,7 @@ pub struct Claims {
|
||||
pub scope: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub lxm: Option<Nsid>,
|
||||
pub jti: String,
|
||||
pub jti: Jti,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub act: Option<ActClaim>,
|
||||
}
|
||||
@@ -240,7 +241,7 @@ pub struct TokenData<T> {
|
||||
|
||||
pub struct TokenWithMetadata {
|
||||
pub token: String,
|
||||
pub jti: String,
|
||||
pub jti: Jti,
|
||||
pub expires_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use hmac::{Hmac, Mac};
|
||||
use k256::ecdsa::{Signature, SigningKey, VerifyingKey, signature::Verifier};
|
||||
use sha2::Sha256;
|
||||
use subtle::ConstantTimeEq;
|
||||
use tranquil_types::{Did, Jti};
|
||||
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
@@ -29,7 +30,7 @@ pub fn get_did_from_token(token: &str) -> Result<String, TokenDecodeError> {
|
||||
Ok(claims.sub.unwrap_or(claims.iss))
|
||||
}
|
||||
|
||||
pub fn get_jti_from_token(token: &str) -> Result<String, TokenDecodeError> {
|
||||
pub fn get_jti_from_token(token: &str) -> Result<Jti, TokenDecodeError> {
|
||||
let parts: Vec<&str> = token.split('.').collect();
|
||||
if parts.len() != 3 {
|
||||
return Err(TokenDecodeError::InvalidFormat);
|
||||
@@ -45,7 +46,7 @@ pub fn get_jti_from_token(token: &str) -> Result<String, TokenDecodeError> {
|
||||
claims
|
||||
.get("jti")
|
||||
.and_then(|j| j.as_str())
|
||||
.map(|s| s.to_string())
|
||||
.map(Jti::new)
|
||||
.ok_or(TokenDecodeError::MissingClaim)
|
||||
}
|
||||
|
||||
|
||||
@@ -84,8 +84,8 @@ impl std::fmt::Display for SessionId {
|
||||
pub struct SessionToken {
|
||||
pub id: SessionId,
|
||||
pub did: Did,
|
||||
pub access_jti: String,
|
||||
pub refresh_jti: String,
|
||||
pub access_jti: Jti,
|
||||
pub refresh_jti: Jti,
|
||||
pub access_expires_at: DateTime<Utc>,
|
||||
pub refresh_expires_at: DateTime<Utc>,
|
||||
pub login_type: LoginType,
|
||||
@@ -100,8 +100,8 @@ pub struct SessionToken {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SessionTokenCreate {
|
||||
pub did: Did,
|
||||
pub access_jti: String,
|
||||
pub refresh_jti: String,
|
||||
pub access_jti: Jti,
|
||||
pub refresh_jti: Jti,
|
||||
pub access_expires_at: DateTime<Utc>,
|
||||
pub refresh_expires_at: DateTime<Utc>,
|
||||
pub login_type: LoginType,
|
||||
@@ -124,7 +124,7 @@ pub struct SessionForRefresh {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SessionListItem {
|
||||
pub id: SessionId,
|
||||
pub access_jti: String,
|
||||
pub access_jti: Jti,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub refresh_expires_at: DateTime<Utc>,
|
||||
}
|
||||
@@ -175,8 +175,8 @@ pub struct RefreshGraceReplay {
|
||||
pub did: Did,
|
||||
pub scope: Option<String>,
|
||||
pub controller_did: Option<Did>,
|
||||
pub access_jti: String,
|
||||
pub refresh_jti: String,
|
||||
pub access_jti: Jti,
|
||||
pub refresh_jti: Jti,
|
||||
pub access_expires_at: DateTime<Utc>,
|
||||
pub refresh_expires_at: DateTime<Utc>,
|
||||
pub key_bytes: Vec<u8>,
|
||||
@@ -205,10 +205,10 @@ pub enum RefreshGraceLookup {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SessionRefreshData {
|
||||
pub did: Did,
|
||||
pub old_refresh_jti: String,
|
||||
pub old_refresh_jti: Jti,
|
||||
pub session_id: SessionId,
|
||||
pub new_access_jti: String,
|
||||
pub new_refresh_jti: String,
|
||||
pub new_access_jti: Jti,
|
||||
pub new_refresh_jti: Jti,
|
||||
pub new_access_expires_at: DateTime<Utc>,
|
||||
pub new_refresh_expires_at: DateTime<Utc>,
|
||||
}
|
||||
@@ -219,17 +219,17 @@ pub trait SessionRepository: Send + Sync {
|
||||
|
||||
async fn get_session_by_access_jti(
|
||||
&self,
|
||||
access_jti: &str,
|
||||
access_jti: &Jti,
|
||||
) -> Result<Option<SessionToken>, DbError>;
|
||||
|
||||
async fn get_session_for_refresh(
|
||||
&self,
|
||||
refresh_jti: &str,
|
||||
refresh_jti: &Jti,
|
||||
) -> Result<Option<SessionForRefresh>, DbError>;
|
||||
|
||||
async fn delete_session_by_access_jti(
|
||||
&self,
|
||||
access_jti: &str,
|
||||
access_jti: &Jti,
|
||||
did: &Did,
|
||||
) -> Result<u64, DbError>;
|
||||
|
||||
@@ -244,7 +244,7 @@ pub trait SessionRepository: Send + Sync {
|
||||
async fn delete_sessions_by_did_except_jti(
|
||||
&self,
|
||||
did: &Did,
|
||||
except_jti: &str,
|
||||
except_jti: &Jti,
|
||||
) -> Result<u64, DbError>;
|
||||
|
||||
async fn list_sessions_by_did(&self, did: &Did) -> Result<Vec<SessionListItem>, DbError>;
|
||||
@@ -253,7 +253,7 @@ pub trait SessionRepository: Send + Sync {
|
||||
&self,
|
||||
session_id: SessionId,
|
||||
did: &Did,
|
||||
) -> Result<Option<String>, DbError>;
|
||||
) -> Result<Option<Jti>, DbError>;
|
||||
|
||||
async fn delete_sessions_by_app_password(
|
||||
&self,
|
||||
@@ -265,9 +265,9 @@ pub trait SessionRepository: Send + Sync {
|
||||
&self,
|
||||
did: &Did,
|
||||
app_password_name: &str,
|
||||
) -> Result<Vec<String>, DbError>;
|
||||
) -> Result<Vec<Jti>, DbError>;
|
||||
|
||||
async fn lookup_refresh_grace(&self, refresh_jti: &str) -> Result<RefreshGraceLookup, DbError>;
|
||||
async fn lookup_refresh_grace(&self, refresh_jti: &Jti) -> Result<RefreshGraceLookup, DbError>;
|
||||
|
||||
async fn list_app_passwords(&self, user_id: Uuid) -> Result<Vec<AppPasswordRecord>, DbError>;
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ pub trait UserRepository: Send + Sync {
|
||||
async fn get_session_access_expiry(
|
||||
&self,
|
||||
did: &Did,
|
||||
access_jti: &str,
|
||||
access_jti: &Jti,
|
||||
) -> Result<Option<DateTime<Utc>>, DbError>;
|
||||
|
||||
async fn get_oauth_token_with_user(
|
||||
@@ -926,7 +926,7 @@ pub struct UserIdAndPasswordHash {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PasswordResetResult {
|
||||
pub did: Did,
|
||||
pub session_jtis: Vec<String>,
|
||||
pub session_jtis: Vec<Jti>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -34,8 +34,8 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
RETURNING id
|
||||
"#,
|
||||
data.did.as_str(),
|
||||
data.access_jti,
|
||||
data.refresh_jti,
|
||||
data.access_jti.as_str(),
|
||||
data.refresh_jti.as_str(),
|
||||
data.access_expires_at,
|
||||
data.refresh_expires_at,
|
||||
data.login_type.is_legacy(),
|
||||
@@ -53,7 +53,7 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
|
||||
async fn get_session_by_access_jti(
|
||||
&self,
|
||||
access_jti: &str,
|
||||
access_jti: &Jti,
|
||||
) -> Result<Option<SessionToken>, DbError> {
|
||||
let row = sqlx::query!(
|
||||
r#"
|
||||
@@ -63,7 +63,7 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
FROM session_tokens
|
||||
WHERE access_jti = $1
|
||||
"#,
|
||||
access_jti
|
||||
access_jti.as_str()
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
@@ -72,8 +72,8 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
Ok(row.map(|r| SessionToken {
|
||||
id: SessionId::new(r.id),
|
||||
did: Did::from(r.did),
|
||||
access_jti: r.access_jti,
|
||||
refresh_jti: r.refresh_jti,
|
||||
access_jti: Jti::from(r.access_jti),
|
||||
refresh_jti: Jti::from(r.refresh_jti),
|
||||
access_expires_at: r.access_expires_at,
|
||||
refresh_expires_at: r.refresh_expires_at,
|
||||
login_type: LoginType::from_legacy_flag(r.legacy_login),
|
||||
@@ -88,7 +88,7 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
|
||||
async fn get_session_for_refresh(
|
||||
&self,
|
||||
refresh_jti: &str,
|
||||
refresh_jti: &Jti,
|
||||
) -> Result<Option<SessionForRefresh>, DbError> {
|
||||
let row = sqlx::query!(
|
||||
r#"
|
||||
@@ -98,7 +98,7 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
JOIN user_keys k ON u.id = k.user_id
|
||||
WHERE st.refresh_jti = $1 AND st.refresh_expires_at > NOW()
|
||||
"#,
|
||||
refresh_jti
|
||||
refresh_jti.as_str()
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
@@ -116,12 +116,12 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
|
||||
async fn delete_session_by_access_jti(
|
||||
&self,
|
||||
access_jti: &str,
|
||||
access_jti: &Jti,
|
||||
did: &Did,
|
||||
) -> Result<u64, DbError> {
|
||||
let result = sqlx::query!(
|
||||
"DELETE FROM session_tokens WHERE access_jti = $1 AND did = $2",
|
||||
access_jti,
|
||||
access_jti.as_str(),
|
||||
did.as_str()
|
||||
)
|
||||
.execute(&self.pool)
|
||||
@@ -160,7 +160,7 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
async fn delete_sessions_by_did_except_jti(
|
||||
&self,
|
||||
did: &Did,
|
||||
except_jti: &str,
|
||||
except_jti: &Jti,
|
||||
) -> Result<u64, DbError> {
|
||||
let result = sqlx::query!(
|
||||
"DELETE FROM session_tokens WHERE did = $1 AND access_jti != $2",
|
||||
@@ -192,7 +192,7 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
.into_iter()
|
||||
.map(|r| SessionListItem {
|
||||
id: SessionId::new(r.id),
|
||||
access_jti: r.access_jti,
|
||||
access_jti: Jti::from(r.access_jti),
|
||||
created_at: r.created_at,
|
||||
refresh_expires_at: r.refresh_expires_at,
|
||||
})
|
||||
@@ -203,7 +203,7 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
&self,
|
||||
session_id: SessionId,
|
||||
did: &Did,
|
||||
) -> Result<Option<String>, DbError> {
|
||||
) -> Result<Option<Jti>, DbError> {
|
||||
let row = sqlx::query_scalar!(
|
||||
"SELECT access_jti FROM session_tokens WHERE id = $1 AND did = $2",
|
||||
session_id.as_i32(),
|
||||
@@ -213,7 +213,7 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
.await
|
||||
.map_err(map_sqlx_error)?;
|
||||
|
||||
Ok(row)
|
||||
Ok(row.map(Jti::from))
|
||||
}
|
||||
|
||||
async fn delete_sessions_by_app_password(
|
||||
@@ -237,7 +237,7 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
&self,
|
||||
did: &Did,
|
||||
app_password_name: &str,
|
||||
) -> Result<Vec<String>, DbError> {
|
||||
) -> Result<Vec<Jti>, DbError> {
|
||||
let rows = sqlx::query_scalar!(
|
||||
"SELECT access_jti FROM session_tokens WHERE did = $1 AND app_password_name = $2",
|
||||
did.as_str(),
|
||||
@@ -247,10 +247,10 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
.await
|
||||
.map_err(map_sqlx_error)?;
|
||||
|
||||
Ok(rows)
|
||||
Ok(rows.into_iter().map(Jti::from).collect())
|
||||
}
|
||||
|
||||
async fn lookup_refresh_grace(&self, refresh_jti: &str) -> Result<RefreshGraceLookup, DbError> {
|
||||
async fn lookup_refresh_grace(&self, refresh_jti: &Jti) -> Result<RefreshGraceLookup, DbError> {
|
||||
let row = sqlx::query!(
|
||||
r#"
|
||||
SELECT u.used_at, st.id AS session_id, st.did, st.scope, st.controller_did,
|
||||
@@ -262,7 +262,7 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
JOIN user_keys k ON us.id = k.user_id
|
||||
WHERE u.refresh_jti = $1
|
||||
"#,
|
||||
refresh_jti
|
||||
refresh_jti.as_str()
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
@@ -281,8 +281,8 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
did: Did::from(r.did),
|
||||
scope: r.scope,
|
||||
controller_did: r.controller_did.map(Did::from),
|
||||
access_jti: r.access_jti,
|
||||
refresh_jti: r.refresh_jti,
|
||||
access_jti: Jti::from(r.access_jti),
|
||||
refresh_jti: Jti::from(r.refresh_jti),
|
||||
access_expires_at: r.access_expires_at,
|
||||
refresh_expires_at: r.refresh_expires_at,
|
||||
key_bytes: r.key_bytes,
|
||||
@@ -524,7 +524,7 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
// rest see `rows_affected == 0`.
|
||||
let claimed = sqlx::query!(
|
||||
"INSERT INTO used_refresh_tokens (refresh_jti, session_id) VALUES ($1, $2) ON CONFLICT (refresh_jti) DO NOTHING",
|
||||
data.old_refresh_jti,
|
||||
data.old_refresh_jti.as_str(),
|
||||
data.session_id.as_i32()
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
@@ -567,8 +567,8 @@ impl SessionRepository for PostgresSessionRepository {
|
||||
refresh_expires_at = $4, updated_at = NOW()
|
||||
WHERE id = $5
|
||||
"#,
|
||||
data.new_access_jti,
|
||||
data.new_refresh_jti,
|
||||
data.new_access_jti.as_str(),
|
||||
data.new_refresh_jti.as_str(),
|
||||
data.new_access_expires_at,
|
||||
data.new_refresh_expires_at,
|
||||
data.session_id.as_i32()
|
||||
|
||||
@@ -176,12 +176,12 @@ impl UserRepository for PostgresUserRepository {
|
||||
async fn get_session_access_expiry(
|
||||
&self,
|
||||
did: &Did,
|
||||
access_jti: &str,
|
||||
access_jti: &Jti,
|
||||
) -> Result<Option<DateTime<Utc>>, DbError> {
|
||||
let row = sqlx::query!(
|
||||
"SELECT access_expires_at FROM session_tokens WHERE did = $1 AND access_jti = $2",
|
||||
did.as_str(),
|
||||
access_jti
|
||||
access_jti.as_str()
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
@@ -1874,13 +1874,16 @@ impl UserRepository for PostgresUserRepository {
|
||||
.await
|
||||
.map_err(map_sqlx_error)?;
|
||||
|
||||
let session_jtis: Vec<String> = sqlx::query_scalar!(
|
||||
let session_jtis: Vec<Jti> = sqlx::query_scalar!(
|
||||
"SELECT access_jti FROM session_tokens WHERE did = $1",
|
||||
user_did
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
.await
|
||||
.map_err(map_sqlx_error)?;
|
||||
.map_err(map_sqlx_error)?
|
||||
.into_iter()
|
||||
.map(Jti::from)
|
||||
.collect();
|
||||
|
||||
sqlx::query!("DELETE FROM session_tokens WHERE did = $1", user_did)
|
||||
.execute(&mut *tx)
|
||||
|
||||
@@ -36,7 +36,7 @@ pub struct DPoPJwk {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DPoPProofPayload {
|
||||
pub jti: String,
|
||||
pub jti: DPoPProofId,
|
||||
pub htm: String,
|
||||
pub htu: String,
|
||||
pub iat: i64,
|
||||
@@ -181,7 +181,7 @@ impl DPoPVerifier {
|
||||
let jkt = compute_jwk_thumbprint(&header.jwk)?;
|
||||
Ok(DPoPVerifyResult {
|
||||
jkt: jkt.into(),
|
||||
jti: payload.jti.clone().into(),
|
||||
jti: payload.jti.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,18 +271,6 @@ pub struct TokenRequest {
|
||||
pub client_secret: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DPoPClaims {
|
||||
pub jti: String,
|
||||
pub htm: String,
|
||||
pub htu: String,
|
||||
pub iat: i64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ath: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub nonce: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct JwkPublicKey {
|
||||
pub kty: String,
|
||||
|
||||
@@ -105,7 +105,7 @@ pub fn extract_auth_token_from_header(auth_header: Option<&str>) -> Option<Extra
|
||||
None
|
||||
}
|
||||
|
||||
pub fn extract_jti_from_headers(headers: &axum::http::HeaderMap) -> Option<String> {
|
||||
pub fn extract_jti_from_headers(headers: &axum::http::HeaderMap) -> Option<crate::types::Jti> {
|
||||
let auth_header = headers.get(AUTHORIZATION)?.to_str().ok()?;
|
||||
let token = extract_bearer_token_from_header(Some(auth_header))?;
|
||||
tranquil_auth::get_jti_from_token(&token).ok()
|
||||
|
||||
@@ -6,7 +6,7 @@ use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use tracing::debug;
|
||||
use tranquil_types::Did;
|
||||
use tranquil_types::{Did, Jti, Nsid};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ServiceTokenError {
|
||||
@@ -122,7 +122,7 @@ pub struct ServiceTokenClaims {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub lxm: Option<String>,
|
||||
#[serde(default)]
|
||||
pub jti: Option<String>,
|
||||
pub jti: Option<Jti>,
|
||||
}
|
||||
|
||||
impl ServiceTokenClaims {
|
||||
|
||||
@@ -1437,12 +1437,12 @@ impl<S: StorageIO + 'static> tranquil_db_traits::SessionRepository for Metastore
|
||||
|
||||
async fn get_session_by_access_jti(
|
||||
&self,
|
||||
access_jti: &str,
|
||||
access_jti: &Jti,
|
||||
) -> Result<Option<tranquil_db_traits::SessionToken>, DbError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.pool.send(MetastoreRequest::Session(
|
||||
SessionRequest::GetSessionByAccessJti {
|
||||
access_jti: access_jti.to_owned(),
|
||||
access_jti: access_jti.to_string(),
|
||||
tx,
|
||||
},
|
||||
))?;
|
||||
@@ -1451,12 +1451,12 @@ impl<S: StorageIO + 'static> tranquil_db_traits::SessionRepository for Metastore
|
||||
|
||||
async fn get_session_for_refresh(
|
||||
&self,
|
||||
refresh_jti: &str,
|
||||
refresh_jti: &Jti,
|
||||
) -> Result<Option<tranquil_db_traits::SessionForRefresh>, DbError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.pool.send(MetastoreRequest::Session(
|
||||
SessionRequest::GetSessionForRefresh {
|
||||
refresh_jti: refresh_jti.to_owned(),
|
||||
refresh_jti: refresh_jti.to_string(),
|
||||
tx,
|
||||
},
|
||||
))?;
|
||||
@@ -1465,13 +1465,13 @@ impl<S: StorageIO + 'static> tranquil_db_traits::SessionRepository for Metastore
|
||||
|
||||
async fn delete_session_by_access_jti(
|
||||
&self,
|
||||
access_jti: &str,
|
||||
access_jti: &Jti,
|
||||
did: &Did,
|
||||
) -> Result<u64, DbError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.pool.send(MetastoreRequest::Session(
|
||||
SessionRequest::DeleteSessionByAccessJti {
|
||||
access_jti: access_jti.to_owned(),
|
||||
access_jti: access_jti.to_string(),
|
||||
did: did.clone(),
|
||||
tx,
|
||||
},
|
||||
@@ -1509,7 +1509,7 @@ impl<S: StorageIO + 'static> tranquil_db_traits::SessionRepository for Metastore
|
||||
async fn delete_sessions_by_did_except_jti(
|
||||
&self,
|
||||
did: &Did,
|
||||
except_jti: &str,
|
||||
except_jti: &Jti,
|
||||
) -> Result<u64, DbError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.pool.send(MetastoreRequest::Session(
|
||||
@@ -1540,7 +1540,7 @@ impl<S: StorageIO + 'static> tranquil_db_traits::SessionRepository for Metastore
|
||||
&self,
|
||||
session_id: tranquil_db_traits::SessionId,
|
||||
did: &Did,
|
||||
) -> Result<Option<String>, DbError> {
|
||||
) -> Result<Option<Jti>, DbError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.pool.send(MetastoreRequest::Session(
|
||||
SessionRequest::GetSessionAccessJtiById {
|
||||
@@ -1549,7 +1549,7 @@ impl<S: StorageIO + 'static> tranquil_db_traits::SessionRepository for Metastore
|
||||
tx,
|
||||
},
|
||||
))?;
|
||||
recv(rx).await
|
||||
recv(rx).await.map(|jti: Option<String>| jti.map(Jti::from))
|
||||
}
|
||||
|
||||
async fn delete_sessions_by_app_password(
|
||||
@@ -1572,7 +1572,7 @@ impl<S: StorageIO + 'static> tranquil_db_traits::SessionRepository for Metastore
|
||||
&self,
|
||||
did: &Did,
|
||||
app_password_name: &str,
|
||||
) -> Result<Vec<String>, DbError> {
|
||||
) -> Result<Vec<Jti>, DbError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.pool.send(MetastoreRequest::Session(
|
||||
SessionRequest::GetSessionJtisByAppPassword {
|
||||
@@ -1581,17 +1581,19 @@ impl<S: StorageIO + 'static> tranquil_db_traits::SessionRepository for Metastore
|
||||
tx,
|
||||
},
|
||||
))?;
|
||||
recv(rx).await
|
||||
recv(rx)
|
||||
.await
|
||||
.map(|jtis: Vec<String>| jtis.into_iter().map(Jti::from).collect())
|
||||
}
|
||||
|
||||
async fn lookup_refresh_grace(
|
||||
&self,
|
||||
refresh_jti: &str,
|
||||
refresh_jti: &Jti,
|
||||
) -> Result<tranquil_db_traits::RefreshGraceLookup, DbError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.pool.send(MetastoreRequest::Session(
|
||||
SessionRequest::LookupRefreshGrace {
|
||||
refresh_jti: refresh_jti.to_owned(),
|
||||
refresh_jti: refresh_jti.to_string(),
|
||||
tx,
|
||||
},
|
||||
))?;
|
||||
@@ -3328,13 +3330,13 @@ impl<S: StorageIO + 'static> tranquil_db_traits::UserRepository for MetastoreCli
|
||||
async fn get_session_access_expiry(
|
||||
&self,
|
||||
did: &Did,
|
||||
access_jti: &str,
|
||||
access_jti: &Jti,
|
||||
) -> Result<Option<DateTime<Utc>>, DbError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.pool.send(MetastoreRequest::User(
|
||||
UserRequest::GetSessionAccessExpiry {
|
||||
did: did.clone(),
|
||||
access_jti: access_jti.to_owned(),
|
||||
access_jti: access_jti.to_string(),
|
||||
tx,
|
||||
},
|
||||
))?;
|
||||
|
||||
@@ -6094,7 +6094,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::eventlog::{EventLog, EventLogConfig};
|
||||
use crate::metastore::MetastoreConfig;
|
||||
use tranquil_types::{Did, Handle};
|
||||
use tranquil_types::{Did, Handle, InviteCode, Jti};
|
||||
|
||||
struct TestHarness {
|
||||
_metastore_dir: tempfile::TempDir,
|
||||
@@ -6308,10 +6308,10 @@ mod tests {
|
||||
let refresh = SessionRequest::RefreshSessionAtomic {
|
||||
data: tranquil_db_traits::SessionRefreshData {
|
||||
did: did.clone(),
|
||||
old_refresh_jti: "r0".to_string(),
|
||||
old_refresh_jti: Jti::new("r0"),
|
||||
session_id: sid,
|
||||
new_access_jti: "a1".to_string(),
|
||||
new_refresh_jti: "r1".to_string(),
|
||||
new_access_jti: Jti::new("a1"),
|
||||
new_refresh_jti: Jti::new("r1"),
|
||||
new_access_expires_at: Utc::now(),
|
||||
new_refresh_expires_at: Utc::now(),
|
||||
},
|
||||
|
||||
@@ -438,8 +438,8 @@ mod tests {
|
||||
let now = chrono::Utc::now();
|
||||
tranquil_db_traits::SessionTokenCreate {
|
||||
did: tranquil_types::Did::new(did.to_string()).unwrap(),
|
||||
access_jti: access_jti.to_string(),
|
||||
refresh_jti: refresh_jti.to_string(),
|
||||
access_jti: tranquil_types::Jti::new(access_jti),
|
||||
refresh_jti: tranquil_types::Jti::new(refresh_jti),
|
||||
access_expires_at: now + chrono::Duration::minutes(120),
|
||||
refresh_expires_at: now + chrono::Duration::days(90),
|
||||
login_type: tranquil_db_traits::LoginType::Legacy,
|
||||
@@ -484,10 +484,10 @@ mod tests {
|
||||
let now = chrono::Utc::now();
|
||||
tranquil_db_traits::SessionRefreshData {
|
||||
did: did.clone(),
|
||||
old_refresh_jti: old_refresh_jti.to_string(),
|
||||
old_refresh_jti: tranquil_types::Jti::new(old_refresh_jti),
|
||||
session_id,
|
||||
new_access_jti: new_access_jti.to_string(),
|
||||
new_refresh_jti: new_refresh_jti.to_string(),
|
||||
new_access_jti: tranquil_types::Jti::new(new_access_jti),
|
||||
new_refresh_jti: tranquil_types::Jti::new(new_refresh_jti),
|
||||
new_access_expires_at: now + chrono::Duration::minutes(120),
|
||||
new_refresh_expires_at: now + chrono::Duration::days(90),
|
||||
}
|
||||
|
||||
@@ -85,8 +85,8 @@ impl SessionOps {
|
||||
id: SessionId::new(v.id),
|
||||
did: Did::new(v.did.clone())
|
||||
.map_err(|_| MetastoreError::CorruptData("invalid session did"))?,
|
||||
access_jti: v.access_jti.clone(),
|
||||
refresh_jti: v.refresh_jti.clone(),
|
||||
access_jti: Jti::from(v.access_jti.clone()),
|
||||
refresh_jti: Jti::from(v.refresh_jti.clone()),
|
||||
access_expires_at: DateTime::from_timestamp_millis(v.access_expires_at_ms)
|
||||
.unwrap_or_default(),
|
||||
refresh_expires_at: DateTime::from_timestamp_millis(v.refresh_expires_at_ms)
|
||||
@@ -241,8 +241,8 @@ impl SessionOps {
|
||||
.controller_did
|
||||
.clone()
|
||||
.and_then(|d| Did::new(d).ok()),
|
||||
access_jti: session.access_jti.clone(),
|
||||
refresh_jti: session.refresh_jti.clone(),
|
||||
access_jti: Jti::from(session.access_jti.clone()),
|
||||
refresh_jti: Jti::from(session.refresh_jti.clone()),
|
||||
access_expires_at,
|
||||
refresh_expires_at,
|
||||
key_bytes,
|
||||
@@ -285,8 +285,8 @@ impl SessionOps {
|
||||
let value = SessionTokenValue {
|
||||
id: session_id,
|
||||
did: data.did.to_string(),
|
||||
access_jti: data.access_jti.clone(),
|
||||
refresh_jti: data.refresh_jti.clone(),
|
||||
access_jti: data.access_jti.to_string(),
|
||||
refresh_jti: data.refresh_jti.to_string(),
|
||||
access_expires_at_ms: data.access_expires_at.timestamp_millis(),
|
||||
refresh_expires_at_ms: data.refresh_expires_at.timestamp_millis(),
|
||||
login_type: login_type_to_u8(data.login_type),
|
||||
@@ -501,7 +501,7 @@ impl SessionOps {
|
||||
.iter()
|
||||
.map(|s| SessionListItem {
|
||||
id: SessionId::new(s.id),
|
||||
access_jti: s.access_jti.clone(),
|
||||
access_jti: Jti::from(s.access_jti.clone()),
|
||||
created_at: DateTime::from_timestamp_millis(s.created_at_ms).unwrap_or_default(),
|
||||
refresh_expires_at: DateTime::from_timestamp_millis(s.refresh_expires_at_ms)
|
||||
.unwrap_or_default(),
|
||||
@@ -848,8 +848,8 @@ impl SessionOps {
|
||||
let old_refresh_jti = session.refresh_jti.clone();
|
||||
let rotated_at_ms = Utc::now().timestamp_millis();
|
||||
|
||||
session.access_jti = data.new_access_jti.clone();
|
||||
session.refresh_jti = data.new_refresh_jti.clone();
|
||||
session.access_jti = data.new_access_jti.to_string();
|
||||
session.refresh_jti = data.new_refresh_jti.to_string();
|
||||
session.access_expires_at_ms = data.new_access_expires_at.timestamp_millis();
|
||||
session.refresh_expires_at_ms = data.new_refresh_expires_at.timestamp_millis();
|
||||
session.updated_at_ms = rotated_at_ms;
|
||||
|
||||
@@ -2210,7 +2210,10 @@ impl UserOps {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let session_jtis: Vec<String> = sessions.iter().map(|(_, _, jti)| jti.clone()).collect();
|
||||
let session_jtis: Vec<Jti> = sessions
|
||||
.iter()
|
||||
.map(|(_, _, jti)| Jti::from(jti.clone()))
|
||||
.collect();
|
||||
|
||||
let mut batch = self.db.batch();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user