Compare commits

...

6 Commits

Author SHA1 Message Date
Stefan Melmuk
a85b48512c add seat limit for the invite dialog (#6371) 2025-10-21 20:23:46 +02:00
Stefan Melmuk
fe1a8f7738 add missing media-src directive (#6381) 2025-10-21 19:22:37 +02:00
Stefan Melmuk
d43edb8f17 add mail address change warning for invited accounts (#6377)
add a new mail template to differentiate between existing accounts and
only invited accounts, so that they can easier delete the
existing placeholder account
2025-10-21 19:21:47 +02:00
Timshel
8043f7eca7 Fix Org identifier (#6364)
* Fix Org identifier

* Org invitation default to SSO when SSO_ENABLED
2025-10-21 19:20:18 +02:00
Timshel
e659a61581 Add auth_request pending endpoint (#6368) 2025-10-21 19:17:52 +02:00
Stefan Melmuk
2d54cc61df add new billing warnings endpoint (#6369) 2025-10-21 19:16:27 +02:00
8 changed files with 98 additions and 10 deletions

View File

@@ -64,6 +64,7 @@ pub fn routes() -> Vec<rocket::Route> {
put_auth_request,
get_auth_request_response,
get_auth_requests,
get_auth_requests_pending,
]
}
@@ -909,10 +910,20 @@ async fn post_email_token(data: Json<EmailTokenData>, headers: Headers, mut conn
err!("Invalid password")
}
if User::find_by_mail(&data.new_email, &mut conn).await.is_some() {
if let Some(existing_user) = User::find_by_mail(&data.new_email, &mut conn).await {
if CONFIG.mail_enabled() {
if let Err(e) = mail::send_change_email_existing(&data.new_email, &user.email).await {
error!("Error sending change-email-existing email: {e:#?}");
// check if existing_user has already registered
if existing_user.password_hash.is_empty() {
// inform an invited user about how to delete their temporary account if the
// request was done intentionally and they want to update their mail address
if let Err(e) = mail::send_change_email_invited(&data.new_email, &user.email).await {
error!("Error sending change-email-invited email: {e:#?}");
}
} else {
// inform existing user about the failed attempt to change their mail address
if let Err(e) = mail::send_change_email_existing(&data.new_email, &user.email).await {
error!("Error sending change-email-existing email: {e:#?}");
}
}
}
err!("Email already in use");
@@ -1605,8 +1616,15 @@ async fn get_auth_request_response(
})))
}
// Now unused but not yet removed
// cf https://github.com/bitwarden/clients/blob/9b2fbdba1c028bf3394064609630d2ec224baefa/libs/common/src/services/api.service.ts#L245
#[get("/auth-requests")]
async fn get_auth_requests(headers: Headers, mut conn: DbConn) -> JsonResult {
async fn get_auth_requests(headers: Headers, conn: DbConn) -> JsonResult {
get_auth_requests_pending(headers, conn).await
}
#[get("/auth-requests/pending")]
async fn get_auth_requests_pending(headers: Headers, mut conn: DbConn) -> JsonResult {
let auth_requests = AuthRequest::find_by_user(&headers.user.uuid, &mut conn).await;
Ok(Json(json!({

View File

@@ -105,6 +105,7 @@ pub fn routes() -> Vec<Route> {
api_key,
rotate_api_key,
get_billing_metadata,
get_billing_warnings,
get_auto_enroll_status,
]
}
@@ -354,9 +355,11 @@ async fn get_auto_enroll_status(identifier: &str, headers: Headers, mut conn: Db
let (id, identifier, rp_auto_enroll) = match org {
None => (get_uuid(), identifier.to_string(), false),
Some(org) => {
(org.uuid.to_string(), org.name, OrgPolicy::org_is_reset_password_auto_enroll(&org.uuid, &mut conn).await)
}
Some(org) => (
org.uuid.to_string(),
org.uuid.to_string(),
OrgPolicy::org_is_reset_password_auto_enroll(&org.uuid, &mut conn).await,
),
};
Ok(Json(json!({
@@ -2273,6 +2276,16 @@ fn get_billing_metadata(_org_id: OrganizationId, _headers: Headers) -> Json<Valu
Json(_empty_data_json())
}
#[get("/organizations/<_org_id>/billing/vnext/warnings")]
fn get_billing_warnings(_org_id: OrganizationId, _headers: Headers) -> Json<Value> {
Json(json!({
"freeTrial":null,
"inactiveSubscription":null,
"resellerRenewal":null,
"taxId":null,
}))
}
fn _empty_data_json() -> Value {
json!({
"object": "list",

View File

@@ -1643,6 +1643,7 @@ where
reg!("email/admin_reset_password", ".html");
reg!("email/change_email_existing", ".html");
reg!("email/change_email_invited", ".html");
reg!("email/change_email", ".html");
reg!("email/delete_account", ".html");
reg!("email/emergency_access_invite_accepted", ".html");

View File

@@ -473,7 +473,7 @@ impl Membership {
"id": self.org_uuid,
"identifier": null, // Not supported
"name": org.name,
"seats": null,
"seats": 20, // hardcoded maxEmailsCount in the web-vault
"maxCollections": null,
"usersGetPremium": true,
"use2fa": true,

View File

@@ -302,9 +302,9 @@ pub async fn send_invite(
.append_pair("organizationUserId", &member_id)
.append_pair("token", &invite_token);
if CONFIG.sso_enabled() && CONFIG.sso_only() {
if CONFIG.sso_enabled() {
query_params.append_pair("orgUserHasExistingUser", "false");
query_params.append_pair("orgSsoIdentifier", org_name);
query_params.append_pair("orgSsoIdentifier", &org_id);
} else if user.private_key.is_some() {
query_params.append_pair("orgUserHasExistingUser", "true");
}
@@ -588,6 +588,20 @@ pub async fn send_change_email_existing(address: &str, acting_address: &str) ->
send_email(address, &subject, body_html, body_text).await
}
pub async fn send_change_email_invited(address: &str, acting_address: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/change_email_invited",
json!({
"url": CONFIG.domain(),
"img_src": CONFIG._smtp_img_src(),
"existing_address": address,
"acting_address": acting_address,
}),
)?;
send_email(address, &subject, body_html, body_text).await
}
pub async fn send_sso_change_email(address: &str) -> EmptyResult {
let (subject, body_html, body_text) = get_text(
"email/sso_change_email",

View File

@@ -0,0 +1,11 @@
Your Email Change
<!---------------->
A user ({{ acting_address }}) recently tried to change their account to use this email address ({{ existing_address }}). You already have been invited to join Vaultwarden using this address.
To change your email address you first would have to delete the account associated with this email address ({{ existing_address }}):
Request account deletion: {{url}}/#/recover-delete
Once that is done you can change the email address of your existing account to this address. Any invitation would have to be redone.
If you did not try to change an email address, contact your administrator.
{{> email/email_footer_text }}

View File

@@ -0,0 +1,30 @@
Your Email Change
<!---------------->
{{> email/email_header }}
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
A user ({{ acting_address }}) recently tried to change their account to use this email address ({{ existing_address }}). You already have been invited to join Vaultwarden using this address.
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
To change your email address you first would have to delete the account associated with this email address ({{ existing_address }}):
<a data-testid="recover-delete" href="{{url}}/#/recover-delete"
clicktracking=off target="_blank" style="color: #ffffff; text-decoration: none; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background-color: #3c8dbc; border-color: #3c8dbc; border-style: solid; border-width: 10px 20px; margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
Request account deletion
</a>
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
Once that is done you can change the email address of your existing account to this address. Any invitation would have to be redone.
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
If you did not try to change an email address, contact your administrator.
</td>
</tr>
</table>
{{> email/email_footer }}

View File

@@ -95,6 +95,7 @@ impl Fairing for AppHeaders {
manifest-src 'self'; \
base-uri 'self'; \
form-action 'self'; \
media-src 'self'; \
object-src 'self' blob:; \
script-src 'self' 'wasm-unsafe-eval'; \
style-src 'self' 'unsafe-inline'; \