[PM-192] Refactor forwarded email providers (#2579)
* PM-192 Refactor Forwarded email providers to use better patterns and code reuse. * PM-192 fix format
This commit is contained in:
parent
3506269811
commit
1014563c75
|
@ -249,27 +249,25 @@
|
|||
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
||||
StyleClass="box-value"
|
||||
AutomationId="ServiceTypePicker" />
|
||||
<!--ANONADDY OPTIONS-->
|
||||
<Grid IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
||||
Grid.RowDefinitions="Auto,*"
|
||||
Grid.ColumnDefinitions="*,Auto">
|
||||
<Grid
|
||||
Grid.RowDefinitions="Auto,*"
|
||||
Grid.ColumnDefinitions="*,Auto">
|
||||
<Label
|
||||
Margin="0,10,0,0"
|
||||
Text="{u:I18n APIAccessToken}"
|
||||
Text="{Binding ForwardedEmailApiSecretLabel}"
|
||||
StyleClass="box-label"/>
|
||||
<Entry
|
||||
x:Name="_anonAddyApiAccessTokenEntry"
|
||||
Text="{Binding AnonAddyApiAccessToken}"
|
||||
IsPassword="{Binding ShowAnonAddyApiAccessToken, Converter={StaticResource inverseBool}}"
|
||||
Text="{Binding ForwardedEmailApiSecret}"
|
||||
IsPassword="{Binding ShowForwardedEmailApiSecret, Converter={StaticResource inverseBool}}"
|
||||
Grid.Row="1"
|
||||
AutomationId="AnonAddyApiAccessTokenEntry" />
|
||||
AutomationId="ForwardedEmailApiSecretEntry" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowAnonAddyApiAccessToken, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||
Text="{Binding ShowForwardedEmailApiSecret, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
AutomationId="ShowAnonAddyApiAccessTokenButton" />
|
||||
AutomationId="ShowForwardedEmailApiSecretButton" />
|
||||
</Grid>
|
||||
<Label IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
||||
Text="{u:I18n DomainNameRequiredParenthesis}"
|
||||
|
@ -280,98 +278,6 @@
|
|||
Text="{Binding AnonAddyDomainName}"
|
||||
StyleClass="box-value"
|
||||
AutomationId="AnonAddyDomainNameEntry" />
|
||||
<!--FIREFOX RELAY OPTIONS-->
|
||||
<Grid StyleClass="box-row, box-row-input"
|
||||
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.FirefoxRelay}}"
|
||||
Grid.RowDefinitions="Auto,*"
|
||||
Grid.ColumnDefinitions="*,Auto">
|
||||
<Label
|
||||
Text="{u:I18n APIAccessToken}"
|
||||
StyleClass="box-label"/>
|
||||
<Entry
|
||||
x:Name="_firefoxRelayApiAccessTokenEntry"
|
||||
Text="{Binding FirefoxRelayApiAccessToken}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
IsPassword="{Binding ShowFirefoxRelayApiAccessToken, Converter={StaticResource inverseBool}}"
|
||||
AutomationId="FirefoxRelayApiAccessTokenEntry" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowFirefoxRelayApiAccessToken, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
AutomationId="ShowFirefoxRelayApiAccessTokenButton" />
|
||||
</Grid>
|
||||
<!--SIMPLELOGIN OPTIONS-->
|
||||
<Grid StyleClass="box-row, box-row-input"
|
||||
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.SimpleLogin}}"
|
||||
Grid.RowDefinitions="Auto,*"
|
||||
Grid.ColumnDefinitions="*,Auto">
|
||||
<Label
|
||||
Text="{u:I18n APIKeyRequiredParenthesis}"
|
||||
StyleClass="box-label"/>
|
||||
<Entry
|
||||
x:Name="_simpleLoginApiKeyEntry"
|
||||
Text="{Binding SimpleLoginApiKey}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
IsPassword="{Binding ShowSimpleLoginApiKey, Converter={StaticResource inverseBool}}"
|
||||
AutomationId="SimpleLoginApiKeyEntry" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowSimpleLoginApiKey, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
AutomationId="ShowSimpleLoginApiKeyButton" />
|
||||
</Grid>
|
||||
<!--DUCKDUCKGO OPTIONS-->
|
||||
<Grid StyleClass="box-row, box-row-input"
|
||||
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.DuckDuckGo}}"
|
||||
Grid.RowDefinitions="Auto,*"
|
||||
Grid.ColumnDefinitions="*,Auto">
|
||||
<Label
|
||||
Text="{u:I18n APIKeyRequiredParenthesis}"
|
||||
StyleClass="box-label"/>
|
||||
<Entry
|
||||
x:Name="_duckDuckGoApiAccessTokenEntry"
|
||||
Text="{Binding DuckDuckGoApiKey}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
IsPassword="{Binding ShowDuckDuckGoApiKey, Converter={StaticResource inverseBool}}"
|
||||
AutomationId="DuckDuckGoApiKeyEntry" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowDuckDuckGoApiKey, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
AutomationId="ShowDuckDuckGoApiKeyButton" />
|
||||
</Grid>
|
||||
<!--FASTMAIL OPTIONS-->
|
||||
<Grid StyleClass="box-row, box-row-input"
|
||||
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.Fastmail}}"
|
||||
Grid.RowDefinitions="Auto,*"
|
||||
Grid.ColumnDefinitions="*,Auto">
|
||||
<Label
|
||||
Text="{u:I18n APIKeyRequiredParenthesis}"
|
||||
StyleClass="box-label"/>
|
||||
<Entry
|
||||
x:Name="_fastmailApiAccessTokenEntry"
|
||||
Text="{Binding FastmailApiKey}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
IsPassword="{Binding ShowFastmailApiKey, Converter={StaticResource inverseBool}}"
|
||||
AutomationId="FastmailApiKeyEntry" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowFastmailApiKey, Converter={StaticResource iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
AutomationId="ShowFastmailApiKeyButton" />
|
||||
</Grid>
|
||||
</StackLayout>
|
||||
<!--RANDOM WORD OPTIONS-->
|
||||
<Grid IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}">
|
||||
|
|
|
@ -8,6 +8,7 @@ using Bit.App.Utilities;
|
|||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
|
@ -23,7 +24,7 @@ namespace Bit.App.Pages
|
|||
private readonly IUsernameGenerationService _usernameGenerationService;
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
|
||||
|
||||
private PasswordGenerationOptions _options;
|
||||
private UsernameGenerationOptions _usernameOptions;
|
||||
|
@ -49,11 +50,7 @@ namespace Bit.App.Pages
|
|||
private bool _doneIniting;
|
||||
private bool _showTypePicker;
|
||||
private string _emailWebsite;
|
||||
private bool _showFirefoxRelayApiAccessToken;
|
||||
private bool _showAnonAddyApiAccessToken;
|
||||
private bool _showSimpleLoginApiKey;
|
||||
private bool _showDuckDuckGoApiKey;
|
||||
private bool _showFastmailApiKey;
|
||||
private bool _showForwardedEmailApiSecret;
|
||||
private bool _editMode;
|
||||
|
||||
public GeneratorPageViewModel()
|
||||
|
@ -96,7 +93,7 @@ namespace Bit.App.Pages
|
|||
UsernameTypePromptHelpCommand = new Command(UsernameTypePromptHelp);
|
||||
RegenerateCommand = new AsyncCommand(RegenerateAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
||||
RegenerateUsernameCommand = new AsyncCommand(RegenerateUsernameAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
||||
ToggleForwardedEmailHiddenValueCommand = new AsyncCommand(ToggleForwardedEmailHiddenValueAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
||||
ToggleForwardedEmailHiddenValueCommand = new Command(() => ShowForwardedEmailApiSecret = !ShowForwardedEmailApiSecret);
|
||||
CopyCommand = new AsyncCommand(CopyAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
||||
CloseCommand = new AsyncCommand(CloseAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
||||
}
|
||||
|
@ -415,7 +412,6 @@ namespace Bit.App.Pages
|
|||
|
||||
public string UsernameTypeDescriptionLabel => GetUsernameTypeLabelDescription(UsernameTypeSelected);
|
||||
|
||||
|
||||
public ForwardedEmailServiceType ForwardedEmailServiceSelected
|
||||
{
|
||||
get => _usernameOptions.ServiceType;
|
||||
|
@ -425,7 +421,11 @@ namespace Bit.App.Pages
|
|||
{
|
||||
_usernameOptions.ServiceType = value;
|
||||
Username = Constants.DefaultUsernameGenerated;
|
||||
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
|
||||
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected), new string[]
|
||||
{
|
||||
nameof(ForwardedEmailApiSecret),
|
||||
nameof(ForwardedEmailApiSecretLabel)
|
||||
});
|
||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||
}
|
||||
}
|
||||
|
@ -445,27 +445,104 @@ namespace Bit.App.Pages
|
|||
}
|
||||
}
|
||||
|
||||
public string AnonAddyApiAccessToken
|
||||
public string ForwardedEmailApiSecret
|
||||
{
|
||||
get => _usernameOptions.AnonAddyApiAccessToken;
|
||||
get
|
||||
{
|
||||
switch (ForwardedEmailServiceSelected)
|
||||
{
|
||||
case ForwardedEmailServiceType.AnonAddy:
|
||||
return _usernameOptions.AnonAddyApiAccessToken;
|
||||
case ForwardedEmailServiceType.DuckDuckGo:
|
||||
return _usernameOptions.DuckDuckGoApiKey;
|
||||
case ForwardedEmailServiceType.Fastmail:
|
||||
return _usernameOptions.FastMailApiKey;
|
||||
case ForwardedEmailServiceType.FirefoxRelay:
|
||||
return _usernameOptions.FirefoxRelayApiAccessToken;
|
||||
case ForwardedEmailServiceType.SimpleLogin:
|
||||
return _usernameOptions.SimpleLoginApiKey;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_usernameOptions.AnonAddyApiAccessToken != value)
|
||||
bool changed = false;
|
||||
switch (ForwardedEmailServiceSelected)
|
||||
{
|
||||
_usernameOptions.AnonAddyApiAccessToken = value;
|
||||
TriggerPropertyChanged(nameof(AnonAddyApiAccessToken));
|
||||
case ForwardedEmailServiceType.AnonAddy:
|
||||
if (_usernameOptions.AnonAddyApiAccessToken != value)
|
||||
{
|
||||
_usernameOptions.AnonAddyApiAccessToken = value;
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
case ForwardedEmailServiceType.DuckDuckGo:
|
||||
if (_usernameOptions.DuckDuckGoApiKey != value)
|
||||
{
|
||||
_usernameOptions.DuckDuckGoApiKey = value;
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
case ForwardedEmailServiceType.Fastmail:
|
||||
if (_usernameOptions.FastMailApiKey != value)
|
||||
{
|
||||
_usernameOptions.FastMailApiKey = value;
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
case ForwardedEmailServiceType.FirefoxRelay:
|
||||
if (_usernameOptions.FirefoxRelayApiAccessToken != value)
|
||||
{
|
||||
_usernameOptions.FirefoxRelayApiAccessToken = value;
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
case ForwardedEmailServiceType.SimpleLogin:
|
||||
if (_usernameOptions.SimpleLoginApiKey != value)
|
||||
{
|
||||
_usernameOptions.SimpleLoginApiKey = value;
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
TriggerPropertyChanged(nameof(ForwardedEmailApiSecret));
|
||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowAnonAddyApiAccessToken
|
||||
public string ForwardedEmailApiSecretLabel
|
||||
{
|
||||
get
|
||||
{
|
||||
return _showAnonAddyApiAccessToken;
|
||||
switch (ForwardedEmailServiceSelected)
|
||||
{
|
||||
case ForwardedEmailServiceType.AnonAddy:
|
||||
case ForwardedEmailServiceType.FirefoxRelay:
|
||||
return AppResources.APIAccessToken;
|
||||
case ForwardedEmailServiceType.DuckDuckGo:
|
||||
case ForwardedEmailServiceType.Fastmail:
|
||||
case ForwardedEmailServiceType.SimpleLogin:
|
||||
return AppResources.APIKeyRequiredParenthesis;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
set => SetProperty(ref _showAnonAddyApiAccessToken, value);
|
||||
}
|
||||
|
||||
public bool ShowForwardedEmailApiSecret
|
||||
{
|
||||
get
|
||||
{
|
||||
return _showForwardedEmailApiSecret;
|
||||
}
|
||||
set => SetProperty(ref _showForwardedEmailApiSecret, value);
|
||||
}
|
||||
|
||||
public string AnonAddyDomainName
|
||||
|
@ -482,99 +559,6 @@ namespace Bit.App.Pages
|
|||
}
|
||||
}
|
||||
|
||||
public string FirefoxRelayApiAccessToken
|
||||
{
|
||||
get => _usernameOptions.FirefoxRelayApiAccessToken;
|
||||
set
|
||||
{
|
||||
if (_usernameOptions.FirefoxRelayApiAccessToken != value)
|
||||
{
|
||||
_usernameOptions.FirefoxRelayApiAccessToken = value;
|
||||
TriggerPropertyChanged(nameof(FirefoxRelayApiAccessToken));
|
||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowFirefoxRelayApiAccessToken
|
||||
{
|
||||
get
|
||||
{
|
||||
return _showFirefoxRelayApiAccessToken;
|
||||
}
|
||||
set => SetProperty(ref _showFirefoxRelayApiAccessToken, value);
|
||||
}
|
||||
|
||||
public string SimpleLoginApiKey
|
||||
{
|
||||
get => _usernameOptions.SimpleLoginApiKey;
|
||||
set
|
||||
{
|
||||
if (_usernameOptions.SimpleLoginApiKey != value)
|
||||
{
|
||||
_usernameOptions.SimpleLoginApiKey = value;
|
||||
TriggerPropertyChanged(nameof(SimpleLoginApiKey));
|
||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowSimpleLoginApiKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return _showSimpleLoginApiKey;
|
||||
}
|
||||
set => SetProperty(ref _showSimpleLoginApiKey, value);
|
||||
}
|
||||
|
||||
public string DuckDuckGoApiKey
|
||||
{
|
||||
get => _usernameOptions.DuckDuckGoApiKey;
|
||||
set
|
||||
{
|
||||
if (_usernameOptions.DuckDuckGoApiKey != value)
|
||||
{
|
||||
_usernameOptions.DuckDuckGoApiKey = value;
|
||||
TriggerPropertyChanged(nameof(DuckDuckGoApiKey));
|
||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowDuckDuckGoApiKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return _showDuckDuckGoApiKey;
|
||||
}
|
||||
set => SetProperty(ref _showDuckDuckGoApiKey, value);
|
||||
}
|
||||
|
||||
|
||||
public string FastmailApiKey
|
||||
{
|
||||
get => _usernameOptions.FastMailApiKey;
|
||||
set
|
||||
{
|
||||
if (_usernameOptions.FastMailApiKey != value)
|
||||
{
|
||||
_usernameOptions.FastMailApiKey = value;
|
||||
TriggerPropertyChanged(nameof(FastmailApiKey));
|
||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowFastmailApiKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return _showFastmailApiKey;
|
||||
}
|
||||
set => SetProperty(ref _showFastmailApiKey, value);
|
||||
}
|
||||
|
||||
public bool CapitalizeRandomWordUsername
|
||||
{
|
||||
get => _usernameOptions.CapitalizeRandomWordUsername;
|
||||
|
@ -807,12 +791,9 @@ namespace Bit.App.Pages
|
|||
TriggerPropertyChanged(nameof(PlusAddressedEmailTypeSelected));
|
||||
TriggerPropertyChanged(nameof(IncludeNumberRandomWordUsername));
|
||||
TriggerPropertyChanged(nameof(CapitalizeRandomWordUsername));
|
||||
TriggerPropertyChanged(nameof(SimpleLoginApiKey));
|
||||
TriggerPropertyChanged(nameof(FirefoxRelayApiAccessToken));
|
||||
TriggerPropertyChanged(nameof(ForwardedEmailApiSecret));
|
||||
TriggerPropertyChanged(nameof(ForwardedEmailApiSecretLabel));
|
||||
TriggerPropertyChanged(nameof(AnonAddyDomainName));
|
||||
TriggerPropertyChanged(nameof(AnonAddyApiAccessToken));
|
||||
TriggerPropertyChanged(nameof(DuckDuckGoApiKey));
|
||||
TriggerPropertyChanged(nameof(FastmailApiKey));
|
||||
TriggerPropertyChanged(nameof(CatchAllEmailDomain));
|
||||
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
|
||||
TriggerPropertyChanged(nameof(UsernameTypeSelected));
|
||||
|
@ -845,15 +826,23 @@ namespace Bit.App.Pages
|
|||
{
|
||||
_logger.Value.Exception(ex);
|
||||
|
||||
string message = AppResources.GenericErrorMessage;
|
||||
|
||||
if (IsUsername && UsernameTypeSelected == UsernameType.ForwardedEmailAlias)
|
||||
{
|
||||
await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(
|
||||
AppResources.AnErrorHasOccurred, string.Format(AppResources.UnknownXErrorMessage, ForwardedEmailServiceSelected), AppResources.Ok));
|
||||
}
|
||||
else
|
||||
{
|
||||
await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok));
|
||||
if (ex is ForwardedEmailInvalidSecretException)
|
||||
{
|
||||
message = ForwardedEmailServiceSelected == ForwardedEmailServiceType.AnonAddy || ForwardedEmailServiceSelected == ForwardedEmailServiceType.FirefoxRelay
|
||||
? AppResources.InvalidAPIToken
|
||||
: AppResources.InvalidAPIKey;
|
||||
}
|
||||
else
|
||||
{
|
||||
message = string.Format(AppResources.UnknownXErrorMessage, ForwardedEmailServiceSelected);
|
||||
}
|
||||
}
|
||||
|
||||
await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(AppResources.AnErrorHasOccurred, message, AppResources.Ok));
|
||||
}
|
||||
|
||||
private string GetUsernameTypeLabelDescription(UsernameType value)
|
||||
|
@ -870,27 +859,5 @@ namespace Bit.App.Pages
|
|||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ToggleForwardedEmailHiddenValueAsync()
|
||||
{
|
||||
switch (ForwardedEmailServiceSelected)
|
||||
{
|
||||
case ForwardedEmailServiceType.AnonAddy:
|
||||
ShowAnonAddyApiAccessToken = !ShowAnonAddyApiAccessToken;
|
||||
break;
|
||||
case ForwardedEmailServiceType.FirefoxRelay:
|
||||
ShowFirefoxRelayApiAccessToken = !ShowFirefoxRelayApiAccessToken;
|
||||
break;
|
||||
case ForwardedEmailServiceType.SimpleLogin:
|
||||
ShowSimpleLoginApiKey = !ShowSimpleLoginApiKey;
|
||||
break;
|
||||
case ForwardedEmailServiceType.DuckDuckGo:
|
||||
ShowDuckDuckGoApiKey = !ShowDuckDuckGoApiKey;
|
||||
break;
|
||||
case ForwardedEmailServiceType.Fastmail:
|
||||
ShowFastmailApiKey = !ShowFastmailApiKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3262,6 +3262,24 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid API key.
|
||||
/// </summary>
|
||||
public static string InvalidAPIKey {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidAPIKey", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid API token.
|
||||
/// </summary>
|
||||
public static string InvalidAPIToken {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidAPIToken", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid email address..
|
||||
/// </summary>
|
||||
|
|
|
@ -2637,4 +2637,10 @@ Do you want to switch to this account?</value>
|
|||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
|
||||
</data>
|
||||
<data name="InvalidAPIKey" xml:space="preserve">
|
||||
<value>Invalid API key</value>
|
||||
</data>
|
||||
<data name="InvalidAPIToken" xml:space="preserve">
|
||||
<value>Invalid API token</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
namespace Bit.Core.Exceptions
|
||||
{
|
||||
public class ForwardedEmailInvalidSecretException : Exception
|
||||
{
|
||||
public ForwardedEmailInvalidSecretException(Exception innerEx)
|
||||
: base("Invalid API Secret", innerEx)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace Bit.Core.Services.EmailForwarders
|
||||
{
|
||||
public class ForwarderOptions
|
||||
{
|
||||
public string ApiKey { get; set; }
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue