enable shell input of TOTP code when 2FA is activated.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
diff --git a/lib/mix/tasks/ovh.ex b/lib/mix/tasks/ovh.ex
index bbee71e..6ed7472 100644
--- a/lib/mix/tasks/ovh.ex
+++ b/lib/mix/tasks/ovh.ex
@@ -351,17 +351,93 @@ defmodule Mix.Tasks.Ovh do
headers = [{"Content-Type", "application/x-www-form-urlencoded"}]
options = @default_options
resp = HTTPoison.request!(method, uri, body, headers, options)
+ |> Og.log_return()
+
+ case check_for_successful_binding(resp, validation_url, ck) do
+ {:ok, ck} -> ck
+ {:ok, :handle_2fa} -> handle_2fa(resp.body, validation_url, ck)
+ {:error, msg} -> raise msg
+ end
+ end
+
+ def check_for_successful_binding(resp, validation_url, ck) do
+ Og.context(__ENV__, :debug)
error_msg1 = "Failed to bind the consumer token to the application. Please try to validate the consumer token manually at #{validation_url}"
error_msg2 = "Invalid validity period entered for the consumer token. Please try to validate the consumer token manually at #{validation_url}"
cond do
- String.contains?(resp.body, "Invalid validity") -> raise error_msg2
- String.contains?(resp.body, "The token is now valid, it can be used in the application") -> ck
- String.contains?(resp.body, "Your token is now valid, you can use it in your application") -> ck
- String.contains?(resp.body, "token is now valid") -> ck
+ String.contains?(resp.body, "Invalid validity") -> {:error, error_msg2}
+ String.contains?(resp.body, "The token is now valid, it can be used in the application") -> {:ok, ck}
+ String.contains?(resp.body, "Your token is now valid, you can use it in your application") -> {:ok, ck}
+ String.contains?(resp.body, "token is now valid") -> {:ok, ck}
+ String.contains?(resp.body, "You have activated the double factor authentication") -> {:ok, :handle_2fa}
# presume the validation was successful if redirected to redirect uri
- resp.status_code == 302 && (resp.headers |> Enum.into(%{}) |> Map.has_key?("Location")) -> ck
- true -> raise error_msg1
+ resp.status_code == 302 && (resp.headers |> Enum.into(%{}) |> Map.has_key?("Location")) -> {:ok, ck}
+ true -> {:error, "Unexpected error " <> error_msg1}
+ end
+ end
+
+
+ defp build_2fa_request(resp_body) do
+ Og.context(__ENV__, :debug)
+
+ Mix.Shell.IO.info("You have activated 2FA on your OVH account, you need to verify your account via 2FA")
+
+ Floki.find(resp_body, "form input")
+ |> Enum.reduce("", fn({type, input, _options}, acc) ->
+ {name_val, value} =
+ cond do
+ type == "input" && {"name", "sessionId"} in input ->
+ name_val = :proplists.get_value("name", input)
+ value = :proplists.get_value("value", input)
+ {name_val, value}
+ type == "input" && {"name", "credentialToken"} in input ->
+ name_val = :proplists.get_value("name", input)
+ value = :proplists.get_value("value", input)
+ {name_val, value}
+ type == "input" && {"name", "duration"} in input ->
+ name_val = :proplists.get_value("name", input)
+ value = "0"
+ {name_val, value}
+ type == "input" && {"type", "number"} in input && {"placeholder", "Code"} in input ->
+ name_val = :proplists.get_value("name", input)
+ # Get value from shell asking user for 2FA code.
+ value = Mix.Shell.IO.prompt("Please enter *promptly* the 2FA (2 Factor Authentication) code generated by your mobile application:")
+ Mix.Shell.IO.info("The code #{value} will be sent as the 2FA code")
+ {name_val, value}
+ true ->
+ # raise "Unexpected input"
+ Og.log("Ignoring unexpected input " <> inspect(input), __ENV__, :warn)
+ {:no_name, :no_val}
+ end
+ case {name_val, value} do
+ {:no_name, :no_val} -> acc
+ {name_val, value} -> acc <> name_val <> "=" <> value <> "&"
+ end
+ end)
+ |> String.trim_trailing("&")
+ end
+
+
+ defp handle_2fa(resp_body, validation_url, ck) do
+ Og.context(__ENV__, :debug)
+
+ method = :post
+ uri = validation_url
+ |> Og.log_return()
+ body = build_2fa_request(resp_body)
+ |> Og.log_return(:error)
+ headers = [{"Content-Type", "application/x-www-form-urlencoded"}]
+ options = @default_options
+ resp = HTTPoison.request!(method, uri, body, headers, options)
+
+ resp.body |> Og.log_return()
+
+ error_msg = "Function check_for_successful_binding seems to be entering an error loop"
+ case check_for_successful_binding(resp, validation_url, ck) do
+ {:ok, ck} -> ck
+ {:ok, :handle_2fa} -> raise error_msg
+ {:error, msg} -> raise "#{error_msg} - #{msg}"
end
end