mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
20241017 3107 token ttl (#3114)
This commit is contained in:
parent
99a799d72a
commit
5a3e5f1e07
|
@ -1807,7 +1807,7 @@ impl KanidmClient {
|
|||
|
||||
pub async fn idm_account_credential_update_exchange(
|
||||
&self,
|
||||
intent_token: CUIntentToken,
|
||||
intent_token: String,
|
||||
) -> Result<(CUSessionToken, CUStatus), ClientError> {
|
||||
// We don't need to send the UAT with these, which is why we use the different path.
|
||||
self.perform_simple_post_request("/v1/credential/_exchange_intent", &intent_token)
|
||||
|
|
|
@ -64,6 +64,8 @@ impl TotpSecret {
|
|||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CUIntentToken {
|
||||
pub token: String,
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
pub expiry_time: time::OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||
|
|
|
@ -17,8 +17,8 @@ use kanidmd_lib::{
|
|||
filter::{Filter, FilterInvalid},
|
||||
idm::account::DestroySessionTokenEvent,
|
||||
idm::credupdatesession::{
|
||||
CredentialUpdateIntentToken, CredentialUpdateSessionToken, InitCredentialUpdateEvent,
|
||||
InitCredentialUpdateIntentEvent,
|
||||
CredentialUpdateIntentTokenExchange, CredentialUpdateSessionToken,
|
||||
InitCredentialUpdateEvent, InitCredentialUpdateIntentEvent,
|
||||
},
|
||||
idm::event::{GeneratePasswordEvent, RegenerateRadiusSecretEvent, UnixPasswordChangeEvent},
|
||||
idm::oauth2::{
|
||||
|
@ -669,6 +669,7 @@ impl QueryServerWriteV1 {
|
|||
})
|
||||
.map(|tok| CUIntentToken {
|
||||
token: tok.intent_id,
|
||||
expiry_time: tok.expiry_time,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -679,14 +680,12 @@ impl QueryServerWriteV1 {
|
|||
)]
|
||||
pub async fn handle_idmcredentialexchangeintent(
|
||||
&self,
|
||||
intent_token: CUIntentToken,
|
||||
intent_id: String,
|
||||
eventid: Uuid,
|
||||
) -> Result<(CUSessionToken, CUStatus), OperationError> {
|
||||
let ct = duration_from_epoch_now();
|
||||
let mut idms_prox_write = self.idms.proxy_write(ct).await?;
|
||||
let intent_token = CredentialUpdateIntentToken {
|
||||
intent_id: intent_token.token,
|
||||
};
|
||||
let intent_token = CredentialUpdateIntentTokenExchange { intent_id };
|
||||
// TODO: this is throwing a 500 error when a session is already in use, that seems bad?
|
||||
idms_prox_write
|
||||
.exchange_intent_credential_update(intent_token, ct)
|
||||
|
|
|
@ -1340,7 +1340,7 @@ pub async fn account_user_auth_token_delete(
|
|||
pub async fn credential_update_exchange_intent(
|
||||
State(state): State<ServerState>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
Json(intent_token): Json<CUIntentToken>,
|
||||
Json(intent_token): Json<String>,
|
||||
) -> Result<Json<(CUSessionToken, CUStatus)>, WebError> {
|
||||
state
|
||||
.qe_w_ref
|
||||
|
|
|
@ -19,9 +19,9 @@ use std::fmt::{Display, Formatter};
|
|||
use uuid::Uuid;
|
||||
|
||||
use kanidm_proto::internal::{
|
||||
CUCredState, CUExtPortal, CUIntentToken, CURegState, CURegWarning, CURequest, CUSessionToken,
|
||||
CUStatus, CredentialDetail, OperationError, PasskeyDetail, PasswordFeedback, TotpAlgo,
|
||||
UserAuthToken, COOKIE_CU_SESSION_TOKEN,
|
||||
CUCredState, CUExtPortal, CURegState, CURegWarning, CURequest, CUSessionToken, CUStatus,
|
||||
CredentialDetail, OperationError, PasskeyDetail, PasswordFeedback, TotpAlgo, UserAuthToken,
|
||||
COOKIE_CU_SESSION_TOKEN,
|
||||
};
|
||||
|
||||
use crate::https::extractors::{DomainInfo, DomainInfoRead, VerifiedClientInformation};
|
||||
|
@ -690,7 +690,7 @@ pub(crate) async fn view_reset_get(
|
|||
// We have a reset token and want to create a new session
|
||||
match state
|
||||
.qe_w_ref
|
||||
.handle_idmcredentialexchangeintent(CUIntentToken { token }, kopid.eventid)
|
||||
.handle_idmcredentialexchangeintent(token, kopid.eventid)
|
||||
.await
|
||||
{
|
||||
Ok((cu_session_token, cu_status)) => {
|
||||
|
|
|
@ -32,12 +32,12 @@ use compact_jwt::jwe::JweBuilder;
|
|||
use super::accountpolicy::ResolvedAccountPolicy;
|
||||
|
||||
const MAXIMUM_CRED_UPDATE_TTL: Duration = Duration::from_secs(900);
|
||||
// Minimum 5 minutes.
|
||||
const MINIMUM_INTENT_TTL: Duration = Duration::from_secs(300);
|
||||
// Default 1 hour.
|
||||
const DEFAULT_INTENT_TTL: Duration = Duration::from_secs(3600);
|
||||
// Default 1 day.
|
||||
const MAXIMUM_INTENT_TTL: Duration = Duration::from_secs(86400);
|
||||
// Minimum 5 minutes.
|
||||
const MINIMUM_INTENT_TTL: Duration = Duration::from_secs(300);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PasswordQuality {
|
||||
|
@ -50,6 +50,20 @@ pub enum PasswordQuality {
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct CredentialUpdateIntentToken {
|
||||
pub intent_id: String,
|
||||
pub expiry_time: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CredentialUpdateIntentTokenExchange {
|
||||
pub intent_id: String,
|
||||
}
|
||||
|
||||
impl From<CredentialUpdateIntentToken> for CredentialUpdateIntentTokenExchange {
|
||||
fn from(tok: CredentialUpdateIntentToken) -> Self {
|
||||
CredentialUpdateIntentTokenExchange {
|
||||
intent_id: tok.intent_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
@ -932,8 +946,12 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
let mttl = event.max_ttl.unwrap_or(DEFAULT_INTENT_TTL);
|
||||
let clamped_mttl = mttl.clamp(MINIMUM_INTENT_TTL, MAXIMUM_INTENT_TTL);
|
||||
debug!(?clamped_mttl, "clamped update intent validity");
|
||||
// Absolute expiry of the intent token in epoch seconds
|
||||
let max_ttl = ct + clamped_mttl;
|
||||
|
||||
// Get the expiry of the intent token as an odt.
|
||||
let expiry_time = OffsetDateTime::UNIX_EPOCH + max_ttl;
|
||||
|
||||
let intent_id = readable_password_from_random();
|
||||
|
||||
// Mark that we have created an intent token on the user.
|
||||
|
@ -984,15 +1002,18 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
e
|
||||
})?;
|
||||
|
||||
Ok(CredentialUpdateIntentToken { intent_id })
|
||||
Ok(CredentialUpdateIntentToken {
|
||||
intent_id,
|
||||
expiry_time,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn exchange_intent_credential_update(
|
||||
&mut self,
|
||||
token: CredentialUpdateIntentToken,
|
||||
token: CredentialUpdateIntentTokenExchange,
|
||||
current_time: Duration,
|
||||
) -> Result<(CredentialUpdateSessionToken, CredentialUpdateSessionStatus), OperationError> {
|
||||
let CredentialUpdateIntentToken { intent_id } = token;
|
||||
let CredentialUpdateIntentTokenExchange { intent_id } = token;
|
||||
|
||||
/*
|
||||
let entry = self.qs_write.internal_search_uuid(&token.target)?;
|
||||
|
@ -2633,24 +2654,24 @@ mod tests {
|
|||
// exchange intent token - invalid - fail
|
||||
// Expired
|
||||
let cur = idms_prox_write
|
||||
.exchange_intent_credential_update(intent_tok.clone(), ct + MINIMUM_INTENT_TTL);
|
||||
.exchange_intent_credential_update(intent_tok.clone().into(), ct + MINIMUM_INTENT_TTL);
|
||||
|
||||
assert!(matches!(cur, Err(OperationError::SessionExpired)));
|
||||
|
||||
let cur = idms_prox_write
|
||||
.exchange_intent_credential_update(intent_tok.clone(), ct + MAXIMUM_INTENT_TTL);
|
||||
.exchange_intent_credential_update(intent_tok.clone().into(), ct + MAXIMUM_INTENT_TTL);
|
||||
|
||||
assert!(matches!(cur, Err(OperationError::SessionExpired)));
|
||||
|
||||
// exchange intent token - success
|
||||
let (cust_a, _c_status) = idms_prox_write
|
||||
.exchange_intent_credential_update(intent_tok.clone(), ct)
|
||||
.exchange_intent_credential_update(intent_tok.clone().into(), ct)
|
||||
.unwrap();
|
||||
|
||||
// Session in progress - This will succeed and then block the former success from
|
||||
// committing.
|
||||
let (cust_b, _c_status) = idms_prox_write
|
||||
.exchange_intent_credential_update(intent_tok, ct + Duration::from_secs(1))
|
||||
.exchange_intent_credential_update(intent_tok.into(), ct + Duration::from_secs(1))
|
||||
.unwrap();
|
||||
|
||||
let cur = idms_prox_write.commit_credential_update(&cust_a, ct);
|
||||
|
|
|
@ -1069,7 +1069,7 @@ async fn test_server_credential_update_session_pw(rsclient: KanidmClient) {
|
|||
let _ = rsclient.logout().await;
|
||||
// Exchange the intent token
|
||||
let (session_token, _status) = rsclient
|
||||
.idm_account_credential_update_exchange(intent_token)
|
||||
.idm_account_credential_update_exchange(intent_token.token)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -1144,7 +1144,7 @@ async fn test_server_credential_update_session_totp_pw(rsclient: KanidmClient) {
|
|||
let _ = rsclient.logout().await;
|
||||
// Exchange the intent token
|
||||
let (session_token, _statu) = rsclient
|
||||
.idm_account_credential_update_exchange(intent_token)
|
||||
.idm_account_credential_update_exchange(intent_token.token)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -1273,7 +1273,7 @@ async fn setup_demo_account_passkey(rsclient: &KanidmClient) -> WebauthnAuthenti
|
|||
let _ = rsclient.logout().await;
|
||||
// Exchange the intent token
|
||||
let (session_token, _status) = rsclient
|
||||
.idm_account_credential_update_exchange(intent_token)
|
||||
.idm_account_credential_update_exchange(intent_token.token)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -1359,7 +1359,7 @@ async fn setup_demo_account_password(
|
|||
|
||||
// Exchange the intent token
|
||||
let (session_token, _status) = rsclient
|
||||
.idm_account_credential_update_exchange(intent_token)
|
||||
.idm_account_credential_update_exchange(intent_token.token)
|
||||
.await
|
||||
.expect("Failed to exchange intent token");
|
||||
|
||||
|
@ -1611,7 +1611,7 @@ async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) {
|
|||
let _ = rsclient.logout().await;
|
||||
// Exchange the intent token
|
||||
let (session_token, _status) = rsclient
|
||||
.idm_account_credential_update_exchange(intent_token)
|
||||
.idm_account_credential_update_exchange(intent_token.token)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -816,7 +816,7 @@ impl CredentialResetApp {
|
|||
}
|
||||
|
||||
async fn exchange_intent_token(token: String) -> Result<Msg, FetchError> {
|
||||
let request = CUIntentToken { token };
|
||||
let request = token;
|
||||
let req_jsvalue = request
|
||||
.serialize(&serde_wasm_bindgen::Serializer::json_compatible())
|
||||
.expect("Failed to serialise request");
|
||||
|
|
|
@ -49,7 +49,7 @@ rpassword = { workspace = true }
|
|||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
shellexpand = { workspace = true }
|
||||
time = { workspace = true, features = ["serde", "std"] }
|
||||
time = { workspace = true, features = ["serde", "std", "local-offset"] }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }
|
||||
tokio = { workspace = true, features = ["rt", "macros", "fs", "signal"] }
|
||||
|
|
|
@ -21,7 +21,7 @@ use kanidm_proto::scim_v1::{client::ScimSshPublicKeys, ScimEntryGetQuery};
|
|||
use qrcode::render::unicode;
|
||||
use qrcode::QrCode;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::OffsetDateTime;
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::webauthn::get_authenticator;
|
||||
|
@ -637,9 +637,7 @@ impl AccountCredential {
|
|||
// The account credential use_reset_token CLI
|
||||
AccountCredential::UseResetToken(aopt) => {
|
||||
let client = aopt.copt.to_unauth_client();
|
||||
let cuintent_token = CUIntentToken {
|
||||
token: aopt.token.clone(),
|
||||
};
|
||||
let cuintent_token = aopt.token.clone();
|
||||
|
||||
match client
|
||||
.idm_account_credential_update_exchange(cuintent_token)
|
||||
|
@ -669,14 +667,13 @@ impl AccountCredential {
|
|||
.idm_person_account_credential_update_intent(aopts.account_id.as_str(), *ttl)
|
||||
.await
|
||||
{
|
||||
Ok(cuintent_token) => {
|
||||
Ok(CUIntentToken { token, expiry_time }) => {
|
||||
let mut url = client.make_url("/ui/reset");
|
||||
url.query_pairs_mut()
|
||||
.append_pair("token", cuintent_token.token.as_str());
|
||||
url.query_pairs_mut().append_pair("token", token.as_str());
|
||||
|
||||
debug!(
|
||||
"Successfully created credential reset token for {}: {}",
|
||||
aopts.account_id, cuintent_token.token
|
||||
aopts.account_id, token
|
||||
);
|
||||
println!(
|
||||
"The person can use one of the following to allow the credential reset"
|
||||
|
@ -700,7 +697,19 @@ impl AccountCredential {
|
|||
println!("This link: {}", url.as_str());
|
||||
println!(
|
||||
"Or run this command: kanidm person credential use-reset-token {}",
|
||||
cuintent_token.token
|
||||
token
|
||||
);
|
||||
|
||||
// Now get the abs time
|
||||
let local_offset =
|
||||
UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);
|
||||
let expiry_time = expiry_time.to_offset(local_offset);
|
||||
|
||||
println!(
|
||||
"This token will expire at: {}",
|
||||
expiry_time
|
||||
.format(&Rfc3339)
|
||||
.expect("Failed to format date time!!!")
|
||||
);
|
||||
println!();
|
||||
}
|
||||
|
|
|
@ -464,6 +464,7 @@ pub enum AccountCredential {
|
|||
#[clap(flatten)]
|
||||
copt: CommonOpt,
|
||||
/// Optionally set how many seconds the reset token should be valid for.
|
||||
/// Default: 3600 seconds
|
||||
ttl: Option<u32>,
|
||||
},
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue