mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-21 16:33:55 +02:00
1115 store credential ids per session (#1386)
This commit is contained in:
parent
87b43d0c14
commit
cd5daf2f1c
|
@ -421,6 +421,22 @@ pub enum DbValueSession {
|
|||
#[serde(rename = "s", default)]
|
||||
scope: DbValueAccessScopeV1,
|
||||
},
|
||||
V2 {
|
||||
#[serde(rename = "u")]
|
||||
refer: Uuid,
|
||||
#[serde(rename = "l")]
|
||||
label: String,
|
||||
#[serde(rename = "e")]
|
||||
expiry: Option<String>,
|
||||
#[serde(rename = "i")]
|
||||
issued_at: String,
|
||||
#[serde(rename = "b")]
|
||||
issued_by: DbValueIdentityId,
|
||||
#[serde(rename = "c")]
|
||||
cred_id: Uuid,
|
||||
#[serde(rename = "s", default)]
|
||||
scope: DbValueAccessScopeV1,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
@ -82,7 +82,7 @@ pub const AUTH_SESSION_EXPIRY: u64 = 3600;
|
|||
// The time that a token can be used before session
|
||||
// status is enforced. This needs to be longer than
|
||||
// replication delay/cycle.
|
||||
pub const GRACE_WINDOW: Duration = Duration::from_secs(600);
|
||||
pub const GRACE_WINDOW: Duration = Duration::from_secs(300);
|
||||
|
||||
/// How long access tokens should last. This is NOT the length
|
||||
/// of the refresh token, which is bound to the issuing session.
|
||||
|
|
|
@ -968,7 +968,8 @@ impl Credential {
|
|||
// Check stuff
|
||||
Ok(Credential {
|
||||
type_,
|
||||
uuid: self.uuid,
|
||||
// Rotate the credential id on any change to invalidate sessions.
|
||||
uuid: Uuid::new_v4(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1010,7 +1011,8 @@ impl Credential {
|
|||
// Check stuff
|
||||
Ok(Credential {
|
||||
type_,
|
||||
uuid: self.uuid,
|
||||
// Rotate the credential id on any change to invalidate sessions.
|
||||
uuid: Uuid::new_v4(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1047,7 +1049,8 @@ impl Credential {
|
|||
|
||||
Ok(Some(Credential {
|
||||
type_,
|
||||
uuid: self.uuid,
|
||||
// Rotate the credential id on any change to invalidate sessions.
|
||||
uuid: Uuid::new_v4(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -1178,7 +1181,8 @@ impl Credential {
|
|||
};
|
||||
Credential {
|
||||
type_,
|
||||
uuid: self.uuid,
|
||||
// Rotate the credential id on any change to invalidate sessions.
|
||||
uuid: Uuid::new_v4(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1207,7 +1211,8 @@ impl Credential {
|
|||
};
|
||||
Credential {
|
||||
type_,
|
||||
uuid: self.uuid,
|
||||
// Rotate the credential id on any change to invalidate sessions.
|
||||
uuid: Uuid::new_v4(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1229,7 +1234,8 @@ impl Credential {
|
|||
};
|
||||
Credential {
|
||||
type_,
|
||||
uuid: self.uuid,
|
||||
// Rotate the credential id on any change to invalidate sessions.
|
||||
uuid: Uuid::new_v4(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1284,7 +1290,8 @@ impl Credential {
|
|||
wan.clone(),
|
||||
Some(backup_codes),
|
||||
),
|
||||
uuid: self.uuid,
|
||||
// Rotate the credential id on any change to invalidate sessions.
|
||||
uuid: Uuid::new_v4(),
|
||||
}),
|
||||
_ => Err(OperationError::InvalidAccountState(
|
||||
"Non-MFA credential type".to_string(),
|
||||
|
@ -1303,6 +1310,8 @@ impl Credential {
|
|||
backup_codes.remove(code_to_remove);
|
||||
Ok(Credential {
|
||||
type_: CredentialType::PasswordMfa(pw, totp, wan, Some(backup_codes)),
|
||||
// Don't rotate uuid here since this is a consumption of a backup
|
||||
// code.
|
||||
uuid: self.uuid,
|
||||
})
|
||||
}
|
||||
|
@ -1321,7 +1330,8 @@ impl Credential {
|
|||
match &self.type_ {
|
||||
CredentialType::PasswordMfa(pw, totp, wan, _) => Ok(Credential {
|
||||
type_: CredentialType::PasswordMfa(pw.clone(), totp.clone(), wan.clone(), None),
|
||||
uuid: self.uuid,
|
||||
// Rotate the credential id on any change to invalidate sessions.
|
||||
uuid: Uuid::new_v4(),
|
||||
}),
|
||||
_ => Err(OperationError::InvalidAccountState(
|
||||
"Non-MFA credential type".to_string(),
|
||||
|
|
|
@ -19,7 +19,8 @@ use uuid::Uuid;
|
|||
// use webauthn_rs::prelude::DeviceKey as DeviceKeyV4;
|
||||
use webauthn_rs::prelude::Passkey as PasskeyV4;
|
||||
use webauthn_rs::prelude::{
|
||||
PasskeyAuthentication, RequestChallengeResponse, SecurityKeyAuthentication, Webauthn,
|
||||
CredentialID, PasskeyAuthentication, RequestChallengeResponse, SecurityKeyAuthentication,
|
||||
Webauthn,
|
||||
};
|
||||
|
||||
use crate::credential::totp::Totp;
|
||||
|
@ -47,7 +48,7 @@ const PW_BADLIST_MSG: &str = "password is in badlist";
|
|||
|
||||
/// A response type to indicate the progress and potential result of an authentication attempt.
|
||||
enum CredState {
|
||||
Success(AuthType),
|
||||
Success { auth_type: AuthType, cred_id: Uuid },
|
||||
Continue(Vec<AuthAllowed>),
|
||||
Denied(&'static str),
|
||||
}
|
||||
|
@ -84,10 +85,22 @@ struct CredWebauthn {
|
|||
/// mechanism.
|
||||
#[derive(Clone, Debug)]
|
||||
enum CredHandler {
|
||||
Anonymous,
|
||||
Password(Password, bool),
|
||||
PasswordMfa(Box<CredMfa>),
|
||||
Passkey(CredWebauthn),
|
||||
Anonymous {
|
||||
cred_id: Uuid,
|
||||
},
|
||||
Password {
|
||||
pw: Password,
|
||||
generated: bool,
|
||||
cred_id: Uuid,
|
||||
},
|
||||
PasswordMfa {
|
||||
cmfa: Box<CredMfa>,
|
||||
cred_id: Uuid,
|
||||
},
|
||||
Passkey {
|
||||
c_wan: CredWebauthn,
|
||||
cred_ids: BTreeMap<CredentialID, Uuid>,
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<(&Credential, &Webauthn)> for CredHandler {
|
||||
|
@ -99,8 +112,16 @@ impl TryFrom<(&Credential, &Webauthn)> for CredHandler {
|
|||
/// inconsistency.
|
||||
fn try_from((c, webauthn): (&Credential, &Webauthn)) -> Result<Self, Self::Error> {
|
||||
match &c.type_ {
|
||||
CredentialType::Password(pw) => Ok(CredHandler::Password(pw.clone(), false)),
|
||||
CredentialType::GeneratedPassword(pw) => Ok(CredHandler::Password(pw.clone(), true)),
|
||||
CredentialType::Password(pw) => Ok(CredHandler::Password {
|
||||
pw: pw.clone(),
|
||||
generated: false,
|
||||
cred_id: c.uuid,
|
||||
}),
|
||||
CredentialType::GeneratedPassword(pw) => Ok(CredHandler::Password {
|
||||
pw: pw.clone(),
|
||||
generated: true,
|
||||
cred_id: c.uuid,
|
||||
}),
|
||||
CredentialType::PasswordMfa(pw, maybe_totp, maybe_wan, maybe_backup_code) => {
|
||||
let wan = if !maybe_wan.is_empty() {
|
||||
let sks: Vec<_> = maybe_wan.values().cloned().collect();
|
||||
|
@ -135,18 +156,26 @@ impl TryFrom<(&Credential, &Webauthn)> for CredHandler {
|
|||
return Err(());
|
||||
}
|
||||
|
||||
Ok(CredHandler::PasswordMfa(cmfa))
|
||||
Ok(CredHandler::PasswordMfa {
|
||||
cmfa,
|
||||
cred_id: c.uuid,
|
||||
})
|
||||
}
|
||||
CredentialType::Webauthn(wan) => {
|
||||
let pks: Vec<_> = wan.values().cloned().collect();
|
||||
let cred_ids: BTreeMap<_, _> = pks
|
||||
.iter()
|
||||
.map(|pk| (pk.cred_id().clone(), c.uuid))
|
||||
.collect();
|
||||
webauthn
|
||||
.start_passkey_authentication(&pks)
|
||||
.map(|(chal, wan_state)| {
|
||||
CredHandler::Passkey(CredWebauthn {
|
||||
.map(|(chal, wan_state)| CredHandler::Passkey {
|
||||
c_wan: CredWebauthn {
|
||||
chal,
|
||||
wan_state,
|
||||
state: CredVerifyState::Init,
|
||||
})
|
||||
},
|
||||
cred_ids,
|
||||
})
|
||||
.map_err(|e| {
|
||||
security_info!(?e, "Unable to create webauthn authentication challenge");
|
||||
|
@ -173,14 +202,20 @@ impl TryFrom<(&BTreeMap<Uuid, (String, PasskeyV4)>, &Webauthn)> for CredHandler
|
|||
}
|
||||
|
||||
let pks: Vec<_> = wan.values().map(|(_, k)| k).cloned().collect();
|
||||
let cred_ids: BTreeMap<_, _> = wan
|
||||
.iter()
|
||||
.map(|(u, (_, k))| (k.cred_id().clone(), *u))
|
||||
.collect();
|
||||
|
||||
webauthn
|
||||
.start_passkey_authentication(&pks)
|
||||
.map(|(chal, wan_state)| {
|
||||
CredHandler::Passkey(CredWebauthn {
|
||||
.map(|(chal, wan_state)| CredHandler::Passkey {
|
||||
c_wan: CredWebauthn {
|
||||
chal,
|
||||
wan_state,
|
||||
state: CredVerifyState::Init,
|
||||
})
|
||||
},
|
||||
cred_ids,
|
||||
})
|
||||
.map_err(|e| {
|
||||
security_info!(
|
||||
|
@ -213,12 +248,15 @@ impl CredHandler {
|
|||
}
|
||||
|
||||
/// validate that the client wants to authenticate as the anonymous user.
|
||||
fn validate_anonymous(cred: &AuthCredential) -> CredState {
|
||||
fn validate_anonymous(cred: &AuthCredential, cred_id: Uuid) -> CredState {
|
||||
match cred {
|
||||
AuthCredential::Anonymous => {
|
||||
// For anonymous, no claims will ever be issued.
|
||||
security_info!("Handler::Anonymous -> Result::Success");
|
||||
CredState::Success(AuthType::Anonymous)
|
||||
CredState::Success {
|
||||
auth_type: AuthType::Anonymous,
|
||||
cred_id,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
security_error!(
|
||||
|
@ -232,6 +270,7 @@ impl CredHandler {
|
|||
/// Validate a singule password credential of the account.
|
||||
fn validate_password(
|
||||
cred: &AuthCredential,
|
||||
cred_id: Uuid,
|
||||
pw: &mut Password,
|
||||
generated: bool,
|
||||
who: Uuid,
|
||||
|
@ -250,9 +289,15 @@ impl CredHandler {
|
|||
security_info!("Handler::Password -> Result::Success");
|
||||
Self::maybe_pw_upgrade(pw, who, cleartext.as_str(), async_tx);
|
||||
if generated {
|
||||
CredState::Success(AuthType::GeneratedPassword)
|
||||
CredState::Success {
|
||||
auth_type: AuthType::GeneratedPassword,
|
||||
cred_id,
|
||||
}
|
||||
} else {
|
||||
CredState::Success(AuthType::Password)
|
||||
CredState::Success {
|
||||
auth_type: AuthType::Password,
|
||||
cred_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -276,6 +321,7 @@ impl CredHandler {
|
|||
/// authentication will fail.
|
||||
fn validate_password_mfa(
|
||||
cred: &AuthCredential,
|
||||
cred_id: Uuid,
|
||||
ts: &Duration,
|
||||
pw_mfa: &mut CredMfa,
|
||||
webauthn: &Webauthn,
|
||||
|
@ -394,7 +440,10 @@ impl CredHandler {
|
|||
cleartext.as_str(),
|
||||
async_tx,
|
||||
);
|
||||
CredState::Success(AuthType::PasswordMfa)
|
||||
CredState::Success {
|
||||
auth_type: AuthType::PasswordMfa,
|
||||
cred_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -423,6 +472,7 @@ impl CredHandler {
|
|||
/// Validate a webauthn authentication attempt
|
||||
pub fn validate_webauthn(
|
||||
cred: &AuthCredential,
|
||||
cred_ids: &BTreeMap<CredentialID, Uuid>,
|
||||
wan_cred: &mut CredWebauthn,
|
||||
webauthn: &Webauthn,
|
||||
who: Uuid,
|
||||
|
@ -438,21 +488,34 @@ impl CredHandler {
|
|||
// lets see how we go.
|
||||
match webauthn.finish_passkey_authentication(resp, &wan_cred.wan_state) {
|
||||
Ok(auth_result) => {
|
||||
wan_cred.state = CredVerifyState::Success;
|
||||
// Success. Determine if we need to update the counter
|
||||
// async from r.
|
||||
if auth_result.needs_update() {
|
||||
// Do async
|
||||
if let Err(_e) = async_tx.send(DelayedAction::WebauthnCounterIncrement(
|
||||
WebauthnCounterIncrement {
|
||||
target_uuid: who,
|
||||
auth_result,
|
||||
},
|
||||
)) {
|
||||
admin_warn!("unable to queue delayed webauthn property update, continuing ... ");
|
||||
if let Some(cred_id) = cred_ids.get(auth_result.cred_id()).copied() {
|
||||
wan_cred.state = CredVerifyState::Success;
|
||||
// Success. Determine if we need to update the counter
|
||||
// async from r.
|
||||
if auth_result.needs_update() {
|
||||
// Do async
|
||||
if let Err(_e) =
|
||||
async_tx.send(DelayedAction::WebauthnCounterIncrement(
|
||||
WebauthnCounterIncrement {
|
||||
target_uuid: who,
|
||||
auth_result,
|
||||
},
|
||||
))
|
||||
{
|
||||
admin_warn!("unable to queue delayed webauthn property update, continuing ... ");
|
||||
};
|
||||
};
|
||||
};
|
||||
CredState::Success(AuthType::Passkey)
|
||||
|
||||
CredState::Success {
|
||||
auth_type: AuthType::Passkey,
|
||||
cred_id,
|
||||
}
|
||||
} else {
|
||||
wan_cred.state = CredVerifyState::Fail;
|
||||
// Denied.
|
||||
security_error!("Handler::Webauthn -> Result::Denied - webauthn credential id not found");
|
||||
CredState::Denied(BAD_WEBAUTHN_MSG)
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
wan_cred.state = CredVerifyState::Fail;
|
||||
|
@ -483,22 +546,37 @@ impl CredHandler {
|
|||
pw_badlist_set: Option<&HashSet<String>>,
|
||||
) -> CredState {
|
||||
match self {
|
||||
CredHandler::Anonymous => Self::validate_anonymous(cred),
|
||||
CredHandler::Password(ref mut pw, generated) => {
|
||||
Self::validate_password(cred, pw, *generated, who, async_tx, pw_badlist_set)
|
||||
}
|
||||
CredHandler::PasswordMfa(ref mut pw_mfa) => Self::validate_password_mfa(
|
||||
CredHandler::Anonymous { cred_id } => Self::validate_anonymous(cred, *cred_id),
|
||||
CredHandler::Password {
|
||||
ref mut pw,
|
||||
generated,
|
||||
cred_id,
|
||||
} => Self::validate_password(
|
||||
cred,
|
||||
*cred_id,
|
||||
pw,
|
||||
*generated,
|
||||
who,
|
||||
async_tx,
|
||||
pw_badlist_set,
|
||||
),
|
||||
CredHandler::PasswordMfa {
|
||||
ref mut cmfa,
|
||||
cred_id,
|
||||
} => Self::validate_password_mfa(
|
||||
cred,
|
||||
*cred_id,
|
||||
ts,
|
||||
pw_mfa,
|
||||
cmfa,
|
||||
webauthn,
|
||||
who,
|
||||
async_tx,
|
||||
pw_badlist_set,
|
||||
),
|
||||
CredHandler::Passkey(ref mut wan_cred) => {
|
||||
Self::validate_webauthn(cred, wan_cred, webauthn, who, async_tx)
|
||||
}
|
||||
CredHandler::Passkey {
|
||||
ref mut c_wan,
|
||||
cred_ids,
|
||||
} => Self::validate_webauthn(cred, cred_ids, c_wan, webauthn, who, async_tx),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -506,45 +584,44 @@ impl CredHandler {
|
|||
/// can proceed.
|
||||
pub fn next_auth_allowed(&self) -> Vec<AuthAllowed> {
|
||||
match &self {
|
||||
CredHandler::Anonymous => vec![AuthAllowed::Anonymous],
|
||||
CredHandler::Password(_, _) => vec![AuthAllowed::Password],
|
||||
CredHandler::PasswordMfa(ref pw_mfa) => pw_mfa
|
||||
CredHandler::Anonymous { .. } => vec![AuthAllowed::Anonymous],
|
||||
CredHandler::Password { .. } => vec![AuthAllowed::Password],
|
||||
CredHandler::PasswordMfa { ref cmfa, .. } => cmfa
|
||||
.backup_code
|
||||
.iter()
|
||||
.map(|_| AuthAllowed::BackupCode)
|
||||
// This looks weird but the idea is that if at least *one*
|
||||
// totp exists, then we only offer TOTP once. If none are
|
||||
// there we offer it none.
|
||||
.chain(pw_mfa.totp.iter().next().map(|_| AuthAllowed::Totp))
|
||||
.chain(cmfa.totp.iter().next().map(|_| AuthAllowed::Totp))
|
||||
// This iter is over an option so it's there or not.
|
||||
.chain(
|
||||
pw_mfa
|
||||
.wan
|
||||
cmfa.wan
|
||||
.iter()
|
||||
.map(|(chal, _)| AuthAllowed::SecurityKey(chal.clone())),
|
||||
)
|
||||
.collect(),
|
||||
CredHandler::Passkey(webauthn) => vec![AuthAllowed::Passkey(webauthn.chal.clone())],
|
||||
CredHandler::Passkey { c_wan, .. } => vec![AuthAllowed::Passkey(c_wan.chal.clone())],
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine which mechanismes can proceed given the requested mechanism.
|
||||
fn can_proceed(&self, mech: &AuthMech) -> bool {
|
||||
match (self, mech) {
|
||||
(CredHandler::Anonymous, AuthMech::Anonymous)
|
||||
| (CredHandler::Password(_, _), AuthMech::Password)
|
||||
| (CredHandler::PasswordMfa(_), AuthMech::PasswordMfa)
|
||||
| (CredHandler::Passkey(_), AuthMech::Passkey) => true,
|
||||
(CredHandler::Anonymous { .. }, AuthMech::Anonymous)
|
||||
| (CredHandler::Password { .. }, AuthMech::Password)
|
||||
| (CredHandler::PasswordMfa { .. }, AuthMech::PasswordMfa)
|
||||
| (CredHandler::Passkey { .. }, AuthMech::Passkey) => true,
|
||||
(_, _) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn allows_mech(&self) -> AuthMech {
|
||||
match self {
|
||||
CredHandler::Anonymous => AuthMech::Anonymous,
|
||||
CredHandler::Password(_, _) => AuthMech::Password,
|
||||
CredHandler::PasswordMfa(_) => AuthMech::PasswordMfa,
|
||||
CredHandler::Passkey(_) => AuthMech::Passkey,
|
||||
CredHandler::Anonymous { .. } => AuthMech::Anonymous,
|
||||
CredHandler::Password { .. } => AuthMech::Password,
|
||||
CredHandler::PasswordMfa { .. } => AuthMech::PasswordMfa,
|
||||
CredHandler::Passkey { .. } => AuthMech::Passkey,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -612,7 +689,9 @@ impl AuthSession {
|
|||
// based on the anonymous ... in theory this could be cleaner
|
||||
// and interact with the account more?
|
||||
if account.is_anonymous() {
|
||||
AuthSessionState::Init(vec![CredHandler::Anonymous])
|
||||
AuthSessionState::Init(vec![CredHandler::Anonymous {
|
||||
cred_id: account.uuid,
|
||||
}])
|
||||
} else {
|
||||
// What's valid to use in this context?
|
||||
let mut handlers = Vec::new();
|
||||
|
@ -667,14 +746,15 @@ impl AuthSession {
|
|||
}
|
||||
}
|
||||
|
||||
// This is used for softlock identification only.
|
||||
pub fn get_credential_uuid(&self) -> Result<Option<Uuid>, OperationError> {
|
||||
match &self.state {
|
||||
AuthSessionState::InProgress(CredHandler::Password(_, _))
|
||||
| AuthSessionState::InProgress(CredHandler::PasswordMfa(_)) => {
|
||||
Ok(self.account.primary_cred_uuid())
|
||||
AuthSessionState::InProgress(CredHandler::Password { cred_id, .. })
|
||||
| AuthSessionState::InProgress(CredHandler::PasswordMfa { cred_id, .. }) => {
|
||||
Ok(Some(*cred_id))
|
||||
}
|
||||
AuthSessionState::InProgress(CredHandler::Anonymous)
|
||||
| AuthSessionState::InProgress(CredHandler::Passkey(_)) => Ok(None),
|
||||
AuthSessionState::InProgress(CredHandler::Anonymous { .. })
|
||||
| AuthSessionState::InProgress(CredHandler::Passkey { .. }) => Ok(None),
|
||||
_ => Err(OperationError::InvalidState),
|
||||
}
|
||||
}
|
||||
|
@ -771,7 +851,7 @@ impl AuthSession {
|
|||
webauthn,
|
||||
pw_badlist_set,
|
||||
) {
|
||||
CredState::Success(auth_type) => {
|
||||
CredState::Success { auth_type, cred_id } => {
|
||||
security_info!("Successful cred handling");
|
||||
let session_id = Uuid::new_v4();
|
||||
let issue = self.issue;
|
||||
|
@ -816,6 +896,7 @@ impl AuthSession {
|
|||
async_tx.send(DelayedAction::AuthSessionRecord(AuthSessionRecord {
|
||||
target_uuid: self.account.uuid,
|
||||
session_id,
|
||||
cred_id,
|
||||
label: "Auth Session".to_string(),
|
||||
expiry: uat.expiry,
|
||||
issued_at: uat.issued_at,
|
||||
|
|
|
@ -65,6 +65,7 @@ pub struct Oauth2ConsentGrant {
|
|||
pub struct AuthSessionRecord {
|
||||
pub target_uuid: Uuid,
|
||||
pub session_id: Uuid,
|
||||
pub cred_id: Uuid,
|
||||
pub label: String,
|
||||
pub expiry: Option<OffsetDateTime>,
|
||||
pub issued_at: OffsetDateTime,
|
||||
|
|
|
@ -158,6 +158,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
issued_at,
|
||||
// Who actually created this?
|
||||
issued_by: gte.ident.get_event_origin_id(),
|
||||
// random id
|
||||
cred_id: Uuid::new_v4(),
|
||||
// What is the access scope of this session? This is
|
||||
// for auditing purposes.
|
||||
scope: (&purpose).into(),
|
||||
|
|
|
@ -2078,6 +2078,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
issued_at: asr.issued_at,
|
||||
// Who actually created this?
|
||||
issued_by: asr.issued_by.clone(),
|
||||
// Which credential was used?
|
||||
cred_id: asr.cred_id,
|
||||
// What is the access scope of this session? This is
|
||||
// for auditing purposes.
|
||||
scope: asr.scope,
|
||||
|
@ -2446,9 +2448,10 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
async fn init_admin_w_password(qs: &QueryServer, pw: &str) -> Result<(), OperationError> {
|
||||
async fn init_admin_w_password(qs: &QueryServer, pw: &str) -> Result<Uuid, OperationError> {
|
||||
let p = CryptoPolicy::minimum();
|
||||
let cred = Credential::new_password_only(&p, pw)?;
|
||||
let cred_id = cred.uuid;
|
||||
let v_cred = Value::new_credential("primary", cred);
|
||||
let mut qs_write = qs.write(duration_from_epoch_now()).await;
|
||||
|
||||
|
@ -2465,7 +2468,7 @@ mod tests {
|
|||
// go!
|
||||
assert!(qs_write.modify(&me_inv_m).is_ok());
|
||||
|
||||
qs_write.commit()
|
||||
qs_write.commit().map(|()| cred_id)
|
||||
}
|
||||
|
||||
fn init_admin_authsession_sid(idms: &IdmServer, ct: Duration, name: &str) -> Uuid {
|
||||
|
@ -3658,92 +3661,98 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_idm_expired_auth_session_cleanup() {
|
||||
run_idm_test!(|_qs: &QueryServer,
|
||||
idms: &IdmServer,
|
||||
_idms_delayed: &mut IdmServerDelayed| {
|
||||
let ct = Duration::from_secs(TEST_CURRENT_TIME);
|
||||
let expiry_a = ct + Duration::from_secs(AUTH_SESSION_EXPIRY + 1);
|
||||
let expiry_b = ct + Duration::from_secs((AUTH_SESSION_EXPIRY + 1) * 2);
|
||||
run_idm_test!(
|
||||
|qs: &QueryServer, idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed| {
|
||||
let ct = Duration::from_secs(TEST_CURRENT_TIME);
|
||||
let expiry_a = ct + Duration::from_secs(AUTH_SESSION_EXPIRY + 1);
|
||||
let expiry_b = ct + Duration::from_secs((AUTH_SESSION_EXPIRY + 1) * 2);
|
||||
|
||||
let session_a = Uuid::new_v4();
|
||||
let session_b = Uuid::new_v4();
|
||||
let session_a = Uuid::new_v4();
|
||||
let session_b = Uuid::new_v4();
|
||||
|
||||
// Assert no sessions present
|
||||
let mut idms_prox_read = task::block_on(idms.proxy_read());
|
||||
let admin = idms_prox_read
|
||||
.qs_read
|
||||
.internal_search_uuid(UUID_ADMIN)
|
||||
.expect("failed");
|
||||
let sessions = admin.get_ava_as_session_map("user_auth_token_session");
|
||||
assert!(sessions.is_none());
|
||||
drop(idms_prox_read);
|
||||
// We need to put the credential on the admin.
|
||||
let cred_id = task::block_on(init_admin_w_password(qs, TEST_PASSWORD))
|
||||
.expect("Failed to setup admin account");
|
||||
|
||||
let da = DelayedAction::AuthSessionRecord(AuthSessionRecord {
|
||||
target_uuid: UUID_ADMIN,
|
||||
session_id: session_a,
|
||||
label: "Test Session A".to_string(),
|
||||
expiry: Some(OffsetDateTime::unix_epoch() + expiry_a),
|
||||
issued_at: OffsetDateTime::unix_epoch() + ct,
|
||||
issued_by: IdentityId::User(UUID_ADMIN),
|
||||
scope: AccessScope::IdentityOnly,
|
||||
});
|
||||
// Persist it.
|
||||
let r = task::block_on(idms.delayed_action(ct, da));
|
||||
assert!(Ok(true) == r);
|
||||
// Assert no sessions present
|
||||
let mut idms_prox_read = task::block_on(idms.proxy_read());
|
||||
let admin = idms_prox_read
|
||||
.qs_read
|
||||
.internal_search_uuid(UUID_ADMIN)
|
||||
.expect("failed");
|
||||
let sessions = admin.get_ava_as_session_map("user_auth_token_session");
|
||||
assert!(sessions.is_none());
|
||||
drop(idms_prox_read);
|
||||
|
||||
// Check it was written, and check
|
||||
let mut idms_prox_read = task::block_on(idms.proxy_read());
|
||||
let admin = idms_prox_read
|
||||
.qs_read
|
||||
.internal_search_uuid(UUID_ADMIN)
|
||||
.expect("failed");
|
||||
let sessions = admin
|
||||
.get_ava_as_session_map("user_auth_token_session")
|
||||
.expect("Sessions must be present!");
|
||||
assert!(sessions.len() == 1);
|
||||
let session_id_a = sessions
|
||||
.keys()
|
||||
.copied()
|
||||
.next()
|
||||
.expect("Could not access session id");
|
||||
assert!(session_id_a == session_a);
|
||||
let da = DelayedAction::AuthSessionRecord(AuthSessionRecord {
|
||||
target_uuid: UUID_ADMIN,
|
||||
session_id: session_a,
|
||||
cred_id,
|
||||
label: "Test Session A".to_string(),
|
||||
expiry: Some(OffsetDateTime::unix_epoch() + expiry_a),
|
||||
issued_at: OffsetDateTime::unix_epoch() + ct,
|
||||
issued_by: IdentityId::User(UUID_ADMIN),
|
||||
scope: AccessScope::IdentityOnly,
|
||||
});
|
||||
// Persist it.
|
||||
let r = task::block_on(idms.delayed_action(ct, da));
|
||||
assert!(Ok(true) == r);
|
||||
|
||||
drop(idms_prox_read);
|
||||
// Check it was written, and check
|
||||
let mut idms_prox_read = task::block_on(idms.proxy_read());
|
||||
let admin = idms_prox_read
|
||||
.qs_read
|
||||
.internal_search_uuid(UUID_ADMIN)
|
||||
.expect("failed");
|
||||
let sessions = admin
|
||||
.get_ava_as_session_map("user_auth_token_session")
|
||||
.expect("Sessions must be present!");
|
||||
assert!(sessions.len() == 1);
|
||||
let session_id_a = sessions
|
||||
.keys()
|
||||
.copied()
|
||||
.next()
|
||||
.expect("Could not access session id");
|
||||
assert!(session_id_a == session_a);
|
||||
|
||||
// When we re-auth, this is what triggers the session cleanup via the delayed action.
|
||||
drop(idms_prox_read);
|
||||
|
||||
let da = DelayedAction::AuthSessionRecord(AuthSessionRecord {
|
||||
target_uuid: UUID_ADMIN,
|
||||
session_id: session_b,
|
||||
label: "Test Session B".to_string(),
|
||||
expiry: Some(OffsetDateTime::unix_epoch() + expiry_b),
|
||||
issued_at: OffsetDateTime::unix_epoch() + ct,
|
||||
issued_by: IdentityId::User(UUID_ADMIN),
|
||||
scope: AccessScope::IdentityOnly,
|
||||
});
|
||||
// Persist it.
|
||||
let r = task::block_on(idms.delayed_action(expiry_a, da));
|
||||
assert!(Ok(true) == r);
|
||||
// When we re-auth, this is what triggers the session cleanup via the delayed action.
|
||||
|
||||
let mut idms_prox_read = task::block_on(idms.proxy_read());
|
||||
let admin = idms_prox_read
|
||||
.qs_read
|
||||
.internal_search_uuid(UUID_ADMIN)
|
||||
.expect("failed");
|
||||
let sessions = admin
|
||||
.get_ava_as_session_map("user_auth_token_session")
|
||||
.expect("Sessions must be present!");
|
||||
trace!(?sessions);
|
||||
assert!(sessions.len() == 1);
|
||||
let session_id_b = sessions
|
||||
.keys()
|
||||
.copied()
|
||||
.next()
|
||||
.expect("Could not access session id");
|
||||
assert!(session_id_b == session_b);
|
||||
let da = DelayedAction::AuthSessionRecord(AuthSessionRecord {
|
||||
target_uuid: UUID_ADMIN,
|
||||
session_id: session_b,
|
||||
cred_id,
|
||||
label: "Test Session B".to_string(),
|
||||
expiry: Some(OffsetDateTime::unix_epoch() + expiry_b),
|
||||
issued_at: OffsetDateTime::unix_epoch() + ct,
|
||||
issued_by: IdentityId::User(UUID_ADMIN),
|
||||
scope: AccessScope::IdentityOnly,
|
||||
});
|
||||
// Persist it.
|
||||
let r = task::block_on(idms.delayed_action(expiry_a, da));
|
||||
assert!(Ok(true) == r);
|
||||
|
||||
assert!(session_id_a != session_id_b);
|
||||
})
|
||||
let mut idms_prox_read = task::block_on(idms.proxy_read());
|
||||
let admin = idms_prox_read
|
||||
.qs_read
|
||||
.internal_search_uuid(UUID_ADMIN)
|
||||
.expect("failed");
|
||||
let sessions = admin
|
||||
.get_ava_as_session_map("user_auth_token_session")
|
||||
.expect("Sessions must be present!");
|
||||
trace!(?sessions);
|
||||
assert!(sessions.len() == 1);
|
||||
let session_id_b = sessions
|
||||
.keys()
|
||||
.copied()
|
||||
.next()
|
||||
.expect("Could not access session id");
|
||||
assert!(session_id_b == session_b);
|
||||
|
||||
assert!(session_id_a != session_id_b);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -224,6 +224,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
issued_at,
|
||||
// Who actually created this?
|
||||
issued_by: gte.ident.get_event_origin_id(),
|
||||
// random id
|
||||
cred_id: Uuid::new_v4(),
|
||||
// What is the access scope of this session? This is
|
||||
// for auditing purposes.
|
||||
scope: (&purpose).into(),
|
||||
|
|
|
@ -264,6 +264,9 @@ mod tests {
|
|||
use time::OffsetDateTime;
|
||||
use uuid::uuid;
|
||||
|
||||
use crate::credential::policy::CryptoPolicy;
|
||||
use crate::credential::Credential;
|
||||
|
||||
// The create references a uuid that doesn't exist - reject
|
||||
#[test]
|
||||
fn test_create_uuid_reference_not_exist() {
|
||||
|
@ -776,6 +779,10 @@ mod tests {
|
|||
let curtime = duration_from_epoch_now();
|
||||
let curtime_odt = OffsetDateTime::unix_epoch() + curtime;
|
||||
|
||||
let p = CryptoPolicy::minimum();
|
||||
let cred = Credential::new_password_only(&p, "test_password").unwrap();
|
||||
let cred_id = cred.uuid;
|
||||
|
||||
// Create a user
|
||||
let mut server_txn = server.write(curtime).await;
|
||||
|
||||
|
@ -789,7 +796,11 @@ mod tests {
|
|||
("name", Value::new_iname("testperson1")),
|
||||
("uuid", Value::Uuid(tuuid)),
|
||||
("description", Value::new_utf8s("testperson1")),
|
||||
("displayname", Value::new_utf8s("testperson1"))
|
||||
("displayname", Value::new_utf8s("testperson1")),
|
||||
(
|
||||
"primary_credential",
|
||||
Value::Cred("primary".to_string(), cred.clone())
|
||||
)
|
||||
);
|
||||
|
||||
let e2 = entry_init!(
|
||||
|
@ -853,6 +864,7 @@ mod tests {
|
|||
issued_at,
|
||||
// Who actually created this?
|
||||
issued_by,
|
||||
cred_id,
|
||||
// What is the access scope of this session? This is
|
||||
// for auditing purposes.
|
||||
scope,
|
||||
|
|
|
@ -49,6 +49,37 @@ impl SessionConsistency {
|
|||
|
||||
// We need to assert a number of properties. We must do these *in order*.
|
||||
cand.iter_mut().try_for_each(|entry| {
|
||||
// * If the session's credential is no longer on the account, we remove the session.
|
||||
let cred_ids: BTreeSet<Uuid> =
|
||||
entry
|
||||
.get_ava_single_credential("primary_credential")
|
||||
.iter()
|
||||
.map(|c| c.uuid)
|
||||
|
||||
.chain(
|
||||
entry.get_ava_passkeys("passkeys")
|
||||
.iter()
|
||||
.flat_map(|pks| pks.keys().copied() )
|
||||
)
|
||||
.collect();
|
||||
|
||||
let invalidate: Option<BTreeSet<_>> = entry.get_ava_as_session_map("user_auth_token_session")
|
||||
.map(|sessions| {
|
||||
sessions.iter().filter_map(|(session_id, session)| {
|
||||
if !cred_ids.contains(&session.cred_id) {
|
||||
info!(%session_id, "Removing auth session whose issuing credential no longer exists");
|
||||
Some(PartialValue::Refer(*session_id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
if let Some(invalidate) = invalidate.as_ref() {
|
||||
entry.remove_avas("user_auth_token_session", invalidate);
|
||||
}
|
||||
|
||||
// * If a UAT is past it's expiry, remove it.
|
||||
let expired: Option<BTreeSet<_>> = entry.get_ava_as_session_map("user_auth_token_session")
|
||||
.map(|sessions| {
|
||||
|
@ -122,6 +153,9 @@ mod tests {
|
|||
use time::OffsetDateTime;
|
||||
use uuid::uuid;
|
||||
|
||||
use crate::credential::policy::CryptoPolicy;
|
||||
use crate::credential::Credential;
|
||||
|
||||
// Test expiry of old sessions
|
||||
|
||||
#[qs_test]
|
||||
|
@ -129,6 +163,10 @@ mod tests {
|
|||
let curtime = duration_from_epoch_now();
|
||||
let curtime_odt = OffsetDateTime::unix_epoch() + curtime;
|
||||
|
||||
let p = CryptoPolicy::minimum();
|
||||
let cred = Credential::new_password_only(&p, "test_password").unwrap();
|
||||
let cred_id = cred.uuid;
|
||||
|
||||
let exp_curtime = curtime + Duration::from_secs(60);
|
||||
let exp_curtime_odt = OffsetDateTime::unix_epoch() + exp_curtime;
|
||||
|
||||
|
@ -144,7 +182,11 @@ mod tests {
|
|||
("name", Value::new_iname("testperson1")),
|
||||
("uuid", Value::Uuid(tuuid)),
|
||||
("description", Value::new_utf8s("testperson1")),
|
||||
("displayname", Value::new_utf8s("testperson1"))
|
||||
("displayname", Value::new_utf8s("testperson1")),
|
||||
(
|
||||
"primary_credential",
|
||||
Value::Cred("primary".to_string(), cred.clone())
|
||||
)
|
||||
);
|
||||
|
||||
let ce = CreateEvent::new_internal(vec![e1]);
|
||||
|
@ -168,6 +210,7 @@ mod tests {
|
|||
issued_at,
|
||||
// Who actually created this?
|
||||
issued_by,
|
||||
cred_id,
|
||||
// What is the access scope of this session? This is
|
||||
// for auditing purposes.
|
||||
scope,
|
||||
|
@ -213,6 +256,10 @@ mod tests {
|
|||
let curtime = duration_from_epoch_now();
|
||||
let curtime_odt = OffsetDateTime::unix_epoch() + curtime;
|
||||
|
||||
let p = CryptoPolicy::minimum();
|
||||
let cred = Credential::new_password_only(&p, "test_password").unwrap();
|
||||
let cred_id = cred.uuid;
|
||||
|
||||
// Set exp to gracewindow.
|
||||
let exp_curtime = curtime + GRACE_WINDOW;
|
||||
let exp_curtime_odt = OffsetDateTime::unix_epoch() + exp_curtime;
|
||||
|
@ -230,7 +277,11 @@ mod tests {
|
|||
("name", Value::new_iname("testperson1")),
|
||||
("uuid", Value::Uuid(tuuid)),
|
||||
("description", Value::new_utf8s("testperson1")),
|
||||
("displayname", Value::new_utf8s("testperson1"))
|
||||
("displayname", Value::new_utf8s("testperson1")),
|
||||
(
|
||||
"primary_credential",
|
||||
Value::Cred("primary".to_string(), cred.clone())
|
||||
)
|
||||
);
|
||||
|
||||
let e2 = entry_init!(
|
||||
|
@ -295,6 +346,7 @@ mod tests {
|
|||
issued_at,
|
||||
// Who actually created this?
|
||||
issued_by,
|
||||
cred_id,
|
||||
// What is the access scope of this session? This is
|
||||
// for auditing purposes.
|
||||
scope,
|
||||
|
@ -346,6 +398,10 @@ mod tests {
|
|||
let curtime_odt = OffsetDateTime::unix_epoch() + curtime;
|
||||
let exp_curtime = curtime + GRACE_WINDOW;
|
||||
|
||||
let p = CryptoPolicy::minimum();
|
||||
let cred = Credential::new_password_only(&p, "test_password").unwrap();
|
||||
let cred_id = cred.uuid;
|
||||
|
||||
// Create a user
|
||||
let mut server_txn = server.write(curtime).await;
|
||||
|
||||
|
@ -359,7 +415,11 @@ mod tests {
|
|||
("name", Value::new_iname("testperson1")),
|
||||
("uuid", Value::Uuid(tuuid)),
|
||||
("description", Value::new_utf8s("testperson1")),
|
||||
("displayname", Value::new_utf8s("testperson1"))
|
||||
("displayname", Value::new_utf8s("testperson1")),
|
||||
(
|
||||
"primary_credential",
|
||||
Value::Cred("primary".to_string(), cred.clone())
|
||||
)
|
||||
);
|
||||
|
||||
let e2 = entry_init!(
|
||||
|
@ -423,6 +483,7 @@ mod tests {
|
|||
issued_at,
|
||||
// Who actually created this?
|
||||
issued_by,
|
||||
cred_id,
|
||||
// What is the access scope of this session? This is
|
||||
// for auditing purposes.
|
||||
scope,
|
||||
|
@ -566,4 +627,95 @@ mod tests {
|
|||
|
||||
assert!(server_txn.commit().is_ok());
|
||||
}
|
||||
|
||||
#[qs_test]
|
||||
async fn test_session_consistency_expire_when_cred_removed(server: &QueryServer) {
|
||||
let curtime = duration_from_epoch_now();
|
||||
let curtime_odt = OffsetDateTime::unix_epoch() + curtime;
|
||||
|
||||
let p = CryptoPolicy::minimum();
|
||||
let cred = Credential::new_password_only(&p, "test_password").unwrap();
|
||||
let cred_id = cred.uuid;
|
||||
|
||||
// Create a user
|
||||
let mut server_txn = server.write(curtime).await;
|
||||
|
||||
let tuuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
|
||||
|
||||
let e1 = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("person")),
|
||||
("class", Value::new_class("account")),
|
||||
("name", Value::new_iname("testperson1")),
|
||||
("uuid", Value::Uuid(tuuid)),
|
||||
("description", Value::new_utf8s("testperson1")),
|
||||
("displayname", Value::new_utf8s("testperson1")),
|
||||
(
|
||||
"primary_credential",
|
||||
Value::Cred("primary".to_string(), cred.clone())
|
||||
)
|
||||
);
|
||||
|
||||
let ce = CreateEvent::new_internal(vec![e1]);
|
||||
assert!(server_txn.create(&ce).is_ok());
|
||||
|
||||
// Create a fake session.
|
||||
let session_id = Uuid::new_v4();
|
||||
let pv_session_id = PartialValue::Refer(session_id);
|
||||
// No expiry!
|
||||
let expiry = None;
|
||||
let issued_at = curtime_odt;
|
||||
let issued_by = IdentityId::User(tuuid);
|
||||
let scope = AccessScope::IdentityOnly;
|
||||
|
||||
let session = Value::Session(
|
||||
session_id,
|
||||
Session {
|
||||
label: "label".to_string(),
|
||||
expiry,
|
||||
// Need the other inner bits?
|
||||
// for the gracewindow.
|
||||
issued_at,
|
||||
// Who actually created this?
|
||||
issued_by,
|
||||
cred_id,
|
||||
// What is the access scope of this session? This is
|
||||
// for auditing purposes.
|
||||
scope,
|
||||
},
|
||||
);
|
||||
|
||||
// Mod the user
|
||||
let modlist = ModifyList::new_append("user_auth_token_session", session);
|
||||
|
||||
server_txn
|
||||
.internal_modify(&filter!(f_eq("uuid", PartialValue::Uuid(tuuid))), &modlist)
|
||||
.expect("Failed to modify user");
|
||||
|
||||
// Still there
|
||||
|
||||
let entry = server_txn.internal_search_uuid(tuuid).expect("failed");
|
||||
|
||||
assert!(entry.attribute_equality("user_auth_token_session", &pv_session_id));
|
||||
|
||||
assert!(server_txn.commit().is_ok());
|
||||
|
||||
// Notice we keep the time the same for the txn.
|
||||
let mut server_txn = server.write(curtime).await;
|
||||
|
||||
// Remove the primary credential
|
||||
let modlist = ModifyList::new_purge("primary_credential");
|
||||
|
||||
server_txn
|
||||
.internal_modify(&filter!(f_eq("uuid", PartialValue::Uuid(tuuid))), &modlist)
|
||||
.expect("Failed to modify user");
|
||||
|
||||
// Session gone.
|
||||
let entry = server_txn.internal_search_uuid(tuuid).expect("failed");
|
||||
|
||||
// Note it's a not condition now.
|
||||
assert!(!entry.attribute_equality("user_auth_token_session", &pv_session_id));
|
||||
|
||||
assert!(server_txn.commit().is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -238,6 +238,7 @@ pub struct ReplSessionV1 {
|
|||
pub expiry: Option<String>,
|
||||
pub issued_at: String,
|
||||
pub issued_by: ReplIdentityIdV1,
|
||||
pub cred_id: Uuid,
|
||||
pub scope: ReplAccessScopeV1,
|
||||
}
|
||||
|
||||
|
|
|
@ -715,6 +715,7 @@ pub struct Session {
|
|||
pub expiry: Option<OffsetDateTime>,
|
||||
pub issued_at: OffsetDateTime,
|
||||
pub issued_by: IdentityId,
|
||||
pub cred_id: Uuid,
|
||||
pub scope: AccessScope,
|
||||
}
|
||||
|
||||
|
|
|
@ -35,12 +35,15 @@ impl ValueSetSession {
|
|||
.into_iter()
|
||||
.filter_map(|dbv| {
|
||||
match dbv {
|
||||
DbValueSession::V1 {
|
||||
// Skip due to lack of credential id
|
||||
DbValueSession::V1 { .. } => None,
|
||||
DbValueSession::V2 {
|
||||
refer,
|
||||
label,
|
||||
expiry,
|
||||
issued_at,
|
||||
issued_by,
|
||||
cred_id,
|
||||
scope,
|
||||
} => {
|
||||
// Convert things.
|
||||
|
@ -98,6 +101,7 @@ impl ValueSetSession {
|
|||
expiry,
|
||||
issued_at,
|
||||
issued_by,
|
||||
cred_id,
|
||||
scope,
|
||||
},
|
||||
))
|
||||
|
@ -118,6 +122,7 @@ impl ValueSetSession {
|
|||
expiry,
|
||||
issued_at,
|
||||
issued_by,
|
||||
cred_id,
|
||||
scope,
|
||||
}| {
|
||||
// Convert things.
|
||||
|
@ -176,6 +181,7 @@ impl ValueSetSession {
|
|||
expiry,
|
||||
issued_at,
|
||||
issued_by,
|
||||
cred_id: *cred_id,
|
||||
scope,
|
||||
},
|
||||
))
|
||||
|
@ -269,7 +275,7 @@ impl ValueSetT for ValueSetSession {
|
|||
DbValueSetV2::Session(
|
||||
self.map
|
||||
.iter()
|
||||
.map(|(u, m)| DbValueSession::V1 {
|
||||
.map(|(u, m)| DbValueSession::V2 {
|
||||
refer: *u,
|
||||
label: m.label.clone(),
|
||||
expiry: m.expiry.map(|odt| {
|
||||
|
@ -285,6 +291,7 @@ impl ValueSetT for ValueSetSession {
|
|||
IdentityId::User(u) => DbValueIdentityId::V1Uuid(u),
|
||||
IdentityId::Synch(u) => DbValueIdentityId::V1Sync(u),
|
||||
},
|
||||
cred_id: m.cred_id,
|
||||
scope: match m.scope {
|
||||
AccessScope::IdentityOnly => DbValueAccessScopeV1::IdentityOnly,
|
||||
AccessScope::ReadOnly => DbValueAccessScopeV1::ReadOnly,
|
||||
|
@ -317,6 +324,7 @@ impl ValueSetT for ValueSetSession {
|
|||
IdentityId::User(u) => ReplIdentityIdV1::Uuid(u),
|
||||
IdentityId::Synch(u) => ReplIdentityIdV1::Synch(u),
|
||||
},
|
||||
cred_id: m.cred_id,
|
||||
scope: match m.scope {
|
||||
AccessScope::IdentityOnly => ReplAccessScopeV1::IdentityOnly,
|
||||
AccessScope::ReadOnly => ReplAccessScopeV1::ReadOnly,
|
||||
|
|
|
@ -2,7 +2,7 @@ import { modal_hide_by_id } from '/pkg/wasmloader.js';
|
|||
|
||||
let wasm;
|
||||
|
||||
const heap = new Array(32).fill(undefined);
|
||||
const heap = new Array(128).fill(undefined);
|
||||
|
||||
heap.push(undefined, null, true, false);
|
||||
|
||||
|
@ -12,19 +12,19 @@ function isLikeNone(x) {
|
|||
return x === undefined || x === null;
|
||||
}
|
||||
|
||||
let cachedFloat64Memory0 = new Float64Array();
|
||||
let cachedFloat64Memory0 = null;
|
||||
|
||||
function getFloat64Memory0() {
|
||||
if (cachedFloat64Memory0.byteLength === 0) {
|
||||
if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) {
|
||||
cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedFloat64Memory0;
|
||||
}
|
||||
|
||||
let cachedInt32Memory0 = new Int32Array();
|
||||
let cachedInt32Memory0 = null;
|
||||
|
||||
function getInt32Memory0() {
|
||||
if (cachedInt32Memory0.byteLength === 0) {
|
||||
if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {
|
||||
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedInt32Memory0;
|
||||
|
@ -32,10 +32,10 @@ function getInt32Memory0() {
|
|||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
let cachedUint8Memory0 = new Uint8Array();
|
||||
let cachedUint8Memory0 = null;
|
||||
|
||||
function getUint8Memory0() {
|
||||
if (cachedUint8Memory0.byteLength === 0) {
|
||||
if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
|
||||
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint8Memory0;
|
||||
|
@ -114,7 +114,7 @@ function getStringFromWasm0(ptr, len) {
|
|||
}
|
||||
|
||||
function dropObject(idx) {
|
||||
if (idx < 36) return;
|
||||
if (idx < 132) return;
|
||||
heap[idx] = heap_next;
|
||||
heap_next = idx;
|
||||
}
|
||||
|
@ -125,10 +125,10 @@ function takeObject(idx) {
|
|||
return ret;
|
||||
}
|
||||
|
||||
let cachedBigInt64Memory0 = new BigInt64Array();
|
||||
let cachedBigInt64Memory0 = null;
|
||||
|
||||
function getBigInt64Memory0() {
|
||||
if (cachedBigInt64Memory0.byteLength === 0) {
|
||||
if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) {
|
||||
cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedBigInt64Memory0;
|
||||
|
@ -224,7 +224,7 @@ function makeMutClosure(arg0, arg1, dtor, f) {
|
|||
return real;
|
||||
}
|
||||
|
||||
let stack_pointer = 32;
|
||||
let stack_pointer = 128;
|
||||
|
||||
function addBorrowedObject(obj) {
|
||||
if (stack_pointer == 1) throw new Error('out of js stack');
|
||||
|
@ -233,19 +233,19 @@ function addBorrowedObject(obj) {
|
|||
}
|
||||
function __wbg_adapter_48(arg0, arg1, arg2) {
|
||||
try {
|
||||
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h5eb2188918e4a7c0(arg0, arg1, addBorrowedObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hef8a7abf8d4fa4dc(arg0, arg1, addBorrowedObject(arg2));
|
||||
} finally {
|
||||
heap[stack_pointer++] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function __wbg_adapter_51(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0d507a903b66501d(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h24986de976067b9f(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_54(arg0, arg1, arg2) {
|
||||
try {
|
||||
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hcd5d9fc409fce3ad(arg0, arg1, addBorrowedObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hc899b3af6aa31a80(arg0, arg1, addBorrowedObject(arg2));
|
||||
} finally {
|
||||
heap[stack_pointer++] = undefined;
|
||||
}
|
||||
|
@ -267,10 +267,10 @@ export function run_app() {
|
|||
}
|
||||
}
|
||||
|
||||
let cachedUint32Memory0 = new Uint32Array();
|
||||
let cachedUint32Memory0 = null;
|
||||
|
||||
function getUint32Memory0() {
|
||||
if (cachedUint32Memory0.byteLength === 0) {
|
||||
if (cachedUint32Memory0 === null || cachedUint32Memory0.byteLength === 0) {
|
||||
cachedUint32Memory0 = new Uint32Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint32Memory0;
|
||||
|
@ -944,15 +944,15 @@ function getImports() {
|
|||
const ret = getObject(arg0).fetch(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_get_57245cc7d7c7619d = function(arg0, arg1) {
|
||||
imports.wbg.__wbg_get_27fe3dac1c4d0224 = function(arg0, arg1) {
|
||||
const ret = getObject(arg0)[arg1 >>> 0];
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_length_6e3bbe7c8bd4dbd8 = function(arg0) {
|
||||
imports.wbg.__wbg_length_e498fbc24f9c1d4f = function(arg0) {
|
||||
const ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_new_1d9a920c6bfc44a8 = function() {
|
||||
imports.wbg.__wbg_new_b525de17f44a8943 = function() {
|
||||
const ret = new Array();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
@ -960,78 +960,78 @@ function getImports() {
|
|||
const ret = typeof(getObject(arg0)) === 'function';
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_newnoargs_b5b063fc6c2f0376 = function(arg0, arg1) {
|
||||
imports.wbg.__wbg_newnoargs_2b8b6bd7753c76ba = function(arg0, arg1) {
|
||||
const ret = new Function(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_new_268f7b7dd3430798 = function() {
|
||||
imports.wbg.__wbg_new_f841cc6f2098f4b5 = function() {
|
||||
const ret = new Map();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_next_579e583d33566a86 = function(arg0) {
|
||||
imports.wbg.__wbg_next_b7d530c04fd8b217 = function(arg0) {
|
||||
const ret = getObject(arg0).next;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_next_aaef7c8aa5e212ac = function() { return handleError(function (arg0) {
|
||||
imports.wbg.__wbg_next_88560ec06a094dea = function() { return handleError(function (arg0) {
|
||||
const ret = getObject(arg0).next();
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_done_1b73b0672e15f234 = function(arg0) {
|
||||
imports.wbg.__wbg_done_1ebec03bbd919843 = function(arg0) {
|
||||
const ret = getObject(arg0).done;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_value_1ccc36bc03462d71 = function(arg0) {
|
||||
imports.wbg.__wbg_value_6ac8da5cc5b3efda = function(arg0) {
|
||||
const ret = getObject(arg0).value;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_iterator_6f9d4f28845f426c = function() {
|
||||
imports.wbg.__wbg_iterator_55f114446221aa5a = function() {
|
||||
const ret = Symbol.iterator;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_get_765201544a2b6869 = function() { return handleError(function (arg0, arg1) {
|
||||
imports.wbg.__wbg_get_baf4855f9a986186 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = Reflect.get(getObject(arg0), getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_call_97ae9d8645dc388b = function() { return handleError(function (arg0, arg1) {
|
||||
imports.wbg.__wbg_call_95d1ea488d03e4e8 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = getObject(arg0).call(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_new_0b9bfdd97583284e = function() {
|
||||
imports.wbg.__wbg_new_f9876326328f45ed = function() {
|
||||
const ret = new Object();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_self_6d479506f72c6a71 = function() { return handleError(function () {
|
||||
imports.wbg.__wbg_self_e7c1f827057f6584 = function() { return handleError(function () {
|
||||
const ret = self.self;
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_window_f2557cc78490aceb = function() { return handleError(function () {
|
||||
imports.wbg.__wbg_window_a09ec664e14b1b81 = function() { return handleError(function () {
|
||||
const ret = window.window;
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_globalThis_7f206bda628d5286 = function() { return handleError(function () {
|
||||
imports.wbg.__wbg_globalThis_87cbb8506fecf3a9 = function() { return handleError(function () {
|
||||
const ret = globalThis.globalThis;
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_global_ba75c50d1cf384f4 = function() { return handleError(function () {
|
||||
imports.wbg.__wbg_global_c85a9259e621f3db = function() { return handleError(function () {
|
||||
const ret = global.global;
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_set_a68214f35c417fa9 = function(arg0, arg1, arg2) {
|
||||
imports.wbg.__wbg_set_17224bc548dd1d7b = function(arg0, arg1, arg2) {
|
||||
getObject(arg0)[arg1 >>> 0] = takeObject(arg2);
|
||||
};
|
||||
imports.wbg.__wbg_from_7ce3cb27cb258569 = function(arg0) {
|
||||
imports.wbg.__wbg_from_67ca20fa722467e6 = function(arg0) {
|
||||
const ret = Array.from(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_isArray_27c46c67f498e15d = function(arg0) {
|
||||
imports.wbg.__wbg_isArray_39d28997bf6b96b4 = function(arg0) {
|
||||
const ret = Array.isArray(getObject(arg0));
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_push_740e4b286702d964 = function(arg0, arg1) {
|
||||
imports.wbg.__wbg_push_49c286f04dd3bf59 = function(arg0, arg1) {
|
||||
const ret = getObject(arg0).push(getObject(arg1));
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_ArrayBuffer_e5e48f4762c5610b = function(arg0) {
|
||||
imports.wbg.__wbg_instanceof_ArrayBuffer_a69f02ee4c4f5065 = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = getObject(arg0) instanceof ArrayBuffer;
|
||||
|
@ -1041,7 +1041,7 @@ function getImports() {
|
|||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_Error_56b496a10a56de66 = function(arg0) {
|
||||
imports.wbg.__wbg_instanceof_Error_749a7378f4439ee0 = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = getObject(arg0) instanceof Error;
|
||||
|
@ -1051,70 +1051,70 @@ function getImports() {
|
|||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_message_fe2af63ccc8985bc = function(arg0) {
|
||||
imports.wbg.__wbg_message_a95c3ef248e4b57a = function(arg0) {
|
||||
const ret = getObject(arg0).message;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_name_48eda3ae6aa697ca = function(arg0) {
|
||||
imports.wbg.__wbg_name_c69a20c4b1197dc0 = function(arg0) {
|
||||
const ret = getObject(arg0).name;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_toString_73c9b562dccf34bd = function(arg0) {
|
||||
imports.wbg.__wbg_toString_cec163b212643722 = function(arg0) {
|
||||
const ret = getObject(arg0).toString();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_set_933729cf5b66ac11 = function(arg0, arg1, arg2) {
|
||||
imports.wbg.__wbg_set_388c4c6422704173 = function(arg0, arg1, arg2) {
|
||||
const ret = getObject(arg0).set(getObject(arg1), getObject(arg2));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_isSafeInteger_dfa0593e8d7ac35a = function(arg0) {
|
||||
imports.wbg.__wbg_isSafeInteger_8c4789029e885159 = function(arg0) {
|
||||
const ret = Number.isSafeInteger(getObject(arg0));
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_entries_65a76a413fc91037 = function(arg0) {
|
||||
imports.wbg.__wbg_entries_4e1315b774245952 = function(arg0) {
|
||||
const ret = Object.entries(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_is_40a66842732708e7 = function(arg0, arg1) {
|
||||
imports.wbg.__wbg_is_8f1618fe9a4fd388 = function(arg0, arg1) {
|
||||
const ret = Object.is(getObject(arg0), getObject(arg1));
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_toString_7be108a12ef03bc2 = function(arg0) {
|
||||
imports.wbg.__wbg_toString_8c529acfe543ce16 = function(arg0) {
|
||||
const ret = getObject(arg0).toString();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_resolve_99fe17964f31ffc0 = function(arg0) {
|
||||
imports.wbg.__wbg_resolve_fd40f858d9db1a04 = function(arg0) {
|
||||
const ret = Promise.resolve(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_then_11f7a54d67b4bfad = function(arg0, arg1) {
|
||||
imports.wbg.__wbg_then_ec5db6d509eb475f = function(arg0, arg1) {
|
||||
const ret = getObject(arg0).then(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_then_cedad20fbbd9418a = function(arg0, arg1, arg2) {
|
||||
imports.wbg.__wbg_then_f753623316e2873a = function(arg0, arg1, arg2) {
|
||||
const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_buffer_3f3d764d4747d564 = function(arg0) {
|
||||
imports.wbg.__wbg_buffer_cf65c07de34b9a08 = function(arg0) {
|
||||
const ret = getObject(arg0).buffer;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_newwithbyteoffsetandlength_d9aa266703cb98be = function(arg0, arg1, arg2) {
|
||||
imports.wbg.__wbg_newwithbyteoffsetandlength_9fb2f11355ecadf5 = function(arg0, arg1, arg2) {
|
||||
const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_new_8c3f0052272a457a = function(arg0) {
|
||||
imports.wbg.__wbg_new_537b7341ce90bb31 = function(arg0) {
|
||||
const ret = new Uint8Array(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_set_83db9690f9353e79 = function(arg0, arg1, arg2) {
|
||||
imports.wbg.__wbg_set_17499e8aa4003ebd = function(arg0, arg1, arg2) {
|
||||
getObject(arg0).set(getObject(arg1), arg2 >>> 0);
|
||||
};
|
||||
imports.wbg.__wbg_length_9e1ae1900cb0fbd5 = function(arg0) {
|
||||
imports.wbg.__wbg_length_27a2afe8ab42b09f = function(arg0) {
|
||||
const ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_Uint8Array_971eeda69eb75003 = function(arg0) {
|
||||
imports.wbg.__wbg_instanceof_Uint8Array_01cebe79ca606cca = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = getObject(arg0) instanceof Uint8Array;
|
||||
|
@ -1124,14 +1124,14 @@ function getImports() {
|
|||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_set_bf3f89b92d5a34bf = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
imports.wbg.__wbg_set_6aa458a4ebdb65cb = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbindgen_bigint_get_as_i64 = function(arg0, arg1) {
|
||||
const v = getObject(arg1);
|
||||
const ret = typeof(v) === 'bigint' ? v : undefined;
|
||||
getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0n : ret;
|
||||
getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
|
||||
|
@ -1148,15 +1148,15 @@ function getImports() {
|
|||
const ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper4738 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1095, __wbg_adapter_48);
|
||||
imports.wbg.__wbindgen_closure_wrapper4739 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1096, __wbg_adapter_48);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper5605 = function(arg0, arg1, arg2) {
|
||||
imports.wbg.__wbindgen_closure_wrapper5606 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1436, __wbg_adapter_51);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper5683 = function(arg0, arg1, arg2) {
|
||||
imports.wbg.__wbindgen_closure_wrapper5684 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1466, __wbg_adapter_54);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
@ -1171,11 +1171,11 @@ function initMemory(imports, maybe_memory) {
|
|||
function finalizeInit(instance, module) {
|
||||
wasm = instance.exports;
|
||||
init.__wbindgen_wasm_module = module;
|
||||
cachedBigInt64Memory0 = new BigInt64Array();
|
||||
cachedFloat64Memory0 = new Float64Array();
|
||||
cachedInt32Memory0 = new Int32Array();
|
||||
cachedUint32Memory0 = new Uint32Array();
|
||||
cachedUint8Memory0 = new Uint8Array();
|
||||
cachedBigInt64Memory0 = null;
|
||||
cachedFloat64Memory0 = null;
|
||||
cachedInt32Memory0 = null;
|
||||
cachedUint32Memory0 = null;
|
||||
cachedUint8Memory0 = null;
|
||||
|
||||
|
||||
return wasm;
|
||||
|
|
Binary file not shown.
Loading…
Reference in a new issue