mirror of
https://github.com/bitwarden/mobile
synced 2025-12-19 01:33:22 +00:00
[SG-223] Mobile username generator (#2033)
* SG-223 - Changed page title and password title * SG-223 - Refactored generated field * Changed position of generated field * Replaced buttons generate and copy for icons * SG-223 - Refactor type to passwordType * SG-223 - Added password or username selector * Added string for label type selection * SG-223 - Added logic for different types of username * Added strings of new types * [SG-223] - Added UI components for different username types * Added static strings for new labels * Added viewmodel properties to support username generation and their respective options * [SG-223] Added control over type picker visibility * [SG-223] Refactored username entry on add edit page and added generate icon * Added GenerateUsername command * [SG-223] - Implemented service for username generation * [SG-223] - Added support for username generation for item creation flow * Implemented cache for username options * Added exception handling for api calls * [SG-223] - Remove unused code * [SG-223] - Added a new display field for username generated and respective command * Added description label for each type of username * Changed defautl value of username from string.Empty to - * [SG-223] - Removed some StackLayouts and refactored some controls * [SG-223] - Refactored properties name * [SG-223] - Added visibility toggle icon for api keys of forwarded email username types * [SG-223] - Refactored nested StackLayouts into grids. * [SG-223] - Refactor and pr fixing * [SG-223] - Removed string keys from Resolve - Added static string to resources * [SG-223] - Refactored Copy_Clicked as AsyncCommand - Improved exception handling - Refactored TypeSelected as GeneratorTypeSelected * [SG-223] - Renamed PasswordFormatter * [SG-223] - Refactored VM properties to use UsernameGenerationOptions * Removed LoadUsernameOptions * [SG-223] - Refactored added pickers to use SelectedItem instead SelectedIndex * Deleted PickerIndexToBoolConverter as it isn't needed anymore * [SG-223] - Refactored and simplified Grid row and column definitions * [SG-223] - Refactored Command into async command * Added exception handling and feedback to the user * [SG-223] - Refactored GeneratorType picker to use Enum GeneratorType instead of string * [SG-223] - Changed some resource keys * [SG-223] - Refactor method name * [SG-223] - Refactored code and added logs for switch default cases * [SG-223] - Added flag to control visibility when in edit mode * [SG-223] - Added suffix Parenthesis to keys to prevent future conflicts * [SG-223] - Refactored multiple methods into one, GetUsernameFromAsync * Removed unused Extensions from enums * [SG-223] - Added exception message * [SG-223] - Added localizable enum values through LocalizableEnumConverter * [SG-223] - Fixed space between controls * [SG-223] - Removed unused code and refactored some variables and methods names * [SG-223] - Removed unused code and refactored constant name to be more elucidative * [SG-223] - Removed unused variable
This commit is contained in:
211
src/Core/Services/UsernameGenerationService.cs
Normal file
211
src/Core/Services/UsernameGenerationService.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class UsernameGenerationService : IUsernameGenerationService
|
||||
{
|
||||
private const string CATCH_ALL_EMAIL_DOMAIN_FORMAT = "{0}@{1}";
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IApiService _apiService;
|
||||
private readonly IStateService _stateService;
|
||||
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||
private UsernameGenerationOptions _optionsCache;
|
||||
|
||||
public UsernameGenerationService(
|
||||
ICryptoService cryptoService,
|
||||
IApiService apiService,
|
||||
IStateService stateService)
|
||||
{
|
||||
_cryptoService = cryptoService;
|
||||
_apiService = apiService;
|
||||
_stateService = stateService;
|
||||
}
|
||||
|
||||
public async Task<string> GenerateAsync(UsernameGenerationOptions options)
|
||||
{
|
||||
switch (options.Type)
|
||||
{
|
||||
case UsernameType.PlusAddressedEmail:
|
||||
return await GeneratePlusAddressedEmailAsync(options);
|
||||
case UsernameType.CatchAllEmail:
|
||||
return await GenerateCatchAllAsync(options);
|
||||
case UsernameType.ForwardedEmailAlias:
|
||||
return await GenerateForwardedEmailAliasAsync(options);
|
||||
case UsernameType.RandomWord:
|
||||
return await GenerateRandomWordAsync(options);
|
||||
default:
|
||||
_logger.Value.Error($"Error UsernameGenerationService: UsernameType {options.Type} not implemented.");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<UsernameGenerationOptions> GetOptionsAsync()
|
||||
{
|
||||
if (_optionsCache == null)
|
||||
{
|
||||
var options = await _stateService.GetUsernameGenerationOptionsAsync();
|
||||
_optionsCache = options ?? new UsernameGenerationOptions();
|
||||
}
|
||||
|
||||
return _optionsCache;
|
||||
}
|
||||
public async Task SaveOptionsAsync(UsernameGenerationOptions options)
|
||||
{
|
||||
await _stateService.SetUsernameGenerationOptionsAsync(options);
|
||||
_optionsCache = options;
|
||||
}
|
||||
|
||||
public void ClearCache()
|
||||
{
|
||||
_optionsCache = null;
|
||||
}
|
||||
|
||||
private async Task<string> GenerateRandomWordAsync(UsernameGenerationOptions options)
|
||||
{
|
||||
var listLength = EEFLongWordList.Instance.List.Count - 1;
|
||||
var wordIndex = await _cryptoService.RandomNumberAsync(0, listLength);
|
||||
var randomWord = EEFLongWordList.Instance.List[wordIndex];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(randomWord))
|
||||
{
|
||||
_logger.Value.Error($"Error UsernameGenerationService: EEFLongWordList has NullOrWhiteSpace value at {wordIndex} index.");
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
}
|
||||
|
||||
if (options.CapitalizeRandomWordUsername)
|
||||
{
|
||||
randomWord = Capitalize(randomWord);
|
||||
}
|
||||
|
||||
if (options.IncludeNumberRandomWordUsername)
|
||||
{
|
||||
randomWord = await AppendRandomNumberToRandomWordAsync(randomWord);
|
||||
}
|
||||
|
||||
return randomWord;
|
||||
}
|
||||
|
||||
private async Task<string> GeneratePlusAddressedEmailAsync(UsernameGenerationOptions options)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(options.PlusAddressedEmail) || options.PlusAddressedEmail.Length < 3)
|
||||
{
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
}
|
||||
|
||||
var atIndex = options.PlusAddressedEmail.IndexOf("@");
|
||||
if (atIndex < 1 || atIndex >= options.PlusAddressedEmail.Length - 1)
|
||||
{
|
||||
return options.PlusAddressedEmail;
|
||||
}
|
||||
|
||||
if (options.PlusAddressedEmailType == UsernameEmailType.Random)
|
||||
{
|
||||
var randomString = await RandomStringAsync(8);
|
||||
return options.PlusAddressedEmail.Insert(atIndex, $"+{randomString}");
|
||||
}
|
||||
else
|
||||
{
|
||||
return options.PlusAddressedEmail.Insert(atIndex, $"+{options.EmailWebsite}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> GenerateCatchAllAsync(UsernameGenerationOptions options)
|
||||
{
|
||||
var catchAllEmailDomain = options.CatchAllEmailDomain;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(catchAllEmailDomain))
|
||||
{
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
}
|
||||
|
||||
if (options.CatchAllEmailType == UsernameEmailType.Random)
|
||||
{
|
||||
var randomString = await RandomStringAsync(8);
|
||||
return string.Format(CATCH_ALL_EMAIL_DOMAIN_FORMAT, randomString, catchAllEmailDomain);
|
||||
}
|
||||
|
||||
return string.Format(CATCH_ALL_EMAIL_DOMAIN_FORMAT, options.EmailWebsite, catchAllEmailDomain);
|
||||
}
|
||||
|
||||
private async Task<string> GenerateForwardedEmailAliasAsync(UsernameGenerationOptions options)
|
||||
{
|
||||
switch (options.ServiceType)
|
||||
{
|
||||
case ForwardedEmailServiceType.AnonAddy:
|
||||
if (string.IsNullOrWhiteSpace(options.AnonAddyApiAccessToken) || string.IsNullOrWhiteSpace(options.AnonAddyDomainName))
|
||||
{
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
}
|
||||
return await _apiService.GetUsernameFromAsync(ForwardedEmailServiceType.AnonAddy,
|
||||
new UsernameGeneratorConfig()
|
||||
{
|
||||
ApiToken = options.AnonAddyApiAccessToken,
|
||||
Domain = options.AnonAddyDomainName,
|
||||
Url = "https://app.anonaddy.com/api/v1/aliases"
|
||||
});
|
||||
|
||||
case ForwardedEmailServiceType.FirefoxRelay:
|
||||
if (string.IsNullOrWhiteSpace(options.FirefoxRelayApiAccessToken))
|
||||
{
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
}
|
||||
return await _apiService.GetUsernameFromAsync(ForwardedEmailServiceType.FirefoxRelay,
|
||||
new UsernameGeneratorConfig()
|
||||
{
|
||||
ApiToken = options.FirefoxRelayApiAccessToken,
|
||||
Url = "https://relay.firefox.com/api/v1/relayaddresses/"
|
||||
});
|
||||
|
||||
case ForwardedEmailServiceType.SimpleLogin:
|
||||
if (string.IsNullOrWhiteSpace(options.SimpleLoginApiKey))
|
||||
{
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
}
|
||||
return await _apiService.GetUsernameFromAsync(ForwardedEmailServiceType.SimpleLogin,
|
||||
new UsernameGeneratorConfig()
|
||||
{
|
||||
ApiToken = options.SimpleLoginApiKey,
|
||||
Url = "https://app.simplelogin.io/api/alias/random/new"
|
||||
});
|
||||
default:
|
||||
_logger.Value.Error($"Error UsernameGenerationService: ForwardedEmailServiceType {options.ServiceType} not implemented.");
|
||||
return Constants.DefaultUsernameGenerated;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> RandomStringAsync(int length)
|
||||
{
|
||||
var str = "";
|
||||
var charSet = "abcdefghijklmnopqrstuvwxyz1234567890";
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var randomCharIndex = await _cryptoService.RandomNumberAsync(0, charSet.Length - 1);
|
||||
str += charSet[randomCharIndex];
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
private string Capitalize(string str)
|
||||
{
|
||||
return char.ToUpper(str[0]) + str.Substring(1);
|
||||
}
|
||||
|
||||
private async Task<string> AppendRandomNumberToRandomWordAsync(string word)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(word))
|
||||
{
|
||||
return word;
|
||||
}
|
||||
|
||||
var randomNumber = await _cryptoService.RandomNumberAsync(1, 9999);
|
||||
|
||||
return word + randomNumber.ToString("0000");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user