[GH-ISSUE #2146] Admin-Invited users can only use mailed link to register - bug or feature? #1173

Closed
opened 2026-03-03 02:06:51 +03:00 by kerem · 7 comments
Owner

Originally created by @fashberg on GitHub (Dec 12, 2021).
Original GitHub issue: https://github.com/dani-garcia/vaultwarden/issues/2146

Subject of the issue

Hey! Thanks for this great project!

I have one issue: if a user was invited (through /admin or using vaultwarden_ldap) he cannot register from scratch using web-vault function "create account" (message: "Account with this email already exists").

The user has to use the link from emailed invitation.

Also there is no possibility to resend the invitation mail (admin panel says "User already exists")

Is this intended?

Deployment environment

  • vaultwarden version: main

  • Install method: built from source

  • Clients used: web vault

  • Reverse proxy and version: none

Steps to reproduce

  • set mail_enabled
  • invite a user at /admin
    • User gets created in db with empy pw-hash
  • try to register with the same e-mail-address not using the link, but using "Create Account" on webpage

Expected behaviour

Users should be able to register.

Actual behaviour

No possibility to register without having the invitation

Troubleshooting data

The code checks at accounts.rs if the account is already created completely (!user.password_hash.is_empty() ), then checks for an invitation.

But the invitations are not saved with mail_enabled()

        if CONFIG.mail_enabled() {
            mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None)?;
        } else {
            let invitation = Invitation::new(user.email.clone());
            invitation.save(&conn)?;
        }

So checking for an active invitation fails.

This patch would resend an invitation if the user tries to register.

diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs
index e3ebcde..cf8586c 100644
--- a/src/api/core/accounts.rs
+++ b/src/api/core/accounts.rs
@@ -93,7 +93,14 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
                 // check if it's invited by emergency contact
                 match EmergencyAccess::find_invited_by_grantee_email(&data.Email, &conn) {
                     Some(_) => user,
-                    _ => err!("Account with this email already exists"),
+                    //_ => err!("Account with this email already exists"),
+                    // allow registering admin-invited users without clicking the link
+                    _ => {
+                        if CONFIG.mail_enabled() {
+                            mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None)?;
+                        }
+                        err!("Please use invitation link to register. Check your mail")
+                    }
                 }
             } else {
                 err!("Registration not allowed or user already exists")

Or just "_ => user" to silently create the new user.

What do you think?

Kind regards

Folke

Originally created by @fashberg on GitHub (Dec 12, 2021). Original GitHub issue: https://github.com/dani-garcia/vaultwarden/issues/2146 ### Subject of the issue Hey! Thanks for this great project! I have one issue: if a user was invited (through /admin or using vaultwarden_ldap) he cannot register from scratch using web-vault function "create account" (message: "Account with this email already exists"). The user has to use the link from emailed invitation. Also there is no possibility to resend the invitation mail (admin panel says "User already exists") Is this intended? ### Deployment environment * vaultwarden version: main * Install method: built from source * Clients used: web vault * Reverse proxy and version: none ### Steps to reproduce * set mail_enabled * invite a user at /admin * User gets created in db with empy pw-hash * try to register with the same e-mail-address _not_ using the link, but using "Create Account" on webpage ### Expected behaviour Users should be able to register. ### Actual behaviour No possibility to register without having the invitation ### Troubleshooting data The code checks at accounts.rs if the account is already created completely (!user.password_hash.is_empty() ), then checks for an invitation. But the invitations are not saved with mail_enabled() ``` if CONFIG.mail_enabled() { mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None)?; } else { let invitation = Invitation::new(user.email.clone()); invitation.save(&conn)?; } ``` So checking for an active invitation fails. This patch would resend an invitation if the user tries to register. ``` diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index e3ebcde..cf8586c 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -93,7 +93,14 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { // check if it's invited by emergency contact match EmergencyAccess::find_invited_by_grantee_email(&data.Email, &conn) { Some(_) => user, - _ => err!("Account with this email already exists"), + //_ => err!("Account with this email already exists"), + // allow registering admin-invited users without clicking the link + _ => { + if CONFIG.mail_enabled() { + mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None)?; + } + err!("Please use invitation link to register. Check your mail") + } } } else { err!("Registration not allowed or user already exists") ``` Or just "_ => user" to silently create the new user. What do you think? Kind regards Folke
kerem closed this issue 2026-03-03 02:06:51 +03:00
Author
Owner

@BlackDex commented on GitHub (Dec 12, 2021):

If you have mail enabled then you need to use the link in the mail.
This is how it is supposed to work.

Same goes for Org invites, they only work by clicking on the link received in the mail.

If you do not have mail enabled, then there are some other steps which are followed.

I think a re-invite button in the admin would be a better way to go then sending out the e-mail again.
Also, i'm not sure if this will work at all, since the invite will create the user again, so that will probably cause an error also.

<!-- gh-comment-id:991916509 --> @BlackDex commented on GitHub (Dec 12, 2021): If you have mail enabled then you need to use the link in the mail. This is how it is supposed to work. Same goes for Org invites, they only work by clicking on the link received in the mail. If you do not have mail enabled, then there are some other steps which are followed. I think a re-invite button in the admin would be a better way to go then sending out the e-mail again. Also, i'm not sure if this will work at all, since the invite will create the user again, so that will probably cause an error also.
Author
Owner

@fashberg commented on GitHub (Dec 12, 2021):

Hey @BlackDex ,

so what's the main goal of invitations then? (Not speaking about org-invites, but the general onboarding invite).

If a user clicks on Join in the invitation mail and creates account, he has to verify it's e-mail afterwards.
The mail-address is now double verified.

Not invited users can just signup without clicking the link (if enabled).

Wouldn't it be userfriendly:

  • resend invitation if user tries to register with following the link (or at least a better error-message)
  • If user follows mail-invitation -> automatically setting verified_at to now() at first login

Kind Regards
Folke

<!-- gh-comment-id:991919945 --> @fashberg commented on GitHub (Dec 12, 2021): Hey @BlackDex , so what's the main goal of invitations then? (Not speaking about org-invites, but the general onboarding invite). If a user clicks on Join in the invitation mail and creates account, he has to verify it's e-mail afterwards. The mail-address is now double verified. Not invited users can just signup without clicking the link (if enabled). Wouldn't it be userfriendly: * resend invitation if user tries to register with following the link (or at least a better error-message) * If user follows mail-invitation -> automatically setting verified_at to now() at first login Kind Regards Folke
Author
Owner

@fashberg commented on GitHub (Dec 12, 2021):

This adds auto-verify when user joins mail invitation (token already checked)

diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs
index e3ebcde..e597149 100644
--- a/src/api/core/accounts.rs
+++ b/src/api/core/accounts.rs
@@ -64,6 +64,7 @@ struct KeysData {
 fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
     let data: RegisterData = data.into_inner().data;
     let email = data.Email.to_lowercase();
+    let mut auto_verify = false;
 
     let mut user = match User::find_by_mail(&email, &conn) {
         Some(user) => {
@@ -78,6 +79,7 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
             if let Some(token) = data.Token {
                 let claims = decode_invite(&token)?;
                 if claims.email == email {
+                    auto_verify = true;
                     user
                 } else {
                     err!("Registration email does not match invite email")
@@ -140,11 +142,13 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult {
     }
 
     if CONFIG.mail_enabled() {
-        if CONFIG.signups_verify() {
+        if auto_verify {
+            user.verified_at = Some(Utc::now().naive_utc());
+        }
+        if CONFIG.signups_verify() && !auto_verify {
             if let Err(e) = mail::send_welcome_must_verify(&user.email, &user.uuid) {
                 error!("Error sending welcome email: {:#?}", e);
             }
-
             user.last_verifying_at = Some(user.created_at);
         } else if let Err(e) = mail::send_welcome(&user.email) {
             error!("Error sending welcome email: {:#?}", e);
<!-- gh-comment-id:991923482 --> @fashberg commented on GitHub (Dec 12, 2021): This adds auto-verify when user joins mail invitation (token already checked) ```diff diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index e3ebcde..e597149 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -64,6 +64,7 @@ struct KeysData { fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { let data: RegisterData = data.into_inner().data; let email = data.Email.to_lowercase(); + let mut auto_verify = false; let mut user = match User::find_by_mail(&email, &conn) { Some(user) => { @@ -78,6 +79,7 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { if let Some(token) = data.Token { let claims = decode_invite(&token)?; if claims.email == email { + auto_verify = true; user } else { err!("Registration email does not match invite email") @@ -140,11 +142,13 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { } if CONFIG.mail_enabled() { - if CONFIG.signups_verify() { + if auto_verify { + user.verified_at = Some(Utc::now().naive_utc()); + } + if CONFIG.signups_verify() && !auto_verify { if let Err(e) = mail::send_welcome_must_verify(&user.email, &user.uuid) { error!("Error sending welcome email: {:#?}", e); } - user.last_verifying_at = Some(user.created_at); } else if let Err(e) = mail::send_welcome(&user.email) { error!("Error sending welcome email: {:#?}", e); ```
Author
Owner

@BlackDex commented on GitHub (Dec 12, 2021):

The main goal is that you can disable signups fully so that nobody can invite them selfs if you have your Vaultwarden server running on the Internet.

Inviting users via the admin interface makes it so that you can still invite people, even though signups are disabled.
The same goes for org invites, you can still allow those, but have random signups still disabled.

Via both ways you need to use the provided link received via the mail. Or if the mail is disabled, then it is a bit less secure, since then anybody who knows there is an invite pending for a specific mail address could create an account.

E-Mail validation is not mandatory, but there could be something said for automatically having it verified if a user does use the link to create an account. That said, Bitwarden does the same thing. Even though an invite is sent per mail, a user still needs to verify there mail address after they created an account. Since we try to keep as close as possible to the same flows i think this still is the best way to go (until they changed it too). It does give some extra sense of security,

<!-- gh-comment-id:991925007 --> @BlackDex commented on GitHub (Dec 12, 2021): The main goal is that you can disable signups fully so that nobody can invite them selfs if you have your Vaultwarden server running on the Internet. Inviting users via the **admin interface** makes it so that you can still invite people, even though signups are disabled. The same goes for org invites, you can still allow those, but have random signups still disabled. Via both ways you need to use the provided link received via the mail. Or if the mail is disabled, then it is a bit less secure, since then anybody who knows there is an invite pending for a specific mail address could create an account. E-Mail validation is not mandatory, but there could be something said for automatically having it verified if a user does use the link to create an account. That said, Bitwarden does the same thing. Even though an invite is sent per mail, a user still needs to verify there mail address after they created an account. Since we try to keep as close as possible to the same flows i think this still is the best way to go (until they changed it too). It does give some extra sense of security,
Author
Owner

@fashberg commented on GitHub (Dec 12, 2021):

If signups are fully disabled this line would not match

if CONFIG.is_signup_allowed(&email) {

So no user can register if prohibited.

If everything should be identical to bitwarden then you have to add the payment process ;)
Why not being better in some situations?

I want to invite all our employees (using vaultwarden_ldap) to make things easier for them. But things are getting more complicated when they are not longer allowed to register the old way and resetting/reinviting has to be done by admins.

<!-- gh-comment-id:991927623 --> @fashberg commented on GitHub (Dec 12, 2021): If signups are fully disabled this line would not match ``` if CONFIG.is_signup_allowed(&email) { ``` So no user can register if prohibited. If everything should be identical to bitwarden then you have to add the payment process ;) Why not being better in some situations? I want to invite all our employees (using vaultwarden_ldap) to make things easier for them. But things are getting more complicated when they are not longer allowed to register the old way and resetting/reinviting has to be done by admins.
Author
Owner

@BlackDex commented on GitHub (Dec 12, 2021):

I said we try to keep as close as possible, not identical.

Well, if they need to be able to register, they should have received a mail, and they can use the link.
Else i suggest to use the SIGNUPS_DOMAINS_WHITELIST github.com/dani-garcia/vaultwarden@d0bf0ab237/.env.template (L187-L189)

Which is what that specific line you pasted above is checking for including some other checks.
Then you can just send a mail to employees@my-company.tld and just point them towards the Vaultwarden domain where then can register.

I also do not see the reason to resend a mail which they should have gotten already. Then they just need to check there mailbox again and link on the right link.

Also, that you are allowed to disable signups, but still invite them your self, and to allow signups for specific domains is already extra features which Bitwarden doesn't provide at the moment.

<!-- gh-comment-id:991931061 --> @BlackDex commented on GitHub (Dec 12, 2021): I said we try to keep as close as possible, not identical. Well, if they need to be able to register, they should have received a mail, and they can use the link. Else i suggest to use the `SIGNUPS_DOMAINS_WHITELIST` https://github.com/dani-garcia/vaultwarden/blob/d0bf0ab2370daff3353baaf22291f2dff34c1a5b/.env.template#L187-L189 Which is what that specific line you pasted above is checking for including some other checks. Then you can just send a mail to employees@my-company.tld and just point them towards the Vaultwarden domain where then can register. I also do not see the reason to resend a mail which they should have gotten already. Then they just need to check there mailbox again and link on the right link. Also, that you are allowed to disable signups, but still invite them your self, and to allow signups for specific domains is already extra features which Bitwarden doesn't provide at the moment.
Author
Owner

@fashberg commented on GitHub (Dec 12, 2021):

SIGNUPS_DOMAINS_WHITELIST

sure, i've already have set up this option ;)

I've invited now 60 users with ldap. 40-50 will sign up the next week (hopefully) and the other 10-20 will come to IT support next year saying "i cannot register, i get 'account already exists'"

No prob for me, i've created now my own container with both above listed code-suggestions and will git pull further upstream changes.

Thanks and kind regards!

<!-- gh-comment-id:991933108 --> @fashberg commented on GitHub (Dec 12, 2021): > SIGNUPS_DOMAINS_WHITELIST sure, i've already have set up this option ;) I've invited now 60 users with ldap. 40-50 will sign up the next week (hopefully) and the other 10-20 will come to IT support next year saying "i cannot register, i get 'account already exists'" No prob for me, i've created now my own container with both above listed code-suggestions and will git pull further upstream changes. Thanks and kind regards!
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vaultwarden#1173
No description provided.