diff --git a/Moonlight/App/Models/Misc/AuditLogType.cs b/Moonlight/App/Models/Misc/AuditLogType.cs index 13fafa1..a2ccf6d 100644 --- a/Moonlight/App/Models/Misc/AuditLogType.cs +++ b/Moonlight/App/Models/Misc/AuditLogType.cs @@ -23,4 +23,5 @@ public enum AuditLogType CleanupEnabled, CleanupDisabled, CleanupTriggered, + PasswordChange, } \ No newline at end of file diff --git a/Moonlight/App/Models/Misc/LoginDataModel.cs b/Moonlight/App/Models/Misc/LoginDataModel.cs new file mode 100644 index 0000000..d6c0020 --- /dev/null +++ b/Moonlight/App/Models/Misc/LoginDataModel.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace Moonlight.App.Models.Misc; + +public class LoginDataModel +{ + [Required(ErrorMessage = "You need to enter an email address")] + [EmailAddress(ErrorMessage = "You need to enter a valid email address")] + public string Email { get; set; } + + [Required(ErrorMessage = "You need to enter a password")] + [MinLength(8, ErrorMessage = "You need to enter a password with minimum 8 characters in lenght")] + public string Password { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Services/TotpService.cs b/Moonlight/App/Services/TotpService.cs index be76546..0e359fe 100644 --- a/Moonlight/App/Services/TotpService.cs +++ b/Moonlight/App/Services/TotpService.cs @@ -46,8 +46,7 @@ public class TotpService public async Task Enable() { var user = (await IdentityService.Get())!; - - user.TotpEnabled = true; + user.TotpSecret = GenerateSecret(); UserRepository.Update(user); @@ -55,6 +54,14 @@ public class TotpService await AuditLogService.Log(AuditLogType.EnableTotp, user.Email); } + public async Task EnforceTotpLogin() + { + var user = (await IdentityService.Get())!; + + user.TotpEnabled = true; + UserRepository.Update(user); + } + public async Task Disable() { var user = (await IdentityService.Get())!; diff --git a/Moonlight/Shared/Components/Auth/Login.razor b/Moonlight/Shared/Components/Auth/Login.razor index f9b27e7..28ca0ce 100644 --- a/Moonlight/Shared/Components/Auth/Login.razor +++ b/Moonlight/Shared/Components/Auth/Login.razor @@ -10,6 +10,7 @@ @using Moonlight.App.Exceptions @using Logging.Net @using Moonlight.App.Database.Entities +@using Moonlight.App.Models.Misc @using Moonlight.App.Services.OAuth2 @using Moonlight.App.Services.Sessions @@ -113,7 +114,7 @@ @code { - private User User = new(); + private LoginDataModel User = new(); private bool TotpRequired = false; private string TotpCode = ""; diff --git a/Moonlight/Shared/Components/Navigations/ProfileNavigation.razor b/Moonlight/Shared/Components/Navigations/ProfileNavigation.razor index 67b3816..ab04ef6 100644 --- a/Moonlight/Shared/Components/Navigations/ProfileNavigation.razor +++ b/Moonlight/Shared/Components/Navigations/ProfileNavigation.razor @@ -27,12 +27,17 @@ diff --git a/Moonlight/Shared/Views/Profile/Security.razor b/Moonlight/Shared/Views/Profile/Security.razor new file mode 100644 index 0000000..8b29c58 --- /dev/null +++ b/Moonlight/Shared/Views/Profile/Security.razor @@ -0,0 +1,288 @@ +@page "/profile/security" + +@using Moonlight.Shared.Components.Navigations +@using QRCoder +@using Moonlight.App.Services.LogServices +@using Moonlight.App.Services.Sessions +@using Moonlight.App.Services +@using Moonlight.App.Services.Interop +@using System.Text.RegularExpressions +@using Moonlight.App.Database.Entities +@using Moonlight.App.Models.Misc + +@inject SmartTranslateService SmartTranslateService +@inject AuditLogService AuditLogService +@inject TotpService TotpService +@inject NavigationManager NavigationManager +@inject IdentityService IdentityService +@inject UserService UserService +@inject AlertService AlertService +@inject ToastService ToastService + + + +
+
+
+

+ Security +

+
+
+ + +
+
+ @if (TotpEnabled) + { +
+ + + + + + +
+
+

+ Your account is secured with 2fa +

+
+ anyone write a fancy text here? +
+
+ + +
+
+ } + else + { +
+ + + + + + +
+
+

+ Secure your account +

+
+ 2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login. +
+
+ + + Enable + +
+
+ } + + + + +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+ +@code +{ + private bool TotpEnabled = false; + private bool EnablingTotp = false; + private string TotpSecret = ""; + private User User; + private string Issuer = "Moonlight"; + private string currentTotp = ""; + + private string Password = ""; + + private async void Enable() + { + await AuditLogService.Log(AuditLogType.EnableTotp, "Totp enabled"); + await TotpService.Enable(); + TotpEnabled = await TotpService.GetEnabled(); + TotpSecret = await TotpService.GetSecret(); + EnablingTotp = true; + StateHasChanged(); + } + + public async Task CheckAndSaveTotp() + { + if (await TotpService.Verify(TotpSecret, currentTotp)) + { + await TotpService.EnforceTotpLogin(); + TotpEnabled = await TotpService.GetEnabled(); + TotpSecret = await TotpService.GetSecret(); + await ToastService.Success("Successfully enabled 2fa!"); + } + else + { + await AlertService.Error("2fa code incorrect", "The given 2fa code is incorrect. Maybe check if the code in your 2fa app has changed."); + } + } + + private async void Disable() + { + await AuditLogService.Log(AuditLogType.DisableTotp, "Totp disabled"); + await TotpService.Disable(); + NavigationManager.NavigateTo(NavigationManager.Uri, true); + } + + private async Task Load(LazyLoader lazyLoader) + { + await lazyLoader.SetText("Requesting secrets"); + + TotpEnabled = await TotpService.GetEnabled(); + TotpSecret = await TotpService.GetSecret(); + + await lazyLoader.SetText("Requesting identity"); + User = await IdentityService.Get(); + + await InvokeAsync(StateHasChanged); + } + + private async Task ChangePassword() + { + if (Regex.IsMatch(Password, @"^(?=.*[A-Za-z])(?=.*\d)[A-Za-z@$!%*#.,?&\d]{8,}$")) + { + await UserService.ChangePassword(User, Password); + + await AuditLogService.Log(AuditLogType.PasswordChange, "The password has been set to a new one"); + + // Reload to make the user login again + NavigationManager.NavigateTo(NavigationManager.Uri, true); + } + else + { + await AlertService.Error("Error", "Your password must be at least 8 characters and must contain a number"); + } + } +} \ No newline at end of file diff --git a/Moonlight/Shared/Views/Profile/Subscriptions.razor b/Moonlight/Shared/Views/Profile/Subscriptions.razor index 2a371a6..30b8d19 100644 --- a/Moonlight/Shared/Views/Profile/Subscriptions.razor +++ b/Moonlight/Shared/Views/Profile/Subscriptions.razor @@ -8,7 +8,7 @@ @inject SmartTranslateService SmartTranslateService @inject NavigationManager NavigationManager - +