From f5f84ce25c96411a30dc6af75968a0a1d1e3cc49 Mon Sep 17 00:00:00 2001 From: "Fritz (Fredrick Seitz)" Date: Thu, 24 Aug 2023 17:13:03 -0400 Subject: [PATCH] fix: update email change flow --- Bulwark.Auth.sln | 1 + docker-compose.yaml | 11 ++++++ src/Bulwark.Auth.Core/AccountService.cs | 11 +++--- src/Bulwark.Auth.Core/IAccountService.cs | 2 +- .../IAccountRepository.cs | 2 +- .../MongoDbAccount.cs | 12 +++++-- src/Bulwark.Auth/Bulwark.Auth.csproj | 4 ++- .../Controllers/AccountsController.cs | 34 +++++++++++++++++-- .../Templates/Email/ChangeEmail.cshtml | 12 +++++++ .../MongoDbAccountTests.cs | 1 + 10 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 docker-compose.yaml create mode 100644 src/Bulwark.Auth/Templates/Email/ChangeEmail.cshtml diff --git a/Bulwark.Auth.sln b/Bulwark.Auth.sln index 96d0275..92b9f5f 100644 --- a/Bulwark.Auth.sln +++ b/Bulwark.Auth.sln @@ -13,6 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md updateVersion.sh = updateVersion.sh .releaserc = .releaserc + docker-compose.yaml = docker-compose.yaml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bulwark.Auth", "src\Bulwark.Auth\Bulwark.Auth.csproj", "{5701FC78-6876-4EB9-8E32-D8EFC12FF9EB}" diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..371cf8c --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,11 @@ +services: + mongodb: + image: "mongo:latest" + ports: + - 27017:27017 + mailhog: + image: "mailhog/mailhog:latest" + ports: + - 1025:1025 + - 8025:8025 + \ No newline at end of file diff --git a/src/Bulwark.Auth.Core/AccountService.cs b/src/Bulwark.Auth.Core/AccountService.cs index 0bcc0ee..7cdf686 100644 --- a/src/Bulwark.Auth.Core/AccountService.cs +++ b/src/Bulwark.Auth.Core/AccountService.cs @@ -97,16 +97,17 @@ public async Task Delete(string email, string accessToken) /// /// /// - public async Task ChangeEmail(string oldEmail, string newEmail, + public async Task ChangeEmail(string oldEmail, string newEmail, string accessToken) { try { var token = await ValidAccessToken(oldEmail, accessToken); - if (token != null) - { - await _accountRepository.ChangeEmail(oldEmail, newEmail); - } + if (token == null) throw new BulwarkTokenException("Invalid access token"); + var verificationModel = await _accountRepository.ChangeEmail(oldEmail, newEmail); + + return new VerificationToken(verificationModel.Token, + verificationModel.Created); } catch (BulwarkDbDuplicateException exception) { diff --git a/src/Bulwark.Auth.Core/IAccountService.cs b/src/Bulwark.Auth.Core/IAccountService.cs index d3fce8c..583a75e 100644 --- a/src/Bulwark.Auth.Core/IAccountService.cs +++ b/src/Bulwark.Auth.Core/IAccountService.cs @@ -8,7 +8,7 @@ Task Create(string email, string password); Task Verify(string email, string verificationToken); Task Delete(string email, string accessToken); - Task ChangeEmail(string oldEmail, string newEmail, + Task ChangeEmail(string oldEmail, string newEmail, string accessToken); Task ChangePassword(string email, string newPassword, string accessToken); diff --git a/src/Bulwark.Auth.Repositories/IAccountRepository.cs b/src/Bulwark.Auth.Repositories/IAccountRepository.cs index aa2a97b..80978ab 100644 --- a/src/Bulwark.Auth.Repositories/IAccountRepository.cs +++ b/src/Bulwark.Auth.Repositories/IAccountRepository.cs @@ -12,7 +12,7 @@ public interface IAccountRepository Task Delete(string email); Task Disable(string email); Task Enable(string email); - Task ChangeEmail(string oldEmail, string newEmail); + Task ChangeEmail(string oldEmail, string newEmail); Task ChangePassword(string email, string newPassword); Task LinkSocial(string email, SocialProvider provider); Task ForgotPassword(string email); diff --git a/src/Bulwark.Auth.Repositories/MongoDbAccount.cs b/src/Bulwark.Auth.Repositories/MongoDbAccount.cs index d016525..f3f5556 100644 --- a/src/Bulwark.Auth.Repositories/MongoDbAccount.cs +++ b/src/Bulwark.Auth.Repositories/MongoDbAccount.cs @@ -172,14 +172,20 @@ public async Task Enable(string email) /// /// /// - public async Task ChangeEmail(string email, string newEmail) + public async Task ChangeEmail(string email, string newEmail) { try { var update = Builders.Update .Set(p => p.Email, newEmail) + .Set(p => p.IsVerified, false) .Set(p => p.Modified, DateTime.Now); - + + var verification = new VerificationModel(newEmail, + Guid.NewGuid().ToString()); + + await _verificationCollection.InsertOneAsync(verification); + var result = await _accountCollection. UpdateOneAsync(a => a.Email == email, update); @@ -188,6 +194,8 @@ public async Task ChangeEmail(string email, string newEmail) throw new BulwarkDbException($"Email: {email} could not be found"); } + + return verification; } catch(MongoWriteException exception) { diff --git a/src/Bulwark.Auth/Bulwark.Auth.csproj b/src/Bulwark.Auth/Bulwark.Auth.csproj index d2599c1..7abf375 100644 --- a/src/Bulwark.Auth/Bulwark.Auth.csproj +++ b/src/Bulwark.Auth/Bulwark.Auth.csproj @@ -41,8 +41,10 @@ - + + Always + Always diff --git a/src/Bulwark.Auth/Controllers/AccountsController.cs b/src/Bulwark.Auth/Controllers/AccountsController.cs index fa9ae70..506d23e 100644 --- a/src/Bulwark.Auth/Controllers/AccountsController.cs +++ b/src/Bulwark.Auth/Controllers/AccountsController.cs @@ -124,20 +124,48 @@ public async Task DeleteAccount(Delete payload) [Route("email")] public async Task ChangeEmail(ChangeEmail payload) { + var subject = "Please verify your account"; + const string templateDir = "Templates/Email/ChangeEmail.cshtml"; + try { EmailValidator.Validate(payload.NewEmail); - await _accountService.ChangeEmail(payload.Email, + var verificationToken = await _accountService.ChangeEmail(payload.Email, payload.NewEmail, payload.AccessToken); + if (Environment.GetEnvironmentVariable("SERVICE_MODE")?.ToLower() == "test") + { + subject = verificationToken.Value; + } + + var verificationEmail = _email + .To(payload.Email) + .Subject(subject) + .UsingTemplateFromFile(templateDir, + new + { + payload.Email, + VerificationToken = verificationToken.Value, + VerificationUrl = Environment.GetEnvironmentVariable("VERIFICATION_URL"), + WebsiteName = Environment.GetEnvironmentVariable("WEBSITE_NAME") + }); + + var emailResponse = await verificationEmail.SendAsync(); + if (!emailResponse.Successful) + { + return Problem( + title: "Email successfully changed but failed to send verification email", + detail: string.Join( ",", emailResponse.ErrorMessages), + statusCode: StatusCodes.Status400BadRequest); + } return NoContent(); } catch (Exception exception) { return Problem( - title: "Bad Input", + title: "Cannot change email", detail: exception.Message, statusCode: StatusCodes.Status400BadRequest - ); + ); } } diff --git a/src/Bulwark.Auth/Templates/Email/ChangeEmail.cshtml b/src/Bulwark.Auth/Templates/Email/ChangeEmail.cshtml new file mode 100644 index 0000000..fbc1d2a --- /dev/null +++ b/src/Bulwark.Auth/Templates/Email/ChangeEmail.cshtml @@ -0,0 +1,12 @@ +Hello, + +Your email on your account has been changed. Please click the link below to verify and +enable the account with the new email. + +@Model.VerificationUrl?email=@Model.Email&vt=@Model.VerificationToken + +Please don't reply to this email - This message is from an automated system. + +Best regards, + +@Model.WebsiteName \ No newline at end of file diff --git a/tests/Bulwark.Auth.Repository.Tests/MongoDbAccountTests.cs b/tests/Bulwark.Auth.Repository.Tests/MongoDbAccountTests.cs index f21ae48..051f6b1 100644 --- a/tests/Bulwark.Auth.Repository.Tests/MongoDbAccountTests.cs +++ b/tests/Bulwark.Auth.Repository.Tests/MongoDbAccountTests.cs @@ -154,6 +154,7 @@ public async void ChangeEmailTest() await _accountRepository.ChangeEmail(user, newEmail); var account = await _accountRepository.GetAccount(newEmail); Assert.True(account.Email == newEmail); + Assert.False(account.IsVerified); } catch (System.Exception exception) {