-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement new registration flow with email verification #5215
base: main
Are you sure you want to change the base?
Changes from all commits
3c7408e
19d4620
efc82da
60a9474
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -70,18 +70,31 @@ pub fn routes() -> Vec<rocket::Route> { | |||||
#[serde(rename_all = "camelCase")] | ||||||
pub struct RegisterData { | ||||||
email: String, | ||||||
|
||||||
kdf: Option<i32>, | ||||||
kdf_iterations: Option<i32>, | ||||||
kdf_memory: Option<i32>, | ||||||
kdf_parallelism: Option<i32>, | ||||||
|
||||||
#[serde(alias = "userSymmetricKey")] | ||||||
key: String, | ||||||
#[serde(alias = "userAsymmetricKeys")] | ||||||
keys: Option<KeysData>, | ||||||
|
||||||
master_password_hash: String, | ||||||
master_password_hint: Option<String>, | ||||||
|
||||||
name: Option<String>, | ||||||
|
||||||
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)] | ||||||
|
@@ -124,13 +137,80 @@ async fn is_email_2fa_required(member_id: Option<MembershipId>, conn: &mut DbCon | |||||
|
||||||
#[post("/accounts/register", data = "<data>")] | ||||||
async fn register(data: Json<RegisterData>, conn: DbConn) -> JsonResult { | ||||||
_register(data, conn).await | ||||||
_register(data, false, conn).await | ||||||
} | ||||||
|
||||||
pub async fn _register(data: Json<RegisterData>, mut conn: DbConn) -> JsonResult { | ||||||
let data: RegisterData = data.into_inner(); | ||||||
pub async fn _register(data: Json<RegisterData>, email_verification: bool, mut conn: DbConn) -> JsonResult { | ||||||
let mut data: RegisterData = data.into_inner(); | ||||||
let email = data.email.to_lowercase(); | ||||||
|
||||||
let mut email_verified = false; | ||||||
|
||||||
let mut pending_emergency_access = None; | ||||||
let mut pending_org_invite = 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)?; | ||||||
|
||||||
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)) => { | ||||||
let claims = decode_invite(org_invite_token)?; | ||||||
|
||||||
if claims.email != data.email { | ||||||
err!("Claim email does not match email") | ||||||
} | ||||||
|
||||||
if &claims.member_id != organization_user_id { | ||||||
err!("Claim org_user_id does not match organization_user_id") | ||||||
} | ||||||
|
||||||
pending_org_invite = Some((organization_user_id, claims)); | ||||||
email_verified = true; | ||||||
} | ||||||
|
||||||
_ => { | ||||||
err!("Registration is missing required parameters") | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
// 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 | ||||||
if let Some(ref name) = data.name { | ||||||
|
@@ -181,7 +261,11 @@ pub async fn _register(data: Json<RegisterData>, mut conn: DbConn) -> JsonResult | |||||
// 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() | ||||||
|| pending_org_invite.is_some() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When inviting an User the entry in the table will already have been created, so this should not be necessary, I think. With the new signup form will a vaultwarden/src/api/core/accounts.rs Lines 235 to 236 in 60a9474
data.org_invite_token instead. (I mean, we'd probably want to keep it for backwards compatibility but accepting an invitation currently fails.)
|
||||||
{ | ||||||
User::new(email.clone()) | ||||||
} else { | ||||||
err!("Registration not allowed or user already exists") | ||||||
|
@@ -200,6 +284,10 @@ pub async fn _register(data: Json<RegisterData>, mut conn: DbConn) -> JsonResult | |||||
user.client_kdf_iter = client_kdf_iter; | ||||||
} | ||||||
|
||||||
if email_verified { | ||||||
user.verified_at = Some(Utc::now().naive_utc()); | ||||||
} | ||||||
|
||||||
user.client_kdf_memory = data.kdf_memory; | ||||||
user.client_kdf_parallelism = data.kdf_parallelism; | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
Verify Your Email | ||
<!----------------> | ||
Verify this email address to finish creating your account by clicking the link below. | ||
|
||
Verify Email Address Now: {{{url}}} | ||
|
||
If you did not request to verify your account, you can safely ignore this email. | ||
{{> email/email_footer_text }} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
Verify Your Email | ||
<!----------------> | ||
{{> 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"> | ||
Verify this email address to finish creating your account by clicking the link below. | ||
</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"> | ||
<a href="{{{url}}}" | ||
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;"> | ||
Verify Email Address Now | ||
</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 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 request to verify your account, you can safely ignore this email. | ||
</td> | ||
</tr> | ||
</table> | ||
{{> email/email_footer }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we get rid of
token
and instead make it an alias for backwords compatibility?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I've tested it that seems to work both with
web-v2025.1.1
(with and without the feature flags set) andweb-v2025.1.2
.