diff --git a/src/Console/Program.cs b/src/Console/Program.cs index 738250e6..d8316685 100644 --- a/src/Console/Program.cs +++ b/src/Console/Program.cs @@ -1,4 +1,5 @@ -using System; +using Bit.Core.Models; +using System; using System.Collections.Generic; using System.Linq; using System.Security; @@ -38,6 +39,7 @@ namespace Bit.Console Con.WriteLine("Main Menu"); Con.WriteLine("================================="); Con.WriteLine("1. Log in to bitwarden"); + Con.WriteLine("2. Log out"); Con.WriteLine("2. Configure directory connection"); Con.WriteLine("3. Sync directory"); Con.WriteLine("4. Start/stop background service"); @@ -56,20 +58,21 @@ namespace Bit.Console await LogInAsync(); break; case "2": + case "logout": + case "signout": + await LogOutAsync(); + break; case "dir": case "directory": - + await DirectoryAsync(); break; - case "3": case "sync": break; - case "4": case "svc": case "service": break; - case "5": case "exit": case "quit": case "q": @@ -96,20 +99,28 @@ namespace Bit.Console private static async Task LogInAsync() { + if(Core.Services.AuthService.Instance.Authenticated) + { + Con.WriteLine("You are already logged in as {0}.", Core.Services.TokenService.Instance.AccessTokenEmail); + return; + } + string email = null; string masterPassword = null; + string token = null; if(_usingArgs) { - if(_args.Length != 3) + var parameters = ParseParameters(); + if(parameters.Count >= 2 && parameters.ContainsKey("e") && parameters.ContainsKey("p")) { - Con.ForegroundColor = ConsoleColor.Red; - Con.WriteLine("Invalid arguments."); - Con.ResetColor(); + email = parameters["e"]; + masterPassword = parameters["p"]; + } + if(parameters.Count == 3 && parameters.ContainsKey("t")) + { + token = parameters["t"]; } - - email = _args[1]; - masterPassword = _args[2]; } else { @@ -119,15 +130,33 @@ namespace Bit.Console masterPassword = ReadSecureLine(); } - var result = await Core.Services.AuthService.Instance.LogInAsync(email, masterPassword); + if(string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(masterPassword)) + { + Con.WriteLine(); + Con.WriteLine(); + Con.ForegroundColor = ConsoleColor.Red; + Con.WriteLine("Invalid input parameters."); + Con.ResetColor(); + return; + } - if(result.TwoFactorRequired) + LoginResult result = null; + if(string.IsNullOrWhiteSpace(token)) + { + result = await Core.Services.AuthService.Instance.LogInAsync(email, masterPassword); + } + else + { + result = await Core.Services.AuthService.Instance.LogInTwoFactorAsync(email, masterPassword, token); + } + + 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: "); - var token = Con.ReadLine().Trim(); + token = Con.ReadLine().Trim(); result = await Core.Services.AuthService.Instance.LogInTwoFactorAsync(token, email, result.MasterPasswordHash); } @@ -149,6 +178,25 @@ namespace Bit.Console masterPassword = null; } + private static Task LogOutAsync() + { + if(Core.Services.AuthService.Instance.Authenticated) + { + Core.Services.AuthService.Instance.LogOut(); + Con.WriteLine("You have successfully logged out!"); + } + else + { + Con.WriteLine("You are not logged in."); + } + + return Task.FromResult(0); + } + + private static async Task DirectoryAsync() + { + } + private static string ReadSecureLine() { var input = string.Empty; @@ -175,5 +223,21 @@ namespace Bit.Console } return input; } + + private static IDictionary ParseParameters() + { + var dict = new Dictionary(); + for(int i = 1; i < _args.Length; i = i + 2) + { + if(!_args[i].StartsWith("-")) + { + continue; + } + + dict.Add(_args[i].Substring(1), _args[i + 1]); + } + + return dict; + } } } diff --git a/src/Core/Services/AuthService.cs b/src/Core/Services/AuthService.cs index cb0d6ec3..ca53e41f 100644 --- a/src/Core/Services/AuthService.cs +++ b/src/Core/Services/AuthService.cs @@ -29,6 +29,12 @@ namespace Bit.Core.Services public bool Authenticated => !string.IsNullOrWhiteSpace(TokenService.Instance.AccessToken); + public void LogOut() + { + TokenService.Instance.AccessToken = null; + TokenService.Instance.RefreshToken = null; + } + public async Task LogInAsync(string email, string masterPassword) { var normalizedEmail = email.Trim().ToLower(); @@ -65,7 +71,21 @@ namespace Bit.Core.Services return result; } - public async Task LogInTwoFactorAsync(string token, string email, string masterPasswordHash) + public async Task LogInTwoFactorAsync(string token, string email, string masterPassword) + { + var normalizedEmail = email.Trim().ToLower(); + var key = CryptoService.Instance.MakeKeyFromPassword(masterPassword, normalizedEmail); + + var result = await LogInTwoFactorWithHashAsync(token, email, + CryptoService.Instance.HashPasswordBase64(key, masterPassword)); + + key = null; + masterPassword = null; + + return result; + } + + public async Task LogInTwoFactorWithHashAsync(string token, string email, string masterPasswordHash) { var request = new TokenRequest { diff --git a/src/Core/Services/SettingsService.cs b/src/Core/Services/SettingsService.cs index 141e3c15..6f11b45e 100644 --- a/src/Core/Services/SettingsService.cs +++ b/src/Core/Services/SettingsService.cs @@ -18,7 +18,7 @@ namespace Bit.Core.Services Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "\\bitwarden\\DirectoryConnector"); - private IDictionary _settings; + private SettingsModel _settings; private SettingsService() { } @@ -35,7 +35,7 @@ namespace Bit.Core.Services } } - public IDictionary Settings + public SettingsModel Settings { get { @@ -47,83 +47,44 @@ namespace Bit.Core.Services using(var sr = new StreamReader(s, Encoding.UTF8)) using(var jsonTextReader = new JsonTextReader(sr)) { - _settings = serializer.Deserialize>(jsonTextReader); + _settings = serializer.Deserialize(jsonTextReader); } } - return _settings == null ? new Dictionary() : _settings; + return _settings == null ? new SettingsModel() : _settings; } - set + } + + private void SaveSettings() + { + lock(_locker) { - lock(_locker) + if(!Directory.Exists(_baseStoragePath)) { - if(!Directory.Exists(_baseStoragePath)) - { - Directory.CreateDirectory(_baseStoragePath); - } + Directory.CreateDirectory(_baseStoragePath); + } - _settings = value; - var filePath = $"{_baseStoragePath}\\settings.json"; - using(var s = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read)) - using(var sw = new StreamWriter(s, Encoding.UTF8)) - { - var json = JsonConvert.SerializeObject(_settings); - sw.Write(json); - } + _settings = Settings; + var filePath = $"{_baseStoragePath}\\settings.json"; + using(var s = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read)) + using(var sw = new StreamWriter(s, Encoding.UTF8)) + { + var json = JsonConvert.SerializeObject(_settings, Formatting.Indented); + sw.Write(json); } } } - public void Set(string key, object value) - { - if(Contains(key)) - { - Settings[key] = value; - } - else - { - Settings.Add(key, value); - } - - Settings = Settings; - } - - public void Remove(string key) - { - Settings.Remove(key); - Settings = Settings; - } - - public bool Contains(string key) - { - return Settings.ContainsKey(key); - } - - public T Get(string key) - { - if(Settings.ContainsKey(key)) - { - return (T)Settings[key]; - } - - return default(T); - } - public EncryptedData AccessToken { get { - return Get("AccessToken"); + return Settings.AccessToken; } set { - if(value == null) - { - Remove("AccessTolen"); - return; - } - - Set("AccessToken", value); + Settings.AccessToken = value; + SaveSettings(); } } @@ -131,18 +92,19 @@ namespace Bit.Core.Services { get { - return Get("RefreshToken"); + return Settings.RefreshToken; } set { - if(value == null) - { - Remove("RefreshToken"); - return; - } - - Set("RefreshToken", value); + Settings.RefreshToken = value; + SaveSettings(); } } + + public class SettingsModel + { + public EncryptedData AccessToken { get; set; } + public EncryptedData RefreshToken { get; set; } + } } }