| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- use reqwest::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE};
- use serde_json::Value;
- use tokio::sync::RwLock;
- use crate::{
- api::{ApiResult, EmptyResult, UpdateType},
- db::models::{Cipher, Device, Folder, Send, User},
- util::get_reqwest_client,
- CONFIG,
- };
- use once_cell::sync::Lazy;
- use std::time::{Duration, Instant};
- #[derive(Deserialize)]
- struct AuthPushToken {
- access_token: String,
- expires_in: i32,
- }
- #[derive(Debug)]
- struct LocalAuthPushToken {
- access_token: String,
- valid_until: Instant,
- }
- async fn get_auth_push_token() -> ApiResult<String> {
- static PUSH_TOKEN: Lazy<RwLock<LocalAuthPushToken>> = Lazy::new(|| {
- RwLock::new(LocalAuthPushToken {
- access_token: String::new(),
- valid_until: Instant::now(),
- })
- });
- let push_token = PUSH_TOKEN.read().await;
- if push_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());
- }
- drop(push_token); // Drop the read lock now
- let installation_id = CONFIG.push_installation_id();
- let client_id = format!("installation.{installation_id}");
- let client_secret = CONFIG.push_installation_key();
- let params = [
- ("grant_type", "client_credentials"),
- ("scope", "api.push"),
- ("client_id", &client_id),
- ("client_secret", &client_secret),
- ];
- let res = match get_reqwest_client().post("https://identity.bitwarden.com/connect/token").form(¶ms).send().await
- {
- Ok(r) => r,
- Err(e) => err!(format!("Error getting push token from bitwarden server: {e}")),
- };
- let json_pushtoken = match res.json::<AuthPushToken>().await {
- Ok(r) => r,
- 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()
- .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;
- debug!("Token still valid for {}", push_token.valid_until.saturating_duration_since(Instant::now()).as_secs());
- Ok(push_token.access_token.clone())
- }
- pub async fn register_push_device(user_uuid: String, device: Device) -> EmptyResult {
- if !CONFIG.push_enabled() {
- return Ok(());
- }
- let auth_push_token = get_auth_push_token().await?;
- //Needed to register a device for push to bitwarden :
- let data = json!({
- "userId": user_uuid,
- "deviceId": device.push_uuid,
- "identifier": device.uuid,
- "type": device.atype,
- "pushToken": device.push_token
- });
- let auth_header = format!("Bearer {}", &auth_push_token);
- get_reqwest_client()
- .post(CONFIG.push_relay_uri() + "/push/register")
- .header(CONTENT_TYPE, "application/json")
- .header(ACCEPT, "application/json")
- .header(AUTHORIZATION, auth_header)
- .json(&data)
- .send()
- .await?
- .error_for_status()?;
- Ok(())
- }
- pub async fn unregister_push_device(uuid: String) -> EmptyResult {
- if !CONFIG.push_enabled() {
- return Ok(());
- }
- let auth_push_token = get_auth_push_token().await?;
- let auth_header = format!("Bearer {}", &auth_push_token);
- match get_reqwest_client()
- .delete(CONFIG.push_relay_uri() + "/push/" + &uuid)
- .header(AUTHORIZATION, auth_header)
- .send()
- .await
- {
- Ok(r) => r,
- Err(e) => err!(format!("An error occured during device unregistration: {e}")),
- };
- Ok(())
- }
- pub async fn push_cipher_update(
- ut: UpdateType,
- cipher: &Cipher,
- acting_device_uuid: &String,
- 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;
- };
- let user_uuid = match &cipher.user_uuid {
- Some(c) => c,
- None => {
- debug!("Cipher has no uuid");
- return;
- }
- };
- for device in Device::find_by_user(user_uuid, conn).await {
- let data = json!({
- "userId": user_uuid,
- "organizationId": (),
- "deviceId": device.push_uuid,
- "identifier": acting_device_uuid,
- "type": ut as i32,
- "payload": {
- "Id": cipher.uuid,
- "UserId": cipher.user_uuid,
- "OrganizationId": (),
- "RevisionDate": cipher.updated_at
- }
- });
- send_to_push_relay(data).await;
- }
- }
- pub async fn push_logout(user: &User, acting_device_uuid: Option<String>, conn: &mut crate::db::DbConn) {
- if let Some(d) = acting_device_uuid {
- for device in Device::find_by_user(&user.uuid, conn).await {
- let data = json!({
- "userId": user.uuid,
- "organizationId": (),
- "deviceId": device.push_uuid,
- "identifier": d,
- "type": UpdateType::LogOut as i32,
- "payload": {
- "UserId": user.uuid,
- "Date": user.updated_at
- }
- });
- send_to_push_relay(data).await;
- }
- } else {
- let data = json!({
- "userId": user.uuid,
- "organizationId": (),
- "deviceId": (),
- "identifier": (),
- "type": UpdateType::LogOut as i32,
- "payload": {
- "UserId": user.uuid,
- "Date": user.updated_at
- }
- });
- send_to_push_relay(data).await;
- }
- }
- pub async fn push_user_update(ut: UpdateType, user: &User) {
- let data = json!({
- "userId": user.uuid,
- "organizationId": (),
- "deviceId": (),
- "identifier": (),
- "type": ut as i32,
- "payload": {
- "UserId": user.uuid,
- "Date": user.updated_at
- }
- });
- send_to_push_relay(data).await;
- }
- pub async fn push_folder_update(
- ut: UpdateType,
- folder: &Folder,
- acting_device_uuid: &String,
- conn: &mut crate::db::DbConn,
- ) {
- for device in Device::find_by_user(&folder.user_uuid, conn).await {
- let data = json!({
- "userId": folder.user_uuid,
- "organizationId": (),
- "deviceId": device.push_uuid,
- "identifier": acting_device_uuid,
- "type": ut as i32,
- "payload": {
- "Id": folder.uuid,
- "UserId": folder.user_uuid,
- "RevisionDate": folder.updated_at
- }
- });
- send_to_push_relay(data).await;
- }
- }
- pub async fn push_send_update(ut: UpdateType, send: &Send, conn: &mut crate::db::DbConn) {
- if let Some(s) = &send.user_uuid {
- for device in Device::find_by_user(s, conn).await {
- let data = json!({
- "userId": send.user_uuid,
- "organizationId": (),
- "deviceId": device.push_uuid,
- "identifier": (),
- "type": ut as i32,
- "payload": {
- "Id": send.uuid,
- "UserId": send.user_uuid,
- "RevisionDate": send.revision_date
- }
- });
- send_to_push_relay(data).await;
- }
- }
- }
- async fn send_to_push_relay(data: Value) {
- if !CONFIG.push_enabled() {
- return;
- }
- let auth_push_token = match get_auth_push_token().await {
- Ok(s) => s,
- Err(e) => {
- debug!("Could not get the auth push token: {}", e);
- return;
- }
- };
- let auth_header = format!("Bearer {}", &auth_push_token);
- if let Err(e) = get_reqwest_client()
- .post(CONFIG.push_relay_uri() + "/push/send")
- .header(ACCEPT, "application/json")
- .header(CONTENT_TYPE, "application/json")
- .header(AUTHORIZATION, auth_header)
- .json(&data)
- .send()
- .await
- {
- error!("An error occured while sending a send update to the push relay: {}", e);
- };
- }
|