You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
83 lines
3.2 KiB
83 lines
3.2 KiB
//! `UserDecryptionOptions` (login) and `userDecryption` (sync) payloads for Bitwarden-compatible clients.
|
|
//!
|
|
//! References: Bitwarden `UserDecryptionOptionsBuilder`, `TrustedDeviceUserDecryptionOption`, and
|
|
//! `libs/common/.../user-decryption-options.response.ts` in bitwarden/clients.
|
|
|
|
use serde_json::{Value, json};
|
|
|
|
use crate::CONFIG;
|
|
use crate::db::DbConn;
|
|
use crate::db::models::{Device, Membership, SsoUser, User};
|
|
|
|
pub async fn build_sync_user_decryption(user: &User, device: &Device, conn: &DbConn) -> Value {
|
|
let with_trusted =
|
|
CONFIG.sso_enabled() && (CONFIG.sso_only() || SsoUser::find_by_user(&user.uuid, conn).await.is_some());
|
|
build_token_user_decryption_options(user, device, with_trusted, conn).await
|
|
}
|
|
|
|
// Bitwarden only builds trusted-device options when SSO Identity context exists (authorization_code grant).
|
|
// Do not return the Trusted information if there is no master password (otherwise onboarding does not allow setting one)
|
|
pub async fn build_token_user_decryption_options(
|
|
user: &User,
|
|
device: &Device,
|
|
with_trusted: bool,
|
|
conn: &DbConn,
|
|
) -> Value {
|
|
let has_master_password = !user.password_hash.is_empty();
|
|
let master_password_unlock = if has_master_password {
|
|
json!({
|
|
"kdf": {
|
|
"kdfType": user.client_kdf_type,
|
|
"iterations": user.client_kdf_iter,
|
|
"memory": user.client_kdf_memory,
|
|
"parallelism": user.client_kdf_parallelism
|
|
},
|
|
"masterKeyEncryptedUserKey": user.akey,
|
|
"masterKeyWrappedUserKey": user.akey,
|
|
"salt": user.email
|
|
})
|
|
} else {
|
|
Value::Null
|
|
};
|
|
|
|
let mut out = json!({
|
|
"hasMasterPassword": has_master_password,
|
|
"masterPasswordUnlock": master_password_unlock,
|
|
"userKeyId": user.key_id,
|
|
"object": "userDecryptionOptions"
|
|
});
|
|
|
|
if with_trusted && CONFIG.sso_trusted_device_encryption() && has_master_password {
|
|
let mut trusted = json!({
|
|
"hasAdminApproval": false,
|
|
"hasLoginApprovingDevice": has_login_approving_device(user, device, conn).await,
|
|
"hasManageResetPasswordPermission": is_owner_admin(user, conn).await,
|
|
"isTdeOffboarding": false,
|
|
});
|
|
|
|
if let Some(key) = device.encrypted_user_key.as_ref() {
|
|
trusted["encryptedUserKey"] = json!(key);
|
|
trusted["EncryptedUserKey"] = json!(key);
|
|
}
|
|
|
|
if let Some(key) = device.encrypted_private_key.as_ref() {
|
|
trusted["encryptedPrivateKey"] = json!(key);
|
|
trusted["EncryptedPrivateKey"] = json!(key);
|
|
}
|
|
|
|
out["trustedDeviceOption"] = trusted.clone();
|
|
out["TrustedDeviceOption"] = trusted;
|
|
}
|
|
|
|
out
|
|
}
|
|
|
|
// Details on trusted settings:
|
|
// https://github.com/bitwarden/clients/blob/web-v2026.4.2/libs/auth/src/common/models/domain/user-decryption-options.ts#L114
|
|
async fn is_owner_admin(user: &User, conn: &DbConn) -> bool {
|
|
Membership::find_confirmed_by_user(&user.uuid, conn).await.iter().any(|m| m.is_owner() || m.is_admin())
|
|
}
|
|
|
|
async fn has_login_approving_device(user: &User, device: &Device, conn: &DbConn) -> bool {
|
|
Device::find_by_user(&user.uuid, conn).await.iter().any(|d| d.uuid != device.uuid && d.can_approve_trusted_login())
|
|
}
|
|
|