public virtual Task <bool> WaitForTwoFactorCode(ITwoFactorChannelInfo[] channels, CancellationToken token) { var twoFactorTask = new TaskCompletionSource <bool>(); Task.Run(async() => { var cancelCallback = token.Register(() => { twoFactorTask.SetResult(true); }); var pushChannelInfo = new Dictionary <TwoFactorPushAction, ITwoFactorPushInfo>(); var codeChannelInfo = new Dictionary <TwoFactorChannel, ITwoFactorAppCodeInfo>(); ITwoFactorAppCodeInfo codeChannel = null; { foreach (var ch in channels) { if (ch is ITwoFactorPushInfo pi) { if (pi.SupportedActions == null) { continue; } foreach (var a in pi.SupportedActions) { if (pushChannelInfo.ContainsKey(a)) { continue; } pushChannelInfo.Add(a, pi); } } if (ch is ITwoFactorAppCodeInfo aci) { if (codeChannel == null) { codeChannel = aci; } aci.Duration = TwoFactorDuration.Every30Days; if (codeChannelInfo.ContainsKey(ch.Channel)) { continue; } codeChannelInfo.Add(ch.Channel, aci); } } } var info = pushChannelInfo.Keys .Select(x => x.GetPushActionText()) .Where(x => !string.IsNullOrEmpty(x)) .Select(x => $"\"{x}\"") .ToList(); if (codeChannelInfo.Count > 1) { var codes = string.Join(", ", codeChannelInfo.Values.Select(x => x.ChannelName)); info.Add($"To switch between app code channels: code=<channel> where channels are {codes}"); } Console.WriteLine("To change default 2FA token persistence use command 2fa=<duration>"); var dur = Enum .GetValues(typeof(TwoFactorDuration)) .OfType <TwoFactorDuration>() .Select(x => $"\"{DurationToText(x)}\"") .ToArray(); Console.WriteLine("Available durations are: " + string.Join(", ", dur)); info.Add("<Enter> to Cancel"); Console.WriteLine("\nTwo Factor Authentication"); Console.WriteLine(string.Join("\n", info)); while (true) { if (codeChannel != null) { Console.Write($"[{codeChannel.ChannelName ?? ""}] ({DurationToText(codeChannel.Duration)})"); } Console.Write(" > "); var code = await InputManager.ReadLine(); if (twoFactorTask.Task.IsCompleted) { break; } if (string.IsNullOrEmpty(code)) { twoFactorTask.TrySetResult(false); break; } if (code.StartsWith("code=")) { var ch = code.Substring(5); var cha = codeChannelInfo.Values .FirstOrDefault(x => x.ChannelName == ch.ToLowerInvariant()); if (cha != null) { codeChannel = cha; } else { Console.WriteLine($"Invalid 2FA code channel: {ch}"); } continue; } if (code.StartsWith("2fa=")) { if (TryParseTextToDuration(code.Substring(4), out var duration)) { if (codeChannel != null) { codeChannel.Duration = duration; } } continue; } if (AuthUIExtensions.TryParsePushAction(code, out var action)) { if (pushChannelInfo.ContainsKey(action)) { await pushChannelInfo[action].InvokeTwoFactorPushAction(action); } else { Console.WriteLine($"Unsupported 2fa push action: {code}"); } continue; } if (codeChannel != null) { try { await codeChannel.InvokeTwoFactorCodeAction(code); } catch (KeeperAuthFailed) { Console.WriteLine("Code is invalid"); } catch (Exception e) { Console.WriteLine(e.Message); } } } cancelCallback.Dispose(); }, token); return(twoFactorTask.Task); }
private static async Task ProcessCommand(AuthSync auth, string command) { if (command == "?") { PrintStepHelp(auth.Step); return; } if (auth.Step is DeviceApprovalStep das) { if (command.StartsWith($"{ChannelCommand}=", StringComparison.InvariantCultureIgnoreCase)) { var channelText = command.Substring(ChannelCommand.Length + 1).ToLowerInvariant(); var channel = das.Channels.FirstOrDefault(x => x.ChannelText() == channelText); if (channel != default) { das.DefaultChannel = channel; } else { Console.WriteLine($"Device Approval push channel {channelText} not found."); } } else if (string.Compare(command, PushCommand, StringComparison.InvariantCultureIgnoreCase) == 0) { await das.SendPush(das.DefaultChannel); } else { await das.SendCode(das.DefaultChannel, command); } } else if (auth.Step is TwoFactorStep tfs) { if (command.StartsWith($"{ChannelCommand}=", StringComparison.InvariantCultureIgnoreCase)) { var channelText = command.Substring(ChannelCommand.Length + 1).ToLowerInvariant(); var channel = tfs.Channels.FirstOrDefault(x => x.ChannelText() == channelText); if (channel != default) { tfs.DefaultChannel = channel; } else { Console.WriteLine($"2FA channel {channelText} not found."); } } else if (command.StartsWith($"{ExpireCommand}=", StringComparison.InvariantCultureIgnoreCase)) { var expireText = command.Substring(ExpireCommand.Length + 1).ToLowerInvariant(); var duration = Expires.FirstOrDefault(x => x.ExpireText() == expireText); if (duration != default) { tfs.Duration = duration; } } else { var push = tfs.Channels .SelectMany(x => tfs.GetChannelPushActions(x) ?? Enumerable.Empty <TwoFactorPushAction>()) .FirstOrDefault(x => x.GetPushActionText() == command); if (push != default) { await tfs.SendPush(push); } else { await tfs.SendCode(tfs.DefaultChannel, command); } } } else if (auth.Step is PasswordStep ps) { await ps.VerifyPassword(command); } else if (auth.Step is SsoTokenStep sts) { if (string.Compare(command, "password", StringComparison.InvariantCultureIgnoreCase) == 0) { await sts.LoginWithPassword(); } else { await sts.SetSsoToken(command); } } else if (auth.Step is SsoDataKeyStep sdks) { if (AuthUIExtensions.TryParseDataKeyShareChannel(command, out var channel)) { await sdks.RequestDataKey(channel); } else { Console.WriteLine($"Invalid data key share channel: {command}"); } } else { Console.WriteLine($"Invalid command. Type \"?\" to list available commands."); } }