1115 store credential ids per session ()

This commit is contained in:
Firstyear 2023-02-19 13:32:47 +10:00 committed by GitHub
parent 87b43d0c14
commit cd5daf2f1c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 521 additions and 226 deletions

View file

@ -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)]

View file

@ -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.

View file

@ -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(),

View file

@ -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,

View file

@ -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,

View file

@ -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(),

View file

@ -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]

View file

@ -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(),

View file

@ -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,

View file

@ -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());
}
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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,

View file

@ -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;