diff --git a/src/Console/Program.cs b/src/Console/Program.cs
index cb402835..68173ecb 100644
--- a/src/Console/Program.cs
+++ b/src/Console/Program.cs
@@ -1,19 +1,16 @@
-using Bit.Core.Models;
+using Bit.Core.Enums;
+using Bit.Core.Models;
using Bit.Core.Services;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
using System.Linq;
-using System.Security;
-using System.Text;
using System.Threading.Tasks;
using Con = System.Console;
namespace Bit.Console
{
- class Program
+ public class Program
{
private static bool _usingArgs = false;
private static bool _exit = false;
@@ -155,6 +152,7 @@ namespace Bit.Console
string masterPassword = null;
string token = null;
string orgId = null;
+ var provider = TwoFactorProviderType.Authenticator;
if(_usingArgs)
{
@@ -164,7 +162,8 @@ namespace Bit.Console
email = parameters["e"];
masterPassword = parameters["p"];
}
- if(parameters.Count >= 3 && parameters.ContainsKey("t"))
+ if(parameters.Count >= 4 && parameters.ContainsKey("t") && parameters.ContainsKey("tp") &&
+ Enum.TryParse(parameters["tp"], out provider))
{
token = parameters["t"];
}
@@ -198,21 +197,81 @@ namespace Bit.Console
}
else
{
- result = await AuthService.Instance.LogInTwoFactorAsync(email, masterPassword, token);
+ result = await AuthService.Instance.LogInTwoFactorAsync(provider, token,
+ email, masterPassword);
}
if(string.IsNullOrWhiteSpace(token) && result.TwoFactorRequired)
{
Con.WriteLine();
Con.WriteLine();
- Con.WriteLine("Two-step login is enabled on this account. Please enter your verification code.");
- Con.Write("Verification code: ");
- token = Con.ReadLine().Trim();
- result = await AuthService.Instance.LogInTwoFactorWithHashAsync(token, email,
- result.MasterPasswordHash);
+ Con.WriteLine("Two-step login is enabled on this account.");
+ if(result.TwoFactorProviders.Count > 1)
+ {
+ for(var i = 0; i < result.TwoFactorProviders.Count; i++)
+ {
+ Con.WriteLine("{0}. {1}{2}", i + 1, result.TwoFactorProviders.ElementAt(i).Key,
+ result.TwoFactorProviders.ElementAt(i).Key == TwoFactorProviderType.Duo ||
+ result.TwoFactorProviders.ElementAt(i).Key == TwoFactorProviderType.U2f ?
+ " - not supported" : string.Empty);
+ }
+ Con.WriteLine();
+ Con.Write("Which provider would you like to use?: ");
+ var providerIndexInput = Con.ReadLine().Trim();
+ int providerIndex;
+ if(int.TryParse(providerIndexInput, out providerIndex) && result.TwoFactorProviders.Count >= providerIndex)
+ {
+ provider = result.TwoFactorProviders.ElementAt(providerIndex - 1).Key;
+ }
+ else
+ {
+ provider = result.TwoFactorProviders.First().Key;
+ }
+ }
+ else
+ {
+ provider = result.TwoFactorProviders.First().Key;
+ Con.WriteLine();
+ }
+
+ var readingTokenInput = true;
+ if(provider == TwoFactorProviderType.YubiKey)
+ {
+ Con.Write("Tap the button on your YubiKey: ");
+ }
+ else if(provider == TwoFactorProviderType.Email)
+ {
+ if(result.TwoFactorProviders.Count > 1)
+ {
+ await ApiService.Instance.PostTwoFactorSendEmailLoginAsync(new TwoFactorEmailRequest
+ {
+ Email = email,
+ MasterPasswordHash = result.MasterPasswordHash
+ });
+ }
+
+ Con.Write("Enter the verification code that was emailed to you: ");
+ }
+ else if(provider == TwoFactorProviderType.Authenticator)
+ {
+ Con.Write("Enter the verification code from your authenticator app: ");
+ }
+ else
+ {
+ Con.WriteLine("The selected two-step login method is not supported on this platform/application. " +
+ "Use a different two step-login method.");
+ readingTokenInput = false;
+ }
+
+ if(readingTokenInput)
+ {
+ token = Con.ReadLine().Trim();
+ result = await AuthService.Instance.LogInTwoFactorWithHashAsync(provider, token, email,
+ result.MasterPasswordHash);
+ }
}
- if(result.Success && result.Organizations.Count > 1)
+ if(result.Success && result.Organizations != null && result.Organizations.Count > 1)
{
Organization org = null;
if(!string.IsNullOrWhiteSpace(orgId))
@@ -253,8 +312,15 @@ namespace Bit.Console
Con.WriteLine();
if(result.Success)
{
- WriteSuccessLine(string.Format("You have successfully logged in as {0}!",
- TokenService.Instance.AccessTokenEmail));
+ if(!result.TwoFactorRequired)
+ {
+ WriteSuccessLine(string.Format("You have successfully logged in as {0}!",
+ TokenService.Instance.AccessTokenEmail));
+ }
+ else
+ {
+ WriteErrorLine("Unable to log in.");
+ }
}
else
{
diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj
index 6c7c410f..787b58f6 100644
--- a/src/Core/Core.csproj
+++ b/src/Core/Core.csproj
@@ -87,6 +87,7 @@
+
@@ -107,6 +108,7 @@
+
diff --git a/src/Core/Enums/TwoFactorProviderType.cs b/src/Core/Enums/TwoFactorProviderType.cs
new file mode 100644
index 00000000..28be1b2c
--- /dev/null
+++ b/src/Core/Enums/TwoFactorProviderType.cs
@@ -0,0 +1,12 @@
+namespace Bit.Core.Enums
+{
+ public enum TwoFactorProviderType : byte
+ {
+ Authenticator = 0,
+ Email = 1,
+ Duo = 2,
+ YubiKey = 3,
+ U2f = 4,
+ Remember = 5
+ }
+}
diff --git a/src/Core/Models/LoginResult.cs b/src/Core/Models/LoginResult.cs
index d84fb0b6..84bf6a99 100644
--- a/src/Core/Models/LoginResult.cs
+++ b/src/Core/Models/LoginResult.cs
@@ -1,4 +1,5 @@
-using System;
+using Bit.Core.Enums;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -10,7 +11,8 @@ namespace Bit.Core.Models
{
public bool Success { get; set; }
public string ErrorMessage { get; set; }
- public bool TwoFactorRequired { get; set; }
+ public bool TwoFactorRequired => TwoFactorProviders != null && TwoFactorProviders.Count > 0;
+ public Dictionary> TwoFactorProviders { get; set; }
public string MasterPasswordHash { get; set; }
public List Organizations { get; set; }
}
diff --git a/src/Core/Models/TokenRequest.cs b/src/Core/Models/TokenRequest.cs
index e53584aa..8f7135e6 100644
--- a/src/Core/Models/TokenRequest.cs
+++ b/src/Core/Models/TokenRequest.cs
@@ -1,4 +1,5 @@
-using System;
+using Bit.Core.Enums;
+using System;
using System.Collections.Generic;
namespace Bit.Core.Models
@@ -8,7 +9,8 @@ namespace Bit.Core.Models
public string Email { get; set; }
public string MasterPasswordHash { get; set; }
public string Token { get; set; }
- public int? Provider { get; set; }
+ public TwoFactorProviderType? Provider { get; set; }
+ public bool Remember { get; set; }
public IDictionary ToIdentityTokenRequest()
{
@@ -24,7 +26,8 @@ namespace Bit.Core.Models
if(Token != null && Provider.HasValue)
{
dict.Add("TwoFactorToken", Token);
- dict.Add("TwoFactorProvider", Provider.Value.ToString());
+ dict.Add("TwoFactorProvider", ((byte)(Provider.Value)).ToString());
+ dict.Add("TwoFactorRemember", Remember ? "1" : "0");
}
return dict;
diff --git a/src/Core/Models/TokenResponse.cs b/src/Core/Models/TokenResponse.cs
index 31f07ad2..0fe96b54 100644
--- a/src/Core/Models/TokenResponse.cs
+++ b/src/Core/Models/TokenResponse.cs
@@ -1,9 +1,6 @@
-using Newtonsoft.Json;
-using System;
+using Bit.Core.Enums;
+using Newtonsoft.Json;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Bit.Core.Models
{
@@ -17,7 +14,9 @@ namespace Bit.Core.Models
public string RefreshToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
- public List TwoFactorProviders { get; set; }
+ public Dictionary> TwoFactorProviders2 { get; set; }
public string PrivateKey { get; set; }
+ public string TwoFactorToken { get; set; }
+ public string Key { get; set; }
}
}
diff --git a/src/Core/Models/TwoFactorEmailRequest.cs b/src/Core/Models/TwoFactorEmailRequest.cs
new file mode 100644
index 00000000..d7a6a5ac
--- /dev/null
+++ b/src/Core/Models/TwoFactorEmailRequest.cs
@@ -0,0 +1,8 @@
+namespace Bit.Core.Models
+{
+ public class TwoFactorEmailRequest
+ {
+ public string Email { get; set; }
+ public string MasterPasswordHash { get; set; }
+ }
+}
diff --git a/src/Core/Services/ApiService.cs b/src/Core/Services/ApiService.cs
index 36ff6fc1..a2d81fd7 100644
--- a/src/Core/Services/ApiService.cs
+++ b/src/Core/Services/ApiService.cs
@@ -1,4 +1,5 @@
-using Bit.Core.Models;
+using Bit.Core.Enums;
+using Bit.Core.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
@@ -51,11 +52,12 @@ namespace Bit.Core.Services
if(!response.IsSuccessStatusCode)
{
var errorResponse = JObject.Parse(responseContent);
- if(errorResponse["TwoFactorProviders"] != null)
+ if(errorResponse["TwoFactorProviders2"] != null)
{
return ApiResult.Success(new TokenResponse
{
- TwoFactorProviders = errorResponse["TwoFactorProviders"].ToObject>()
+ TwoFactorProviders2 = errorResponse["TwoFactorProviders2"]
+ .ToObject>>()
}, response.StatusCode);
}
@@ -140,6 +142,33 @@ namespace Bit.Core.Services
}
}
+ public virtual async Task PostTwoFactorSendEmailLoginAsync(TwoFactorEmailRequest requestObj)
+ {
+ var stringContent = JsonConvert.SerializeObject(requestObj);
+
+ var requestMessage = new HttpRequestMessage()
+ {
+ Method = HttpMethod.Post,
+ RequestUri = new Uri(string.Concat(SettingsService.Instance.ApiBaseUrl, "/two-factor/send-email-login")),
+ Content = new StringContent(stringContent, Encoding.UTF8, "application/json")
+ };
+
+ try
+ {
+ var response = await Client.SendAsync(requestMessage).ConfigureAwait(false);
+ if(!response.IsSuccessStatusCode)
+ {
+ return await HandleErrorAsync(response).ConfigureAwait(false);
+ }
+
+ return ApiResult.Success(response.StatusCode);
+ }
+ catch
+ {
+ return HandledWebException();
+ }
+ }
+
protected ApiResult HandledWebException()
{
return ApiResult.Failed(HttpStatusCode.BadGateway,
diff --git a/src/Core/Services/AuthService.cs b/src/Core/Services/AuthService.cs
index 91c49be0..181af6e0 100644
--- a/src/Core/Services/AuthService.cs
+++ b/src/Core/Services/AuthService.cs
@@ -63,9 +63,9 @@ namespace Bit.Core.Services
}
result.Success = true;
- if(response.Result.TwoFactorProviders != null && response.Result.TwoFactorProviders.Count > 0)
+ if(response.Result.TwoFactorProviders2 != null && response.Result.TwoFactorProviders2.Count > 0)
{
- result.TwoFactorRequired = true;
+ result.TwoFactorProviders = response.Result.TwoFactorProviders2;
result.MasterPasswordHash = request.MasterPasswordHash;
return result;
}
@@ -73,12 +73,13 @@ namespace Bit.Core.Services
return await ProcessLogInSuccessAsync(response.Result);
}
- public async Task LogInTwoFactorAsync(string token, string email, string masterPassword)
+ public async Task LogInTwoFactorAsync(TwoFactorProviderType type, string token, string email,
+ string masterPassword)
{
var normalizedEmail = email.Trim().ToLower();
var key = Crypto.MakeKeyFromPassword(masterPassword, normalizedEmail);
- var result = await LogInTwoFactorWithHashAsync(token, email, Crypto.HashPasswordBase64(key, masterPassword));
+ var result = await LogInTwoFactorWithHashAsync(type, token, email, Crypto.HashPasswordBase64(key, masterPassword));
key = null;
masterPassword = null;
@@ -86,14 +87,21 @@ namespace Bit.Core.Services
return result;
}
- public async Task LogInTwoFactorWithHashAsync(string token, string email, string masterPasswordHash)
+ public async Task LogInTwoFactorWithHashAsync(TwoFactorProviderType type, string token, string email,
+ string masterPasswordHash)
{
+ if(type == TwoFactorProviderType.Email || type == TwoFactorProviderType.Authenticator)
+ {
+ token = token.Trim().Replace(" ", "");
+ }
+
var request = new TokenRequest
{
Email = email.Trim().ToLower(),
MasterPasswordHash = masterPasswordHash,
- Token = token.Trim().Replace(" ", ""),
- Provider = 0 // Authenticator app (only 1 provider for now, so hard coded)
+ Token = token,
+ Provider = type,
+ Remember = false
};
var response = await ApiService.Instance.PostTokenAsync(request);