// Returns the transaction id internal static string SubmitFactor(string sid, DuoChoice choice, string passcode, RestClient rest) { var parameters = new Dictionary <string, object> { { "sid", sid }, { "device", choice.Device.Id }, { "factor", GetFactorParameterValue(choice.Factor) }, }; if (!passcode.IsNullOrEmpty()) { parameters["passcode"] = passcode; } var response = PostForm <R.SubmitFactor>("frame/prompt", parameters, rest); var id = response.TransactionId; if (id.IsNullOrEmpty()) { throw MakeInvalidResponseError("Duo: transaction ID (txid) is expected but wasn't found"); } return(id); }
// Returns the second factor token from Duo or null when canceled by the user. public static Result Authenticate(string host, string signature, IDuoUi ui, IRestTransport transport) { var rest = new RestClient(transport, $"https://{host}"); var(tx, app) = ParseSignature(signature); var html = DownloadFrame(tx, rest); var(sid, devices) = ParseFrame(html); while (true) { // Ask the user to choose what to do var choice = ui.ChooseDuoFactor(devices); if (choice == null) { return(null); // Canceled by user } // SMS is a special case: it doesn't submit any codes, it rather tells the server to send // a new batch of passcodes to the phone via SMS. if (choice.Factor == DuoFactor.SendPasscodesBySms) { SubmitFactor(sid, choice, "", rest); choice = new DuoChoice(choice.Device, DuoFactor.Passcode, choice.RememberMe); } // Ask for the passcode var passcode = ""; if (choice.Factor == DuoFactor.Passcode) { passcode = ui.ProvideDuoPasscode(choice.Device); if (passcode.IsNullOrEmpty()) { return(null); // Canceled by user } } var token = SubmitFactorAndWaitForToken(sid, choice, passcode, ui, rest); // Flow error like an incorrect passcode. The UI has been updated with the error. Keep going. if (token.IsNullOrEmpty()) { continue; } // All good return(new Result($"{token}:{app}", choice.RememberMe)); } }
// Returns null when a recoverable flow error (like incorrect code or time out) happened // TODO: Don't return null, use something more obvious internal static string SubmitFactorAndWaitForToken(string sid, DuoChoice choice, string passcode, IDuoUi ui, RestClient rest) { var txid = SubmitFactor(sid, choice, passcode, rest); var url = PollForResultUrl(sid, txid, ui, rest); if (url.IsNullOrEmpty()) { return(null); } return(FetchToken(sid, url, ui, rest)); }