1
0
Fork 1
Spiegel von https://github.com/dani-garcia/vaultwarden.git synchronisiert 2025-03-03 15:17:02 +01:00

Optional name, emergency access, and signups_allowed

Dieser Commit ist enthalten in:
Daniel García 2025-02-05 23:46:48 +01:00
Ursprung 19d4620db0
Commit efc82da5f6
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: FC8A7D14C3CD543A
5 geänderte Dateien mit 114 neuen und 30 gelöschten Zeilen

Datei anzeigen

@ -86,13 +86,15 @@ pub struct RegisterData {
name: Option<String>,
#[serde(alias = "orgInviteToken")]
token: Option<String>,
#[allow(dead_code)]
organization_user_id: Option<MembershipId>,
// Used only from the register/finish endpoint
email_verification_token: Option<String>,
accept_emergency_access_id: Option<EmergencyAccessId>,
accept_emergency_access_invite_token: Option<String>,
org_invite_token: Option<String>,
}
#[derive(Debug, Deserialize)]
@ -142,23 +144,62 @@ pub async fn _register(data: Json<RegisterData>, email_verification: bool, mut c
let mut data: RegisterData = data.into_inner();
let email = data.email.to_lowercase();
if email_verification && data.email_verification_token.is_none() {
err!("Email verification token is required");
}
let mut email_verified = false;
let email_verified = match &data.email_verification_token {
Some(token) if email_verification => {
let claims = crate::auth::decode_register_verify(token)?;
if claims.sub != data.email {
err!("Email verification token does not match email");
let mut pending_emergency_access = None;
// First, validate the provided verification tokens
if email_verification {
match (
&data.email_verification_token,
&data.accept_emergency_access_id,
&data.accept_emergency_access_invite_token,
&data.organization_user_id,
&data.org_invite_token,
) {
// Normal user registration, when email verification is required
(Some(email_verification_token), None, None, None, None) => {
let claims = crate::auth::decode_register_verify(email_verification_token)?;
if claims.sub != data.email {
err!("Email verification token does not match email");
}
// During this call we don't get the name, so extract it from the claims
if claims.name.is_some() {
data.name = claims.name;
}
email_verified = claims.verified;
}
// Emergency access registration
(None, Some(accept_emergency_access_id), Some(accept_emergency_access_invite_token), None, None) => {
if !CONFIG.emergency_access_allowed() {
err!("Emergency access is not enabled.")
}
let claims = crate::auth::decode_emergency_access_invite(accept_emergency_access_invite_token)?;
// This can happen if the user who received the invite used a different email to signup.
// Since we do not know if this is intended, we error out here and do nothing with the invite.
if claims.email != data.email {
err!("Claim email does not match email")
}
if &claims.emer_id != accept_emergency_access_id {
err!("Claim emer_id does not match accept_emergency_access_id")
}
pending_emergency_access = Some((accept_emergency_access_id, claims));
email_verified = true;
}
// Org invite
(None, None, None, Some(_organization_user_id), Some(_org_invite_token)) => {
err!("Org invite")
}
// During this call, we don't get the name, so extract it from the claims
data.name = Some(claims.name);
claims.verified
_ => {
err!("Registration is missing required parameters")
}
}
_ => false,
};
}
// Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden)
// This also prevents issues with very long usernames causing to large JWT's. See #2419
@ -210,7 +251,10 @@ pub async fn _register(data: Json<RegisterData>, email_verification: bool, mut c
// Order is important here; the invitation check must come first
// because the vaultwarden admin can invite anyone, regardless
// of other signup restrictions.
if Invitation::take(&email, &mut conn).await || CONFIG.is_signup_allowed(&email) {
if Invitation::take(&email, &mut conn).await
|| CONFIG.is_signup_allowed(&email)
|| pending_emergency_access.is_some()
{
User::new(email.clone())
} else {
err!("Registration not allowed or user already exists")
@ -266,10 +310,38 @@ pub async fn _register(data: Json<RegisterData>, email_verification: bool, mut c
user.save(&mut conn).await?;
// accept any open emergency access invitations
if !CONFIG.mail_enabled() && CONFIG.emergency_access_allowed() {
for mut emergency_invite in EmergencyAccess::find_all_invited_by_grantee_email(&user.email, &mut conn).await {
emergency_invite.accept_invite(&user.uuid, &user.email, &mut conn).await.ok();
if CONFIG.emergency_access_allowed() {
// Accept the emergency access invitation
if let Some((accept_emergency_access_id, claims)) = pending_emergency_access {
let Some(mut emergency_access) =
EmergencyAccess::find_by_uuid_and_grantee_email(accept_emergency_access_id, &data.email, &mut conn)
.await
else {
err!("Emergency access not valid.")
};
// get grantor user to send Accepted email
let Some(grantor_user) = User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await else {
err!("Grantor user not found.")
};
if grantor_user.name == claims.grantor_name && grantor_user.email == claims.grantor_email {
emergency_access.accept_invite(&user.uuid, &user.email, &mut conn).await?;
if CONFIG.mail_enabled() {
mail::send_emergency_access_invite_accepted(&grantor_user.email, &user.email).await?;
}
} else {
err!("Emergency access invitation error.")
}
}
// accept any open emergency access invitations
if !CONFIG.mail_enabled() {
for mut emergency_invite in EmergencyAccess::find_all_invited_by_grantee_email(&user.email, &mut conn).await
{
emergency_invite.accept_invite(&user.uuid, &user.email, &mut conn).await.ok();
}
}
}

Datei anzeigen

@ -721,7 +721,7 @@ async fn identity_register(data: Json<RegisterData>, conn: DbConn) -> JsonResult
#[serde(rename_all = "camelCase")]
struct RegisterVerificationData {
email: String,
name: String,
name: Option<String>,
// receiveMarketingEmails: bool,
}
@ -742,22 +742,28 @@ async fn register_verification_email(
err!("Registration not allowed or user already exists")
}
// TODO: We might want to do some rate limiting here
// Also, test this with invites/emergency access etc
let should_send_mail = CONFIG.mail_enabled() && CONFIG.signups_verify();
if User::find_by_mail(&data.email, &mut conn).await.is_some() {
// TODO: Add some random delay here to prevent timing attacks?
if should_send_mail {
// There is still a timing side channel here in that the code
// paths that send mail take noticeably longer than ones that
// don't. Add a randomized sleep to mitigate this somewhat.
use rand::{rngs::SmallRng, Rng, SeedableRng};
let mut rng = SmallRng::from_os_rng();
let delta: i32 = 100;
let sleep_ms = (1_000 + rng.random_range(-delta..=delta)) as u64;
tokio::time::sleep(tokio::time::Duration::from_millis(sleep_ms)).await;
}
return Ok(RegisterVerificationResponse::NoContent(()));
}
let should_send_mail = CONFIG.mail_enabled() && CONFIG.signups_verify();
let token_claims =
crate::auth::generate_register_verify_claims(data.email.clone(), data.name.clone(), should_send_mail);
let token = crate::auth::encode_jwt(&token_claims);
if should_send_mail {
mail::send_register_verify_email(&data.email, &data.name, &token).await?;
mail::send_register_verify_email(&data.email, &token).await?;
Ok(RegisterVerificationResponse::NoContent(()))
} else {

Datei anzeigen

@ -331,11 +331,11 @@ pub struct RegisterVerifyClaims {
// Subject
pub sub: String,
pub name: String,
pub name: Option<String>,
pub verified: bool,
}
pub fn generate_register_verify_claims(email: String, name: String, verified: bool) -> RegisterVerifyClaims {
pub fn generate_register_verify_claims(email: String, name: Option<String>, verified: bool) -> RegisterVerifyClaims {
let time_now = Utc::now();
RegisterVerifyClaims {
nbf: time_now.timestamp(),

Datei anzeigen

@ -201,7 +201,7 @@ pub async fn send_verify_email(address: &str, user_id: &UserId) -> EmptyResult {
send_email(address, &subject, body_html, body_text).await
}
pub async fn send_register_verify_email(email: &str, name: &str, token: &str) -> EmptyResult {
pub async fn send_register_verify_email(email: &str, token: &str) -> EmptyResult {
let mut query = url::Url::parse("https://query.builder").unwrap();
query.query_pairs_mut().append_pair("email", email).append_pair("token", token);
let query_string = match query.query() {
@ -215,7 +215,6 @@ pub async fn send_register_verify_email(email: &str, name: &str, token: &str) ->
// `url.Url` would place the anchor `#` after the query parameters
"url": format!("{}/#/finish-signup/?{}", CONFIG.domain(), query_string),
"img_src": CONFIG._smtp_img_src(),
"name": name,
"email": email,
}),
)?;

Datei anzeigen

@ -93,12 +93,19 @@ bit-nav-logo bit-nav-item .bwi-shield {
/**** END Static Vaultwarden Changes ****/
/**** START Dynamic Vaultwarden Changes ****/
{{#if signup_disabled}}
/* From web vault 2025.1.2 and onwards, the signup button is hidden
when signups are disabled as the web vault checks the /api/config endpoint.
Note that the clients tend to aggressively cache this endpoint, so it might
take a while for the change to take effect. To avoid the button appearing
when it shouldn't, we'll keep this style in place for a couple of versions */
{{#if webver "<2025.3.0"}}
/* Hide the register link on the login screen */
app-login form div + div + div + div + hr,
app-login form div + div + div + div + hr + p {
@extend %vw-hide;
}
{{/if}}
{{/if}}
{{#unless mail_enabled}}
/* Hide `Email` 2FA if mail is not enabled */