From 30fcc905510ef7bffc62d0e937ac214b49d6b51a Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Fri, 9 Mar 2018 15:26:51 -0500 Subject: [PATCH 1/8] Replacing IsCustom with IsTwoFactorRequired --- src/GitHub.Api/Authentication/LoginManager.cs | 2 +- src/GitHub.Api/Tasks/OctorunTask.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GitHub.Api/Authentication/LoginManager.cs b/src/GitHub.Api/Authentication/LoginManager.cs index c7b39dd5a..cc3a1af94 100644 --- a/src/GitHub.Api/Authentication/LoginManager.cs +++ b/src/GitHub.Api/Authentication/LoginManager.cs @@ -177,7 +177,7 @@ string password return ret.Output[0]; } - if (ret.IsCustom && ret.Status == "2fa") + if (ret.IsTwoFactorRequired) { keychain.SetToken(host, ret.Output[0]); await keychain.Save(host); diff --git a/src/GitHub.Api/Tasks/OctorunTask.cs b/src/GitHub.Api/Tasks/OctorunTask.cs index b435b1996..a5bbcbed0 100644 --- a/src/GitHub.Api/Tasks/OctorunTask.cs +++ b/src/GitHub.Api/Tasks/OctorunTask.cs @@ -123,6 +123,6 @@ public OctorunResult(string status, string[] output) public bool IsSuccess => Status.Equals("success", StringComparison.InvariantCultureIgnoreCase); public bool IsError => Status.Equals("error", StringComparison.InvariantCultureIgnoreCase); - public bool IsCustom => !IsSuccess && !IsError; + public bool IsTwoFactorRequired => Status.Equals("2fa", StringComparison.InvariantCultureIgnoreCase); } } \ No newline at end of file From ff0ae2764c36ab3193f1364ea07ff7610edb2bb6 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Fri, 9 Mar 2018 15:34:31 -0500 Subject: [PATCH 2/8] Returning IsLocked and IsBadCredentials --- octorun/src/authentication.js | 11 +++++++---- src/GitHub.Api/Tasks/OctorunTask.cs | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/octorun/src/authentication.js b/octorun/src/authentication.js index 7f7b45685..10acb71c9 100644 --- a/octorun/src/authentication.js +++ b/octorun/src/authentication.js @@ -8,6 +8,9 @@ var lockedRegex = new RegExp("number of login attempts exceeded", "gi"); var twoFactorRegex = new RegExp("must specify two-factor authentication otp code", "gi"); var badCredentialsRegex = new RegExp("bad credentials", "gi"); +var lockedErrorMessage = "locked"; +var badCredentialsErrorMessage = "badcredentials"; + var handleBasicAuthentication = function (username, password, onSuccess, onRequiresTwoFa, onFailure) { var octokit = octokitWrapper.createOctokit(); @@ -28,10 +31,10 @@ var handleBasicAuthentication = function (username, password, onSuccess, onRequi onRequiresTwoFa(); } else if (lockedRegex.test(err.message)) { - onFailure("Account locked.") + onFailure(lockedErrorMessage) } else if (badCredentialsRegex.test(err.message)) { - onFailure("Bad credentials.") + onFailure(badCredentialsErrorMessage) } else { onFailure(err) @@ -63,10 +66,10 @@ var handleTwoFactorAuthentication = function (username, password, twoFactor, onS }, function (err, res) { if (err) { if (lockedRegex.test(err.message)) { - onFailure("Account locked.") + onFailure(lockedErrorMessage) } else if (badCredentialsRegex.test(err.message)) { - onFailure("Bad credentials.") + onFailure(badCredentialsErrorMessage) } else { onFailure(err) diff --git a/src/GitHub.Api/Tasks/OctorunTask.cs b/src/GitHub.Api/Tasks/OctorunTask.cs index a5bbcbed0..339bdcad3 100644 --- a/src/GitHub.Api/Tasks/OctorunTask.cs +++ b/src/GitHub.Api/Tasks/OctorunTask.cs @@ -124,5 +124,7 @@ public OctorunResult(string status, string[] output) public bool IsSuccess => Status.Equals("success", StringComparison.InvariantCultureIgnoreCase); public bool IsError => Status.Equals("error", StringComparison.InvariantCultureIgnoreCase); public bool IsTwoFactorRequired => Status.Equals("2fa", StringComparison.InvariantCultureIgnoreCase); + public bool IsLocked => Status.Equals("locked", StringComparison.InvariantCultureIgnoreCase); + public bool IsBadCredentials => Status.Equals("badcredentials", StringComparison.InvariantCultureIgnoreCase); } } \ No newline at end of file From 7c350d9011aaacdb4278fd799d136380b53f6a2a Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Fri, 9 Mar 2018 15:48:11 -0500 Subject: [PATCH 3/8] Refactoring authentication.js --- octorun/src/authentication.js | 61 +++++++++-------------------------- octorun/src/bin/app-login.js | 47 +++++++++++---------------- 2 files changed, 34 insertions(+), 74 deletions(-) diff --git a/octorun/src/authentication.js b/octorun/src/authentication.js index 10acb71c9..e45dc36cb 100644 --- a/octorun/src/authentication.js +++ b/octorun/src/authentication.js @@ -2,16 +2,13 @@ var endOfLine = require('os').EOL; var config = require("./configuration"); var octokitWrapper = require("./octokit"); -var scopes = ["user", "repo", "gist", "write:public_key"]; - var lockedRegex = new RegExp("number of login attempts exceeded", "gi"); var twoFactorRegex = new RegExp("must specify two-factor authentication otp code", "gi"); var badCredentialsRegex = new RegExp("bad credentials", "gi"); -var lockedErrorMessage = "locked"; -var badCredentialsErrorMessage = "badcredentials"; +var scopes = ["user", "repo", "gist", "write:public_key"]; -var handleBasicAuthentication = function (username, password, onSuccess, onRequiresTwoFa, onFailure) { +var handleAuthentication = function (username, password, onSuccess, onFailure, twoFactor) { var octokit = octokitWrapper.createOctokit(); octokit.authenticate({ @@ -20,56 +17,29 @@ var handleBasicAuthentication = function (username, password, onSuccess, onRequi password: password }); + var headers; + if (twoFactor) { + headers = { + "X-GitHub-OTP": twoFactor + }; + } + octokit.authorization.create({ scopes: scopes, note: config.appName, client_id: config.clientId, - client_secret: config.clientSecret + client_secret: config.clientSecret, + headers: headers }, function (err, res) { if (err) { if (twoFactorRegex.test(err.message)) { - onRequiresTwoFa(); + onSuccess(password, "2fa"); } else if (lockedRegex.test(err.message)) { - onFailure(lockedErrorMessage) - } - else if (badCredentialsRegex.test(err.message)) { - onFailure(badCredentialsErrorMessage) - } - else { - onFailure(err) - } - } - else { - onSuccess(res.data.token); - } - }); -} - -var handleTwoFactorAuthentication = function (username, password, twoFactor, onSuccess, onFailure) { - var octokit = octokitWrapper.createOctokit(); - - octokit.authenticate({ - type: "basic", - username: username, - password: password - }); - - octokit.authorization.create({ - scopes: scopes, - note: config.appName, - client_id: config.clientId, - client_secret: config.clientSecret, - headers: { - "X-GitHub-OTP": twoFactor - } - }, function (err, res) { - if (err) { - if (lockedRegex.test(err.message)) { - onFailure(lockedErrorMessage) + onFailure("locked") } else if (badCredentialsRegex.test(err.message)) { - onFailure(badCredentialsErrorMessage) + onFailure("badcredentials") } else { onFailure(err) @@ -82,6 +52,5 @@ var handleTwoFactorAuthentication = function (username, password, twoFactor, onS } module.exports = { - handleBasicAuthentication: handleBasicAuthentication, - handleTwoFactorAuthentication: handleTwoFactorAuthentication, + handleAuthentication: handleAuthentication, }; \ No newline at end of file diff --git a/octorun/src/bin/app-login.js b/octorun/src/bin/app-login.js index 0e450bfd4..910b1d73e 100644 --- a/octorun/src/bin/app-login.js +++ b/octorun/src/bin/app-login.js @@ -9,19 +9,24 @@ commander .option('-t, --twoFactor') .parse(process.argv); -var encoding = 'utf-8'; - -if (commander.twoFactor) { - var handleTwoFactorAuthentication = function (username, password, token) { - authentication.handleTwoFactorAuthentication(username, password, token, function (token) { - output.success(token); +var handleAuthentication = function (username, password, twoFactor) { + authentication.handleAuthentication(username, password, function (token, status) { + if (status) { + output.custom(status, token); process.exit(); - }, function (error) { - output.error(error); + } + else { + output.success(token); process.exit(); - }); - } + } + }, function (error) { + output.error(error); + process.exit(); + }, twoFactor); +} +var encoding = 'utf-8'; +if (commander.twoFactor) { if (process.stdin.isTTY) { var readlineSync = require("readline-sync"); var username = readlineSync.question('User: '); @@ -31,7 +36,7 @@ if (commander.twoFactor) { var twoFactor = readlineSync.question('Two Factor: '); - handleTwoFactorAuthentication(username, password, twoFactor); + handleAuthentication(username, password, twoFactor); } else { var data = ''; @@ -49,25 +54,11 @@ if (commander.twoFactor) { .split(/\r?\n/) .filter(function (item) { return item; }); - handleTwoFactorAuthentication(items[0], items[1], items[2]); + handleAuthentication(items[0], items[1], items[2]); }); } } else { - var handleBasicAuthentication = function (username, password) { - authentication.handleBasicAuthentication(username, password, - function (token) { - output.success(token); - process.exit(); - }, function () { - output.custom("2fa", password); - process.exit(); - }, function (error) { - output.error(error); - process.exit(); - }); - } - if (process.stdin.isTTY) { var readlineSync = require("readline-sync"); @@ -76,7 +67,7 @@ else { hideEchoBack: true }); - handleBasicAuthentication(username, password); + handleAuthentication(username, password); } else { var data = ''; @@ -94,7 +85,7 @@ else { .split(/\r?\n/) .filter(function (item) { return item; }); - handleBasicAuthentication(items[0], items[1]); + handleAuthentication(items[0], items[1]); }); } } \ No newline at end of file From 887514fc3c43170632ac2178fa6334cda4f12060 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Fri, 9 Mar 2018 16:22:02 -0500 Subject: [PATCH 4/8] Responding to error messages --- src/GitHub.Api/Authentication/LoginManager.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/GitHub.Api/Authentication/LoginManager.cs b/src/GitHub.Api/Authentication/LoginManager.cs index cc3a1af94..75d47cb75 100644 --- a/src/GitHub.Api/Authentication/LoginManager.cs +++ b/src/GitHub.Api/Authentication/LoginManager.cs @@ -184,6 +184,16 @@ string password throw new TwoFactorRequiredException(); } + if (ret.IsBadCredentials) + { + throw new Exception("Bad credentials."); + } + + if (ret.IsLocked) + { + throw new Exception("Account locked."); + } + if (ret.Output.Any()) { throw new Exception(string.Join(Environment.NewLine, ret.Output)); @@ -227,6 +237,23 @@ string code return ret.Output[0]; } + if (ret.IsTwoFactorRequired) + { + keychain.SetToken(host, ret.Output[0]); + await keychain.Save(host); + throw new TwoFactorRequiredException(); + } + + if (ret.IsBadCredentials) + { + throw new Exception("Bad credentials."); + } + + if (ret.IsLocked) + { + throw new Exception("Account locked."); + } + if (ret.Output.Any()) { throw new Exception(string.Join(Environment.NewLine, ret.Output)); @@ -260,6 +287,7 @@ internal LoginResultData(LoginResultCodes code, string message, UriString host) class TwoFactorRequiredException : Exception { - + public TwoFactorRequiredException() : base("Two-Factor authentication required.") + { } } } From 8b9740d3ec577e18275f4090f5831d8badb0df0b Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Mon, 12 Mar 2018 10:37:30 -0400 Subject: [PATCH 5/8] Simplifying LoginManager --- src/GitHub.Api/Authentication/LoginManager.cs | 90 +++++++++---------- src/GitHub.Api/Tasks/OctorunTask.cs | 5 +- 2 files changed, 43 insertions(+), 52 deletions(-) diff --git a/src/GitHub.Api/Authentication/LoginManager.cs b/src/GitHub.Api/Authentication/LoginManager.cs index 75d47cb75..60732ea8f 100644 --- a/src/GitHub.Api/Authentication/LoginManager.cs +++ b/src/GitHub.Api/Authentication/LoginManager.cs @@ -74,22 +74,23 @@ public async Task Login( keychain.Connect(host); keychain.SetCredentials(new Credential(host, username, password)); - string token; try { - token = await TryLogin(host, username, password); - if (string.IsNullOrEmpty(token)) + var loginResultData = await TryLogin(host, username, password); + if (loginResultData.Code == LoginResultCodes.Success || loginResultData.Code == LoginResultCodes.CodeRequired) { - throw new InvalidOperationException("Returned token is null or empty"); + if (string.IsNullOrEmpty(loginResultData.Token)) + { + throw new InvalidOperationException("Returned token is null or empty"); + } + + keychain.SetToken(host, loginResultData.Token); + await keychain.Save(host); + + return loginResultData; } - } - catch (TwoFactorRequiredException e) - { - LoginResultCodes result; - result = LoginResultCodes.CodeRequired; - logger.Trace("2FA TwoFactorAuthorizationException: {0} {1}", LoginResultCodes.CodeRequired, e.Message); - return new LoginResultData(result, e.Message, host, password); + return loginResultData; } catch (Exception e) { @@ -98,16 +99,10 @@ public async Task Login( await keychain.Clear(host, false); return new LoginResultData(LoginResultCodes.Failed, Localization.LoginFailed, host); } - - keychain.SetToken(host, token); - await keychain.Save(host); - - return new LoginResultData(LoginResultCodes.Success, "Success", host); } public async Task ContinueLogin(LoginResultData loginResultData, string twofacode) { - var token = loginResultData.Token; var host = loginResultData.Host; var keychainAdapter = keychain.Connect(host); var username = keychainAdapter.Credential.Username; @@ -115,24 +110,29 @@ public async Task ContinueLogin(LoginResultData loginResultData try { logger.Trace("2FA Continue"); - token = await TryContinueLogin(host, username, password, twofacode); + loginResultData = await TryContinueLogin(host, username, password, twofacode); - if (string.IsNullOrEmpty(token)) + if (loginResultData.Code == LoginResultCodes.Success) { - throw new InvalidOperationException("Returned token is null or empty"); - } + if (string.IsNullOrEmpty(loginResultData.Token)) + { + throw new InvalidOperationException("Returned token is null or empty"); + } - keychain.SetToken(host, token); - await keychain.Save(host); + keychain.SetToken(host, loginResultData.Token); + await keychain.Save(host); - return new LoginResultData(LoginResultCodes.Success, "", host); + return loginResultData; + } + + return loginResultData; } catch (Exception e) { - logger.Trace(e, "Exception: {0}", e.Message); + logger.Warning(e, "Login Exception"); await keychain.Clear(host, false); - return new LoginResultData(LoginResultCodes.Failed, e.Message, host); + return new LoginResultData(LoginResultCodes.Failed, Localization.LoginFailed, host); } } @@ -144,7 +144,7 @@ public async Task Logout(UriString hostAddress) await new ActionTask(keychain.Clear(hostAddress, true)).StartAwait(); } - private async Task TryLogin( + private async Task TryLogin( UriString host, string username, string password @@ -174,35 +174,33 @@ string password if (ret.IsSuccess) { - return ret.Output[0]; + return new LoginResultData(LoginResultCodes.Success, null, host, ret.Output[0]); } if (ret.IsTwoFactorRequired) { - keychain.SetToken(host, ret.Output[0]); - await keychain.Save(host); - throw new TwoFactorRequiredException(); + return new LoginResultData(LoginResultCodes.CodeRequired, "Two Factor Required.", host, ret.Output[0]); } if (ret.IsBadCredentials) { - throw new Exception("Bad credentials."); + return new LoginResultData(LoginResultCodes.Failed, "Bad credentials.", host, ret.Output[0]); } if (ret.IsLocked) { - throw new Exception("Account locked."); + return new LoginResultData(LoginResultCodes.LockedOut, "Account locked.", host, ret.Output[0]); } if (ret.Output.Any()) { - throw new Exception(string.Join(Environment.NewLine, ret.Output)); + return new LoginResultData(LoginResultCodes.Failed, "Failed.", host, ret.Output[0]); } - throw new Exception("Authentication failed"); + return new LoginResultData(LoginResultCodes.Failed, "Failed.", host); } - private async Task TryContinueLogin( + private async Task TryContinueLogin( UriString host, string username, string password, @@ -234,32 +232,30 @@ string code if (ret.IsSuccess) { - return ret.Output[0]; + return new LoginResultData(LoginResultCodes.Success, null, host, ret.Output[0]); } if (ret.IsTwoFactorRequired) { - keychain.SetToken(host, ret.Output[0]); - await keychain.Save(host); - throw new TwoFactorRequiredException(); + return new LoginResultData(LoginResultCodes.CodeFailed, "Incorrect code. Two Factor Required.", host, ret.Output[0]); } if (ret.IsBadCredentials) { - throw new Exception("Bad credentials."); + return new LoginResultData(LoginResultCodes.Failed, "Bad credentials.", host, ret.Output[0]); } if (ret.IsLocked) { - throw new Exception("Account locked."); + return new LoginResultData(LoginResultCodes.LockedOut, "Account locked.", host, ret.Output[0]); } if (ret.Output.Any()) { - throw new Exception(string.Join(Environment.NewLine, ret.Output)); + return new LoginResultData(LoginResultCodes.Failed, "Failed.", host, ret.Output[0]); } - throw new Exception("Authentication failed"); + return new LoginResultData(LoginResultCodes.Failed, "Failed.", host); } } @@ -284,10 +280,4 @@ internal LoginResultData(LoginResultCodes code, string message, UriString host) { } } - - class TwoFactorRequiredException : Exception - { - public TwoFactorRequiredException() : base("Two-Factor authentication required.") - { } - } } diff --git a/src/GitHub.Api/Tasks/OctorunTask.cs b/src/GitHub.Api/Tasks/OctorunTask.cs index 339bdcad3..80b3c3371 100644 --- a/src/GitHub.Api/Tasks/OctorunTask.cs +++ b/src/GitHub.Api/Tasks/OctorunTask.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; using GitHub.Logging; @@ -124,7 +125,7 @@ public OctorunResult(string status, string[] output) public bool IsSuccess => Status.Equals("success", StringComparison.InvariantCultureIgnoreCase); public bool IsError => Status.Equals("error", StringComparison.InvariantCultureIgnoreCase); public bool IsTwoFactorRequired => Status.Equals("2fa", StringComparison.InvariantCultureIgnoreCase); - public bool IsLocked => Status.Equals("locked", StringComparison.InvariantCultureIgnoreCase); - public bool IsBadCredentials => Status.Equals("badcredentials", StringComparison.InvariantCultureIgnoreCase); + public bool IsLocked => Output.First().Equals("locked", StringComparison.InvariantCultureIgnoreCase); + public bool IsBadCredentials => Output.First().Equals("badcredentials", StringComparison.InvariantCultureIgnoreCase); } } \ No newline at end of file From a4c6a3174a3d1f689f3344e77fe388403d478015 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Mon, 12 Mar 2018 10:54:54 -0400 Subject: [PATCH 6/8] Capturing response for enterprise workaround As per #621 we are treating the 422 status code as a signal that the password should be used as the token. --- octorun/src/authentication.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/octorun/src/authentication.js b/octorun/src/authentication.js index e45dc36cb..c54b3b956 100644 --- a/octorun/src/authentication.js +++ b/octorun/src/authentication.js @@ -31,8 +31,14 @@ var handleAuthentication = function (username, password, onSuccess, onFailure, t client_secret: config.clientSecret, headers: headers }, function (err, res) { + + console.log("octokit.authorization.create", typeof(err), err, res); if (err) { - if (twoFactorRegex.test(err.message)) { + if (twoFactor && err.code && err.code === 422) { + //Two Factor Enterprise workaround + onSuccess(password); + } + else if (twoFactorRegex.test(err.message)) { onSuccess(password, "2fa"); } else if (lockedRegex.test(err.message)) { From 7fe720576d3c1bf5f7ae47f1831e89fb0dbaee06 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Mon, 12 Mar 2018 14:48:33 -0400 Subject: [PATCH 7/8] Removing unintentional console log --- octorun/src/authentication.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/octorun/src/authentication.js b/octorun/src/authentication.js index c54b3b956..fbde0faf1 100644 --- a/octorun/src/authentication.js +++ b/octorun/src/authentication.js @@ -31,8 +31,6 @@ var handleAuthentication = function (username, password, onSuccess, onFailure, t client_secret: config.clientSecret, headers: headers }, function (err, res) { - - console.log("octokit.authorization.create", typeof(err), err, res); if (err) { if (twoFactor && err.code && err.code === 422) { //Two Factor Enterprise workaround From f435e4d238f1bfc5ee42f1863b631473b8ff05e6 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Mon, 12 Mar 2018 14:49:13 -0400 Subject: [PATCH 8/8] Conslidating TryLogin and TryContinueLogin --- src/GitHub.Api/Authentication/LoginManager.cs | 75 ++++--------------- 1 file changed, 14 insertions(+), 61 deletions(-) diff --git a/src/GitHub.Api/Authentication/LoginManager.cs b/src/GitHub.Api/Authentication/LoginManager.cs index 60732ea8f..269458668 100644 --- a/src/GitHub.Api/Authentication/LoginManager.cs +++ b/src/GitHub.Api/Authentication/LoginManager.cs @@ -110,7 +110,7 @@ public async Task ContinueLogin(LoginResultData loginResultData try { logger.Trace("2FA Continue"); - loginResultData = await TryContinueLogin(host, username, password, twofacode); + loginResultData = await TryLogin(host, username, password, twofacode); if (loginResultData.Code == LoginResultCodes.Success) { @@ -145,66 +145,10 @@ public async Task Logout(UriString hostAddress) } private async Task TryLogin( - UriString host, - string username, - string password - ) - { - if (!nodeJsExecutablePath.HasValue) - { - throw new InvalidOperationException("nodeJsExecutablePath must be set"); - } - - if (!octorunScript.HasValue) - { - throw new InvalidOperationException("octorunScript must be set"); - } - - var loginTask = new OctorunTask(taskManager.Token, nodeJsExecutablePath.Value, octorunScript.Value, - "login", ApplicationInfo.ClientId, ApplicationInfo.ClientSecret); - loginTask.Configure(processManager, workingDirectory: octorunScript.Value.Parent.Parent, withInput: true); - loginTask.OnStartProcess += proc => - { - proc.StandardInput.WriteLine(username); - proc.StandardInput.WriteLine(password); - proc.StandardInput.Close(); - }; - - var ret = await loginTask.StartAwait(); - - if (ret.IsSuccess) - { - return new LoginResultData(LoginResultCodes.Success, null, host, ret.Output[0]); - } - - if (ret.IsTwoFactorRequired) - { - return new LoginResultData(LoginResultCodes.CodeRequired, "Two Factor Required.", host, ret.Output[0]); - } - - if (ret.IsBadCredentials) - { - return new LoginResultData(LoginResultCodes.Failed, "Bad credentials.", host, ret.Output[0]); - } - - if (ret.IsLocked) - { - return new LoginResultData(LoginResultCodes.LockedOut, "Account locked.", host, ret.Output[0]); - } - - if (ret.Output.Any()) - { - return new LoginResultData(LoginResultCodes.Failed, "Failed.", host, ret.Output[0]); - } - - return new LoginResultData(LoginResultCodes.Failed, "Failed.", host); - } - - private async Task TryContinueLogin( UriString host, string username, string password, - string code + string code = null ) { if (!nodeJsExecutablePath.HasValue) @@ -217,14 +161,20 @@ string code throw new InvalidOperationException("octorunScript must be set"); } + var hasTwoFactorCode = code != null; + + var arguments = hasTwoFactorCode ? "login --twoFactor" : "login"; var loginTask = new OctorunTask(taskManager.Token, nodeJsExecutablePath.Value, octorunScript.Value, - "login --twoFactor", ApplicationInfo.ClientId, ApplicationInfo.ClientSecret); + arguments, ApplicationInfo.ClientId, ApplicationInfo.ClientSecret); loginTask.Configure(processManager, workingDirectory: octorunScript.Value.Parent.Parent, withInput: true); loginTask.OnStartProcess += proc => { proc.StandardInput.WriteLine(username); proc.StandardInput.WriteLine(password); - proc.StandardInput.WriteLine(code); + if (hasTwoFactorCode) + { + proc.StandardInput.WriteLine(code); + } proc.StandardInput.Close(); }; @@ -237,7 +187,10 @@ string code if (ret.IsTwoFactorRequired) { - return new LoginResultData(LoginResultCodes.CodeFailed, "Incorrect code. Two Factor Required.", host, ret.Output[0]); + var resultCodes = hasTwoFactorCode ? LoginResultCodes.CodeFailed : LoginResultCodes.CodeRequired; + var message = hasTwoFactorCode ? "Incorrect code. Two Factor Required." : "Two Factor Required."; + + return new LoginResultData(resultCodes, message, host, ret.Output[0]); } if (ret.IsBadCredentials)