vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit 924ba153aa7a9db0d5493f221680c46a7f0edfc0
parent cf5a985b3114d59b6c7b71e954cedcdd67479497
Author: Daniel García <dani-garcia@users.noreply.github.com>
Date:   Mon, 25 Nov 2019 08:21:18 +0100

Merge pull request #730 from tomuta/email_verification

Implement change-email, email-verification, account-recovery, and welcome notifications
Diffstat:
M.env.template | 18++++++++++++++----
Amigrations/mysql/2019-11-17-011009_add_email_verification/down.sql | 1+
Amigrations/mysql/2019-11-17-011009_add_email_verification/up.sql | 5+++++
Amigrations/postgresql/2019-11-17-011009_add_email_verification/down.sql | 1+
Amigrations/postgresql/2019-11-17-011009_add_email_verification/up.sql | 5+++++
Amigrations/sqlite/2019-11-17-011009_add_email_verification/down.sql | 1+
Amigrations/sqlite/2019-11-17-011009_add_email_verification/up.sql | 5+++++
Msrc/api/core/accounts.rs | 175++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/api/core/two_factor/email.rs | 20++------------------
Msrc/api/identity.rs | 29+++++++++++++++++++++++++++++
Msrc/auth.rs | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/config.rs | 11+++++++++++
Msrc/crypto.rs | 16++++++++++++++++
Msrc/db/models/device.rs | 3++-
Msrc/db/models/user.rs | 12+++++++++++-
Msrc/db/schemas/mysql/schema.rs | 5+++++
Msrc/db/schemas/postgresql/schema.rs | 8++++++--
Msrc/db/schemas/sqlite/schema.rs | 5+++++
Msrc/mail.rs | 81++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Asrc/static/templates/email/change_email.hbs | 6++++++
Asrc/static/templates/email/change_email.html.hbs | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/static/templates/email/delete_account.hbs | 12++++++++++++
Asrc/static/templates/email/delete_account.html.hbs | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/static/templates/email/pw_hint_none.hbs | 8++++++--
Msrc/static/templates/email/pw_hint_none.html.hbs | 5+++++
Msrc/static/templates/email/pw_hint_some.hbs | 2++
Msrc/static/templates/email/pw_hint_some.html.hbs | 5+++++
Asrc/static/templates/email/verify_email.hbs | 12++++++++++++
Asrc/static/templates/email/verify_email.html.hbs | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/static/templates/email/welcome.hbs | 8++++++++
Asrc/static/templates/email/welcome.html.hbs | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/static/templates/email/welcome_must_verify.hbs | 12++++++++++++
Asrc/static/templates/email/welcome_must_verify.html.hbs | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
33 files changed, 1164 insertions(+), 34 deletions(-)

diff --git a/.env.template b/.env.template @@ -95,12 +95,22 @@ ## Controls if new users can register # SIGNUPS_ALLOWED=true +## Controls if new users need to verify their email address upon registration +## Note that setting this option to true prevents logins until the email address has been verified! +## The welcome email will include a verification link, and login attempts will periodically +## trigger another verification email to be sent. +# SIGNUPS_VERIFY=false + +## If SIGNUPS_VERIFY is set to true, this limits how many seconds after the last time +## an email verification link has been sent another verification email will be sent +# SIGNUPS_VERIFY_RESEND_TIME=3600 + +## If SIGNUPS_VERIFY is set to true, this limits how many times an email verification +## email will be re-sent upon an attempted login. +# SIGNUPS_VERIFY_RESEND_LIMIT=6 + ## Controls if new users from a list of comma-separated domains can register ## even if SIGNUPS_ALLOWED is set to false -## -## WARNING: There is currently no validation that prevents anyone from -## signing up with any made-up email address from one of these -## whitelisted domains! # SIGNUPS_DOMAINS_WHITELIST=example.com,example.net,example.org ## Token for the admin interface, preferably use a long random string diff --git a/migrations/mysql/2019-11-17-011009_add_email_verification/down.sql b/migrations/mysql/2019-11-17-011009_add_email_verification/down.sql @@ -0,0 +1 @@ + diff --git a/migrations/mysql/2019-11-17-011009_add_email_verification/up.sql b/migrations/mysql/2019-11-17-011009_add_email_verification/up.sql @@ -0,0 +1,5 @@ +ALTER TABLE users ADD COLUMN verified_at DATETIME DEFAULT NULL; +ALTER TABLE users ADD COLUMN last_verifying_at DATETIME DEFAULT NULL; +ALTER TABLE users ADD COLUMN login_verify_count INTEGER NOT NULL DEFAULT 0; +ALTER TABLE users ADD COLUMN email_new VARCHAR(255) DEFAULT NULL; +ALTER TABLE users ADD COLUMN email_new_token VARCHAR(16) DEFAULT NULL; diff --git a/migrations/postgresql/2019-11-17-011009_add_email_verification/down.sql b/migrations/postgresql/2019-11-17-011009_add_email_verification/down.sql @@ -0,0 +1 @@ + diff --git a/migrations/postgresql/2019-11-17-011009_add_email_verification/up.sql b/migrations/postgresql/2019-11-17-011009_add_email_verification/up.sql @@ -0,0 +1,5 @@ +ALTER TABLE users ADD COLUMN verified_at TIMESTAMP DEFAULT NULL; +ALTER TABLE users ADD COLUMN last_verifying_at TIMESTAMP DEFAULT NULL; +ALTER TABLE users ADD COLUMN login_verify_count INTEGER NOT NULL DEFAULT 0; +ALTER TABLE users ADD COLUMN email_new VARCHAR(255) DEFAULT NULL; +ALTER TABLE users ADD COLUMN email_new_token VARCHAR(16) DEFAULT NULL; diff --git a/migrations/sqlite/2019-11-17-011009_add_email_verification/down.sql b/migrations/sqlite/2019-11-17-011009_add_email_verification/down.sql @@ -0,0 +1 @@ + diff --git a/migrations/sqlite/2019-11-17-011009_add_email_verification/up.sql b/migrations/sqlite/2019-11-17-011009_add_email_verification/up.sql @@ -0,0 +1,5 @@ +ALTER TABLE users ADD COLUMN verified_at DATETIME DEFAULT NULL; +ALTER TABLE users ADD COLUMN last_verifying_at DATETIME DEFAULT NULL; +ALTER TABLE users ADD COLUMN login_verify_count INTEGER NOT NULL DEFAULT 0; +ALTER TABLE users ADD COLUMN email_new TEXT DEFAULT NULL; +ALTER TABLE users ADD COLUMN email_new_token TEXT DEFAULT NULL; diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs @@ -1,11 +1,13 @@ use rocket_contrib::json::Json; +use chrono::Utc; use crate::db::models::*; use crate::db::DbConn; use crate::api::{EmptyResult, JsonResult, JsonUpcase, Notify, NumberOrString, PasswordData, UpdateType}; -use crate::auth::{decode_invite, Headers}; +use crate::auth::{decode_invite, decode_delete, decode_verify_email, Headers}; use crate::mail; +use crate::crypto; use crate::CONFIG; @@ -25,6 +27,10 @@ pub fn routes() -> Vec<Route> { post_sstamp, post_email_token, post_email, + post_verify_email, + post_verify_email_token, + post_delete_recover, + post_delete_recover_token, delete_account, post_delete_account, revision_date, @@ -126,6 +132,20 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { user.public_key = Some(keys.PublicKey); } + if CONFIG.mail_enabled() { + if CONFIG.signups_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); + } + } + } + user.save(&conn) } @@ -341,8 +361,9 @@ struct EmailTokenData { #[post("/accounts/email-token", data = "<data>")] fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: DbConn) -> EmptyResult { let data: EmailTokenData = data.into_inner().data; + let mut user = headers.user; - if !headers.user.check_valid_password(&data.MasterPasswordHash) { + if !user.check_valid_password(&data.MasterPasswordHash) { err!("Invalid password") } @@ -350,7 +371,21 @@ fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: Db err!("Email already in use"); } - Ok(()) + if !CONFIG.signups_allowed() && !CONFIG.can_signup_user(&data.NewEmail) { + err!("Email cannot be changed to this address"); + } + + let token = crypto::generate_token(6)?; + + if CONFIG.mail_enabled() { + if let Err(e) = mail::send_change_email(&data.NewEmail, &token) { + error!("Error sending change-email email: {:#?}", e); + } + } + + user.email_new = Some(data.NewEmail); + user.email_new_token = Some(token); + user.save(&conn) } #[derive(Deserialize)] @@ -361,8 +396,7 @@ struct ChangeEmailData { Key: String, NewMasterPasswordHash: String, - #[serde(rename = "Token")] - _Token: NumberOrString, + Token: NumberOrString, } #[post("/accounts/email", data = "<data>")] @@ -378,7 +412,32 @@ fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) err!("Email already in use"); } + match user.email_new { + Some(ref val) => { + if *val != data.NewEmail.to_string() { + err!("Email change mismatch"); + } + }, + None => err!("No email change pending"), + } + + if CONFIG.mail_enabled() { + // Only check the token if we sent out an email... + match user.email_new_token { + Some(ref val) => + if *val != data.Token.into_string() { + err!("Token mismatch"); + } + None => err!("No email change pending"), + } + user.verified_at = Some(Utc::now().naive_utc()); + } else { + user.verified_at = None; + } + user.email = data.NewEmail; + user.email_new = None; + user.email_new_token = None; user.set_password(&data.NewMasterPasswordHash); user.akey = data.Key; @@ -386,6 +445,112 @@ fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) user.save(&conn) } +#[post("/accounts/verify-email")] +fn post_verify_email(headers: Headers, _conn: DbConn) -> EmptyResult { + let user = headers.user; + + if !CONFIG.mail_enabled() { + err!("Cannot verify email address"); + } + + if let Err(e) = mail::send_verify_email(&user.email, &user.uuid) { + error!("Error sending delete account email: {:#?}", e); + } + + Ok(()) +} + +#[derive(Deserialize)] +#[allow(non_snake_case)] +struct VerifyEmailTokenData { + UserId: String, + Token: String, +} + +#[post("/accounts/verify-email-token", data = "<data>")] +fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>, conn: DbConn) -> EmptyResult { + let data: VerifyEmailTokenData = data.into_inner().data; + + let mut user = match User::find_by_uuid(&data.UserId, &conn) { + Some(user) => user, + None => err!("User doesn't exist"), + }; + + let claims = match decode_verify_email(&data.Token) { + Ok(claims) => claims, + Err(_) => err!("Invalid claim"), + }; + + if claims.sub != user.uuid { + err!("Invalid claim"); + } + + user.verified_at = Some(Utc::now().naive_utc()); + user.last_verifying_at = None; + user.login_verify_count = 0; + if let Err(e) = user.save(&conn) { + error!("Error saving email verification: {:#?}", e); + } + + Ok(()) +} + +#[derive(Deserialize)] +#[allow(non_snake_case)] +struct DeleteRecoverData { + Email: String, +} + +#[post("/accounts/delete-recover", data="<data>")] +fn post_delete_recover(data: JsonUpcase<DeleteRecoverData>, conn: DbConn) -> EmptyResult { + let data: DeleteRecoverData = data.into_inner().data; + + let user = User::find_by_mail(&data.Email, &conn); + + if CONFIG.mail_enabled() { + if let Some(user) = user { + if let Err(e) = mail::send_delete_account(&user.email, &user.uuid) { + error!("Error sending delete account email: {:#?}", e); + } + } + Ok(()) + } else { + // We don't support sending emails, but we shouldn't allow anybody + // to delete accounts without at least logging in... And if the user + // cannot remember their password then they will need to contact + // the administrator to delete it... + err!("Please contact the administrator to delete your account"); + } +} + +#[derive(Deserialize)] +#[allow(non_snake_case)] +struct DeleteRecoverTokenData { + UserId: String, + Token: String, +} + +#[post("/accounts/delete-recover-token", data="<data>")] +fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, conn: DbConn) -> EmptyResult { + let data: DeleteRecoverTokenData = data.into_inner().data; + + let user = match User::find_by_uuid(&data.UserId, &conn) { + Some(user) => user, + None => err!("User doesn't exist"), + }; + + let claims = match decode_delete(&data.Token) { + Ok(claims) => claims, + Err(_) => err!("Invalid claim"), + }; + + if claims.sub != user.uuid { + err!("Invalid claim"); + } + + user.delete(&conn) +} + #[post("/accounts/delete", data = "<data>")] fn post_delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { delete_account(data, headers, conn) diff --git a/src/api/core/two_factor/email.rs b/src/api/core/two_factor/email.rs @@ -66,7 +66,7 @@ pub fn send_token(user_uuid: &str, conn: &DbConn) -> EmptyResult { let type_ = TwoFactorType::Email as i32; let mut twofactor = TwoFactor::find_by_user_and_type(user_uuid, type_, &conn)?; - let generated_token = generate_token(CONFIG.email_token_size())?; + let generated_token = crypto::generate_token(CONFIG.email_token_size())?; let mut twofactor_data = EmailTokenData::from_json(&twofactor.data)?; twofactor_data.set_token(generated_token); @@ -109,22 +109,6 @@ struct SendEmailData { MasterPasswordHash: String, } - -fn generate_token(token_size: u32) -> Result<String, Error> { - if token_size > 19 { - err!("Generating token failed") - } - - // 8 bytes to create an u64 for up to 19 token digits - let bytes = crypto::get_random(vec![0; 8]); - let mut bytes_array = [0u8; 8]; - bytes_array.copy_from_slice(&bytes); - - let number = u64::from_be_bytes(bytes_array) % 10u64.pow(token_size); - let token = format!("{:0size$}", number, size = token_size as usize); - Ok(token) -} - /// Send a verification email to the specified email address to check whether it exists/belongs to user. #[post("/two-factor/send-email", data = "<data>")] fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) -> EmptyResult { @@ -145,7 +129,7 @@ fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) - tf.delete(&conn)?; } - let generated_token = generate_token(CONFIG.email_token_size())?; + let generated_token = crypto::generate_token(CONFIG.email_token_size())?; let twofactor_data = EmailTokenData::new(data.Email, generated_token); // Uses EmailVerificationChallenge as type to show that it's not verified yet. diff --git a/src/api/identity.rs b/src/api/identity.rs @@ -3,6 +3,7 @@ use rocket::request::{Form, FormItems, FromForm}; use rocket::Route; use rocket_contrib::json::Json; use serde_json::Value; +use chrono::Utc; use crate::api::core::two_factor::email::EmailTokenData; use crate::api::core::two_factor::{duo, email, yubikey}; @@ -96,6 +97,34 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult ) } + if !user.verified_at.is_some() && CONFIG.mail_enabled() && CONFIG.signups_verify() { + let now = Utc::now().naive_utc(); + if user.last_verifying_at.is_none() || now.signed_duration_since(user.last_verifying_at.unwrap()).num_seconds() > CONFIG.signups_verify_resend_time() as i64 { + let resend_limit = CONFIG.signups_verify_resend_limit() as i32; + if resend_limit == 0 || user.login_verify_count < resend_limit { + // We want to send another email verification if we require signups to verify + // their email address, and we haven't sent them a reminder in a while... + let mut user = user; + user.last_verifying_at = Some(now); + user.login_verify_count = user.login_verify_count + 1; + + if let Err(e) = user.save(&conn) { + error!("Error updating user: {:#?}", e); + } + + if let Err(e) = mail::send_verify_email(&user.email, &user.uuid) { + error!("Error auto-sending email verification email: {:#?}", e); + } + } + } + + // We still want the login to fail until they actually verified the email address + err!( + "Please verify your email before trying again.", + format!("IP: {}. Username: {}.", ip.ip, username) + ) + } + let (mut device, new_device) = get_device(&data, &conn, &user); let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, &conn)?; diff --git a/src/auth.rs b/src/auth.rs @@ -18,6 +18,8 @@ lazy_static! { static ref JWT_HEADER: Header = Header::new(JWT_ALGORITHM); pub static ref JWT_LOGIN_ISSUER: String = format!("{}|login", CONFIG.domain()); pub static ref JWT_INVITE_ISSUER: String = format!("{}|invite", CONFIG.domain()); + pub static ref JWT_DELETE_ISSUER: String = format!("{}|delete", CONFIG.domain()); + pub static ref JWT_VERIFYEMAIL_ISSUER: String = format!("{}|verifyemail", CONFIG.domain()); pub static ref JWT_ADMIN_ISSUER: String = format!("{}|admin", CONFIG.domain()); static ref PRIVATE_RSA_KEY: Vec<u8> = match read_file(&CONFIG.private_rsa_key()) { Ok(key) => key, @@ -62,6 +64,14 @@ pub fn decode_invite(token: &str) -> Result<InviteJWTClaims, Error> { decode_jwt(token, JWT_INVITE_ISSUER.to_string()) } +pub fn decode_delete(token: &str) -> Result<DeleteJWTClaims, Error> { + decode_jwt(token, JWT_DELETE_ISSUER.to_string()) +} + +pub fn decode_verify_email(token: &str) -> Result<VerifyEmailJWTClaims, Error> { + decode_jwt(token, JWT_VERIFYEMAIL_ISSUER.to_string()) +} + pub fn decode_admin(token: &str) -> Result<AdminJWTClaims, Error> { decode_jwt(token, JWT_ADMIN_ISSUER.to_string()) } @@ -135,6 +145,54 @@ pub fn generate_invite_claims( } #[derive(Debug, Serialize, Deserialize)] +pub struct DeleteJWTClaims { + // Not before + pub nbf: i64, + // Expiration time + pub exp: i64, + // Issuer + pub iss: String, + // Subject + pub sub: String, +} + +pub fn generate_delete_claims( + uuid: String, +) -> DeleteJWTClaims { + let time_now = Utc::now().naive_utc(); + DeleteJWTClaims { + nbf: time_now.timestamp(), + exp: (time_now + Duration::days(5)).timestamp(), + iss: JWT_DELETE_ISSUER.to_string(), + sub: uuid, + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct VerifyEmailJWTClaims { + // Not before + pub nbf: i64, + // Expiration time + pub exp: i64, + // Issuer + pub iss: String, + // Subject + pub sub: String, +} + +pub fn generate_verify_email_claims( + uuid: String, +) -> DeleteJWTClaims { + let time_now = Utc::now().naive_utc(); + DeleteJWTClaims { + nbf: time_now.timestamp(), + exp: (time_now + Duration::days(5)).timestamp(), + iss: JWT_VERIFYEMAIL_ISSUER.to_string(), + sub: uuid, + } +} + +#[derive(Debug, Serialize, Deserialize)] pub struct AdminJWTClaims { // Not before pub nbf: i64, diff --git a/src/config.rs b/src/config.rs @@ -243,6 +243,12 @@ make_config! { disable_icon_download: bool, true, def, false; /// Allow new signups |> Controls if new users can register. Note that while this is disabled, users could still be invited signups_allowed: bool, true, def, true; + /// Require email verification on signups. This will prevent logins from succeeding until the address has been verified + signups_verify: bool, true, def, false; + /// If signups require email verification, automatically re-send verification email if it hasn't been sent for a while (in seconds) + signups_verify_resend_time: u64, true, def, 3_600; + /// If signups require email verification, limit how many emails are automatically sent when login is attempted (0 means no limit) + signups_verify_resend_limit: u32, true, def, 6; /// Allow signups only from this list of comma-separated domains signups_domains_whitelist: String, true, def, "".to_string(); /// Allow invitations |> Controls whether users can be invited by organization admins, even when signups are disabled @@ -595,6 +601,8 @@ fn load_templates(path: &str) -> Handlebars { } // First register default templates here + reg!("email/change_email", ".html"); + reg!("email/delete_account", ".html"); reg!("email/invite_accepted", ".html"); reg!("email/invite_confirmed", ".html"); reg!("email/new_device_logged_in", ".html"); @@ -602,6 +610,9 @@ fn load_templates(path: &str) -> Handlebars { reg!("email/pw_hint_some", ".html"); reg!("email/send_org_invite", ".html"); reg!("email/twofactor_email", ".html"); + reg!("email/verify_email", ".html"); + reg!("email/welcome", ".html"); + reg!("email/welcome_must_verify", ".html"); reg!("admin/base"); reg!("admin/login"); diff --git a/src/crypto.rs b/src/crypto.rs @@ -4,6 +4,7 @@ use ring::{digest, hmac, pbkdf2}; use std::num::NonZeroU32; +use crate::error::Error; static DIGEST_ALG: &digest::Algorithm = &digest::SHA256; const OUTPUT_LEN: usize = digest::SHA256_OUTPUT_LEN; @@ -52,6 +53,21 @@ pub fn get_random(mut array: Vec<u8>) -> Vec<u8> { array } +pub fn generate_token(token_size: u32) -> Result<String, Error> { + if token_size > 19 { + err!("Generating token failed") + } + + // 8 bytes to create an u64 for up to 19 token digits + let bytes = get_random(vec![0; 8]); + let mut bytes_array = [0u8; 8]; + bytes_array.copy_from_slice(&bytes); + + let number = u64::from_be_bytes(bytes_array) % 10u64.pow(token_size); + let token = format!("{:0size$}", number, size = token_size as usize); + Ok(token) +} + // // Constant time compare // diff --git a/src/db/models/device.rs b/src/db/models/device.rs @@ -1,6 +1,7 @@ use chrono::{NaiveDateTime, Utc}; use super::User; +use crate::CONFIG; #[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)] #[table_name = "devices"] @@ -87,7 +88,7 @@ impl Device { premium: true, name: user.name.to_string(), email: user.email.to_string(), - email_verified: true, + email_verified: !CONFIG.mail_enabled() || user.verified_at.is_some(), orgowner, orgadmin, diff --git a/src/db/models/user.rs b/src/db/models/user.rs @@ -11,8 +11,13 @@ pub struct User { pub uuid: String, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, + pub verified_at: Option<NaiveDateTime>, + pub last_verifying_at: Option<NaiveDateTime>, + pub login_verify_count: i32, pub email: String, + pub email_new: Option<String>, + pub email_new_token: Option<String>, pub name: String, pub password_hash: Vec<u8>, @@ -56,9 +61,14 @@ impl User { uuid: crate::util::get_uuid(), created_at: now, updated_at: now, + verified_at: None, + last_verifying_at: None, + login_verify_count: 0, name: email.clone(), email, akey: String::new(), + email_new: None, + email_new_token: None, password_hash: Vec::new(), salt: crypto::get_random_64(), @@ -135,7 +145,7 @@ impl User { "Id": self.uuid, "Name": self.name, "Email": self.email, - "EmailVerified": true, + "EmailVerified": !CONFIG.mail_enabled() || self.verified_at.is_some(), "Premium": true, "MasterPasswordHint": self.password_hint, "Culture": "en-US", diff --git a/src/db/schemas/mysql/schema.rs b/src/db/schemas/mysql/schema.rs @@ -101,7 +101,12 @@ table! { uuid -> Varchar, created_at -> Datetime, updated_at -> Datetime, + verified_at -> Nullable<Datetime>, + last_verifying_at -> Nullable<Datetime>, + login_verify_count -> Integer, email -> Varchar, + email_new -> Nullable<Varchar>, + email_new_token -> Nullable<Varchar>, name -> Text, password_hash -> Blob, salt -> Blob, diff --git a/src/db/schemas/postgresql/schema.rs b/src/db/schemas/postgresql/schema.rs @@ -101,7 +101,12 @@ table! { uuid -> Text, created_at -> Timestamp, updated_at -> Timestamp, + verified_at -> Nullable<Timestamp>, + last_verifying_at -> Nullable<Timestamp>, + login_verify_count -> Integer, email -> Text, + email_new -> Nullable<Text>, + email_new_token -> Nullable<Text>, name -> Text, password_hash -> Binary, salt -> Binary, @@ -170,4 +175,4 @@ allow_tables_to_appear_in_same_query!( users, users_collections, users_organizations, -); -\ No newline at end of file +); diff --git a/src/db/schemas/sqlite/schema.rs b/src/db/schemas/sqlite/schema.rs @@ -101,7 +101,12 @@ table! { uuid -> Text, created_at -> Timestamp, updated_at -> Timestamp, + verified_at -> Nullable<Timestamp>, + last_verifying_at -> Nullable<Timestamp>, + login_verify_count -> Integer, email -> Text, + email_new -> Nullable<Text>, + email_new_token -> Nullable<Text>, name -> Text, password_hash -> Binary, salt -> Binary, diff --git a/src/mail.rs b/src/mail.rs @@ -8,7 +8,7 @@ use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; use quoted_printable::encode_to_str; use crate::api::EmptyResult; -use crate::auth::{encode_jwt, generate_invite_claims}; +use crate::auth::{encode_jwt, generate_invite_claims, generate_delete_claims, generate_verify_email_claims}; use crate::error::Error; use crate::CONFIG; use chrono::NaiveDateTime; @@ -95,6 +95,73 @@ pub fn send_password_hint(address: &str, hint: Option<String>) -> EmptyResult { send_email(&address, &subject, &body_html, &body_text) } +pub fn send_delete_account(address: &str, uuid: &str) -> EmptyResult { + let claims = generate_delete_claims( + uuid.to_string(), + ); + let delete_token = encode_jwt(&claims); + + let (subject, body_html, body_text) = get_text( + "email/delete_account", + json!({ + "url": CONFIG.domain(), + "user_id": uuid, + "email": percent_encode(address.as_bytes(), NON_ALPHANUMERIC).to_string(), + "token": delete_token, + }), + )?; + + send_email(&address, &subject, &body_html, &body_text) +} + +pub fn send_verify_email(address: &str, uuid: &str) -> EmptyResult { + let claims = generate_verify_email_claims( + uuid.to_string(), + ); + let verify_email_token = encode_jwt(&claims); + + let (subject, body_html, body_text) = get_text( + "email/verify_email", + json!({ + "url": CONFIG.domain(), + "user_id": uuid, + "email": percent_encode(address.as_bytes(), NON_ALPHANUMERIC).to_string(), + "token": verify_email_token, + }), + )?; + + send_email(&address, &subject, &body_html, &body_text) +} + +pub fn send_welcome(address: &str) -> EmptyResult { + let (subject, body_html, body_text) = get_text( + "email/welcome", + json!({ + "url": CONFIG.domain(), + }), + )?; + + send_email(&address, &subject, &body_html, &body_text) +} + +pub fn send_welcome_must_verify(address: &str, uuid: &str) -> EmptyResult { + let claims = generate_verify_email_claims( + uuid.to_string(), + ); + let verify_email_token = encode_jwt(&claims); + + let (subject, body_html, body_text) = get_text( + "email/welcome_must_verify", + json!({ + "url": CONFIG.domain(), + "user_id": uuid, + "token": verify_email_token, + }), + )?; + + send_email(&address, &subject, &body_html, &body_text) +} + pub fn send_invite( address: &str, uuid: &str, @@ -183,6 +250,18 @@ pub fn send_token(address: &str, token: &str) -> EmptyResult { send_email(&address, &subject, &body_html, &body_text) } +pub fn send_change_email(address: &str, token: &str) -> EmptyResult { + let (subject, body_html, body_text) = get_text( + "email/change_email", + json!({ + "url": CONFIG.domain(), + "token": token, + }), + )?; + + send_email(&address, &subject, &body_html, &body_text) +} + fn send_email(address: &str, subject: &str, body_html: &str, body_text: &str) -> EmptyResult { let html = PartBuilder::new() .body(encode_to_str(body_html)) diff --git a/src/static/templates/email/change_email.hbs b/src/static/templates/email/change_email.hbs @@ -0,0 +1,6 @@ +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> diff --git a/src/static/templates/email/change_email.html.hbs b/src/static/templates/email/change_email.html.hbs @@ -0,0 +1,129 @@ +Your Email Change +<!----------------> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <head> + <meta name="viewport" content="width=device-width" /> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <title>Bitwarden_rs</title> + </head> + <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> + <style type="text/css"> +  body { + 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; + } + body * { + 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; + } + img { + max-width: 100%; + border: none; + } + body { + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; + width: 100% !important; + height: 100%; + line-height: 25px; + } + body { + background-color: #f6f6f6; + } + @media only screen and (max-width: 600px) { + body { + padding: 0 !important; + } + .container { + padding: 0 !important; + width: 100% !important; + } + .container-table { + padding: 0 !important; + width: 100% !important; + } + .content { + padding: 0 0 10px 0 !important; + } + .content-wrap { + padding: 10px !important; + } + .invoice { + width: 100% !important; + } + .main { + border-right: none !important; + border-left: none !important; + border-radius: 0 !important; + } + .logo { + padding-top: 10px !important; + } + .footer { + margin-top: 10px !important; + } + .indented { + padding-left: 10px; + } + } + </style> + <table class="body-wrap" cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; width: 100%;" bgcolor="#f6f6f6"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td valign="middle" class="aligncenter middle logo" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; padding: 20px 0 10px;" align="center"> + <img src="{{url}}/bwrs_static/logo-gray.png" alt="" width="250" height="39" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /> + </td> + </tr> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="container" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: 600px;" valign="top"> + <table cellpadding="0" cellspacing="0" class="container-table" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: 600px;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="content" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; display: block; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 0; line-height: 0; margin: 0 auto; max-width: 600px; padding-bottom: 20px;" valign="top"> + <table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; margin: 0; -webkit-text-size-adjust: none; border: 1px solid #e9e9e9; border-radius: 3px;" bgcolor="white"> + <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-wrap" 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: 20px; -webkit-text-size-adjust: none;" valign="top"> + <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"> + To finalize changing your email address enter the following code in web vault: <b 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;">{{token}}</b> + </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 try to change an email address, you can safely ignore this email. + </td> + </tr> + </table> + </td> + </tr> + </table> + <table class="footer" cellpadding="0" cellspacing="0" width="100%" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; width: 100%;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> + <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + </body> +</html> diff --git a/src/static/templates/email/delete_account.hbs b/src/static/templates/email/delete_account.hbs @@ -0,0 +1,12 @@ +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> diff --git a/src/static/templates/email/delete_account.html.hbs b/src/static/templates/email/delete_account.html.hbs @@ -0,0 +1,137 @@ +Delete Your Account +<!----------------> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <head> + <meta name="viewport" content="width=device-width" /> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <title>Bitwarden_rs</title> + </head> + <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> + <style type="text/css"> +  body { + 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; + } + body * { + 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; + } + img { + max-width: 100%; + border: none; + } + body { + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; + width: 100% !important; + height: 100%; + line-height: 25px; + } + body { + background-color: #f6f6f6; + } + @media only screen and (max-width: 600px) { + body { + padding: 0 !important; + } + .container { + padding: 0 !important; + width: 100% !important; + } + .container-table { + padding: 0 !important; + width: 100% !important; + } + .content { + padding: 0 0 10px 0 !important; + } + .content-wrap { + padding: 10px !important; + } + .invoice { + width: 100% !important; + } + .main { + border-right: none !important; + border-left: none !important; + border-radius: 0 !important; + } + .logo { + padding-top: 10px !important; + } + .footer { + margin-top: 10px !important; + } + .indented { + padding-left: 10px; + } + } + </style> + <table class="body-wrap" cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; width: 100%;" bgcolor="#f6f6f6"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td valign="middle" class="aligncenter middle logo" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; padding: 20px 0 10px;" align="center"> + <img src="{{url}}/bwrs_static/logo-gray.png" alt="" width="250" height="39" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /> + </td> + </tr> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="container" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: 600px;" valign="top"> + <table cellpadding="0" cellspacing="0" class="container-table" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: 600px;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="content" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; display: block; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 0; line-height: 0; margin: 0 auto; max-width: 600px; padding-bottom: 20px;" valign="top"> + <table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; margin: 0; -webkit-text-size-adjust: none; border: 1px solid #e9e9e9; border-radius: 3px;" bgcolor="white"> + <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-wrap" 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: 20px; -webkit-text-size-adjust: none;" valign="top"> + <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"> + click the link below to delete your account. + </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}}/#/verify-recover-delete?userId={{user_id}}&token={{token}}&email={{email}}" + 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;"> + Delete Your Account + </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 this email to delete your account, you can safely ignore this email. + </td> + </tr> + </table> + </td> + </tr> + </table> + <table class="footer" cellpadding="0" cellspacing="0" width="100%" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; width: 100%;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> + <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + </body> +</html> diff --git a/src/static/templates/email/pw_hint_none.hbs b/src/static/templates/email/pw_hint_none.hbs @@ -1,3 +1,7 @@ -Sorry, you have no password hint... +Your master password hint <!----------------> -Sorry, you have not specified any 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 did not request your master password hint you can safely ignore this email. diff --git a/src/static/templates/email/pw_hint_none.html.hbs b/src/static/templates/email/pw_hint_none.html.hbs @@ -101,6 +101,11 @@ Sorry, you have no password hint... </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;" valign="top"> + 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. + </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;" valign="top"> If you did not request your master password hint you can safely ignore this email. </td> </tr> diff --git a/src/static/templates/email/pw_hint_some.hbs b/src/static/templates/email/pw_hint_some.hbs @@ -5,4 +5,6 @@ You (or someone) recently requested your master password hint. Your hint is: "{{hint}}" Log in: <a href="{{url}}">Web Vault</a> +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 did not request your master password hint you can safely ignore this email. diff --git a/src/static/templates/email/pw_hint_some.html.hbs b/src/static/templates/email/pw_hint_some.html.hbs @@ -107,6 +107,11 @@ Your master password hint </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;" valign="top"> + 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. + </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;" valign="top"> If you did not request your master password hint you can safely ignore this email. </td> </tr> diff --git a/src/static/templates/email/verify_email.hbs b/src/static/templates/email/verify_email.hbs @@ -0,0 +1,12 @@ +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> diff --git a/src/static/templates/email/verify_email.html.hbs b/src/static/templates/email/verify_email.html.hbs @@ -0,0 +1,137 @@ +Verify Your Email +<!----------------> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <head> + <meta name="viewport" content="width=device-width" /> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <title>Bitwarden_rs</title> + </head> + <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> + <style type="text/css"> +  body { + 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; + } + body * { + 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; + } + img { + max-width: 100%; + border: none; + } + body { + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; + width: 100% !important; + height: 100%; + line-height: 25px; + } + body { + background-color: #f6f6f6; + } + @media only screen and (max-width: 600px) { + body { + padding: 0 !important; + } + .container { + padding: 0 !important; + width: 100% !important; + } + .container-table { + padding: 0 !important; + width: 100% !important; + } + .content { + padding: 0 0 10px 0 !important; + } + .content-wrap { + padding: 10px !important; + } + .invoice { + width: 100% !important; + } + .main { + border-right: none !important; + border-left: none !important; + border-radius: 0 !important; + } + .logo { + padding-top: 10px !important; + } + .footer { + margin-top: 10px !important; + } + .indented { + padding-left: 10px; + } + } + </style> + <table class="body-wrap" cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; width: 100%;" bgcolor="#f6f6f6"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td valign="middle" class="aligncenter middle logo" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; padding: 20px 0 10px;" align="center"> + <img src="{{url}}/bwrs_static/logo-gray.png" alt="" width="250" height="39" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /> + </td> + </tr> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="container" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: 600px;" valign="top"> + <table cellpadding="0" cellspacing="0" class="container-table" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: 600px;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="content" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; display: block; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 0; line-height: 0; margin: 0 auto; max-width: 600px; padding-bottom: 20px;" valign="top"> + <table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; margin: 0; -webkit-text-size-adjust: none; border: 1px solid #e9e9e9; border-radius: 3px;" bgcolor="white"> + <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-wrap" 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: 20px; -webkit-text-size-adjust: none;" valign="top"> + <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 for 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}}/#/verify-email/?userId={{user_id}}&token={{token}}" + 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> + </td> + </tr> + </table> + <table class="footer" cellpadding="0" cellspacing="0" width="100%" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; width: 100%;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> + <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + </body> +</html> diff --git a/src/static/templates/email/welcome.hbs b/src/static/templates/email/welcome.hbs @@ -0,0 +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> diff --git a/src/static/templates/email/welcome.html.hbs b/src/static/templates/email/welcome.html.hbs @@ -0,0 +1,129 @@ +Welcome +<!----------------> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <head> + <meta name="viewport" content="width=device-width" /> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <title>Bitwarden_rs</title> + </head> + <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> + <style type="text/css"> +  body { + 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; + } + body * { + 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; + } + img { + max-width: 100%; + border: none; + } + body { + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; + width: 100% !important; + height: 100%; + line-height: 25px; + } + body { + background-color: #f6f6f6; + } + @media only screen and (max-width: 600px) { + body { + padding: 0 !important; + } + .container { + padding: 0 !important; + width: 100% !important; + } + .container-table { + padding: 0 !important; + width: 100% !important; + } + .content { + padding: 0 0 10px 0 !important; + } + .content-wrap { + padding: 10px !important; + } + .invoice { + width: 100% !important; + } + .main { + border-right: none !important; + border-left: none !important; + border-radius: 0 !important; + } + .logo { + padding-top: 10px !important; + } + .footer { + margin-top: 10px !important; + } + .indented { + padding-left: 10px; + } + } + </style> + <table class="body-wrap" cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; width: 100%;" bgcolor="#f6f6f6"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td valign="middle" class="aligncenter middle logo" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; padding: 20px 0 10px;" align="center"> + <img src="{{url}}/bwrs_static/logo-gray.png" alt="" width="250" height="39" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /> + </td> + </tr> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="container" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: 600px;" valign="top"> + <table cellpadding="0" cellspacing="0" class="container-table" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: 600px;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="content" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; display: block; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 0; line-height: 0; margin: 0 auto; max-width: 600px; padding-bottom: 20px;" valign="top"> + <table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; margin: 0; -webkit-text-size-adjust: none; border: 1px solid #e9e9e9; border-radius: 3px;" bgcolor="white"> + <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-wrap" 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: 20px; -webkit-text-size-adjust: none;" valign="top"> + <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"> + Thank you for creating an account at <a href="{{url}}">{{url}}</a>. You may now log in with your new account. + </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 create an account, you can safely ignore this email. + </td> + </tr> + </table> + </td> + </tr> + </table> + <table class="footer" cellpadding="0" cellspacing="0" width="100%" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; width: 100%;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> + <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + </body> +</html> diff --git a/src/static/templates/email/welcome_must_verify.hbs b/src/static/templates/email/welcome_must_verify.hbs @@ -0,0 +1,12 @@ +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> diff --git a/src/static/templates/email/welcome_must_verify.html.hbs b/src/static/templates/email/welcome_must_verify.html.hbs @@ -0,0 +1,137 @@ +Welcome +<!----------------> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <head> + <meta name="viewport" content="width=device-width" /> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <title>Bitwarden_rs</title> + </head> + <body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 25px; width: 100% !important;" bgcolor="#f6f6f6"> + <style type="text/css"> +  body { + 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; + } + body * { + 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; + } + img { + max-width: 100%; + border: none; + } + body { + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; + width: 100% !important; + height: 100%; + line-height: 25px; + } + body { + background-color: #f6f6f6; + } + @media only screen and (max-width: 600px) { + body { + padding: 0 !important; + } + .container { + padding: 0 !important; + width: 100% !important; + } + .container-table { + padding: 0 !important; + width: 100% !important; + } + .content { + padding: 0 0 10px 0 !important; + } + .content-wrap { + padding: 10px !important; + } + .invoice { + width: 100% !important; + } + .main { + border-right: none !important; + border-left: none !important; + border-radius: 0 !important; + } + .logo { + padding-top: 10px !important; + } + .footer { + margin-top: 10px !important; + } + .indented { + padding-left: 10px; + } + } + </style> + <table class="body-wrap" cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; width: 100%;" bgcolor="#f6f6f6"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td valign="middle" class="aligncenter middle logo" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; padding: 20px 0 10px;" align="center"> + <img src="{{url}}/bwrs_static/logo-gray.png" alt="" width="250" height="39" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /> + </td> + </tr> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="container" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: 600px;" valign="top"> + <table cellpadding="0" cellspacing="0" class="container-table" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both !important; color: #333; display: block !important; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto; max-width: 600px !important; width: 600px;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="content" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; display: block; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 0; line-height: 0; margin: 0 auto; max-width: 600px; padding-bottom: 20px;" valign="top"> + <table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; margin: 0; -webkit-text-size-adjust: none; border: 1px solid #e9e9e9; border-radius: 3px;" bgcolor="white"> + <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-wrap" 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: 20px; -webkit-text-size-adjust: none;" valign="top"> + <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"> + 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. + </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}}/#/verify-email/?userId={{user_id}}&token={{token}}" + 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 create an account, you can safely ignore this email. + </td> + </tr> + </table> + </td> + </tr> + </table> + <table class="footer" cellpadding="0" cellspacing="0" width="100%" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; clear: both; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; width: 100%;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td class="aligncenter social-icons" align="center" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 15px 0 0 0;" valign="top"> + <table cellpadding="0" cellspacing="0" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0 auto;"> + <tr style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0;"> + <td style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/dani-garcia/bitwarden_rs" target="_blank" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12px; line-height: 20px; margin: 0; text-decoration: underline;"><img src="{{url}}/bwrs_static/mail-github.png" alt="GitHub" width="30" height="30" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border: none; box-sizing: border-box; color: #333; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; max-width: 100%;" /></a></td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + </body> +</html>