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:
Ursprung
19d4620db0
Commit
efc82da5f6
5 geänderte Dateien mit 114 neuen und 30 gelöschten Zeilen
|
@ -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)?;
|
||||
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
|
||||
data.name = Some(claims.name);
|
||||
claims.verified
|
||||
// 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")
|
||||
}
|
||||
|
||||
_ => {
|
||||
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,12 +310,40 @@ pub async fn _register(data: Json<RegisterData>, email_verification: bool, mut c
|
|||
|
||||
user.save(&mut conn).await?;
|
||||
|
||||
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() && CONFIG.emergency_access_allowed() {
|
||||
for mut emergency_invite in EmergencyAccess::find_all_invited_by_grantee_email(&user.email, &mut conn).await {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Json(json!({
|
||||
"object": "register",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
)?;
|
||||
|
|
|
@ -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 */
|
||||
|
|
Laden …
Tabelle hinzufügen
In neuem Issue referenzieren