From 73e432b2dcafd8ab8ebb755f997100724cdeb08e Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 5 Aug 2024 17:24:51 +0200 Subject: [PATCH] Refactor identity generation logic (#108) --- src/AliasGenerators/AliasGenerators.csproj | 12 +++--- .../Implementations/Base/IdentityGenerator.cs | 6 +-- .../en/firstnames_female | 0 .../en/firstnames_male | 0 .../{Lists => Dictionaries}/en/lastnames | 0 .../nl/firstnames_female | 0 .../nl/firstnames_male | 0 .../{Lists => Dictionaries}/nl/lastnames | 0 .../Factories/IdentityGeneratorFactory.cs | 32 +++++++++++++++ .../Implementations/FigIdentityGenerator.cs | 40 ------------------- .../Implementations/IdentityGeneratorEn.cs | 6 +-- .../Implementations/IdentityGeneratorNl.cs | 6 +-- .../StaticIdentityGenerator.cs | 27 ------------- .../Identity/UsernameEmailGenerator.cs | 33 ++++++++++----- .../Controllers/IdentityController.cs | 4 +- .../Main/Pages/Credentials/AddEdit.razor | 2 +- .../Main/Pages/Settings/General.razor | 26 ++++++++++++ .../Services/SettingsService.cs | 21 ++++++++-- .../Generators/IdentityGeneratorTest.cs | 6 +-- 19 files changed, 119 insertions(+), 102 deletions(-) rename src/AliasGenerators/Identity/Implementations/{Lists => Dictionaries}/en/firstnames_female (100%) rename src/AliasGenerators/Identity/Implementations/{Lists => Dictionaries}/en/firstnames_male (100%) rename src/AliasGenerators/Identity/Implementations/{Lists => Dictionaries}/en/lastnames (100%) rename src/AliasGenerators/Identity/Implementations/{Lists => Dictionaries}/nl/firstnames_female (100%) rename src/AliasGenerators/Identity/Implementations/{Lists => Dictionaries}/nl/firstnames_male (100%) rename src/AliasGenerators/Identity/Implementations/{Lists => Dictionaries}/nl/lastnames (100%) create mode 100644 src/AliasGenerators/Identity/Implementations/Factories/IdentityGeneratorFactory.cs delete mode 100644 src/AliasGenerators/Identity/Implementations/FigIdentityGenerator.cs delete mode 100644 src/AliasGenerators/Identity/Implementations/StaticIdentityGenerator.cs diff --git a/src/AliasGenerators/AliasGenerators.csproj b/src/AliasGenerators/AliasGenerators.csproj index c092d8981..05d0f107a 100644 --- a/src/AliasGenerators/AliasGenerators.csproj +++ b/src/AliasGenerators/AliasGenerators.csproj @@ -28,14 +28,14 @@ - - - - + + + + - + - + diff --git a/src/AliasGenerators/Identity/Implementations/Base/IdentityGenerator.cs b/src/AliasGenerators/Identity/Implementations/Base/IdentityGenerator.cs index 7e8145ce4..e0ef3dc60 100644 --- a/src/AliasGenerators/Identity/Implementations/Base/IdentityGenerator.cs +++ b/src/AliasGenerators/Identity/Implementations/Base/IdentityGenerator.cs @@ -50,17 +50,17 @@ public abstract class IdentityGenerator : IIdentityGenerator /// /// Gets namespace path to the male first names list for the correct language. /// - protected virtual string FirstNamesListMale => "AliasGenerators.Identity.Implementations.Lists.nl.firstnames_male"; + protected virtual string FirstNamesListMale => "AliasGenerators.Identity.Implementations.Dictionaries.nl.firstnames_male"; /// /// Gets namespace path to the female first names list for the correct language. /// - protected virtual string FirstNamesListFemale => "AliasGenerators.Identity.Implementations.Lists.nl.firstnames_female"; + protected virtual string FirstNamesListFemale => "AliasGenerators.Identity.Implementations.Dictionaries.nl.firstnames_female"; /// /// Gets namespace path to the last names list for the correct language. /// - protected virtual string LastNamesList => "AliasGenerators.Identity.Implementations.Lists.nl.lastnames"; + protected virtual string LastNamesList => "AliasGenerators.Identity.Implementations.Dictionaries.nl.lastnames"; /// public async Task GenerateRandomIdentityAsync() diff --git a/src/AliasGenerators/Identity/Implementations/Lists/en/firstnames_female b/src/AliasGenerators/Identity/Implementations/Dictionaries/en/firstnames_female similarity index 100% rename from src/AliasGenerators/Identity/Implementations/Lists/en/firstnames_female rename to src/AliasGenerators/Identity/Implementations/Dictionaries/en/firstnames_female diff --git a/src/AliasGenerators/Identity/Implementations/Lists/en/firstnames_male b/src/AliasGenerators/Identity/Implementations/Dictionaries/en/firstnames_male similarity index 100% rename from src/AliasGenerators/Identity/Implementations/Lists/en/firstnames_male rename to src/AliasGenerators/Identity/Implementations/Dictionaries/en/firstnames_male diff --git a/src/AliasGenerators/Identity/Implementations/Lists/en/lastnames b/src/AliasGenerators/Identity/Implementations/Dictionaries/en/lastnames similarity index 100% rename from src/AliasGenerators/Identity/Implementations/Lists/en/lastnames rename to src/AliasGenerators/Identity/Implementations/Dictionaries/en/lastnames diff --git a/src/AliasGenerators/Identity/Implementations/Lists/nl/firstnames_female b/src/AliasGenerators/Identity/Implementations/Dictionaries/nl/firstnames_female similarity index 100% rename from src/AliasGenerators/Identity/Implementations/Lists/nl/firstnames_female rename to src/AliasGenerators/Identity/Implementations/Dictionaries/nl/firstnames_female diff --git a/src/AliasGenerators/Identity/Implementations/Lists/nl/firstnames_male b/src/AliasGenerators/Identity/Implementations/Dictionaries/nl/firstnames_male similarity index 100% rename from src/AliasGenerators/Identity/Implementations/Lists/nl/firstnames_male rename to src/AliasGenerators/Identity/Implementations/Dictionaries/nl/firstnames_male diff --git a/src/AliasGenerators/Identity/Implementations/Lists/nl/lastnames b/src/AliasGenerators/Identity/Implementations/Dictionaries/nl/lastnames similarity index 100% rename from src/AliasGenerators/Identity/Implementations/Lists/nl/lastnames rename to src/AliasGenerators/Identity/Implementations/Dictionaries/nl/lastnames diff --git a/src/AliasGenerators/Identity/Implementations/Factories/IdentityGeneratorFactory.cs b/src/AliasGenerators/Identity/Implementations/Factories/IdentityGeneratorFactory.cs new file mode 100644 index 000000000..86314c0e9 --- /dev/null +++ b/src/AliasGenerators/Identity/Implementations/Factories/IdentityGeneratorFactory.cs @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasGenerators.Identity.Implementations; + +using AliasGenerators.Identity.Implementations.Base; + +/// +/// Identity generator factory which creates an identity generator based on the language code. +/// +public static class IdentityGeneratorFactory +{ + /// + /// Creates an identity generator based on the language code. + /// + /// Two letter language code. + /// The IdentityGenerator for the requested language. + /// Thrown if no identity generator is found for the requested language. + public static IdentityGenerator CreateIdentityGenerator(string languageCode) + { + return languageCode.ToLower() switch + { + "nl" => new IdentityGeneratorNl(), + "en" => new IdentityGeneratorEn(), + _ => throw new ArgumentException($"Unsupported language code: {languageCode}", nameof(languageCode)), + }; + } +} diff --git a/src/AliasGenerators/Identity/Implementations/FigIdentityGenerator.cs b/src/AliasGenerators/Identity/Implementations/FigIdentityGenerator.cs deleted file mode 100644 index eb1b6cfef..000000000 --- a/src/AliasGenerators/Identity/Implementations/FigIdentityGenerator.cs +++ /dev/null @@ -1,40 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) lanedirt. All rights reserved. -// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. -// -//----------------------------------------------------------------------- - -namespace AliasGenerators.Identity.Implementations; - -using System.Text.Json; - -/// -/// Identity generator which generates random identities using the identiteitgenerator.nl semi-public API. -/// -public class FigIdentityGenerator : IIdentityGenerator -{ - private static readonly HttpClient HttpClient = new(); - private static readonly string Url = "https://api.identiteitgenerator.nl/generate/identity"; - private static readonly JsonSerializerOptions JsonSerializerOptions = new() - { - PropertyNameCaseInsensitive = true, - }; - - /// - public async Task GenerateRandomIdentityAsync() - { - var response = await HttpClient.GetAsync(Url); - response.EnsureSuccessStatusCode(); - - var json = await response.Content.ReadAsStringAsync(); - var identity = JsonSerializer.Deserialize(json, JsonSerializerOptions); - - if (identity is null) - { - throw new InvalidOperationException("Failed to deserialize the identity from FIG WebApi."); - } - - return identity; - } -} diff --git a/src/AliasGenerators/Identity/Implementations/IdentityGeneratorEn.cs b/src/AliasGenerators/Identity/Implementations/IdentityGeneratorEn.cs index 8679fc8a7..0df424c9f 100644 --- a/src/AliasGenerators/Identity/Implementations/IdentityGeneratorEn.cs +++ b/src/AliasGenerators/Identity/Implementations/IdentityGeneratorEn.cs @@ -16,11 +16,11 @@ using AliasGenerators.Identity.Implementations.Base; public class IdentityGeneratorEn : IdentityGenerator { /// - protected override string FirstNamesListMale => "AliasGenerators.Identity.Implementations.Lists.en.firstnames_male"; + protected override string FirstNamesListMale => "AliasGenerators.Identity.Implementations.Dictionaries.en.firstnames_male"; /// - protected override string FirstNamesListFemale => "AliasGenerators.Identity.Implementations.Lists.en.firstnames_female"; + protected override string FirstNamesListFemale => "AliasGenerators.Identity.Implementations.Dictionaries.en.firstnames_female"; /// - protected override string LastNamesList => "AliasGenerators.Identity.Implementations.Lists.en.lastnames"; + protected override string LastNamesList => "AliasGenerators.Identity.Implementations.Dictionaries.en.lastnames"; } diff --git a/src/AliasGenerators/Identity/Implementations/IdentityGeneratorNl.cs b/src/AliasGenerators/Identity/Implementations/IdentityGeneratorNl.cs index d9602f834..ba38c2d20 100644 --- a/src/AliasGenerators/Identity/Implementations/IdentityGeneratorNl.cs +++ b/src/AliasGenerators/Identity/Implementations/IdentityGeneratorNl.cs @@ -16,11 +16,11 @@ using AliasGenerators.Identity.Implementations.Base; public class IdentityGeneratorNl : IdentityGenerator { /// - protected override string FirstNamesListMale => "AliasGenerators.Identity.Implementations.Lists.nl.firstnames_male"; + protected override string FirstNamesListMale => "AliasGenerators.Identity.Implementations.Dictionaries.nl.firstnames_male"; /// - protected override string FirstNamesListFemale => "AliasGenerators.Identity.Implementations.Lists.nl.firstnames_female"; + protected override string FirstNamesListFemale => "AliasGenerators.Identity.Implementations.Dictionaries.nl.firstnames_female"; /// - protected override string LastNamesList => "AliasGenerators.Identity.Implementations.Lists.nl.lastnames"; + protected override string LastNamesList => "AliasGenerators.Identity.Implementations.Dictionaries.nl.lastnames"; } diff --git a/src/AliasGenerators/Identity/Implementations/StaticIdentityGenerator.cs b/src/AliasGenerators/Identity/Implementations/StaticIdentityGenerator.cs deleted file mode 100644 index 5b56f52d0..000000000 --- a/src/AliasGenerators/Identity/Implementations/StaticIdentityGenerator.cs +++ /dev/null @@ -1,27 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) lanedirt. All rights reserved. -// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. -// -//----------------------------------------------------------------------- -namespace AliasGenerators.Identity.Implementations; - -using AliasGenerators.Identity; - -/// -/// Static identity generator which implements IIdentityGenerator but always returns -/// the same static identity for testing purposes. -/// -public class StaticIdentityGenerator : IIdentityGenerator -{ - /// - public async Task GenerateRandomIdentityAsync() - { - await Task.Yield(); // Add an await statement to make the method truly asynchronous. - return new Identity.Models.Identity - { - FirstName = "John", - LastName = "Doe", - }; - } -} diff --git a/src/AliasGenerators/Identity/UsernameEmailGenerator.cs b/src/AliasGenerators/Identity/UsernameEmailGenerator.cs index 23e29cf4e..ad7b43f7e 100644 --- a/src/AliasGenerators/Identity/UsernameEmailGenerator.cs +++ b/src/AliasGenerators/Identity/UsernameEmailGenerator.cs @@ -22,6 +22,9 @@ public class UsernameEmailGenerator /// private const int MaxLength = 20; + /// + /// Create a new random instance for generating random values. + /// private readonly Random _random = new(); /// @@ -60,7 +63,7 @@ public class UsernameEmailGenerator /// Valid email prefix as string. public string GenerateEmailPrefix(Models.Identity identity) { - List parts = new List(); + var parts = new List(); // Use first initial + last name if (_random.Next(2) == 0) @@ -86,7 +89,7 @@ public class UsernameEmailGenerator } // Join parts and sanitize - string emailPrefix = string.Join(GetRandomSymbol(), parts); + var emailPrefix = string.Join(GetRandomSymbol(), parts); emailPrefix = SanitizeEmailPrefix(emailPrefix); // Adjust length @@ -103,15 +106,11 @@ public class UsernameEmailGenerator } /// - /// Get a random symbol from the list of symbols. + /// Sanitize the email prefix by removing invalid characters and ensuring it's a valid email prefix. /// - /// Random symbol. - private string GetRandomSymbol() - { - return _random.Next(3) == 0 ? _symbols[_random.Next(_symbols.Count)] : string.Empty; - } - - private string SanitizeEmailPrefix(string input) + /// The input string to sanitize. + /// The sanitized string. + private static string SanitizeEmailPrefix(string input) { // Remove any character that's not a letter, number, dot, underscore, or hyphen including special characters string sanitized = System.Text.RegularExpressions.Regex.Replace(input, @"[^a-zA-Z0-9._-]", string.Empty); @@ -125,6 +124,20 @@ public class UsernameEmailGenerator return sanitized; } + /// + /// Get a random symbol from the list of symbols. + /// + /// Random symbol. + private string GetRandomSymbol() + { + return _random.Next(3) == 0 ? _symbols[_random.Next(_symbols.Count)] : string.Empty; + } + + /// + /// Generate a random string of a given length. + /// + /// Length of string to generate. + /// String with random characters. private string GenerateRandomString(int length) { const string chars = "abcdefghijklmnopqrstuvwxyz0123456789"; diff --git a/src/AliasVault.Api/Controllers/IdentityController.cs b/src/AliasVault.Api/Controllers/IdentityController.cs index e30e1a5f1..d99aece27 100644 --- a/src/AliasVault.Api/Controllers/IdentityController.cs +++ b/src/AliasVault.Api/Controllers/IdentityController.cs @@ -21,7 +21,7 @@ using Microsoft.AspNetCore.Mvc; public class IdentityController(UserManager userManager) : AuthenticatedRequestController(userManager) { /// - /// Proxies the request to the identity generator to generate a random identity. + /// Proxies the request to the english identity generator to generate a random identity. /// /// Identity model. [HttpGet("Generate")] @@ -33,7 +33,7 @@ public class IdentityController(UserManager userManager) : Authe return Unauthorized(); } - var identityGenerator = new FigIdentityGenerator(); + var identityGenerator = new IdentityGeneratorEn(); return Ok(await identityGenerator.GenerateRandomIdentityAsync()); } } diff --git a/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor b/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor index fc9885025..fbaab63de 100644 --- a/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor +++ b/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor @@ -244,7 +244,7 @@ else StateHasChanged(); // Generate a random identity using the IIdentityGenerator implementation. - var identity = await new IdentityGeneratorNl().GenerateRandomIdentityAsync(); + var identity = await IdentityGeneratorFactory.CreateIdentityGenerator(DbService.Settings.DefaultIdentityLanguage).GenerateRandomIdentityAsync(); // Generate random values for the Identity properties Obj.Username = identity.NickName; diff --git a/src/AliasVault.Client/Main/Pages/Settings/General.razor b/src/AliasVault.Client/Main/Pages/Settings/General.razor index cd9419348..a1be0d2f7 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/General.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/General.razor @@ -44,6 +44,21 @@ +
+

Identity Settings

+ +
+ + + + Set the default language that will be used when generating new identities. + +
+
+ @code { private List PrivateDomains => Config.PrivateEmailDomains; private List PublicDomains => Config.PublicEmailDomains; @@ -51,6 +66,7 @@ private string DefaultEmailDomain { get; set; } = string.Empty; private bool AutoEmailRefresh { get; set; } + private string DefaultIdentityLanguage { get; set; } = string.Empty; /// protected override async Task OnInitializedAsync() @@ -60,6 +76,7 @@ DefaultEmailDomain = DbService.Settings.DefaultEmailDomain; AutoEmailRefresh = DbService.Settings.AutoEmailRefresh; + DefaultIdentityLanguage = DbService.Settings.DefaultIdentityLanguage; } /// @@ -79,4 +96,13 @@ await DbService.Settings.SetAutoEmailRefresh(AutoEmailRefresh); StateHasChanged(); } + + /// + /// Updates the auto email refresh setting. + /// + private async Task UpdateDefaultIdentityLanguage() + { + await DbService.Settings.SetDefaultIdentityLanguage(DefaultIdentityLanguage); + StateHasChanged(); + } } diff --git a/src/AliasVault.Client/Services/SettingsService.cs b/src/AliasVault.Client/Services/SettingsService.cs index d7f97ad15..242cf70c5 100644 --- a/src/AliasVault.Client/Services/SettingsService.cs +++ b/src/AliasVault.Client/Services/SettingsService.cs @@ -27,7 +27,7 @@ public class SettingsService private bool _initialized; /// - /// Gets the DefaultEmailDomain setting asynchronously. + /// Gets the DefaultEmailDomain setting. /// /// Default email domain as string. public string DefaultEmailDomain => GetSetting("DefaultEmailDomain"); @@ -39,19 +39,32 @@ public class SettingsService public bool AutoEmailRefresh => GetSetting("AutoEmailRefresh", true); /// - /// Sets the DefaultEmailDomain setting asynchronously. + /// Gets the DefaultIdentityLanguage setting. /// - /// The new DeafultEmailDomain setting. + /// Default identity language as two-letter code. + public string DefaultIdentityLanguage => GetSetting("DefaultIdentityLanguage", "en")!; + + /// + /// Sets the DefaultEmailDomain setting. + /// + /// The new DefaultEmailDomain setting. /// Task. public Task SetDefaultEmailDomain(string value) => SetSettingAsync("DefaultEmailDomain", value); /// - /// Sets the AutoEmailRefresh setting asynchronously as a string. + /// Sets the AutoEmailRefresh setting as a string. /// /// The new value. /// Task. public Task SetAutoEmailRefresh(bool value) => SetSettingAsync("AutoEmailRefresh", value); + /// + /// Sets the DefaultIdentityLanguage setting. + /// + /// The new value. + /// Task. + public Task SetDefaultIdentityLanguage(string value) => SetSettingAsync("DefaultIdentityLanguage", value); + /// /// Initializes the settings service asynchronously. /// diff --git a/src/Tests/AliasVault.UnitTests/Generators/IdentityGeneratorTest.cs b/src/Tests/AliasVault.UnitTests/Generators/IdentityGeneratorTest.cs index 96e51f287..158b2aa40 100644 --- a/src/Tests/AliasVault.UnitTests/Generators/IdentityGeneratorTest.cs +++ b/src/Tests/AliasVault.UnitTests/Generators/IdentityGeneratorTest.cs @@ -24,7 +24,7 @@ public class IdentityGeneratorTest public async Task TestNlGenerator() { // Generate random NL identity. - var generator = new IdentityGeneratorNl(); + var generator = IdentityGeneratorFactory.CreateIdentityGenerator("nl"); // Generate 10 identities, check if they are all unique. var identities = new List(); @@ -62,8 +62,8 @@ public class IdentityGeneratorTest [Test] public async Task TestEnGenerator() { - // Generate random NL identity. - var generator = new IdentityGeneratorEn(); + // Generate random EN identity. + var generator = IdentityGeneratorFactory.CreateIdentityGenerator("en"); // Generate 10 identities, check if they are all unique. var identities = new List();