diff --git a/src/App/Pages/Generator/GeneratorPage.xaml b/src/App/Pages/Generator/GeneratorPage.xaml
index 73bc146ca..a9f27574e 100644
--- a/src/App/Pages/Generator/GeneratorPage.xaml
+++ b/src/App/Pages/Generator/GeneratorPage.xaml
@@ -278,6 +278,15 @@
Text="{Binding AddyIoDomainName}"
StyleClass="box-value"
AutomationId="AnonAddyDomainNameEntry" />
+
+
diff --git a/src/App/Pages/Generator/GeneratorPageViewModel.cs b/src/App/Pages/Generator/GeneratorPageViewModel.cs
index dd9201a09..526c669ba 100644
--- a/src/App/Pages/Generator/GeneratorPageViewModel.cs
+++ b/src/App/Pages/Generator/GeneratorPageViewModel.cs
@@ -81,6 +81,7 @@ namespace Bit.App.Pages
ForwardedEmailServiceType.DuckDuckGo,
ForwardedEmailServiceType.Fastmail,
ForwardedEmailServiceType.FirefoxRelay,
+ ForwardedEmailServiceType.ForwardEmail,
ForwardedEmailServiceType.SimpleLogin
};
@@ -461,6 +462,8 @@ namespace Bit.App.Pages
return _usernameOptions.FirefoxRelayApiAccessToken;
case ForwardedEmailServiceType.SimpleLogin:
return _usernameOptions.SimpleLoginApiKey;
+ case ForwardedEmailServiceType.ForwardEmail:
+ return _usernameOptions.ForwardEmailApiAccessToken;
default:
return null;
}
@@ -505,6 +508,14 @@ namespace Bit.App.Pages
changed = true;
}
break;
+
+ case ForwardedEmailServiceType.ForwardEmail:
+ if (_usernameOptions.ForwardEmailApiAccessToken != value)
+ {
+ _usernameOptions.ForwardEmailApiAccessToken = value;
+ changed = true;
+ }
+ break;
default:
break;
}
@@ -529,6 +540,7 @@ namespace Bit.App.Pages
case ForwardedEmailServiceType.DuckDuckGo:
case ForwardedEmailServiceType.Fastmail:
case ForwardedEmailServiceType.SimpleLogin:
+ case ForwardedEmailServiceType.ForwardEmail:
return AppResources.APIKeyRequiredParenthesis;
default:
return null;
@@ -559,6 +571,20 @@ namespace Bit.App.Pages
}
}
+ public string ForwardEmailDomainName
+ {
+ get => _usernameOptions.ForwardEmailDomainName;
+ set
+ {
+ if (_usernameOptions.ForwardEmailDomainName != value)
+ {
+ _usernameOptions.ForwardEmailDomainName = value;
+ TriggerPropertyChanged(nameof(ForwardEmailDomainName));
+ SaveUsernameOptionsAsync(false).FireAndForget();
+ }
+ }
+ }
+
public bool CapitalizeRandomWordUsername
{
get => _usernameOptions.CapitalizeRandomWordUsername;
@@ -803,6 +829,7 @@ namespace Bit.App.Pages
TriggerPropertyChanged(nameof(GeneratorTypeSelected));
TriggerPropertyChanged(nameof(UsernameTypeDescriptionLabel));
TriggerPropertyChanged(nameof(EmailWebsite));
+ TriggerPropertyChanged(nameof(ForwardEmailDomainName));
}
private void SetOptions()
diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs
index 513d8f2fa..bdb644256 100644
--- a/src/App/Resources/AppResources.Designer.cs
+++ b/src/App/Resources/AppResources.Designer.cs
@@ -3235,6 +3235,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to ForwardEmail.
+ ///
+ public static string ForwardEmail {
+ get {
+ return ResourceManager.GetString("ForwardEmail", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to 4 hours.
///
diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx
index ffbc3afed..0709e6934 100644
--- a/src/App/Resources/AppResources.resx
+++ b/src/App/Resources/AppResources.resx
@@ -2425,6 +2425,10 @@ select Add TOTP to store the key safely
Fastmail
"Fastmail" is the product name and should not be translated.
+
+ ForwardEmail
+ "ForwardEmail" is the product name and should not be translated.
+
API access token
diff --git a/src/Core/Enums/ForwardedEmailServiceType.cs b/src/Core/Enums/ForwardedEmailServiceType.cs
index b969e207d..8d73ed03e 100644
--- a/src/Core/Enums/ForwardedEmailServiceType.cs
+++ b/src/Core/Enums/ForwardedEmailServiceType.cs
@@ -15,5 +15,7 @@ namespace Bit.Core.Enums
DuckDuckGo = 3,
[LocalizableEnum("Fastmail")]
Fastmail = 4,
+ [LocalizableEnum("ForwardEmail")]
+ ForwardEmail = 5,
}
}
diff --git a/src/Core/Models/Domain/UsernameGenerationOptions.cs b/src/Core/Models/Domain/UsernameGenerationOptions.cs
index deb98521d..554783b66 100644
--- a/src/Core/Models/Domain/UsernameGenerationOptions.cs
+++ b/src/Core/Models/Domain/UsernameGenerationOptions.cs
@@ -24,6 +24,8 @@ namespace Bit.Core.Models.Domain
public string FastMailApiKey { get; set; }
public string AnonAddyApiAccessToken { get; set; }
public string AnonAddyDomainName { get; set; }
+ public string ForwardEmailApiAccessToken { get; set; }
+ public string ForwardEmailDomainName { get; set; }
public string EmailWebsite { get; set; }
public ForwarderOptions GetForwarderOptions()
@@ -49,6 +51,12 @@ namespace Bit.Core.Models.Domain
return new ForwarderOptions { ApiKey = FirefoxRelayApiAccessToken };
case ForwardedEmailServiceType.SimpleLogin:
return new ForwarderOptions { ApiKey = SimpleLoginApiKey };
+ case ForwardedEmailServiceType.ForwardEmail:
+ return new ForwardEmailForwarderOptions
+ {
+ ApiKey = ForwardEmailApiAccessToken,
+ DomainName = ForwardEmailDomainName
+ };
default:
return null;
}
diff --git a/src/Core/Services/EmailForwarders/ForwardEmailForwarder.cs b/src/Core/Services/EmailForwarders/ForwardEmailForwarder.cs
new file mode 100644
index 000000000..8f3728c07
--- /dev/null
+++ b/src/Core/Services/EmailForwarders/ForwardEmailForwarder.cs
@@ -0,0 +1,52 @@
+using System;
+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 ForwardEmailForwarderOptions : ForwarderOptions
+ {
+ public string DomainName { get; set; }
+ }
+
+ public class ForwardEmailForwarder : BaseForwarder
+ {
+ private readonly string _domain;
+ protected override string RequestUri => $"https://api.forwardemail.net/v1/domains/{_domain}/aliases";
+
+ public ForwardEmailForwarder(string domain)
+ {
+ _domain = domain;
+ }
+
+ protected override bool CanGenerate(ForwardEmailForwarderOptions options)
+ {
+ return !string.IsNullOrWhiteSpace(options.ApiKey) && !string.IsNullOrWhiteSpace(options.DomainName);
+ }
+
+ protected override void ConfigureHeaders(HttpRequestHeaders headers, ForwardEmailForwarderOptions options)
+ {
+ headers.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes(options.ApiKey + ":"))}");
+ }
+
+ protected override Task GetContentAsync(IApiService apiService, ForwardEmailForwarderOptions options)
+ {
+ return Task.FromResult(new StringContent(
+ JsonConvert.SerializeObject(
+ new
+ {
+ description = "Generated by Bitwarden."
+ }), Encoding.UTF8, "application/json"));
+ }
+
+ protected override string HandleResponse(JObject result)
+ {
+ return $"{result["name"]}@{result["domain"]?["name"] ?? _domain}";
+ }
+ }
+}
diff --git a/src/Core/Services/UsernameGenerationService.cs b/src/Core/Services/UsernameGenerationService.cs
index 9ec9fa215..5789dac5c 100644
--- a/src/Core/Services/UsernameGenerationService.cs
+++ b/src/Core/Services/UsernameGenerationService.cs
@@ -141,6 +141,13 @@ namespace Bit.Core.Services
.GenerateAsync(_apiService, (AnonAddyForwarderOptions)options.GetForwarderOptions());
}
+ if (options.ServiceType == ForwardedEmailServiceType.ForwardEmail)
+ {
+ var forwardedEmailOptions = (ForwardEmailForwarderOptions)options.GetForwarderOptions();
+ return await new ForwardEmailForwarder(forwardedEmailOptions.DomainName)
+ .GenerateAsync(_apiService, forwardedEmailOptions);
+ }
+
BaseForwarder simpleForwarder = null;
switch (options.ServiceType)