diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index 558cafcd4..1f7f77268 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -10,7 +10,6 @@ using Bit.Core.Models.Table; using Bit.Core.Enums; using System.Linq; using Bit.Core.Repositories; -using System.Collections.Generic; namespace Bit.Api.Controllers { @@ -104,6 +103,30 @@ namespace Bit.Api.Controllers await Task.Delay(2000); throw new BadRequestException(ModelState); } + + [HttpPost("verify-email")] + public async Task PostVerifyEmail() + { + var user = await _userService.GetUserByPrincipalAsync(User); + if(user == null) + { + throw new UnauthorizedAccessException(); + } + + await _userService.SendEmailVerificationAsync(user); + } + + [HttpPost("verify-email-token")] + [AllowAnonymous] + public async Task PostVerifyEmailToken() + { + var user = await _userService.GetUserByIdAsync(new Guid()); + if(user == null) + { + throw new UnauthorizedAccessException(); + } + await _userService.ConfirmEmailAsync(user, ""); + } [HttpPut("password")] [HttpPost("password")] diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 29897c7dd..9214db84c 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -14,6 +14,8 @@ + + diff --git a/src/Core/Identity/TwoFactorRememberTokenProvider.cs b/src/Core/Identity/TwoFactorRememberTokenProvider.cs index f7ee1d07a..450bc938a 100644 --- a/src/Core/Identity/TwoFactorRememberTokenProvider.cs +++ b/src/Core/Identity/TwoFactorRememberTokenProvider.cs @@ -7,8 +7,6 @@ namespace Bit.Core.Identity { public class TwoFactorRememberTokenProvider : DataProtectorTokenProvider { - private readonly GlobalSettings _globalSettings; - public TwoFactorRememberTokenProvider( IDataProtectionProvider dataProtectionProvider, IOptions options) diff --git a/src/Core/MailTemplates/VerifyEmail.cshtml b/src/Core/MailTemplates/VerifyEmail.cshtml new file mode 100644 index 000000000..9dcc5847a --- /dev/null +++ b/src/Core/MailTemplates/VerifyEmail.cshtml @@ -0,0 +1,8 @@ +@model Bit.Core.Models.Mail.VerifyEmailModel +@{ + Layout = "_BasicMailLayout"; +} +

+ Verify this email address for your bitwarden account by clicking the following link: +

+

@Model.Url

diff --git a/src/Core/MailTemplates/VerifyEmail.text.cshtml b/src/Core/MailTemplates/VerifyEmail.text.cshtml new file mode 100644 index 000000000..ffa21fae2 --- /dev/null +++ b/src/Core/MailTemplates/VerifyEmail.text.cshtml @@ -0,0 +1,8 @@ +@model Bit.Core.Models.Mail.VerifyEmailModel +@{ + Layout = "_BasicMailLayout.text"; +} +Verify this email address for your bitwarden +account by clicking the following link: + +@Model.Url diff --git a/src/Core/Models/Mail/VerifyEmailModel.cs b/src/Core/Models/Mail/VerifyEmailModel.cs new file mode 100644 index 000000000..2f150ae61 --- /dev/null +++ b/src/Core/Models/Mail/VerifyEmailModel.cs @@ -0,0 +1,15 @@ +using System; + +namespace Bit.Core.Models.Mail +{ + public class VerifyEmailModel : BaseMailModel + { + public string Url => string.Format("{0}/verify-email?userId={1}&token={2}", + WebVaultUrl, + UserId, + Token); + + public Guid UserId { get; set; } + public string Token { get; set; } + } +} diff --git a/src/Core/Services/IMailService.cs b/src/Core/Services/IMailService.cs index 7e382b6ba..1ed6370b6 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -1,12 +1,14 @@ using System.Threading.Tasks; using Bit.Core.Models.Table; using System.Collections.Generic; +using System; namespace Bit.Core.Services { public interface IMailService { Task SendWelcomeEmailAsync(User user); + Task SendVerifyEmailEmailAsync(string email, Guid userId, string token); Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail); Task SendChangeEmailEmailAsync(string newEmailAddress, string token); Task SendTwoFactorEmailAsync(string email, string token); diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index 02f3969fc..ffeaffd41 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -24,6 +24,8 @@ namespace Bit.Core.Services Task VerifyTwoFactorEmailAsync(User user, string token); Task StartU2fRegistrationAsync(User user); Task CompleteU2fRegistrationAsync(User user, string deviceResponse); + Task SendEmailVerificationAsync(User user); + Task ConfirmEmailAsync(User user, string token); Task InitiateEmailChangeAsync(User user, string newEmail); Task ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword, string token, string key); diff --git a/src/Core/Services/Implementations/RazorViewMailService.cs b/src/Core/Services/Implementations/RazorViewMailService.cs index a29f90bc9..7da465c07 100644 --- a/src/Core/Services/Implementations/RazorViewMailService.cs +++ b/src/Core/Services/Implementations/RazorViewMailService.cs @@ -30,6 +30,23 @@ namespace Bit.Core.Services _engine = new RazorLightEngine(core, lookup); } + public async Task SendVerifyEmailEmailAsync(string email, Guid userId, string token) + { + var message = CreateDefaultMessage("Verify Your Email", email); + var model = new VerifyEmailModel + { + Token = token, + UserId = userId, + WebVaultUrl = _globalSettings.BaseVaultUri, + SiteName = _globalSettings.SiteName + }; + message.HtmlContent = _engine.Parse("VerifyEmail", model); + message.TextContent = _engine.Parse("VerifyEmail.text", model); + message.MetaData.Add("SendGridBypassListManagement", true); + + await _mailDeliveryService.SendEmailAsync(message); + } + public async Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail) { var message = CreateDefaultMessage("Your Email Change", toEmail); diff --git a/src/Core/Services/Implementations/SendGridTemplateMailService.cs b/src/Core/Services/Implementations/SendGridTemplateMailService.cs index bf496ec7a..9d799de21 100644 --- a/src/Core/Services/Implementations/SendGridTemplateMailService.cs +++ b/src/Core/Services/Implementations/SendGridTemplateMailService.cs @@ -10,6 +10,7 @@ namespace Bit.Core.Services public class SendGridTemplateMailService : IMailService { private const string WelcomeTemplateId = "045f8ad5-5547-4fa2-8d3d-6d46e401164d"; + private const string VerifyEmailTemplateId = "TODO"; private const string ChangeEmailAlreadyExistsTemplateId = "b69d2038-6ad9-4cf6-8f7f-7880921cba43"; private const string ChangeEmailTemplateId = "ec2c1471-8292-4f17-b6b6-8223d514f86e"; private const string TwoFactorEmailTemplateId = "264cfe69-5258-4c89-8d90-76b4659de589"; @@ -45,6 +46,21 @@ namespace Bit.Core.Services await _mailDeliveryService.SendEmailAsync(message); } + public async Task SendVerifyEmailEmailAsync(string email, Guid userId, string token) + { + var message = CreateDefaultMessage( + "Verify Your Email", + email, + VerifyEmailTemplateId); + + AddSubstitution(message, "{{token}}", Uri.EscapeDataString(token)); + AddSubstitution(message, "{{userId}}", userId.ToString()); + AddCategories(message, new List { AdministrativeCategoryName, "Verify Email" }); + message.MetaData.Add("SendGridBypassListManagement", true); + + await _mailDeliveryService.SendEmailAsync(message); + } + public async Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail) { var message = CreateDefaultMessage( diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 2f37e410c..5b6fc9527 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -15,6 +15,7 @@ using Bit.Core.Models.Business; using U2fLib = U2F.Core.Crypto.U2F; using U2F.Core.Models; using U2F.Core.Utils; +using Bit.Core.Exceptions; namespace Bit.Core.Services { @@ -289,6 +290,17 @@ namespace Bit.Core.Services return true; } + public async Task SendEmailVerificationAsync(User user) + { + if(user.EmailVerified) + { + throw new BadRequestException("Email already verifed."); + } + + var token = await base.GenerateEmailConfirmationTokenAsync(user); + await _mailService.SendVerifyEmailEmailAsync(user.Email, user.Id, token); + } + public async Task InitiateEmailChangeAsync(User user, string newEmail) { var existingUser = await _userRepository.GetByEmailAsync(newEmail); diff --git a/src/Core/Services/NoopImplementations/NoopMailService.cs b/src/Core/Services/NoopImplementations/NoopMailService.cs index ff319c5e4..705cccdfc 100644 --- a/src/Core/Services/NoopImplementations/NoopMailService.cs +++ b/src/Core/Services/NoopImplementations/NoopMailService.cs @@ -12,6 +12,11 @@ namespace Bit.Core.Services return Task.FromResult(0); } + public Task SendVerifyEmailEmailAsync(string email, Guid userId, string hint) + { + return Task.FromResult(0); + } + public Task SendChangeEmailEmailAsync(string newEmailAddress, string token) { return Task.FromResult(0);