1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-09 03:53:15 +00:00

Merged master into PM-1575-display-passkeys

This commit is contained in:
Federico Maccaroni
2023-06-29 15:45:55 -03:00
118 changed files with 1787 additions and 1187 deletions

View File

@@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Models.Response;
@@ -48,6 +49,7 @@ namespace Bit.Core.Abstractions
Task<SsoPrevalidateResponse> PreValidateSso(string identifier);
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
TRequest body, bool authed, bool hasResponse, Action<HttpRequestMessage> alterRequest, bool logoutOnUnauthorized = true);
Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default);
void SetUrls(EnvironmentUrls urls);
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
Task<CipherResponse> PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data);
@@ -89,9 +91,9 @@ namespace Bit.Core.Abstractions
Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode);
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest);
Task<string> GetUsernameFromAsync(ForwardedEmailServiceType service, UsernameGeneratorConfig config);
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
Task<OrganizationDomainSsoDetailsResponse> GetOrgDomainSsoDetailsAsync(string email);
Task<ConfigResponse> GetConfigsAsync();
Task<string> GetFastmailAccountIdAsync(string apiKey);
}
}

View File

@@ -43,6 +43,7 @@ namespace Bit.Core.Abstractions
Task<Tuple<EncString, SymmetricCryptoKey>> MakeShareKeyAsync();
Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial);
Task<int> RandomNumberAsync(int min, int max);
Task<string> RandomStringAsync(int length);
Task<Tuple<SymmetricCryptoKey, EncString>> RemakeEncKeyAsync(SymmetricCryptoKey key);
Task<EncString> RsaEncryptAsync(byte[] data, byte[] publicKey = null);
Task<byte[]> RsaDecryptAsync(string encValue, byte[] privateKey = null);

View File

@@ -20,6 +20,7 @@
<None Remove="Attributes\" />
<None Remove="MessagePack" />
<None Remove="MessagePack.MSBuild.Tasks" />
<None Remove="Services\EmailForwarders\" />
</ItemGroup>
<ItemGroup>
@@ -44,5 +45,6 @@
<ItemGroup>
<Folder Include="Services\Logging\" />
<Folder Include="Attributes\" />
<Folder Include="Services\EmailForwarders\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,11 @@
using System;
namespace Bit.Core.Exceptions
{
public class ForwardedEmailInvalidSecretException : Exception
{
public ForwardedEmailInvalidSecretException(Exception innerEx)
: base("Invalid API Secret", innerEx)
{
}
}
}

View File

@@ -1,4 +1,5 @@
using Bit.Core.Enums;
using Bit.Core.Services.EmailForwarders;
namespace Bit.Core.Models.Domain
{
@@ -24,5 +25,33 @@ namespace Bit.Core.Models.Domain
public string AnonAddyApiAccessToken { get; set; }
public string AnonAddyDomainName { get; set; }
public string EmailWebsite { get; set; }
public ForwarderOptions GetForwarderOptions()
{
if (Type != UsernameType.ForwardedEmailAlias)
{
return null;
}
switch (ServiceType)
{
case ForwardedEmailServiceType.AnonAddy:
return new AnonAddyForwarderOptions
{
ApiKey = AnonAddyApiAccessToken,
DomainName = AnonAddyDomainName
};
case ForwardedEmailServiceType.DuckDuckGo:
return new ForwarderOptions { ApiKey = DuckDuckGoApiKey };
case ForwardedEmailServiceType.Fastmail:
return new ForwarderOptions { ApiKey = FastMailApiKey };
case ForwardedEmailServiceType.FirefoxRelay:
return new ForwarderOptions { ApiKey = FirefoxRelayApiAccessToken };
case ForwardedEmailServiceType.SimpleLogin:
return new ForwarderOptions { ApiKey = SimpleLoginApiKey };
default:
return null;
}
}
}
}

View File

@@ -1,9 +0,0 @@
namespace Bit.Core.Models.Domain
{
public class UsernameGeneratorConfig
{
public string ApiToken { get; set; }
public string Domain { get; set; }
public string Url { get; set; }
}
}

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
@@ -763,111 +764,29 @@ namespace Bit.Core.Services
}
}
public async Task<string> GetUsernameFromAsync(ForwardedEmailServiceType service, UsernameGeneratorConfig config)
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default)
{
using (var requestMessage = new HttpRequestMessage())
HttpResponseMessage response;
try
{
requestMessage.Version = new Version(1, 0);
requestMessage.Method = HttpMethod.Post;
requestMessage.RequestUri = new Uri(config.Url);
requestMessage.Headers.Add("Accept", "application/json");
switch (service)
{
case ForwardedEmailServiceType.AnonAddy:
requestMessage.Headers.Add("Authorization", $"Bearer {config.ApiToken}");
requestMessage.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["domain"] = config.Domain
});
break;
case ForwardedEmailServiceType.FirefoxRelay:
requestMessage.Headers.Add("Authorization", $"Token {config.ApiToken}");
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(
new
{
enabled = true,
description = "Generated by Bitwarden."
}), Encoding.UTF8, "application/json");
break;
case ForwardedEmailServiceType.SimpleLogin:
requestMessage.Headers.Add("Authentication", config.ApiToken);
break;
case ForwardedEmailServiceType.DuckDuckGo:
requestMessage.Headers.Add("Authorization", $"Bearer {config.ApiToken}");
break;
case ForwardedEmailServiceType.Fastmail:
requestMessage.Headers.Add("Authorization", $"Bearer {config.ApiToken}");
requestMessage.Content = new StringContent(await CreateFastmailRequest(config.ApiToken),
Encoding.UTF8, "application/json");
break;
}
HttpResponseMessage response;
try
{
response = await _httpClient.SendAsync(requestMessage);
}
catch (Exception e)
{
throw new ApiException(HandleWebError(e));
}
if (!response.IsSuccessStatusCode)
{
throw new ApiException(new ErrorResponse
{
StatusCode = response.StatusCode,
Message = $"{service} error: {(int)response.StatusCode} {response.ReasonPhrase}."
});
}
var responseJsonString = await response.Content.ReadAsStringAsync();
var result = JObject.Parse(responseJsonString);
switch (service)
{
case ForwardedEmailServiceType.AnonAddy:
return result["data"]?["email"]?.ToString();
case ForwardedEmailServiceType.FirefoxRelay:
return result["full_address"]?.ToString();
case ForwardedEmailServiceType.SimpleLogin:
return result["alias"]?.ToString();
case ForwardedEmailServiceType.DuckDuckGo:
return $"{result["address"]?.ToString()}@duck.com";
case ForwardedEmailServiceType.Fastmail:
return HandleFastMailResponse(result);
default:
return string.Empty;
}
response = await _httpClient.SendAsync(requestMessage, cancellationToken);
}
catch (Exception e)
{
throw new ApiException(HandleWebError(e));
}
if (!response.IsSuccessStatusCode)
{
throw new ApiException(new ErrorResponse
{
StatusCode = response.StatusCode,
Message = $"{requestMessage.RequestUri} error: {(int)response.StatusCode} {response.ReasonPhrase}."
});
}
return response;
}
private string HandleFastMailResponse(JObject result)
{
if (result["methodResponses"] == null || !result["methodResponses"].HasValues ||
!result["methodResponses"][0].HasValues)
{
throw new Exception("Fastmail error: could not parse response.");
}
if (result["methodResponses"][0][0].ToString() == "MaskedEmail/set")
{
if (result["methodResponses"][0][1]?["created"]?["new-masked-email"] != null)
{
return result["methodResponses"][0][1]?["created"]?["new-masked-email"]?["email"].ToString();
}
if (result["methodResponses"][0][1]?["notCreated"]?["new-masked-email"] != null)
{
throw new Exception("Fastmail error: " +
result["methodResponses"][0][1]?["created"]?["new-masked-email"]?["description"].ToString());
}
}
else if (result["methodResponses"][0][0].ToString() == "error")
{
throw new Exception("Fastmail error: " + result["methodResponses"][0][1]?["description"].ToString());
}
throw new Exception("Fastmail error: could not parse response.");
}
private async Task<string> CreateFastmailRequest(string apiKey)
public async Task<string> GetFastmailAccountIdAsync(string apiKey)
{
using (var httpclient = new HttpClient())
{
@@ -891,36 +810,7 @@ namespace Bit.Core.Services
});
}
var result = JObject.Parse(await response.Content.ReadAsStringAsync());
var accountId = result["primaryAccounts"]?["https://www.fastmail.com/dev/maskedemail"]?.ToString();
var requestJObj = new JObject
{
new JProperty("using",
new JArray { "https://www.fastmail.com/dev/maskedemail", "urn:ietf:params:jmap:core" }),
new JProperty("methodCalls",
new JArray
{
new JArray
{
"MaskedEmail/set",
new JObject
{
["accountId"] = accountId,
["create"] = new JObject
{
["new-masked-email"] = new JObject
{
["state"] = "enabled",
["description"] = "",
["url"] = "",
["emailPrefix"] = ""
}
}
},
"0"
}
})
};
return requestJObj.ToString();
return result["primaryAccounts"]?["https://www.fastmail.com/dev/maskedemail"]?.ToString();
}
}

View File

@@ -14,6 +14,8 @@ namespace Bit.Core.Services
{
public class CryptoService : ICryptoService
{
private const string RANDOM_STRING_CHARSET = "abcdefghijklmnopqrstuvwxyz1234567890";
private readonly IStateService _stateService;
private readonly ICryptoFunctionService _cryptoFunctionService;
@@ -633,6 +635,22 @@ namespace Bit.Core.Services
return (int)(min + (ui % diff));
}
/// <summary>
/// Makes random string with length <paramref name="length"/> based on the charset <see cref="RANDOM_STRING_CHARSET"/>
/// </summary>
public async Task<string> RandomStringAsync(int length)
{
var sb = new StringBuilder();
for (var i = 0; i < length; i++)
{
var randomCharIndex = await RandomNumberAsync(0, RANDOM_STRING_CHARSET.Length - 1);
sb.Append(RANDOM_STRING_CHARSET[randomCharIndex]);
}
return sb.ToString();
}
// Helpers
private async Task<EncryptedObject> AesEncryptAsync(byte[] data, SymmetricCryptoKey key)

View File

@@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Newtonsoft.Json.Linq;
namespace Bit.Core.Services.EmailForwarders
{
public class AnonAddyForwarderOptions : ForwarderOptions
{
public string DomainName { get; set; }
}
public class AnonAddyForwarder : BaseForwarder<AnonAddyForwarderOptions>
{
protected override string RequestUri => "https://app.anonaddy.com/api/v1/aliases";
protected override bool CanGenerate(AnonAddyForwarderOptions options)
{
return !string.IsNullOrWhiteSpace(options.ApiKey) && !string.IsNullOrWhiteSpace(options.DomainName);
}
protected override void ConfigureHeaders(HttpRequestHeaders headers, AnonAddyForwarderOptions options)
{
headers.Add("Authorization", $"Bearer {options.ApiKey}");
}
protected override Task<HttpContent> GetContentAsync(IApiService apiService, AnonAddyForwarderOptions options)
{
return Task.FromResult<HttpContent>(new FormUrlEncodedContent(new Dictionary<string, string>
{
["domain"] = options.DomainName
}));
}
protected override string HandleResponse(JObject result)
{
return result["data"]?["email"]?.ToString();
}
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Newtonsoft.Json.Linq;
namespace Bit.Core.Services.EmailForwarders
{
public abstract class BaseForwarder<T>
where T : ForwarderOptions
{
protected abstract string RequestUri { get; }
public async Task<string> GenerateAsync(IApiService apiService, T options)
{
if (!CanGenerate(options))
{
return Constants.DefaultUsernameGenerated;
}
using (var requestMessage = new HttpRequestMessage())
{
requestMessage.Version = new Version(1, 0);
requestMessage.Method = HttpMethod.Post;
requestMessage.RequestUri = new Uri(RequestUri);
requestMessage.Headers.Add("Accept", "application/json");
ConfigureHeaders(requestMessage.Headers, options);
requestMessage.Content = await GetContentAsync(apiService, options);
try
{
var response = await apiService.SendAsync(requestMessage);
var responseJsonString = await response.Content.ReadAsStringAsync();
return HandleResponse(JObject.Parse(responseJsonString));
}
catch (ApiException ex)
{
if (IsRequestSecretInvalid(ex))
{
throw new ForwardedEmailInvalidSecretException(ex);
}
throw;
}
}
}
protected virtual bool CanGenerate(T options) => !string.IsNullOrWhiteSpace(options.ApiKey);
protected abstract void ConfigureHeaders(HttpRequestHeaders headers, T options);
protected abstract Task<HttpContent> GetContentAsync(IApiService apiService, T options);
protected abstract string HandleResponse(JObject result);
protected virtual bool IsRequestSecretInvalid(ApiException ex) => ex.Error?.StatusCode == System.Net.HttpStatusCode.Unauthorized;
}
}

View File

@@ -0,0 +1,25 @@
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Newtonsoft.Json.Linq;
namespace Bit.Core.Services.EmailForwarders
{
public class DuckDuckGoForwarder : BaseForwarder<ForwarderOptions>
{
protected override string RequestUri => "https://quack.duckduckgo.com/api/email/addresses";
protected override void ConfigureHeaders(HttpRequestHeaders headers, ForwarderOptions options)
{
headers.Add("Authorization", $"Bearer {options.ApiKey}");
}
protected override Task<HttpContent> GetContentAsync(IApiService apiService, ForwarderOptions options) => Task.FromResult<HttpContent>(null);
protected override string HandleResponse(JObject result)
{
return $"{result["address"]?.ToString()}@duck.com";
}
}
}

View File

@@ -0,0 +1,98 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Newtonsoft.Json.Linq;
namespace Bit.Core.Services.EmailForwarders
{
public class FastmailForwarder : BaseForwarder<ForwarderOptions>
{
protected override string RequestUri => "https://api.fastmail.com/jmap/api/";
protected override void ConfigureHeaders(HttpRequestHeaders headers, ForwarderOptions options)
{
headers.Add("Authorization", $"Bearer {options.ApiKey}");
}
protected override async Task<HttpContent> GetContentAsync(IApiService apiService, ForwarderOptions options)
{
string accountId = null;
try
{
accountId = await apiService.GetFastmailAccountIdAsync(options.ApiKey);
}
catch (ApiException ex)
{
if (IsRequestSecretInvalid(ex))
{
throw new ForwardedEmailInvalidSecretException(ex);
}
throw;
}
var requestJObj = new JObject
{
new JProperty("using",
new JArray { "https://www.fastmail.com/dev/maskedemail", "urn:ietf:params:jmap:core" }),
new JProperty("methodCalls",
new JArray
{
new JArray
{
"MaskedEmail/set",
new JObject
{
["accountId"] = accountId,
["create"] = new JObject
{
["new-masked-email"] = new JObject
{
["state"] = "enabled",
["description"] = "",
["url"] = "",
["emailPrefix"] = ""
}
}
},
"0"
}
})
};
return new StringContent(requestJObj.ToString(), Encoding.UTF8, "application/json");
}
protected override string HandleResponse(JObject result)
{
if (result["methodResponses"] == null || !result["methodResponses"].HasValues ||
!result["methodResponses"][0].HasValues)
{
throw new Exception("Fastmail error: could not parse response.");
}
if (result["methodResponses"][0][0].ToString() == "MaskedEmail/set")
{
if (result["methodResponses"][0][1]?["created"]?["new-masked-email"] != null)
{
return result["methodResponses"][0][1]?["created"]?["new-masked-email"]?["email"].ToString();
}
if (result["methodResponses"][0][1]?["notCreated"]?["new-masked-email"] != null)
{
throw new Exception("Fastmail error: " +
result["methodResponses"][0][1]?["created"]?["new-masked-email"]?["description"].ToString());
}
}
else if (result["methodResponses"][0][0].ToString() == "error")
{
throw new Exception("Fastmail error: " + result["methodResponses"][0][1]?["description"].ToString());
}
throw new Exception("Fastmail error: could not parse response.");
}
protected override bool IsRequestSecretInvalid(ApiException ex) => base.IsRequestSecretInvalid(ex) || ex.Error?.StatusCode == System.Net.HttpStatusCode.Forbidden;
}
}

View File

@@ -0,0 +1,36 @@
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Bit.Core.Services.EmailForwarders
{
public class FirefoxRelayForwarder : BaseForwarder<ForwarderOptions>
{
protected override string RequestUri => "https://relay.firefox.com/api/v1/relayaddresses/";
protected override void ConfigureHeaders(HttpRequestHeaders headers, ForwarderOptions options)
{
headers.Add("Authorization", $"Token {options.ApiKey}");
}
protected override Task<HttpContent> GetContentAsync(IApiService apiService, ForwarderOptions options)
{
return Task.FromResult<HttpContent>(new StringContent(
JsonConvert.SerializeObject(
new
{
enabled = true,
description = "Generated by Bitwarden."
}), Encoding.UTF8, "application/json"));
}
protected override string HandleResponse(JObject result)
{
return result["full_address"]?.ToString();
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Bit.Core.Services.EmailForwarders
{
public class ForwarderOptions
{
public string ApiKey { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Newtonsoft.Json.Linq;
namespace Bit.Core.Services.EmailForwarders
{
public class SimpleLoginForwarder : BaseForwarder<ForwarderOptions>
{
protected override string RequestUri => "https://app.simplelogin.io/api/alias/random/new";
protected override void ConfigureHeaders(HttpRequestHeaders headers, ForwarderOptions options)
{
headers.Add("Authentication", options.ApiKey);
}
protected override Task<HttpContent> GetContentAsync(IApiService apiService, ForwarderOptions options) => Task.FromResult<HttpContent>(null);
protected override string HandleResponse(JObject result)
{
return result["alias"]?.ToString();
}
}
}

View File

@@ -1,7 +1,9 @@
using System.Threading.Tasks;
using System.Text;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Services.EmailForwarders;
using Bit.Core.Utilities;
namespace Bit.Core.Services
@@ -12,7 +14,7 @@ namespace Bit.Core.Services
private readonly ICryptoService _cryptoService;
private readonly IApiService _apiService;
private readonly IStateService _stateService;
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
private UsernameGenerationOptions _optionsCache;
public UsernameGenerationService(
@@ -104,7 +106,7 @@ namespace Bit.Core.Services
if (options.PlusAddressedEmailType == UsernameEmailType.Random)
{
var randomString = await RandomStringAsync(8);
var randomString = await _cryptoService.RandomStringAsync(8);
return options.PlusAddressedEmail.Insert(atIndex, $"+{randomString}");
}
else
@@ -124,7 +126,7 @@ namespace Bit.Core.Services
if (options.CatchAllEmailType == UsernameEmailType.Random)
{
var randomString = await RandomStringAsync(8);
var randomString = await _cryptoService.RandomStringAsync(8);
return string.Format(CATCH_ALL_EMAIL_DOMAIN_FORMAT, randomString, catchAllEmailDomain);
}
@@ -133,85 +135,34 @@ namespace Bit.Core.Services
private async Task<string> GenerateForwardedEmailAliasAsync(UsernameGenerationOptions options)
{
if (options.ServiceType == ForwardedEmailServiceType.AnonAddy)
{
return await new AnonAddyForwarder()
.GenerateAsync(_apiService, (AnonAddyForwarderOptions)options.GetForwarderOptions());
}
BaseForwarder<ForwarderOptions> simpleForwarder = null;
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/"
});
simpleForwarder = new FirefoxRelayForwarder();
break;
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"
});
simpleForwarder = new SimpleLoginForwarder();
break;
case ForwardedEmailServiceType.DuckDuckGo:
if (string.IsNullOrWhiteSpace(options.DuckDuckGoApiKey))
{
return Constants.DefaultUsernameGenerated;
}
return await _apiService.GetUsernameFromAsync(ForwardedEmailServiceType.DuckDuckGo,
new UsernameGeneratorConfig()
{
ApiToken = options.DuckDuckGoApiKey,
Url = "https://quack.duckduckgo.com/api/email/addresses"
});
simpleForwarder = new DuckDuckGoForwarder();
break;
case ForwardedEmailServiceType.Fastmail:
if (string.IsNullOrWhiteSpace(options.FastMailApiKey))
{
return Constants.DefaultUsernameGenerated;
}
return await _apiService.GetUsernameFromAsync(ForwardedEmailServiceType.Fastmail,
new UsernameGeneratorConfig()
{
ApiToken = options.FastMailApiKey,
Url = "https://api.fastmail.com/jmap/api/"
});
simpleForwarder = new FastmailForwarder();
break;
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;
return await simpleForwarder.GenerateAsync(_apiService, options.GetForwarderOptions());
}
private string Capitalize(string str)