Sync with Upstream (#5798)

* WIP Sync with Upstream

WIP on syncing API Responses with upstream.
This to prevent issues with new clients, and find possible current issues like members, collections, groups etc..

Signed-off-by: BlackDex <black.dex@gmail.com>

* More API Response fixes

- Some 2fa checks
- Some org checks
- Reconfigured the experimental flags and noted which are deprecated
  Also removed some hard-coded defaults.
- Updated crates

Signed-off-by: BlackDex <black.dex@gmail.com>

* Add avatar color to emergency access api

Signed-off-by: BlackDex <black.dex@gmail.com>

* Fix spelling and some crate updates

Signed-off-by: BlackDex <black.dex@gmail.com>

* Use PushId and always generate the PushId

Signed-off-by: BlackDex <black.dex@gmail.com>

* Fix clippy lints

Signed-off-by: BlackDex <black.dex@gmail.com>

* Fix several Push issues and API's

Signed-off-by: BlackDex <black.dex@gmail.com>

* Check if push_uuid is empty and generate when needed

Signed-off-by: BlackDex <black.dex@gmail.com>

* Updated some comments and removed old export format

Signed-off-by: BlackDex <black.dex@gmail.com>

* cargo update

Signed-off-by: BlackDex <black.dex@gmail.com>

* Fix bulk edit Fixes #5737

Signed-off-by: BlackDex <black.dex@gmail.com>

* Send an email when an account exists already

When you want to change your email address into an account which already exists, upstream sends an email to the existing account.
Lets do the same.

Kinda fixes #5630

Signed-off-by: BlackDex <black.dex@gmail.com>

* Update 2fa removal/revoke email

Signed-off-by: BlackDex <black.dex@gmail.com>

* Allow col managers to import

This commit adds functionality to allow users with manage access to a collection, or managers with all access to import into an organization.

Fixes #5592

Signed-off-by: BlackDex <black.dex@gmail.com>

* Filter deprected flags and only return active flags

Signed-off-by: BlackDex <black.dex@gmail.com>

* Fix grammer

Signed-off-by: BlackDex <black.dex@gmail.com>

* Rename Small to Compact

Signed-off-by: BlackDex <black.dex@gmail.com>

* Rebase with upstream and fix conflicts

Signed-off-by: BlackDex <black.dex@gmail.com>

---------

Signed-off-by: BlackDex <black.dex@gmail.com>
This commit is contained in:
Mathijs van Veluw
2025-05-26 21:00:59 +02:00
committed by GitHub
parent 3a44dc963b
commit ef7835d1b0
37 changed files with 636 additions and 371 deletions

View File

@@ -7,9 +7,9 @@ use tokio::sync::RwLock;
use crate::{
api::{ApiResult, EmptyResult, UpdateType},
db::models::{AuthRequestId, Cipher, Device, DeviceId, Folder, Send, User, UserId},
db::models::{AuthRequestId, Cipher, Device, DeviceId, Folder, PushId, Send, User, UserId},
http_client::make_http_request,
util::format_date,
util::{format_date, get_uuid},
CONFIG,
};
@@ -28,20 +28,20 @@ struct LocalAuthPushToken {
valid_until: Instant,
}
async fn get_auth_push_token() -> ApiResult<String> {
static PUSH_TOKEN: Lazy<RwLock<LocalAuthPushToken>> = Lazy::new(|| {
async fn get_auth_api_token() -> ApiResult<String> {
static API_TOKEN: Lazy<RwLock<LocalAuthPushToken>> = Lazy::new(|| {
RwLock::new(LocalAuthPushToken {
access_token: String::new(),
valid_until: Instant::now(),
})
});
let push_token = PUSH_TOKEN.read().await;
let api_token = API_TOKEN.read().await;
if push_token.valid_until.saturating_duration_since(Instant::now()).as_secs() > 0 {
if api_token.valid_until.saturating_duration_since(Instant::now()).as_secs() > 0 {
debug!("Auth Push token still valid, no need for a new one");
return Ok(push_token.access_token.clone());
return Ok(api_token.access_token.clone());
}
drop(push_token); // Drop the read lock now
drop(api_token); // Drop the read lock now
let installation_id = CONFIG.push_installation_id();
let client_id = format!("installation.{installation_id}");
@@ -68,44 +68,48 @@ async fn get_auth_push_token() -> ApiResult<String> {
Err(e) => err!(format!("Unexpected push token received from bitwarden server: {e}")),
};
let mut push_token = PUSH_TOKEN.write().await;
push_token.valid_until = Instant::now()
let mut api_token = API_TOKEN.write().await;
api_token.valid_until = Instant::now()
.checked_add(Duration::new((json_pushtoken.expires_in / 2) as u64, 0)) // Token valid for half the specified time
.unwrap();
push_token.access_token = json_pushtoken.access_token;
api_token.access_token = json_pushtoken.access_token;
debug!("Token still valid for {}", push_token.valid_until.saturating_duration_since(Instant::now()).as_secs());
Ok(push_token.access_token.clone())
debug!("Token still valid for {}", api_token.valid_until.saturating_duration_since(Instant::now()).as_secs());
Ok(api_token.access_token.clone())
}
pub async fn register_push_device(device: &mut Device, conn: &mut crate::db::DbConn) -> EmptyResult {
if !CONFIG.push_enabled() || !device.is_push_device() || device.is_registered() {
if !CONFIG.push_enabled() || !device.is_push_device() {
return Ok(());
}
if device.push_token.is_none() {
warn!("Skipping the registration of the device {} because the push_token field is empty.", device.uuid);
warn!("To get rid of this message you need to clear the app data and reconnect the device.");
warn!("Skipping the registration of the device {:?} because the push_token field is empty.", device.uuid);
warn!("To get rid of this message you need to logout, clear the app data and login again on the device.");
return Ok(());
}
debug!("Registering Device {}", device.uuid);
debug!("Registering Device {:?}", device.push_uuid);
// generate a random push_uuid so we know the device is registered
device.push_uuid = Some(uuid::Uuid::new_v4().to_string());
// Generate a random push_uuid so if it doesn't already have one
if device.push_uuid.is_none() {
device.push_uuid = Some(PushId(get_uuid()));
}
//Needed to register a device for push to bitwarden :
let data = json!({
"deviceId": device.push_uuid, // Unique UUID per user/device
"pushToken": device.push_token,
"userId": device.user_uuid,
"deviceId": device.push_uuid,
"identifier": device.uuid,
"type": device.atype,
"pushToken": device.push_token
"identifier": device.uuid, // Unique UUID of the device/app, determined by the device/app it self currently registering
// "organizationIds:" [] // TODO: This is not yet implemented by Vaultwarden!
"installationId": CONFIG.push_installation_id(),
});
let auth_push_token = get_auth_push_token().await?;
let auth_header = format!("Bearer {}", &auth_push_token);
let auth_api_token = get_auth_api_token().await?;
let auth_header = format!("Bearer {auth_api_token}");
if let Err(e) = make_http_request(Method::POST, &(CONFIG.push_relay_uri() + "/push/register"))?
.header(CONTENT_TYPE, "application/json")
@@ -126,18 +130,21 @@ pub async fn register_push_device(device: &mut Device, conn: &mut crate::db::DbC
Ok(())
}
pub async fn unregister_push_device(push_id: Option<String>) -> EmptyResult {
pub async fn unregister_push_device(push_id: &Option<PushId>) -> EmptyResult {
if !CONFIG.push_enabled() || push_id.is_none() {
return Ok(());
}
let auth_push_token = get_auth_push_token().await?;
let auth_api_token = get_auth_api_token().await?;
let auth_header = format!("Bearer {}", &auth_push_token);
let auth_header = format!("Bearer {auth_api_token}");
match make_http_request(Method::DELETE, &(CONFIG.push_relay_uri() + "/push/" + &push_id.unwrap()))?
.header(AUTHORIZATION, auth_header)
.send()
.await
match make_http_request(
Method::POST,
&format!("{}/push/delete/{}", CONFIG.push_relay_uri(), push_id.as_ref().unwrap()),
)?
.header(AUTHORIZATION, auth_header)
.send()
.await
{
Ok(r) => r,
Err(e) => err!(format!("An error occurred during device unregistration: {e}")),
@@ -145,12 +152,7 @@ pub async fn unregister_push_device(push_id: Option<String>) -> EmptyResult {
Ok(())
}
pub async fn push_cipher_update(
ut: UpdateType,
cipher: &Cipher,
acting_device_id: &DeviceId,
conn: &mut crate::db::DbConn,
) {
pub async fn push_cipher_update(ut: UpdateType, cipher: &Cipher, device: &Device, conn: &mut crate::db::DbConn) {
// We shouldn't send a push notification on cipher update if the cipher belongs to an organization, this isn't implemented in the upstream server too.
if cipher.organization_uuid.is_some() {
return;
@@ -163,87 +165,97 @@ pub async fn push_cipher_update(
if Device::check_user_has_push_device(user_id, conn).await {
send_to_push_relay(json!({
"userId": user_id,
"organizationId": (),
"deviceId": acting_device_id,
"identifier": acting_device_id,
"organizationId": null,
"deviceId": device.push_uuid, // Should be the records unique uuid of the acting device (unique uuid per user/device)
"identifier": device.uuid, // Should be the acting device id (aka uuid per device/app)
"type": ut as i32,
"payload": {
"Id": cipher.uuid,
"UserId": cipher.user_uuid,
"OrganizationId": (),
"RevisionDate": format_date(&cipher.updated_at)
}
"id": cipher.uuid,
"userId": cipher.user_uuid,
"organizationId": null,
"collectionIds": null,
"revisionDate": format_date(&cipher.updated_at)
},
"clientType": null,
"installationId": null
}))
.await;
}
}
pub fn push_logout(user: &User, acting_device_id: Option<DeviceId>) {
pub async fn push_logout(user: &User, acting_device_id: Option<DeviceId>, conn: &mut crate::db::DbConn) {
let acting_device_id: Value = acting_device_id.map(|v| v.to_string().into()).unwrap_or_else(|| Value::Null);
tokio::task::spawn(send_to_push_relay(json!({
"userId": user.uuid,
"organizationId": (),
"deviceId": acting_device_id,
"identifier": acting_device_id,
"type": UpdateType::LogOut as i32,
"payload": {
"UserId": user.uuid,
"Date": format_date(&user.updated_at)
}
})));
}
pub fn push_user_update(ut: UpdateType, user: &User) {
tokio::task::spawn(send_to_push_relay(json!({
"userId": user.uuid,
"organizationId": (),
"deviceId": (),
"identifier": (),
"type": ut as i32,
"payload": {
"UserId": user.uuid,
"Date": format_date(&user.updated_at)
}
})));
}
pub async fn push_folder_update(
ut: UpdateType,
folder: &Folder,
acting_device_id: &DeviceId,
conn: &mut crate::db::DbConn,
) {
if Device::check_user_has_push_device(&folder.user_uuid, conn).await {
if Device::check_user_has_push_device(&user.uuid, conn).await {
tokio::task::spawn(send_to_push_relay(json!({
"userId": folder.user_uuid,
"userId": user.uuid,
"organizationId": (),
"deviceId": acting_device_id,
"identifier": acting_device_id,
"type": ut as i32,
"type": UpdateType::LogOut as i32,
"payload": {
"Id": folder.uuid,
"UserId": folder.user_uuid,
"RevisionDate": format_date(&folder.updated_at)
}
"userId": user.uuid,
"date": format_date(&user.updated_at)
},
"clientType": null,
"installationId": null
})));
}
}
pub async fn push_send_update(ut: UpdateType, send: &Send, acting_device_id: &DeviceId, conn: &mut crate::db::DbConn) {
pub async fn push_user_update(ut: UpdateType, user: &User, push_uuid: &Option<PushId>, conn: &mut crate::db::DbConn) {
if Device::check_user_has_push_device(&user.uuid, conn).await {
tokio::task::spawn(send_to_push_relay(json!({
"userId": user.uuid,
"organizationId": null,
"deviceId": push_uuid,
"identifier": null,
"type": ut as i32,
"payload": {
"userId": user.uuid,
"date": format_date(&user.updated_at)
},
"clientType": null,
"installationId": null
})));
}
}
pub async fn push_folder_update(ut: UpdateType, folder: &Folder, device: &Device, conn: &mut crate::db::DbConn) {
if Device::check_user_has_push_device(&folder.user_uuid, conn).await {
tokio::task::spawn(send_to_push_relay(json!({
"userId": folder.user_uuid,
"organizationId": null,
"deviceId": device.push_uuid, // Should be the records unique uuid of the acting device (unique uuid per user/device)
"identifier": device.uuid, // Should be the acting device id (aka uuid per device/app)
"type": ut as i32,
"payload": {
"id": folder.uuid,
"userId": folder.user_uuid,
"revisionDate": format_date(&folder.updated_at)
},
"clientType": null,
"installationId": null
})));
}
}
pub async fn push_send_update(ut: UpdateType, send: &Send, device: &Device, conn: &mut crate::db::DbConn) {
if let Some(s) = &send.user_uuid {
if Device::check_user_has_push_device(s, conn).await {
tokio::task::spawn(send_to_push_relay(json!({
"userId": send.user_uuid,
"organizationId": (),
"deviceId": acting_device_id,
"identifier": acting_device_id,
"organizationId": null,
"deviceId": device.push_uuid, // Should be the records unique uuid of the acting device (unique uuid per user/device)
"identifier": device.uuid, // Should be the acting device id (aka uuid per device/app)
"type": ut as i32,
"payload": {
"Id": send.uuid,
"UserId": send.user_uuid,
"RevisionDate": format_date(&send.revision_date)
}
"id": send.uuid,
"userId": send.user_uuid,
"revisionDate": format_date(&send.revision_date)
},
"clientType": null,
"installationId": null
})));
}
}
@@ -254,7 +266,7 @@ async fn send_to_push_relay(notification_data: Value) {
return;
}
let auth_push_token = match get_auth_push_token().await {
let auth_api_token = match get_auth_api_token().await {
Ok(s) => s,
Err(e) => {
debug!("Could not get the auth push token: {e}");
@@ -262,7 +274,7 @@ async fn send_to_push_relay(notification_data: Value) {
}
};
let auth_header = format!("Bearer {}", &auth_push_token);
let auth_header = format!("Bearer {auth_api_token}");
let req = match make_http_request(Method::POST, &(CONFIG.push_relay_uri() + "/push/send")) {
Ok(r) => r,
@@ -284,18 +296,20 @@ async fn send_to_push_relay(notification_data: Value) {
};
}
pub async fn push_auth_request(user_id: UserId, auth_request_id: String, conn: &mut crate::db::DbConn) {
if Device::check_user_has_push_device(&user_id, conn).await {
pub async fn push_auth_request(user_id: &UserId, auth_request_id: &str, device: &Device, conn: &mut crate::db::DbConn) {
if Device::check_user_has_push_device(user_id, conn).await {
tokio::task::spawn(send_to_push_relay(json!({
"userId": user_id,
"organizationId": (),
"deviceId": null,
"identifier": null,
"organizationId": null,
"deviceId": device.push_uuid, // Should be the records unique uuid of the acting device (unique uuid per user/device)
"identifier": device.uuid, // Should be the acting device id (aka uuid per device/app)
"type": UpdateType::AuthRequest as i32,
"payload": {
"Id": auth_request_id,
"UserId": user_id,
}
"userId": user_id,
"id": auth_request_id,
},
"clientType": null,
"installationId": null
})));
}
}
@@ -303,20 +317,22 @@ pub async fn push_auth_request(user_id: UserId, auth_request_id: String, conn: &
pub async fn push_auth_response(
user_id: &UserId,
auth_request_id: &AuthRequestId,
approving_device_id: &DeviceId,
device: &Device,
conn: &mut crate::db::DbConn,
) {
if Device::check_user_has_push_device(user_id, conn).await {
tokio::task::spawn(send_to_push_relay(json!({
"userId": user_id,
"organizationId": (),
"deviceId": approving_device_id,
"identifier": approving_device_id,
"organizationId": null,
"deviceId": device.push_uuid, // Should be the records unique uuid of the acting device (unique uuid per user/device)
"identifier": device.uuid, // Should be the acting device id (aka uuid per device/app)
"type": UpdateType::AuthRequestResponse as i32,
"payload": {
"Id": auth_request_id,
"UserId": user_id,
}
"userId": user_id,
"id": auth_request_id,
},
"clientType": null,
"installationId": null
})));
}
}