diff --git a/src/Cryptie.Client/Core/Navigation/ContentCoordinator.cs b/src/Cryptie.Client/Core/Navigation/ContentCoordinator.cs index 4411b42..d459722 100644 --- a/src/Cryptie.Client/Core/Navigation/ContentCoordinator.cs +++ b/src/Cryptie.Client/Core/Navigation/ContentCoordinator.cs @@ -10,16 +10,25 @@ namespace Cryptie.Client.Core.Navigation; public class ContentCoordinator(DashboardViewModel dashboard, IViewModelFactory factory, IScreen screen) : IContentCoordinator { + /// + /// Displays the chat list within the dashboard content region. + /// public void ShowChats() { dashboard.Content = factory.Create(screen); } + /// + /// Displays the current user's account view. + /// public void ShowAccount() { dashboard.Content = factory.Create(screen); } + /// + /// Displays the application settings view. + /// public void ShowSettings() { dashboard.Content = factory.Create(screen); diff --git a/src/Cryptie.Client/Core/Navigation/ShellCoordinator.cs b/src/Cryptie.Client/Core/Navigation/ShellCoordinator.cs index 3cc2c9c..7a3e74c 100644 --- a/src/Cryptie.Client/Core/Navigation/ShellCoordinator.cs +++ b/src/Cryptie.Client/Core/Navigation/ShellCoordinator.cs @@ -25,6 +25,9 @@ ShellStateDependencies stateDeps { public RoutingState Router { get; } = new(); + /// + /// Initializes the application routing based on the persisted session. + /// public async Task StartAsync() { if (!TryInitializeSession(out var sessionToken)) @@ -63,11 +66,17 @@ public async Task StartAsync() } } + /// + /// Navigates to the login view. + /// public void ShowLogin() { NavigateTo(); } + /// + /// Clears the navigation stack and shows the login screen. + /// public void ResetAndShowLogin() { var vm = factory.Create(this); @@ -78,31 +87,51 @@ public void ResetAndShowLogin() .Subscribe(); } + /// + /// Navigates to the registration view. + /// public void ShowRegister() { NavigateTo(); } + /// + /// Navigates to the TOTP QR setup view. + /// public void ShowQrSetup() { NavigateTo(); } + /// + /// Navigates to the TOTP code view. + /// public void ShowTotpCode() { NavigateTo(); } + /// + /// Navigates to the dashboard view. + /// public void ShowDashboard() { NavigateTo(); } + /// + /// Navigates to the pin code setup view. + /// public void ShowPinSetup() { NavigateTo(); } + /// + /// Attempts to read the persisted session token from the keychain. + /// + /// Outputs the parsed session token when successful. + /// true if a valid token was retrieved. private bool TryInitializeSession(out Guid sessionToken) { sessionToken = Guid.Empty; @@ -117,6 +146,11 @@ private bool TryInitializeSession(out Guid sessionToken) return true; } + /// + /// Gets the user's GUID associated with the provided session token. + /// + /// Valid session token. + /// User GUID or when not found. private async Task GetUserGuidAsync(Guid sessionToken) { var dto = new UserGuidFromTokenRequestDto { SessionToken = sessionToken }; @@ -124,6 +158,11 @@ private async Task GetUserGuidAsync(Guid sessionToken) return result?.Guid ?? Guid.Empty; } + /// + /// Loads the current user's private key from the keychain and populates user state. + /// + /// Identifier of the authenticated user. + /// true when the key was successfully loaded. private bool TryInitializeUser(Guid userGuid) { stateDeps.UserState.UserId = userGuid; @@ -137,6 +176,9 @@ private bool TryInitializeUser(Guid userGuid) return true; } + /// + /// Clears any cached authentication information from state and keychain. + /// private void ClearUserState() { keychain.TryClearSessionToken(out _); @@ -156,6 +198,9 @@ private void ClearUserState() stateDeps.RegistrationState.LastResponse = null; } + /// + /// Determines whether the HTTP exception represents an authentication failure. + /// private static bool IsAuthError(HttpRequestException ex) { return ex.StatusCode is HttpStatusCode.Unauthorized @@ -163,6 +208,9 @@ or HttpStatusCode.Forbidden or HttpStatusCode.BadRequest; } + /// + /// Helper method to create and navigate to a view model instance. + /// private void NavigateTo() where TViewModel : RoutableViewModelBase { var vm = factory.Create(this); diff --git a/src/Cryptie.Client/Core/Services/ConnectionMonitor.cs b/src/Cryptie.Client/Core/Services/ConnectionMonitor.cs index 471dbc9..f8b6252 100644 --- a/src/Cryptie.Client/Core/Services/ConnectionMonitor.cs +++ b/src/Cryptie.Client/Core/Services/ConnectionMonitor.cs @@ -23,11 +23,19 @@ private readonly IServerStatus public IObservable ConnectionStatusChanged => _subject; + /// + /// Performs a single check against the backend service. + /// + /// true when the backend responds successfully. public async Task IsBackendAliveAsync() { return await CheckServerAsync(CancellationToken.None); } + /// + /// Starts monitoring the backend availability and publishes changes via . + /// + /// Optional cancellation token to stop monitoring. public void Start(CancellationToken token = default) { ThrowIfDisposed(); @@ -57,6 +65,7 @@ public void Start(CancellationToken token = default) }, _cts.Token); } + /// public void Dispose() { if (_disposed) diff --git a/src/Cryptie.Client/Core/Services/KeyService.cs b/src/Cryptie.Client/Core/Services/KeyService.cs index b685e23..f194d90 100644 --- a/src/Cryptie.Client/Core/Services/KeyService.cs +++ b/src/Cryptie.Client/Core/Services/KeyService.cs @@ -8,6 +8,12 @@ namespace Cryptie.Client.Core.Services; public class KeyService(HttpClient httpClient) : IKeyService { + /// + /// Retrieves the RSA public key for the specified user. + /// + /// Request containing the user identifier. + /// Token used to cancel the request. + /// The user's key information or null when not found. public async Task GetUserKeyAsync( GetUserKeyRequestDto getUserKeyRequest, CancellationToken cancellationToken = default) @@ -22,6 +28,12 @@ public class KeyService(HttpClient httpClient) : IKeyService .ReadFromJsonAsync(cancellationToken); } + /// + /// Retrieves symmetric keys for a collection of groups. + /// + /// Request specifying groups to retrieve. + /// Token used to cancel the request. + /// Response containing keys for the requested groups. public async Task GetGroupsKeyAsync( GetGroupsKeyRequestDto getGroupsKeyRequest, CancellationToken cancellationToken = default) diff --git a/src/Cryptie.Client/Core/Services/UserDetailsService.cs b/src/Cryptie.Client/Core/Services/UserDetailsService.cs index e8903fb..9d840cc 100644 --- a/src/Cryptie.Client/Core/Services/UserDetailsService.cs +++ b/src/Cryptie.Client/Core/Services/UserDetailsService.cs @@ -8,6 +8,12 @@ namespace Cryptie.Client.Core.Services; public class UserDetailsService(HttpClient httpClient) : IUserDetailsService { + /// + /// Retrieves a user's display name based on their GUID. + /// + /// Request containing the user GUID. + /// Token used to cancel the request. + /// The user's display name or null when not found. public async Task GetUsernameFromGuidAsync( NameFromGuidRequestDto nameFromGuidRequest, CancellationToken cancellationToken = default) @@ -21,6 +27,9 @@ public class UserDetailsService(HttpClient httpClient) : IUserDetailsService return await response.Content.ReadFromJsonAsync(cancellationToken); } + /// + /// Gets the user's GUID associated with a session token. + /// public async Task GetUserGuidFromTokenAsync( UserGuidFromTokenRequestDto userGuidFromTokenRequest, CancellationToken cancellationToken = default) @@ -34,6 +43,9 @@ public class UserDetailsService(HttpClient httpClient) : IUserDetailsService return await response.Content.ReadFromJsonAsync(cancellationToken); } + /// + /// Retrieves the private key for the specified user. + /// public async Task GetUserPrivateKeyAsync( UserPrivateKeyRequestDto userPrivateKeyRequest, CancellationToken cancellationToken = default) @@ -48,6 +60,9 @@ public class UserDetailsService(HttpClient httpClient) : IUserDetailsService } + /// + /// Resolves a user's GUID from their login name. + /// public async Task GetGuidFromLoginAsync( GuidFromLoginRequestDto guidFromLoginRequest, CancellationToken cancellationToken = default) diff --git a/src/Cryptie.Client/Encryption/AesDataEncryption.cs b/src/Cryptie.Client/Encryption/AesDataEncryption.cs index 572bf3a..203c5c1 100644 --- a/src/Cryptie.Client/Encryption/AesDataEncryption.cs +++ b/src/Cryptie.Client/Encryption/AesDataEncryption.cs @@ -6,6 +6,12 @@ namespace Cryptie.Client.Encryption; public static class AesDataEncryption { + /// + /// Encrypts the provided string with the given AES key. + /// + /// Plain text to encrypt. + /// Base64 encoded AES key. + /// Base64 encoded cipher text containing the IV and encrypted payload. public static string Encrypt(string data, string key) { using var aes = Aes.Create(); @@ -26,6 +32,12 @@ public static string Encrypt(string data, string key) return Convert.ToBase64String(ms.ToArray()); } + /// + /// Decrypts the given cipher text using the supplied AES key. + /// + /// Base64 encoded cipher that contains IV and encrypted data. + /// Base64 encoded AES key. + /// The decrypted plain text. public static string Decrypt(string encryptedData, string key) { var fullCipher = Convert.FromBase64String(encryptedData); diff --git a/src/Cryptie.Client/Encryption/CertificateGenerator.cs b/src/Cryptie.Client/Encryption/CertificateGenerator.cs index b9a2f7e..392d005 100644 --- a/src/Cryptie.Client/Encryption/CertificateGenerator.cs +++ b/src/Cryptie.Client/Encryption/CertificateGenerator.cs @@ -6,6 +6,10 @@ namespace Cryptie.Client.Encryption; public static class CertificateGenerator { + /// + /// Generates a self-signed certificate that can be used for RSA encryption. + /// + /// A new containing both private and public keys. public static X509Certificate2 GenerateCertificate() { using var rsa = RSA.Create(2048); @@ -23,6 +27,11 @@ public static X509Certificate2 GenerateCertificate() DateTimeOffset.Now.AddYears(1)); } + /// + /// Extracts the public portion of the provided certificate. + /// + /// Certificate containing the key pair. + /// A certificate containing only the public key. public static X509Certificate2 ExtractPublicKey(X509Certificate2 certificate) { return X509CertificateLoader.LoadCertificate(certificate.Export(X509ContentType.Cert)); diff --git a/src/Cryptie.Client/Encryption/EncryptionKeyGenerator.cs b/src/Cryptie.Client/Encryption/EncryptionKeyGenerator.cs index c02474e..0c61200 100644 --- a/src/Cryptie.Client/Encryption/EncryptionKeyGenerator.cs +++ b/src/Cryptie.Client/Encryption/EncryptionKeyGenerator.cs @@ -4,6 +4,11 @@ namespace Cryptie.Client.Encryption; public static class EncryptionKeyGenerator { + /// + /// Generates a new AES key with the specified key size. + /// + /// Size of the key in bits. Defaults to 256. + /// Byte array containing the generated key. public static byte[] GenerateAesKey(int keySize = 256) { using var aes = Aes.Create(); diff --git a/src/Cryptie.Client/Encryption/RsaDataEncryption.cs b/src/Cryptie.Client/Encryption/RsaDataEncryption.cs index 49dd54f..e470b5e 100644 --- a/src/Cryptie.Client/Encryption/RsaDataEncryption.cs +++ b/src/Cryptie.Client/Encryption/RsaDataEncryption.cs @@ -7,6 +7,12 @@ namespace Cryptie.Client.Encryption; public static class RsaDataEncryption { + /// + /// Encrypts the provided with the recipient's public key. + /// + /// Plain text message to encrypt. + /// Recipient's RSA public certificate. + /// Base64 encoded encrypted payload. public static string Encrypt(string message, X509Certificate2 publicKey) { var messageBytes = Encoding.UTF8.GetBytes(message); @@ -19,6 +25,12 @@ public static string Encrypt(string message, X509Certificate2 publicKey) return Convert.ToBase64String(envelopedCms.Encode()); } + /// + /// Decrypts an encrypted CMS message with the given private key. + /// + /// Base64 encoded encrypted data. + /// Certificate containing the private key used for decryption. + /// The decrypted plain text message. public static string Decrypt(string message, X509Certificate2 privateKey) { var messageBytes = Convert.FromBase64String(message); @@ -30,6 +42,13 @@ public static string Decrypt(string message, X509Certificate2 privateKey) return Encoding.UTF8.GetString(envelopedCms.ContentInfo.Content); } + /// + /// Loads an instance from a Base64 encoded string. + /// + /// The Base64 encoded certificate. + /// The format of the encoded certificate. + /// Optional password for PFX certificates. + /// The decoded certificate. public static X509Certificate2 LoadCertificateFromBase64(string base64, X509ContentType contentType, string? password = null) { diff --git a/src/Cryptie.Client/Features/AddFriend/Services/FriendsService.cs b/src/Cryptie.Client/Features/AddFriend/Services/FriendsService.cs index 621da1e..10b4ead 100644 --- a/src/Cryptie.Client/Features/AddFriend/Services/FriendsService.cs +++ b/src/Cryptie.Client/Features/AddFriend/Services/FriendsService.cs @@ -8,6 +8,11 @@ namespace Cryptie.Client.Features.AddFriend.Services; public class FriendsService(HttpClient httpClient) : IFriendsService { + /// + /// Sends a friend request to the backend. + /// + /// Request DTO describing the friend to add. + /// Cancellation token. public async Task AddFriendAsync(AddFriendRequestDto addFriendRequest, CancellationToken cancellationToken = default) { diff --git a/src/Cryptie.Client/Features/AddFriend/ViewModels/AddFriendViewModel.cs b/src/Cryptie.Client/Features/AddFriend/ViewModels/AddFriendViewModel.cs index 1715be8..6e8c60b 100644 --- a/src/Cryptie.Client/Features/AddFriend/ViewModels/AddFriendViewModel.cs +++ b/src/Cryptie.Client/Features/AddFriend/ViewModels/AddFriendViewModel.cs @@ -88,6 +88,10 @@ public string ConfirmationMessage public ReactiveCommand SendFriendRequest { get; } + /// + /// Executes the workflow of adding a new friend based on the current input. + /// + /// Cancellation token to abort the operation. private async Task AddFriendAsync(CancellationToken ct) { ErrorMessage = string.Empty; @@ -173,22 +177,34 @@ private async Task AddFriendAsync(CancellationToken ct) } + /// + /// Validates that the provided session token string can be parsed into a . + /// private static bool IsSessionTokenValid(string? token, out Guid sessionToken) { sessionToken = Guid.Empty; return !string.IsNullOrWhiteSpace(token) && Guid.TryParse(token, out sessionToken); } + /// + /// Determines whether the user is attempting to add themselves as a friend. + /// private static bool IsAddingSelf(string user, string friend) { return string.Equals(user, friend, StringComparison.OrdinalIgnoreCase); } + /// + /// Validates the using the injected validator. + /// private async Task IsValidFriendRequest(AddFriendRequestDto dto, CancellationToken ct) { return (await _validator.ValidateAsync(dto, ct)).IsValid; } + /// + /// Retrieves the GUID of a user by their login name. + /// private async Task GetUserGuidByName(string friendName, CancellationToken ct) { try @@ -207,6 +223,9 @@ private async Task IsValidFriendRequest(AddFriendRequestDto dto, Cancellat } } + /// + /// Retrieves the GUID of the currently logged in user using the session token. + /// private async Task GetMyGuidFromSession(Guid sessionToken, CancellationToken ct) { try @@ -221,6 +240,9 @@ private async Task IsValidFriendRequest(AddFriendRequestDto dto, Cancellat } } + /// + /// Downloads the certificate associated with the specified user. + /// private async Task GetUserCertificate(Guid userGuid, CancellationToken ct) { try @@ -239,6 +261,9 @@ private async Task IsValidFriendRequest(AddFriendRequestDto dto, Cancellat return null; } + /// + /// Generates a new random AES key. + /// private static byte[] GenerateAesKey() { using var aes = Aes.Create(); @@ -246,6 +271,9 @@ private static byte[] GenerateAesKey() return aes.Key; } + /// + /// Sends the friend request to the backend and handles common errors. + /// private async Task TryAddFriend(AddFriendRequestDto dto, CancellationToken ct) { try @@ -265,6 +293,9 @@ private async Task TryAddFriend(AddFriendRequestDto dto, CancellationToken return false; } + /// + /// Sets a generic error message for the view. + /// private void SetGenericError() { ErrorMessage = "An error occurred. Please try again."; diff --git a/src/Cryptie.Client/Features/Authentication/Services/AuthenticationService.cs b/src/Cryptie.Client/Features/Authentication/Services/AuthenticationService.cs index 0ab9347..dd0f41a 100644 --- a/src/Cryptie.Client/Features/Authentication/Services/AuthenticationService.cs +++ b/src/Cryptie.Client/Features/Authentication/Services/AuthenticationService.cs @@ -8,6 +8,12 @@ namespace Cryptie.Client.Features.Authentication.Services; public class AuthenticationService(HttpClient httpClient) : IAuthenticationService { + /// + /// Sends a registration request to the backend. + /// + /// User registration data. + /// Token used to cancel the request. + /// Registration result or null on failure. public async Task RegisterAsync(RegisterRequestDto registerRequest, CancellationToken cancellationToken = default) { @@ -20,6 +26,9 @@ public class AuthenticationService(HttpClient httpClient) : IAuthenticationServi return (await response.Content.ReadFromJsonAsync(cancellationToken))!; } + /// + /// Logs a user into the system. + /// public async Task LoginAsync(LoginRequestDto loginRequest, CancellationToken cancellationToken = default) { @@ -32,6 +41,9 @@ public class AuthenticationService(HttpClient httpClient) : IAuthenticationServi return (await response.Content.ReadFromJsonAsync(cancellationToken))!; } + /// + /// Performs the second factor TOTP verification. + /// public async Task TotpAsync(TotpRequestDto totpRequest, CancellationToken cancellationToken = default) { diff --git a/src/Cryptie.Client/Features/Authentication/Services/KeychainManagerService.cs b/src/Cryptie.Client/Features/Authentication/Services/KeychainManagerService.cs index 498c1b9..5fdefbd 100644 --- a/src/Cryptie.Client/Features/Authentication/Services/KeychainManagerService.cs +++ b/src/Cryptie.Client/Features/Authentication/Services/KeychainManagerService.cs @@ -19,6 +19,12 @@ public class KeychainManagerService : IKeychainManagerService private const string ChunkAccountPrefix = "CurrentUser_Chunk_"; private const int ChunkSize = 1024; + /// + /// Stores the session token in the operating system keychain. + /// + /// Token to persist. + /// Outputs an error message when the operation fails. + /// true when the token was saved successfully. public bool TrySaveSessionToken(string token, [NotNullWhen(false)] out string? errorMessage) { errorMessage = null; @@ -40,6 +46,9 @@ public bool TrySaveSessionToken(string token, [NotNullWhen(false)] out string? e } } + /// + /// Attempts to retrieve the session token from the keychain. + /// public bool TryGetSessionToken([NotNullWhen(true)] out string? token, [NotNullWhen(false)] out string? errorMessage) { token = null; @@ -63,6 +72,9 @@ public bool TryGetSessionToken([NotNullWhen(true)] out string? token, [NotNullWh } } + /// + /// Removes the session token from the keychain. + /// public void TryClearSessionToken([NotNullWhen(false)] out string? errorMessage) { errorMessage = null; @@ -77,6 +89,9 @@ public void TryClearSessionToken([NotNullWhen(false)] out string? errorMessage) } } + /// + /// Saves the user's private key in the keychain, chunking it if necessary. + /// public bool TrySavePrivateKey(string privateKey, [NotNullWhen(false)] out string? errorMessage) { errorMessage = null; @@ -129,6 +144,9 @@ public bool TrySavePrivateKey(string privateKey, [NotNullWhen(false)] out string return true; } + /// + /// Retrieves the private key from the keychain. + /// public bool TryGetPrivateKey([NotNullWhen(true)] out string? privateKey, [NotNullWhen(false)] out string? errorMessage) { @@ -184,6 +202,9 @@ public bool TryGetPrivateKey([NotNullWhen(true)] out string? privateKey, return true; } + /// + /// Removes the stored private key from the keychain. + /// public void TryClearPrivateKey([NotNullWhen(false)] out string? errorMessage) { errorMessage = null;