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;