< Summary

Information
Class: LOCKnet.App.ViewModels.LoginViewModel
Assembly: LOCKnet.App
File(s): /home/runner/work/LOCKnet/LOCKnet/src/LOCKnet.App/ViewModels/LoginViewModel.cs
Line coverage
100%
Covered lines: 66
Uncovered lines: 0
Coverable lines: 66
Total lines: 106
Line coverage: 100%
Branch coverage
87%
Covered branches: 14
Total branches: 16
Branch coverage: 87.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
Unlock(...)75%44100%
Setup(...)87.5%88100%
SecureEquals(...)100%11100%
MapError(...)100%44100%

File(s)

/home/runner/work/LOCKnet/LOCKnet/src/LOCKnet.App/ViewModels/LoginViewModel.cs

#LineLine coverage
 1using CommunityToolkit.Mvvm.ComponentModel;
 2using LOCKnet.App.ViewModels;
 3using LOCKnet.Core.Crypto;
 4using System.Security;
 5
 6namespace LOCKnet.App.ViewModels;
 7
 8/// <summary>
 9/// ViewModel für den Login-Screen (Ersteinrichtung + Entsperren).
 10/// </summary>
 11public partial class LoginViewModel : ViewModelBase
 12{
 13  public event EventHandler? UnlockSucceeded;
 14
 15  [ObservableProperty]
 916  private string _errorMessage = string.Empty;
 17
 18  [ObservableProperty]
 19  private bool _isSetupMode;
 20
 921  public LoginViewModel()
 922  {
 923    IsSetupMode = !AppServices.Current.MasterKeyManager.IsInitialized;
 924  }
 25
 26  public void Unlock(SecureString password)
 427  {
 428    ArgumentNullException.ThrowIfNull(password);
 429    ErrorMessage = string.Empty;
 30    try
 431    {
 432      var unlock = AppServices.Current.MasterKeyManager.Unlock(password);
 333      if (unlock is null)
 234      {
 235        ErrorMessage = "Falsches Passwort.";
 236        return;
 37      }
 38
 139      AppServices.Current.SessionManager.Open(unlock.VaultKey);
 140      AppServices.Current.ActivityMonitor.Start();
 141      UnlockSucceeded?.Invoke(this, EventArgs.Empty);
 142    }
 143    catch (Exception ex)
 144    {
 145      ErrorMessage = MapError(ex);
 146    }
 447  }
 48
 49  public void Setup(SecureString password, SecureString confirmPassword)
 550  {
 551    ArgumentNullException.ThrowIfNull(password);
 552    ArgumentNullException.ThrowIfNull(confirmPassword);
 53
 554    ErrorMessage = string.Empty;
 555    if (!SecureEquals(password, confirmPassword))
 156    {
 157      ErrorMessage = "Passwörter stimmen nicht überein.";
 158      return;
 59    }
 460    if (password.Length < 8)
 161    {
 162      ErrorMessage = "Passwort muss mindestens 8 Zeichen lang sein.";
 163      return;
 64    }
 65
 66    try
 367    {
 368      AppServices.Current.MasterKeyManager.Initialize(password);
 269      IsSetupMode = false;
 70
 271      var unlock = AppServices.Current.MasterKeyManager.Unlock(password)
 272        ?? throw new InvalidOperationException("Neu angelegte Vault konnte nicht entsperrt werden.");
 273      AppServices.Current.SessionManager.Open(unlock.VaultKey);
 274      AppServices.Current.ActivityMonitor.Start();
 275      UnlockSucceeded?.Invoke(this, EventArgs.Empty);
 276    }
 177    catch (Exception ex)
 178    {
 179      ErrorMessage = MapError(ex);
 180    }
 581  }
 82
 83  private static bool SecureEquals(SecureString left, SecureString right)
 584  {
 585    var secureStringService = new SecureStringService();
 586    var leftBytes = secureStringService.ToByteArray(left);
 587    var rightBytes = secureStringService.ToByteArray(right);
 88    try
 589    {
 590      return System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(leftBytes, rightBytes);
 91    }
 92    finally
 593    {
 594      secureStringService.ZeroMemory(leftBytes);
 595      secureStringService.ZeroMemory(rightBytes);
 596    }
 597  }
 98
 99  private static string MapError(Exception ex)
 4100    => ex switch
 4101    {
 2102      InvalidOperationException => ex.Message,
 1103      ArgumentException => ex.Message,
 1104      _ => "Der Vorgang konnte nicht abgeschlossen werden."
 4105    };
 106}