Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Crypter.API/Controllers/UserSettingController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,8 @@ IActionResult MakeErrorResponse(UpdateNotificationSettingsError error)
#pragma warning disable CS8524
return error switch
{
UpdateNotificationSettingsError.UnknownError => MakeErrorResponseBase(HttpStatusCode.InternalServerError, error)
UpdateNotificationSettingsError.UnknownError => MakeErrorResponseBase(HttpStatusCode.InternalServerError, error),
UpdateNotificationSettingsError.MissingNotificationChannel => MakeErrorResponseBase(HttpStatusCode.BadRequest, error)
};
#pragma warning restore CS8524
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ namespace Crypter.Common.Contracts.Features.UserSettings.NotificationSettings;

public enum UpdateNotificationSettingsError
{
UnknownError
UnknownError,
MissingNotificationChannel
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,20 @@ public async Task<Either<UpdateNotificationSettingsError, NotificationSettings>>
{
var userData = await _dataContext.Users
.Where(x => x.Id == request.UserId)
.Select(x => new { x.NotificationSetting })
.Select(x => new { x.EmailAddress, x.NotificationSetting })
.FirstOrDefaultAsync(CancellationToken.None);

if (userData is null)
{
return UpdateNotificationSettingsError.UnknownError;
}


bool enablingNotifications = request.Request.NotifyOnTransferReceived || request.Request.EmailNotifications;
if (enablingNotifications && string.IsNullOrEmpty(userData.EmailAddress))
{
return UpdateNotificationSettingsError.MissingNotificationChannel;
}

if (userData.NotificationSetting is null)
{
UserNotificationSettingEntity newNotificationSettings =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,24 @@
* Contact the current copyright holder to discuss commercial license options.
*/

using System.Linq;
using System.Threading.Tasks;
using Crypter.Common.Client.Interfaces.HttpClients;
using Crypter.Common.Client.Interfaces.Repositories;
using Crypter.Common.Contracts.Features.UserAuthentication;
using Crypter.Common.Contracts.Features.UserSettings;
using Crypter.Common.Contracts.Features.UserSettings.NotificationSettings;
using Crypter.Common.Enums;
using Crypter.Common.Infrastructure;
using Crypter.Crypto.Common;
using Crypter.Crypto.Common.DigitalSignature;
using Crypter.Crypto.Providers.Default;
using Crypter.DataAccess;
using Crypter.DataAccess.Entities;
using EasyMonads;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;

namespace Crypter.Test.Integration_Tests.UserSettings_Tests;
Expand All @@ -43,10 +53,19 @@ internal class UpdateNotificationSettings_Tests
private ICrypterApiClient? _client;
private ITokenRepository? _clientTokenRepository;

private DefaultCryptoProvider? _cryptoProvider;
private Ed25519KeyPair? _emailVerificationKeyPair;

[SetUp]
public async Task SetupTestAsync()
{
_factory = await AssemblySetup.CreateWebApplicationFactoryAsync();
_cryptoProvider = new DefaultCryptoProvider();
_emailVerificationKeyPair = _cryptoProvider.DigitalSignature.GenerateKeyPair();
ICryptoProvider mockCryptoProvider = Mocks.CreateDeterministicCryptoProvider(_emailVerificationKeyPair);
IServiceCollection overrideServices = new ServiceCollection();
overrideServices.AddSingleton(mockCryptoProvider);

_factory = await AssemblySetup.CreateWebApplicationFactoryAsync(true, overrideServices);
(_client, _clientTokenRepository) = AssemblySetup.SetupCrypterApiClient(_factory.CreateClient());
await AssemblySetup.InitializeRespawnerAsync();
}
Expand All @@ -56,32 +75,124 @@ public async Task TeardownTestAsync()
{
if (_factory is not null)
{
await _factory.DisposeAsync();
await _factory.DisposeAsync();
}
await AssemblySetup.ResetServerDataAsync();
}

[Test]
public async Task Update_Notification_Preferences_Works_Async()
{
await RegisterAndLoginAsync();

NotificationSettings request = new NotificationSettings(false, false);
Either<UpdateNotificationSettingsError, NotificationSettings> result =
await _client!.UserSetting.UpdateNotificationSettingsAsync(request);

Assert.That(result.IsRight, Is.True);
}

[Test]
public async Task Update_Notification_Preferences_Works_For_Verified_Email_Address_Async()
{
await RegisterAndLoginAsync(TestData.DefaultEmailAdress);
await VerifyEmailAddressAsync();

NotificationSettings request = new NotificationSettings(true, true);
Either<UpdateNotificationSettingsError, NotificationSettings> result =
await _client!.UserSetting.UpdateNotificationSettingsAsync(request);

Assert.That(result.IsRight, Is.True);
result.DoRight(settings =>
{
Assert.That(settings.EmailNotifications, Is.True);
Assert.That(settings.NotifyOnTransferReceived, Is.True);
});
}

[Test]
public async Task Update_Notification_Preferences_Fails_Without_Email_Address_Async()
{
await RegisterAndLoginAsync();

NotificationSettings request = new NotificationSettings(true, true);
Either<UpdateNotificationSettingsError, NotificationSettings> result =
await _client!.UserSetting.UpdateNotificationSettingsAsync(request);

Assert.That(result.IsLeft, Is.True);
Assert.That(result.LeftOrDefault(UpdateNotificationSettingsError.UnknownError),
Is.EqualTo(UpdateNotificationSettingsError.MissingNotificationChannel));
}

[Test]
public async Task Update_Notification_Preferences_Fails_For_Unverified_Email_Address_Async()
{
await RegisterAndLoginAsync(TestData.DefaultEmailAdress);

NotificationSettings request = new NotificationSettings(true, true);
Either<UpdateNotificationSettingsError, NotificationSettings> result =
await _client!.UserSetting.UpdateNotificationSettingsAsync(request);

Assert.That(result.IsLeft, Is.True);
Assert.That(result.LeftOrDefault(UpdateNotificationSettingsError.UnknownError),
Is.EqualTo(UpdateNotificationSettingsError.MissingNotificationChannel));
}

[Test]
public async Task Disable_Notification_Preferences_Works_Without_Email_Address_Async()
{
await RegisterAndLoginAsync();

NotificationSettings request = new NotificationSettings(false, false);
Either<UpdateNotificationSettingsError, NotificationSettings> result =
await _client!.UserSetting.UpdateNotificationSettingsAsync(request);

Assert.That(result.IsRight, Is.True);
result.DoRight(settings =>
{
Assert.That(settings.EmailNotifications, Is.False);
Assert.That(settings.NotifyOnTransferReceived, Is.False);
});
}

private async Task RegisterAndLoginAsync(string? emailAddress = null)
{
RegistrationRequest registrationRequest =
TestData.GetRegistrationRequest(TestData.DefaultUsername, TestData.DefaultPassword);
Either<RegistrationError, Unit> _ = await _client!.UserAuthentication.RegisterAsync(registrationRequest);
TestData.GetRegistrationRequest(TestData.DefaultUsername, TestData.DefaultPassword, emailAddress);
Either<RegistrationError, Unit> registrationResult =
await _client!.UserAuthentication.RegisterAsync(registrationRequest);
Assert.That(registrationResult.IsRight, Is.True);

LoginRequest loginRequest =
TestData.GetLoginRequest(TestData.DefaultUsername, TestData.DefaultPassword);
Either<LoginError, LoginResponse> loginResult = await _client!.UserAuthentication.LoginAsync(loginRequest);
Assert.That(loginResult.IsRight, Is.True);

await loginResult.DoRightAsync(async loginResponse =>
{
await _clientTokenRepository!.StoreAuthenticationTokenAsync(loginResponse.AuthenticationToken);
await _clientTokenRepository!.StoreRefreshTokenAsync(loginResponse.RefreshToken, TokenType.Session);
});
}

NotificationSettings request = new NotificationSettings(false, false);
Either<UpdateNotificationSettingsError, NotificationSettings> result =
await _client!.UserSetting.UpdateNotificationSettingsAsync(request);
private async Task VerifyEmailAddressAsync()
{
// Allow the background service to "send" the verification email and save the email verification data
await Task.Delay(5000);

using IServiceScope scope = _factory!.Services.CreateScope();
DataContext dataContext = scope.ServiceProvider.GetRequiredService<DataContext>();
UserEmailChangeEntity changeData = await dataContext.UserEmailChangeRequests
.Where(x => x.User!.Username == TestData.DefaultUsername)
.FirstAsync();

string encodedVerificationCode = UrlSafeEncoder.EncodeGuidUrlSafe(changeData.Code!.Value);
byte[] signedVerificationCode = _cryptoProvider!.DigitalSignature.GenerateSignature(
_emailVerificationKeyPair!.PrivateKey, changeData.Code.Value.ToByteArray());
string encodedSignature = UrlSafeEncoder.EncodeBytesUrlSafe(signedVerificationCode);

VerifyEmailAddressRequest request = new VerifyEmailAddressRequest(encodedVerificationCode, encodedSignature);
Either<VerifyEmailAddressError, Unit> result = await _client!.UserSetting.VerifyUserEmailAddressAsync(request);
Assert.That(result.IsRight, Is.True);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
<form>
<div class="form-check form-switch">
<label class="form-check-label" for="enableTransferNotifications">Notify me when I receive something</label>
<input @bind="_enableTransferNotificationsEdit" class="form-check-input" type="checkbox" id="enableTransferNotifications" disabled="@(!_isEditing)"/>
<input @bind="_enableTransferNotificationsEdit" class="form-check-input" type="checkbox" id="enableTransferNotifications" disabled="@(!_isEditing || !_emailAddressVerified)"/>
</div>
<h3>Notification Methods</h3>
<div class="form-check form-switch">
<label class="form-check-label" for="enableEmailNotifications">Email</label>
<input @bind="_enableTransferNotificationsEdit" class="form-check-input" type="checkbox" id="enableEmailNotifications" disabled="@(!_isEditing)"/>
<input @bind="_enableTransferNotificationsEdit" class="form-check-input" type="checkbox" id="enableEmailNotifications" disabled="@(!_isEditing || !_emailAddressVerified)"/>
</div>
<div class="mb-3">
<span class="small text-secondary">*Alternative notifications methods are not available yet</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ await CrypterApiService.UserSetting.UpdateNotificationSettingsAsync(newNotificat

private void OnContactInfoChanged(object? sender, UserContactInfoChangedEventArgs args)
{
_emailAddressVerified = args.RequestedEmailAddress;
_emailAddressVerified = args.VerifiedEmailAddress.IsSome;
StateHasChanged();
}

Expand Down
Loading