diff --git a/apps/server/AliasVault.Client/Auth/Pages/Unlock.razor b/apps/server/AliasVault.Client/Auth/Pages/Unlock.razor index 11fa80ddd..1c4ba48ba 100644 --- a/apps/server/AliasVault.Client/Auth/Pages/Unlock.razor +++ b/apps/server/AliasVault.Client/Auth/Pages/Unlock.razor @@ -11,12 +11,13 @@ @using Microsoft.Extensions.Localization - @if (IsLoading) { + } else if (IsWebAuthnLoading) { +

@Localizer["LoggingInWithWebAuthn"] @@ -35,6 +36,9 @@ else

@Localizer["QuickUnlockDescription"]

+ + +
+
+ +@code { + private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Settings.ImportExport.ResetVaultSection", "AliasVault.Client"); + + /// + /// Navigates to the reset vault page. + /// + private void ResetVault() + { + NavigationManager.NavigateTo("settings/import-export/reset-vault"); + } +} diff --git a/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/ImportExport.razor b/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/ImportExport.razor index 545faf6b9..a0711ed84 100644 --- a/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/ImportExport.razor +++ b/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/ImportExport.razor @@ -7,8 +7,6 @@ @using AliasVault.RazorComponents.Services @using AliasVault.Client.Main.Pages.Settings.ImportExport.Components @using AliasVault.ImportExport -@using System.ComponentModel.DataAnnotations -@using AliasVault.Client.Resources @Localizer["PageTitle"] @@ -54,90 +52,12 @@ -
-

@Localizer["ResetVaultSectionTitle"]

-
-

- @Localizer["ResetVaultSectionDescription"] -

- @if (!_showResetPasswordConfirm) - { -
- - -
-

@Localizer["ResetVaultPleaseNote"]

-
    -
  • @Localizer["ResetVaultCredentialsDeletedNote"]
  • -
  • @Localizer["ResetVaultEmailAliasesKeptNote"]
  • -
  • @Localizer["ResetVaultSettingsKeptNote"]
  • -
  • @Localizer["ResetVaultIrreversibleNote"]
  • -
-
- - -
- - -
- -
- - -
-
-
- } - else - { -
- - -
-

@Localizer["ResetVaultPleaseNote"]

-
    -
  • @Localizer["ResetVaultDeletionIrreversibleNote"]
  • -
-
- - - - - -
- - -
- -
- - -
-
-
- } -
-
+ @code { private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Settings.ImportExport.ImportExport", "AliasVault.Client"); private ExportType _currentExportType; - /// - /// The model for the reset vault username confirmation step. - /// - private readonly ResetVaultUsernameModel _resetUsernameModel = new(); - - /// - /// The model for the reset vault password confirmation step. - /// - private readonly ResetVaultPasswordModel _resetPasswordModel = new(); - - /// - /// Whether to show the password confirmation step for reset vault. - /// - private bool _showResetPasswordConfirm; - private enum ExportType { Csv, @@ -230,105 +150,4 @@ var username = await GetUsernameAsync(); return $"aliasvault-export-{username}-{dateStr}.{extension}"; } - - /// - /// Confirms the username for vault reset. - /// - private async Task ConfirmResetUsername() - { - GlobalNotificationService.ClearMessages(); - - if (string.IsNullOrEmpty(_resetUsernameModel.Username)) - { - GlobalNotificationService.AddErrorMessage(Localizer["ResetVaultUsernameRequired"], true); - return; - } - - var username = await GetUsernameAsync(); - var usernameMatches = string.Equals(_resetUsernameModel.Username.Trim(), username.Trim(), StringComparison.OrdinalIgnoreCase); - if (!usernameMatches) - { - GlobalNotificationService.AddErrorMessage(Localizer["ResetVaultUsernameDoesNotMatch"], true); - return; - } - - _showResetPasswordConfirm = true; - StateHasChanged(); - } - - /// - /// Confirms the password and performs the vault reset. - /// - private async Task ResetVaultConfirmed() - { - GlobalLoadingSpinner.Show(Localizer["ResetVaultProgressMessage"]); - GlobalNotificationService.ClearMessages(); - - try - { - // Verify the password locally using the AuthService - var isValidPassword = await AuthService.ValidateEncryptionKeyAsync(_resetPasswordModel.Password); - if (!isValidPassword) - { - GlobalNotificationService.AddErrorMessage(Localizer["ResetVaultPasswordIncorrect"], true); - return; - } - - // Clear local vault data - await CredentialService.DeleteAllAsync(); - await DbService.SaveAsync(); - - GlobalNotificationService.AddSuccessMessage(Localizer["ResetVaultSuccessMessage"], true); - - // Reset the form state - _showResetPasswordConfirm = false; - _resetUsernameModel.Username = string.Empty; - _resetPasswordModel.Password = string.Empty; - StateHasChanged(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error resetting vault"); - GlobalNotificationService.AddErrorMessage(Localizer["ResetVaultErrorMessage"], true); - } - finally - { - GlobalLoadingSpinner.Hide(); - } - } - - /// - /// Cancels the vault reset process. - /// - private void CancelResetVault() - { - _showResetPasswordConfirm = false; - _resetUsernameModel.Username = string.Empty; - _resetPasswordModel.Password = string.Empty; - StateHasChanged(); - } - - /// - /// Model for the reset vault username confirmation step. - /// - public class ResetVaultUsernameModel - { - /// - /// Gets or sets the username. - /// - [Required(ErrorMessageResourceType = typeof(ValidationMessages), ErrorMessageResourceName = nameof(ValidationMessages.UsernameRequired))] - public string Username { get; set; } = string.Empty; - } - - /// - /// Model for the reset vault password confirmation step. - /// - public class ResetVaultPasswordModel - { - /// - /// Gets or sets the password. - /// - [Required(ErrorMessageResourceType = typeof(ValidationMessages), ErrorMessageResourceName = nameof(ValidationMessages.PasswordRequired))] - public string Password { get; set; } = string.Empty; - } } diff --git a/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/ResetVault.razor b/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/ResetVault.razor new file mode 100644 index 000000000..4342c8f15 --- /dev/null +++ b/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/ResetVault.razor @@ -0,0 +1,231 @@ +@page "/settings/import-export/reset-vault" +@inherits MainBase +@inject HttpClient Http +@inject CredentialService CredentialService +@inject ILogger Logger +@using System.Text.Json +@using AliasVault.Client.Utilities +@using AliasVault.Shared.Models.WebApi.Auth +@using AliasVault.Cryptography.Client +@using System.ComponentModel.DataAnnotations +@using AliasVault.Client.Resources +@using Microsoft.Extensions.Localization + +@Localizer["PageTitle"] + +
+
+ +

@Localizer["PageTitle"]

+
+
+ +
+ @if (!_showPasswordConfirm) + { +
+

@Localizer["ResetVaultPleaseNote"]

+
    +
  • @Localizer["ResetVaultCredentialsDeletedNote"]
  • +
  • @Localizer["ResetVaultEmailAliasesKeptNote"]
  • +
  • @Localizer["ResetVaultSettingsKeptNote"]
  • +
  • @Localizer["ResetVaultIrreversibleNote"]
  • +
+
+ + +
+ + +
+ +
+ + +
+
+ } + else + { + + +
+

@Localizer["ResetVaultPleaseNote"]

+
    +
  • @Localizer["ResetVaultDeletionIrreversibleNote"]
  • +
+
+ + + + + +
+ + +
+ +
+ + +
+
+ } +
+ +@code { + /// + /// The model for the username confirmation step. + /// + private readonly ResetVaultUsernameModel _usernameModel = new(); + + /// + /// The model for the password confirmation step. + /// + private readonly ResetVaultPasswordModel _passwordModel = new(); + + private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Pages.Settings.ImportExport.ResetVault", "AliasVault.Client"); + private IStringLocalizer ApiErrorLocalizer => LocalizerFactory.Create("ApiErrors", "AliasVault.Client"); + + /// + /// Whether to show the password confirmation step. + /// + private bool _showPasswordConfirm; + + /// + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbImportExport"], Url = "/settings/import-export" }); + BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbResetVault"] }); + } + + /// + /// Confirms the username for vault reset. + /// + private async Task ConfirmUsername() + { + GlobalNotificationService.ClearMessages(); + + if (string.IsNullOrEmpty(_usernameModel.Username)) + { + GlobalNotificationService.AddErrorMessage(Localizer["ResetVaultUsernameRequired"], true); + return; + } + + var username = await GetUsernameAsync(); + var usernameMatches = string.Equals(_usernameModel.Username.Trim(), username.Trim(), StringComparison.OrdinalIgnoreCase); + if (!usernameMatches) + { + GlobalNotificationService.AddErrorMessage(Localizer["ResetVaultUsernameDoesNotMatch"], true); + return; + } + + _showPasswordConfirm = true; + StateHasChanged(); + } + + /// + /// Confirms the password and performs the vault reset. + /// + private async Task ResetVaultConfirmed() + { + GlobalLoadingSpinner.Show(Localizer["ResetVaultProgressMessage"]); + GlobalNotificationService.ClearMessages(); + + try + { + // Get current username + var username = await GetUsernameAsync(); + + // Send request to server to get user salt and encryption parameters + var result = await Http.PostAsJsonAsync("v1/Auth/login", new LoginInitiateRequest(username)); + var responseContent = await result.Content.ReadAsStringAsync(); + + if (!result.IsSuccessStatusCode) + { + var errors = ApiResponseUtility.ParseErrorResponse(responseContent, ApiErrorLocalizer); + foreach (var error in errors) + { + GlobalNotificationService.AddErrorMessage(error, true); + } + return; + } + + var loginResponse = JsonSerializer.Deserialize(responseContent); + if (loginResponse == null) + { + GlobalNotificationService.AddErrorMessage(Localizer["ResetVaultErrorMessage"], true); + return; + } + + // Derive password hash using server parameters + byte[] passwordHash = await Encryption.DeriveKeyFromPasswordAsync(_passwordModel.Password, loginResponse.Salt, loginResponse.EncryptionType, loginResponse.EncryptionSettings); + + // Verify the password locally using the derived password hash + var isValidPassword = await AuthService.ValidateEncryptionKeyAsync(passwordHash); + if (!isValidPassword) + { + GlobalNotificationService.AddErrorMessage(Localizer["ResetVaultPasswordIncorrect"], true); + return; + } + + // Clear local vault data by soft-deleting all credentials + var credentials = await CredentialService.LoadAllAsync(); + foreach (var credential in credentials) + { + await CredentialService.SoftDeleteEntryAsync(credential.Id); + } + + // Save the database + await DbService.SaveDatabaseAsync(); + + GlobalNotificationService.AddSuccessMessage(Localizer["ResetVaultSuccessMessage"]); + + // Redirect to credentials overview page to show the empty vault + NavigationManager.NavigateTo("/credentials"); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error resetting vault"); + GlobalNotificationService.AddErrorMessage(Localizer["ResetVaultErrorMessage"], true); + } + finally + { + GlobalLoadingSpinner.Hide(); + } + } + + /// + /// Cancels the vault reset process. + /// + private void Cancel() + { + NavigationManager.NavigateTo("/settings/import-export"); + } + + /// + /// Model for the username confirmation step. + /// + public class ResetVaultUsernameModel + { + /// + /// Gets or sets the username. + /// + [Required(ErrorMessageResourceType = typeof(ValidationMessages), ErrorMessageResourceName = nameof(ValidationMessages.UsernameRequired))] + public string Username { get; set; } = string.Empty; + } + + /// + /// Model for the password confirmation step. + /// + public class ResetVaultPasswordModel + { + /// + /// Gets or sets the password. + /// + [Required(ErrorMessageResourceType = typeof(ValidationMessages), ErrorMessageResourceName = nameof(ValidationMessages.PasswordRequired))] + public string Password { get; set; } = string.Empty; + } +} diff --git a/apps/server/AliasVault.Client/Resources/Components/Main/Pages/Settings/ImportExport/ResetVault.en.resx b/apps/server/AliasVault.Client/Resources/Components/Main/Pages/Settings/ImportExport/ResetVault.en.resx new file mode 100644 index 000000000..a218cb334 --- /dev/null +++ b/apps/server/AliasVault.Client/Resources/Components/Main/Pages/Settings/ImportExport/ResetVault.en.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Reset Vault + Page title + + + Import / Export + Breadcrumb for import export + + + Reset Vault + Breadcrumb for reset vault + + + Please note: + Reset vault please note prefix + + + All encrypted credentials in your vault will be permanently deleted + Reset vault note about credentials being deleted + + + Your email aliases will be preserved and can be re-used after resetting your vault + Reset vault note about email aliases being kept + + + Your account settings and preferences will be preserved + Reset vault note about settings being kept + + + This action cannot be undone + Reset vault note about action being irreversible + + + To continue, please type your username to confirm + Reset vault username confirmation label + + + Continue with vault reset + Reset vault continue button + + + Final warning: You are about to permanently delete all your credentials! + Reset vault final warning message + + + This deletion is irreversible and cannot be undone + Reset vault final step irreversible note + + + Enter your password to confirm + Reset vault password confirmation label + + + Reset my vault + Reset vault final confirmation button + + + Username is required. + Reset vault username required error + + + The username you entered does not match your account username. + Reset vault username mismatch error + + + The password you entered is incorrect. + Reset vault password incorrect error + + + Resetting vault... + Reset vault progress message + + + Your vault has been successfully reset. All credentials have been deleted and you can now start fresh. + Reset vault success message + + + An error occurred while resetting your vault. Please try again. + Reset vault error message + + \ No newline at end of file diff --git a/apps/server/AliasVault.Client/Resources/Components/Main/Settings/ImportExport/ResetVaultSection.en.resx b/apps/server/AliasVault.Client/Resources/Components/Main/Settings/ImportExport/ResetVaultSection.en.resx new file mode 100644 index 000000000..028f8f473 --- /dev/null +++ b/apps/server/AliasVault.Client/Resources/Components/Main/Settings/ImportExport/ResetVaultSection.en.resx @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Reset Vault + Reset vault section title + + + This option allows you to completely empty your vault while keeping your account and email aliases. Use this if you want to start fresh after importing data from another password manager or if you want to clear all existing credentials to start over. + Reset vault section description + + + Reset vault + Reset vault button + + diff --git a/apps/server/AliasVault.Client/Resources/Pages/Main/Settings/ImportExport/ImportExport.en.resx b/apps/server/AliasVault.Client/Resources/Pages/Main/Settings/ImportExport/ImportExport.en.resx index 3e09fe7ce..86c76cce3 100644 --- a/apps/server/AliasVault.Client/Resources/Pages/Main/Settings/ImportExport/ImportExport.en.resx +++ b/apps/server/AliasVault.Client/Resources/Pages/Main/Settings/ImportExport/ImportExport.en.resx @@ -112,76 +112,4 @@ Are you sure you want to continue with the export? This option allows you to completely empty your vault while keeping your account and email aliases. Use this if you want to start fresh after importing data from another password manager or if you want to clear all existing credentials to start over. Reset vault section description - - Warning: This action is irreversible! - Reset vault warning message - - - Please note: - Reset vault please note prefix - - - All encrypted credentials in your vault will be permanently deleted - Reset vault note about credentials being deleted - - - Your email aliases will be preserved and remain claimed by your account - Reset vault note about email aliases being kept - - - Your account settings and preferences will be preserved - Reset vault note about settings being kept - - - This action cannot be undone - Reset vault note about action being irreversible - - - To continue, please type your username to confirm - Reset vault username confirmation label - - - Continue with vault reset - Reset vault continue button - - - Final warning: You are about to permanently delete all your credentials! - Reset vault final warning message - - - This deletion is irreversible and cannot be undone - Reset vault final step irreversible note - - - Enter your password to confirm - Reset vault password confirmation label - - - Reset my vault - Reset vault final confirmation button - - - Username is required. - Reset vault username required error - - - The username you entered does not match your account username. - Reset vault username mismatch error - - - The password you entered is incorrect. - Reset vault password incorrect error - - - Resetting vault... - Reset vault progress message - - - Your vault has been successfully reset. All credentials have been deleted and you can now start fresh. - Reset vault success message - - - An error occurred while resetting your vault. Please try again. - Reset vault error message - \ No newline at end of file