1
0
Fork 1
Spiegel von https://github.com/dani-garcia/vaultwarden.git synchronisiert 2024-11-16 04:12:53 +01:00

Merge pull request #1229 from BlackDex/email-fixes

Email fixes
Dieser Commit ist enthalten in:
Daniel García 2020-11-18 16:16:27 +01:00 committet von GitHub
Commit 5379329ef7
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
20 geänderte Dateien mit 242 neuen und 153 gelöschten Zeilen

Datei anzeigen

@ -242,9 +242,9 @@
# SMTP_HOST=smtp.domain.tld
# SMTP_FROM=bitwarden-rs@domain.tld
# SMTP_FROM_NAME=Bitwarden_RS
# SMTP_PORT=587
# SMTP_SSL=true # (Explicit) - This variable by default configures Explicit STARTTLS, it will upgrade an insecure connection to a secure one. Unless SMTP_EXPLICIT_TLS is set to true.
# SMTP_EXPLICIT_TLS=true # (Implicit) - N.B. This variable configures Implicit TLS. It's currently mislabelled (see bug #851) - SMTP_SSL Needs to be set to true for this option to work.
# SMTP_PORT=587 # Ports 587 (submission) and 25 (smtp) are standard without encryption and with encryption via STARTTLS (Explicit TLS). Port 465 is outdated and used with Implicit TLS.
# SMTP_SSL=true # (Explicit) - This variable by default configures Explicit STARTTLS, it will upgrade an insecure connection to a secure one. Unless SMTP_EXPLICIT_TLS is set to true. Either port 587 or 25 are default.
# SMTP_EXPLICIT_TLS=true # (Implicit) - N.B. This variable configures Implicit TLS. It's currently mislabelled (see bug #851) - SMTP_SSL Needs to be set to true for this option to work. Usually port 465 is used here.
# SMTP_USERNAME=username
# SMTP_PASSWORD=password
# SMTP_TIMEOUT=15
@ -259,6 +259,22 @@
## but might need to be changed in case it trips some anti-spam filters
# HELO_NAME=
## SMTP debugging
## When set to true this will output very detailed SMTP messages.
## WARNING: This could contain sensitive information like passwords and usernames! Only enable this during troubleshooting!
# SMTP_DEBUG=false
## Accept Invalid Hostnames
## DANGEROUS: This option introduces significant vulnerabilities to man-in-the-middle attacks!
## Only use this as a last resort if you are not able to use a valid certificate.
# SMTP_ACCEPT_INVALID_HOSTNAMES=false
## Accept Invalid Certificates
## DANGEROUS: This option introduces significant vulnerabilities to man-in-the-middle attacks!
## Only use this as a last resort if you are not able to use a valid certificate.
## If the Certificate is valid but the hostname doesn't match, please use SMTP_ACCEPT_INVALID_HOSTNAMES instead.
# SMTP_ACCEPT_INVALID_CERTS=false
## Require new device emails. When a user logs in an email is required to be sent.
## If sending the email fails the login attempt will fail!!
# REQUIRE_DEVICE_EMAIL=false

72
Cargo.lock generiert
Datei anzeigen

@ -33,12 +33,6 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "atty"
version = "0.2.14"
@ -131,6 +125,18 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bitvec"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7ba35e9565969edb811639dbebfe34edc0368e472c5018474c8eb2543397f81"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "bitwarden_rs"
version = "1.0.0"
@ -638,6 +644,12 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
name = "funty"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8"
[[package]]
name = "futf"
version = "0.1.4"
@ -1144,9 +1156,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "lettre"
version = "0.10.0-alpha.3"
version = "0.10.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e422b6c03563bc47db09bb61a8ece4f1462de131455beb96c091e2998fa316a2"
checksum = "dc8c2fc7873920aca23647e5e86d44ff3f40bbc5a5efaab445c9eb0e001c9f71"
dependencies = [
"base64 0.13.0",
"hostname",
@ -1160,22 +1172,10 @@ dependencies = [
"rand 0.7.3",
"regex",
"serde",
"tracing",
"uuid",
]
[[package]]
name = "lexical-core"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616"
dependencies = [
"arrayvec",
"bitflags",
"cfg-if 0.1.10",
"ryu",
"static_assertions",
]
[[package]]
name = "libc"
version = "0.2.80"
@ -1448,11 +1448,11 @@ checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "nom"
version = "5.1.2"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
checksum = "4489ccc7d668957ddf64af7cd027c081728903afa6479d35da7e99bf5728f75f"
dependencies = [
"lexical-core",
"bitvec",
"memchr",
"version_check 0.9.2",
]
@ -1978,6 +1978,12 @@ dependencies = [
"scheduled-thread-pool",
]
[[package]]
name = "radium"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
[[package]]
name = "rand"
version = "0.4.6"
@ -2617,12 +2623,6 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3015a7d0a5fd5105c91c3710d42f9ccf0abfb287d62206484dcc67f9569a6483"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stdweb"
version = "0.4.20"
@ -2782,6 +2782,12 @@ dependencies = [
"time 0.1.44",
]
[[package]]
name = "tap"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e"
[[package]]
name = "tempfile"
version = "3.1.0"
@ -3371,6 +3377,12 @@ dependencies = [
"winapi-build",
]
[[package]]
name = "wyz"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]]
name = "yansi"
version = "0.5.0"

Datei anzeigen

@ -100,7 +100,7 @@ num-traits = "0.2.14"
num-derive = "0.3.3"
# Email libraries
lettre = { version = "0.10.0-alpha.3", features = ["smtp-transport", "builder", "serde", "native-tls", "hostname"], default-features = false }
lettre = { version = "0.10.0-alpha.4", features = ["smtp-transport", "builder", "serde", "native-tls", "hostname", "tracing"], default-features = false }
newline-converter = "0.1.0"
# Template library

Datei anzeigen

@ -436,6 +436,12 @@ make_config! {
smtp_timeout: u64, true, def, 15;
/// Server name sent during HELO |> By default this value should be is on the machine's hostname, but might need to be changed in case it trips some anti-spam filters
helo_name: String, true, option;
/// Enable SMTP debugging (Know the risks!) |> DANGEROUS: Enabling this will output very detailed SMTP messages. This could contain sensitive information like passwords and usernames! Only enable this during troubleshooting!
smtp_debug: bool, true, def, false;
/// Accept Invalid Certs (Know the risks!) |> DANGEROUS: Allow invalid certificates. This option introduces significant vulnerabilities to man-in-the-middle attacks!
smtp_accept_invalid_certs: bool, true, def, false;
/// Accept Invalid Hostnames (Know the risks!) |> DANGEROUS: Allow invalid hostnames. This option introduces significant vulnerabilities to man-in-the-middle attacks!
smtp_accept_invalid_hostnames: bool, true, def, false;
},
/// Email 2FA Settings

Datei anzeigen

@ -7,6 +7,7 @@ use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
use lettre::{
message::{header, Mailbox, Message, MultiPart, SinglePart},
transport::smtp::authentication::{Credentials, Mechanism as SmtpAuthMechanism},
transport::smtp::client::{Tls, TlsParameters},
transport::smtp::extension::ClientId,
Address, SmtpTransport, Transport,
};
@ -22,21 +23,30 @@ fn mailer() -> SmtpTransport {
use std::time::Duration;
let host = CONFIG.smtp_host().unwrap();
// Determine security
let smtp_client = if CONFIG.smtp_ssl() {
if CONFIG.smtp_explicit_tls() {
SmtpTransport::relay(host.as_str())
} else {
SmtpTransport::starttls_relay(host.as_str())
}
} else {
Ok(SmtpTransport::builder_dangerous(host.as_str()))
};
let smtp_client = smtp_client.unwrap()
let smtp_client = SmtpTransport::builder_dangerous(host.as_str())
.port(CONFIG.smtp_port())
.timeout(Some(Duration::from_secs(CONFIG.smtp_timeout())));
// Determine security
let smtp_client = if CONFIG.smtp_ssl() {
let mut tls_parameters = TlsParameters::builder(host);
if CONFIG.smtp_accept_invalid_hostnames() {
tls_parameters.dangerous_accept_invalid_hostnames(true);
}
if CONFIG.smtp_accept_invalid_certs() {
tls_parameters.dangerous_accept_invalid_certs(true);
}
let tls_parameters = tls_parameters.build().unwrap();
if CONFIG.smtp_explicit_tls() {
smtp_client.tls(Tls::Wrapper(tls_parameters))
} else {
smtp_client.tls(Tls::Required(tls_parameters))
}
} else {
smtp_client
};
let smtp_client = match (CONFIG.smtp_username(), CONFIG.smtp_password()) {
(Some(user), Some(pass)) => smtp_client.credentials(Credentials::new(user, pass)),
_ => smtp_client,
@ -318,14 +328,17 @@ fn send_email(address: &str, subject: &str, body_html: &str, body_text: &str) ->
// The boundary generated by Lettre it self is mostly too large based on the RFC822, so we generate one our selfs.
use uuid::Uuid;
let boundary = format!("_Part_{}_", Uuid::new_v4().to_simple());
let unique_id = Uuid::new_v4().to_simple();
let boundary = format!("_Part_{}_", unique_id);
let alternative = MultiPart::alternative().boundary(boundary).singlepart(text).singlepart(html);
let smtp_from = &CONFIG.smtp_from();
let email = Message::builder()
.message_id(Some(format!("<{}.{}>", unique_id, smtp_from)))
.to(Mailbox::new(None, Address::from_str(&address)?))
.from(Mailbox::new(
Some(CONFIG.smtp_from_name()),
Address::from_str(&CONFIG.smtp_from())?,
Address::from_str(smtp_from)?,
))
.subject(subject)
.multipart(alternative)?;

Datei anzeigen

@ -115,6 +115,16 @@ fn init_logging(level: log::LevelFilter) -> Result<(), fern::InitError> {
.level_for("rocket::fairing", log::LevelFilter::Off)
.chain(std::io::stdout());
// Enable smtp debug logging only specifically for smtp when need.
// This can contain sensitive information we do not want in the default debug/trace logging.
if CONFIG.smtp_debug() {
println!("[WARNING] SMTP Debugging is enabled (SMTP_DEBUG=true). Sensitive information could be disclosed via logs!");
println!("[WARNING] Only enable SMTP_DEBUG during troubleshooting!\n");
logger = logger.level_for("lettre::transport::smtp", log::LevelFilter::Debug)
} else {
logger = logger.level_for("lettre::transport::smtp", log::LevelFilter::Off)
}
if CONFIG.extended_logging() {
logger = logger.format(|out, message, record| {
out.finish(format_args!(

Datei anzeigen

@ -17,7 +17,7 @@
<div id="g_{{group}}" class="card-body collapse" data-parent="#config-form">
{{#each elements}}
{{#if editable}}
<div class="form-group row" title="[{{name}}] {{doc.description}}">
<div class="form-group row align-items-center" title="[{{name}}] {{doc.description}}">
{{#case type "text" "number" "password"}}
<label for="input_{{name}}" class="col-sm-3 col-form-label">{{doc.name}}</label>
<div class="col-sm-8 input-group">
@ -34,7 +34,7 @@
</div>
{{/case}}
{{#case type "checkbox"}}
<div class="col-sm-3">{{doc.name}}</div>
<div class="col-sm-3 col-form-label">{{doc.name}}</div>
<div class="col-sm-8">
<div class="form-check">
<input class="form-check-input conf-{{type}}" type="checkbox" id="input_{{name}}"
@ -48,7 +48,7 @@
{{/if}}
{{/each}}
{{#case group "smtp"}}
<div class="form-group row pt-3 border-top" title="Send a test email to given email address">
<div class="form-group row align-items-center pt-3 border-top" title="Send a test email to given email address">
<label for="smtp-test-email" class="col-sm-3 col-form-label">Test SMTP</label>
<div class="col-sm-8 input-group">
<input class="form-control" id="smtp-test-email" type="email" placeholder="Enter test email">
@ -76,7 +76,7 @@
{{#each config}}
{{#each elements}}
{{#unless editable}}
<div class="form-group row" title="[{{name}}] {{doc.description}}">
<div class="form-group row align-items-center" title="[{{name}}] {{doc.description}}">
{{#case type "text" "number" "password"}}
<label for="input_{{name}}" class="col-sm-3 col-form-label">{{doc.name}}</label>
<div class="col-sm-8 input-group">
@ -92,9 +92,9 @@
</div>
{{/case}}
{{#case type "checkbox"}}
<div class="col-sm-3">{{doc.name}}</div>
<div class="col-sm-3 col-form-label">{{doc.name}}</div>
<div class="col-sm-8">
<div class="form-check">
<div class="form-check align-middle">
<input disabled class="form-check-input" type="checkbox" id="input_{{name}}"
{{#if value}} checked {{/if}}>
@ -139,6 +139,10 @@
<script>
function smtpTest() {
if (formHasChanges(config_form)) {
alert("Config has been changed but not yet saved.\nPlease save the changes first before sending a test email.");
return false;
}
test_email = document.getElementById("smtp-test-email");
data = JSON.stringify({ "email": test_email.value });
_post("{{urlpath}}/admin/test/smtp/",
@ -205,4 +209,35 @@
// {{#each config}} {{#if grouptoggle}}
masterCheck("input_{{grouptoggle}}", "#g_{{group}} input");
// {{/if}} {{/each}}
// Two functions to help check if there were changes to the form fields
// Useful for example during the smtp test to prevent people from clicking save before testing there new settings
function initChangeDetection(form) {
const ignore_fields = ["smtp-test-email"];
Array.from(form).forEach((el) => {
if (! ignore_fields.includes(el.id)) {
el.dataset.origValue = el.value
}
});
}
function formHasChanges(form) {
return Array.from(form).some(el => 'origValue' in el.dataset && ( el.dataset.origValue !== el.value));
}
// Trigger Form Change Detection
const config_form = document.getElementById('config-form');
initChangeDetection(config_form);
// Colorize some settings which are high risk
const risk_items = document.getElementsByClassName('col-form-label');
function colorRiskSettings(risk_el) {
Array.from(risk_el).forEach((el) => {
if (el.innerText.toLowerCase().includes('risks') ) {
el.parentElement.className += ' alert-danger'
console.log(el)
}
});
}
colorRiskSettings(risk_items);
</script>

Datei anzeigen

@ -1,6 +1,8 @@
Your Email Change
<!---------------->
<html>
<p>To finalize changing your email address enter the following code in web vault: <b>{{token}}</b></p>
<p>If you did not try to change an email address, you can safely ignore this email.</p>
</html>
To finalize changing your email address enter the following code in web vault: {{token}}
If you did not try to change an email address, you can safely ignore this email.
===
Github: https://github.com/dani-garcia/bitwarden_rs

Datei anzeigen

@ -1,12 +1,10 @@
Delete Your Account
<!---------------->
<html>
<p>
click the link below to delete your account.
<br>
<br>
<a href="{{url}}/#/verify-recover-delete?userId={{user_id}}&token={{token}}&email={{email}}">
Delete Your Account</a>
</p>
<p>If you did not request this email to delete your account, you can safely ignore this email.</p>
</html>
Click the link below to delete your account.
Delete Your Account: {{url}}/#/verify-recover-delete?userId={{user_id}}&token={{token}}&email={{email}}
If you did not request this email to delete your account, you can safely ignore this email.
===
Github: https://github.com/dani-garcia/bitwarden_rs

Datei anzeigen

@ -1,8 +1,7 @@
Invitation to {{{org_name}}} accepted
<!---------------->
<html>
<p>
Your invitation for <b>{{email}}</b> to join <b>{{org_name}}</b> was accepted.
Please <a href="{{url}}/">log in</a> to the bitwarden_rs server and confirm them from the organization management page.
</p>
</html>
Your invitation for *{{email}}* to join *{{org_name}}* was accepted.
Please log in via {{url}} to the bitwarden_rs server and confirm them from the organization management page.
===
Github: https://github.com/dani-garcia/bitwarden_rs

Datei anzeigen

@ -1,8 +1,7 @@
Invitation to {{{org_name}}} confirmed
<!---------------->
<html>
<p>
Your invitation to join <b>{{org_name}}</b> was confirmed.
It will now appear under the Organizations the next time you <a href="{{url}}/">log in</a> to the web vault.
</p>
</html>
Your invitation to join *{{org_name}}* was confirmed.
It will now appear under the Organizations the next time you log in to the web vault at {{url}}.
===
Github: https://github.com/dani-garcia/bitwarden_rs

Datei anzeigen

@ -1,14 +1,12 @@
New Device Logged In From {{{device}}}
<!---------------->
<html>
<p>
Your account was just logged into from a new device.
Your account was just logged into from a new device.
Date: {{datetime}}
IP Address: {{ip}}
Device Type: {{device}}
* Date: {{datetime}}
* IP Address: {{ip}}
* Device Type: {{device}}
You can deauthorize all devices that have access to your account from the
<a href="{{url}}/">web vault</a> under Settings > My Account > Deauthorize Sessions.
</p>
</html>
You can deauthorize all devices that have access to your account from the web vault ( {{url}} ) under Settings > My Account > Deauthorize Sessions.
===
Github: https://github.com/dani-garcia/bitwarden_rs

Datei anzeigen

@ -2,6 +2,9 @@ Your master password hint
<!---------------->
You (or someone) recently requested your master password hint. Unfortunately, your account does not have a master password hint.
If you cannot remember your master password, there is no way to recover your data. The only option to gain access to your account again is to <a href="{{url}}/#/recover-delete">delete the account</a> so that you can register again and start over. All data associated with your account will be deleted.
If you cannot remember your master password, there is no way to recover your data. The only option to gain access to your account again is to delete the account ( {{url}}/#/recover-delete ) so that you can register again and start over. All data associated with your account will be deleted.
If you did not request your master password hint you can safely ignore this email.
===
Github: https://github.com/dani-garcia/bitwarden_rs

Datei anzeigen

@ -2,9 +2,12 @@ Your master password hint
<!---------------->
You (or someone) recently requested your master password hint.
Your hint is: "{{hint}}"
Log in: <a href="{{url}}/">Web Vault</a>
Your hint is: *{{hint}}*
Log in to the web vault: {{url}}
If you cannot remember your master password, there is no way to recover your data. The only option to gain access to your account again is to <a href="{{url}}/#/recover-delete">delete the account</a> so that you can register again and start over. All data associated with your account will be deleted.
If you cannot remember your master password, there is no way to recover your data. The only option to gain access to your account again is to delete the account ( {{url}}/#/recover-delete ) so that you can register again and start over. All data associated with your account will be deleted.
If you did not request your master password hint you can safely ignore this email.
===
Github: https://github.com/dani-garcia/bitwarden_rs

Datei anzeigen

@ -1,12 +1,12 @@
Join {{{org_name}}}
<!---------------->
<html>
<p>
You have been invited to join the <b>{{org_name}}</b> organization.
<br>
<br>
<a href="{{url}}/#/accept-organization/?organizationId={{org_id}}&organizationUserId={{org_user_id}}&email={{email}}&organizationName={{org_name}}&token={{token}}">
Click here to join</a>
</p>
<p>If you do not wish to join this organization, you can safely ignore this email.</p>
</html>
You have been invited to join the *{{org_name}}* organization.
Click here to join: {{url}}/#/accept-organization/?organizationId={{org_id}}&organizationUserId={{org_user_id}}&email={{email}}&organizationName={{org_name}}&token={{token}}
If you do not wish to join this organization, you can safely ignore this email.
===
Github: https://github.com/dani-garcia/bitwarden_rs

Datei anzeigen

@ -1,8 +1,8 @@
Bitwarden_rs SMTP Test
<!---------------->
<html>
<p>
This is a test email to verify the SMTP configuration for <a href="{{url}}">{{url}}</a>.
</p>
<p>When you can read this email it is probably configured correctly.</p>
</html>
This is a test email to verify the SMTP configuration for {{url}}.
When you can read this email it is probably configured correctly.
===
Github: https://github.com/dani-garcia/bitwarden_rs

Datei anzeigen

@ -1,9 +1,8 @@
Your Two-step Login Verification Code
<!---------------->
<html>
<p>
Your two-step verification code is: <b>{{token}}</b>
Your two-step verification code is: {{token}}
Use this code to complete logging in with Bitwarden.
</p>
</html>
Use this code to complete logging in with Bitwarden.
===
Github: https://github.com/dani-garcia/bitwarden_rs

Datei anzeigen

@ -1,12 +1,10 @@
Verify Your Email
<!---------------->
<html>
<p>
Verify this email address for your account by clicking the link below.
<br>
<br>
<a href="{{url}}/#/verify-email/?userId={{user_id}}&token={{token}}">
Verify Email Address Now</a>
</p>
<p>If you did not request to verify your account, you can safely ignore this email.</p>
</html>
Verify Email Address Now: {{url}}/#/verify-email/?userId={{user_id}}&token={{token}}
If you did not request to verify your account, you can safely ignore this email.
===
Github: https://github.com/dani-garcia/bitwarden_rs

Datei anzeigen

@ -1,8 +1,8 @@
Welcome
<!---------------->
<html>
<p>
Thank you for creating an account at <a href="{{url}}/">{{url}}</a>. You may now log in with your new account.
</p>
<p>If you did not request to create an account, you can safely ignore this email.</p>
</html>
Thank you for creating an account at {{url}}. You may now log in with your new account.
If you did not request to create an account, you can safely ignore this email.
===
Github: https://github.com/dani-garcia/bitwarden_rs

Datei anzeigen

@ -1,12 +1,10 @@
Welcome
<!---------------->
<html>
<p>
Thank you for creating an account at <a href="{{url}}/">{{url}}</a>. Before you can login with your new account, you must verify this email address by clicking the link below.
<br>
<br>
<a href="{{url}}/#/verify-email/?userId={{user_id}}&token={{token}}">
Verify Email Address Now</a>
</p>
<p>If you did not request to create an account, you can safely ignore this email.</p>
</html>
Thank you for creating an account at {{url}}. Before you can login with your new account, you must verify this email address by clicking the link below.
Verify Email Address Now: {{url}}/#/verify-email/?userId={{user_id}}&token={{token}}
If you did not request to create an account, you can safely ignore this email.
===
Github: https://github.com/dani-garcia/bitwarden_rs