mirror of
https://github.com/dani-garcia/vaultwarden.wiki.git
synced 2026-10-30 23:46:47 +02:00
Send logout notification when deleting a user (#7839)
This commit is contained in:
1 parent
f1c7eccd4a
commit
e056359a42
5 files changed
+63
-27
No files matched your search
+11
-3
@@ -17,7 +17,7 @@ use crate::{
|
|||||||
api::{
|
api::{
|
||||||
ApiResult, EmptyResult, JsonResult, Notify,
|
ApiResult, EmptyResult, JsonResult, Notify,
|
||||||
core::{log_event, two_factor},
|
core::{log_event, two_factor},
|
||||||
unregister_push_device,
|
push_logout_to_relay, unregister_push_device,
|
||||||
},
|
},
|
||||||
auth::{ClientIp, Secure, decode_admin, encode_jwt, generate_admin_claims},
|
auth::{ClientIp, Secure, decode_admin, encode_jwt, generate_admin_claims},
|
||||||
config::ConfigBuilder,
|
config::ConfigBuilder,
|
||||||
@@ -419,13 +419,21 @@ async fn get_user_json(user_id: UserId, _token: AdminToken, conn: DbConn) -> Jso
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/users/<user_id>/delete", format = "application/json")]
|
#[post("/users/<user_id>/delete", format = "application/json")]
|
||||||
async fn delete_user(user_id: UserId, token: AdminToken, conn: DbConn) -> EmptyResult {
|
async fn delete_user(user_id: UserId, token: AdminToken, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
let user = get_user_or_404(&user_id, &conn).await?;
|
let user = get_user_or_404(&user_id, &conn).await?;
|
||||||
|
|
||||||
// Get the membership records before deleting the actual user
|
// Get the membership records and push devices before deleting the actual user
|
||||||
let memberships = Membership::find_any_state_by_user(&user_id, &conn).await;
|
let memberships = Membership::find_any_state_by_user(&user_id, &conn).await;
|
||||||
|
let has_push_device = CONFIG.push_enabled() && Device::check_user_has_push_device(&user_id, &conn).await;
|
||||||
let res = user.delete(&conn).await;
|
let res = user.delete(&conn).await;
|
||||||
|
|
||||||
|
if res.is_ok() {
|
||||||
|
nt.send_logout(&user, None, &conn).await;
|
||||||
|
if has_push_device {
|
||||||
|
push_logout_to_relay(&user, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for membership in memberships {
|
for membership in memberships {
|
||||||
log_event(
|
log_event(
|
||||||
EventType::OrganizationUserDeleted,
|
EventType::OrganizationUserDeleted,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::{
|
|||||||
api::{
|
api::{
|
||||||
AnonymousNotify, ApiResult, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType,
|
AnonymousNotify, ApiResult, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType,
|
||||||
core::{accept_org_invite, log_user_event, two_factor::email},
|
core::{accept_org_invite, log_user_event, two_factor::email},
|
||||||
master_password_policy, register_push_device, unregister_push_device,
|
master_password_policy, push_logout_to_relay, register_push_device, unregister_push_device,
|
||||||
},
|
},
|
||||||
auth::{ClientHeaders, ClientIp, Headers, decode_delete, decode_invite, decode_verify_email},
|
auth::{ClientHeaders, ClientIp, Headers, decode_delete, decode_invite, decode_verify_email},
|
||||||
crypto,
|
crypto,
|
||||||
@@ -1301,7 +1301,7 @@ struct DeleteRecoverTokenData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/accounts/delete-recover-token", data = "<data>")]
|
#[post("/accounts/delete-recover-token", data = "<data>")]
|
||||||
async fn post_delete_recover_token(data: Json<DeleteRecoverTokenData>, conn: DbConn) -> EmptyResult {
|
async fn post_delete_recover_token(data: Json<DeleteRecoverTokenData>, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
let data: DeleteRecoverTokenData = data.into_inner();
|
let data: DeleteRecoverTokenData = data.into_inner();
|
||||||
|
|
||||||
let Ok(claims) = decode_delete(&data.token) else {
|
let Ok(claims) = decode_delete(&data.token) else {
|
||||||
@@ -1315,22 +1315,44 @@ async fn post_delete_recover_token(data: Json<DeleteRecoverTokenData>, conn: DbC
|
|||||||
if claims.sub != *user.uuid {
|
if claims.sub != *user.uuid {
|
||||||
err!("Invalid claim");
|
err!("Invalid claim");
|
||||||
}
|
}
|
||||||
user.delete(&conn).await
|
|
||||||
|
// The devices are deleted together with the user, so check for push devices beforehand
|
||||||
|
let has_push_device = CONFIG.push_enabled() && Device::check_user_has_push_device(&user.uuid, &conn).await;
|
||||||
|
user.delete(&conn).await?;
|
||||||
|
|
||||||
|
nt.send_logout(&user, None, &conn).await;
|
||||||
|
if has_push_device {
|
||||||
|
push_logout_to_relay(&user, None);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/accounts/delete", data = "<data>")]
|
#[post("/accounts/delete", data = "<data>")]
|
||||||
async fn post_delete_account(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> EmptyResult {
|
async fn post_delete_account(
|
||||||
delete_account(data, headers, conn).await
|
data: Json<PasswordOrOtpData>,
|
||||||
|
headers: Headers,
|
||||||
|
conn: DbConn,
|
||||||
|
nt: Notify<'_>,
|
||||||
|
) -> EmptyResult {
|
||||||
|
delete_account(data, headers, conn, nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/accounts", data = "<data>")]
|
#[delete("/accounts", data = "<data>")]
|
||||||
async fn delete_account(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> EmptyResult {
|
async fn delete_account(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
let data: PasswordOrOtpData = data.into_inner();
|
let data: PasswordOrOtpData = data.into_inner();
|
||||||
let user = headers.user;
|
let user = headers.user;
|
||||||
|
|
||||||
data.validate(&user, true, &conn).await?;
|
data.validate(&user, true, &conn).await?;
|
||||||
|
|
||||||
user.delete(&conn).await
|
// The devices are deleted together with the user, so check for push devices beforehand
|
||||||
|
let has_push_device = CONFIG.push_enabled() && Device::check_user_has_push_device(&user.uuid, &conn).await;
|
||||||
|
user.delete(&conn).await?;
|
||||||
|
|
||||||
|
nt.send_logout(&user, None, &conn).await;
|
||||||
|
if has_push_device {
|
||||||
|
push_logout_to_relay(&user, None);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(clippy::needless_pass_by_value, reason = "Not beneficial for Headers")]
|
#[expect(clippy::needless_pass_by_value, reason = "Not beneficial for Headers")]
|
||||||
|
|||||||
+2
-2
@@ -25,8 +25,8 @@ pub use crate::api::{
|
|||||||
notifications::routes as notifications_routes,
|
notifications::routes as notifications_routes,
|
||||||
notifications::{AnonymousNotify, Notify, UpdateType, WS_ANONYMOUS_SUBSCRIPTIONS, WS_USERS},
|
notifications::{AnonymousNotify, Notify, UpdateType, WS_ANONYMOUS_SUBSCRIPTIONS, WS_USERS},
|
||||||
push::{
|
push::{
|
||||||
push_cipher_update, push_folder_update, push_logout, push_send_update, push_user_update, register_push_device,
|
push_cipher_update, push_folder_update, push_logout, push_logout_to_relay, push_send_update, push_user_update,
|
||||||
unregister_push_device,
|
register_push_device, unregister_push_device,
|
||||||
},
|
},
|
||||||
web::catchers as web_catchers,
|
web::catchers as web_catchers,
|
||||||
web::routes as web_routes,
|
web::routes as web_routes,
|
||||||
|
|||||||
+19
-13
@@ -194,22 +194,28 @@ pub async fn push_cipher_update(ut: UpdateType, cipher: &Cipher, device: &Device
|
|||||||
|
|
||||||
pub async fn push_logout(user: &User, acting_device: Option<&Device>, conn: &DbConn) {
|
pub async fn push_logout(user: &User, acting_device: Option<&Device>, conn: &DbConn) {
|
||||||
if Device::check_user_has_push_device(&user.uuid, conn).await {
|
if Device::check_user_has_push_device(&user.uuid, conn).await {
|
||||||
tokio::task::spawn(send_to_push_relay(json!({
|
push_logout_to_relay(user, acting_device);
|
||||||
"userId": user.uuid,
|
|
||||||
"organizationId": (),
|
|
||||||
"deviceId": acting_device.and_then(|d| d.push_uuid.as_ref()),
|
|
||||||
"identifier": acting_device.map(|d| &d.uuid),
|
|
||||||
"type": UpdateType::LogOut as i32,
|
|
||||||
"payload": {
|
|
||||||
"userId": user.uuid,
|
|
||||||
"date": format_date(&user.updated_at)
|
|
||||||
},
|
|
||||||
"clientType": null,
|
|
||||||
"installationId": null
|
|
||||||
})));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sends the logout without checking for push devices first.
|
||||||
|
// Needed after deleting a user, when the devices are already gone from the database.
|
||||||
|
pub fn push_logout_to_relay(user: &User, acting_device: Option<&Device>) {
|
||||||
|
tokio::task::spawn(send_to_push_relay(json!({
|
||||||
|
"userId": user.uuid,
|
||||||
|
"organizationId": (),
|
||||||
|
"deviceId": acting_device.and_then(|d| d.push_uuid.as_ref()),
|
||||||
|
"identifier": acting_device.map(|d| &d.uuid),
|
||||||
|
"type": UpdateType::LogOut as i32,
|
||||||
|
"payload": {
|
||||||
|
"userId": user.uuid,
|
||||||
|
"date": format_date(&user.updated_at)
|
||||||
|
},
|
||||||
|
"clientType": null,
|
||||||
|
"installationId": null
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn push_user_update(ut: UpdateType, user: &User, push_uuid: Option<&PushId>, conn: &DbConn) {
|
pub async fn push_user_update(ut: UpdateType, user: &User, push_uuid: Option<&PushId>, conn: &DbConn) {
|
||||||
if Device::check_user_has_push_device(&user.uuid, conn).await {
|
if Device::check_user_has_push_device(&user.uuid, conn).await {
|
||||||
tokio::task::spawn(send_to_push_relay(json!({
|
tokio::task::spawn(send_to_push_relay(json!({
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ impl User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(self, conn: &DbConn) -> EmptyResult {
|
pub async fn delete(&self, conn: &DbConn) -> EmptyResult {
|
||||||
for member in Membership::find_confirmed_by_user(&self.uuid, conn).await {
|
for member in Membership::find_confirmed_by_user(&self.uuid, conn).await {
|
||||||
if member.atype == MembershipType::Owner
|
if member.atype == MembershipType::Owner
|
||||||
&& Membership::count_confirmed_by_org_and_type(&member.org_uuid, MembershipType::Owner, conn).await <= 1
|
&& Membership::count_confirmed_by_org_and_type(&member.org_uuid, MembershipType::Owner, conn).await <= 1
|
||||||
@@ -385,7 +385,7 @@ impl User {
|
|||||||
Invitation::take(&self.email, conn).await; // Delete invitation if any
|
Invitation::take(&self.email, conn).await; // Delete invitation if any
|
||||||
|
|
||||||
conn.run(move |conn| {
|
conn.run(move |conn| {
|
||||||
diesel::delete(users::table.filter(users::uuid.eq(self.uuid))).execute(conn).map_res("Error deleting user")
|
diesel::delete(users::table.filter(users::uuid.eq(&self.uuid))).execute(conn).map_res("Error deleting user")
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user