logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma

mfa_controller.ex (2923B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.OAuth.MFAController do
  5. @moduledoc """
  6. The model represents api to use Multi Factor authentications.
  7. """
  8. use Pleroma.Web, :controller
  9. alias Pleroma.MFA
  10. alias Pleroma.Web.Auth.TOTPAuthenticator
  11. alias Pleroma.Web.OAuth.MFAView, as: View
  12. alias Pleroma.Web.OAuth.OAuthController
  13. alias Pleroma.Web.OAuth.OAuthView
  14. alias Pleroma.Web.OAuth.Token
  15. plug(:fetch_session when action in [:show, :verify])
  16. plug(:fetch_flash when action in [:show, :verify])
  17. @doc """
  18. Display form to input mfa code or recovery code.
  19. """
  20. def show(conn, %{"mfa_token" => mfa_token} = params) do
  21. template = Map.get(params, "challenge_type", "totp")
  22. conn
  23. |> put_view(View)
  24. |> render("#{template}.html", %{
  25. mfa_token: mfa_token,
  26. redirect_uri: params["redirect_uri"],
  27. state: params["state"]
  28. })
  29. end
  30. @doc """
  31. Verification code and continue authorization.
  32. """
  33. def verify(conn, %{"mfa" => %{"mfa_token" => mfa_token} = mfa_params} = _) do
  34. with {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token),
  35. {:ok, _} <- validates_challenge(user, mfa_params) do
  36. conn
  37. |> OAuthController.after_create_authorization(auth, %{
  38. "authorization" => %{
  39. "redirect_uri" => mfa_params["redirect_uri"],
  40. "state" => mfa_params["state"]
  41. }
  42. })
  43. else
  44. _ ->
  45. conn
  46. |> put_flash(:error, "Two-factor authentication failed.")
  47. |> put_status(:unauthorized)
  48. |> show(mfa_params)
  49. end
  50. end
  51. @doc """
  52. Verification second step of MFA (or recovery) and returns access token.
  53. ## Endpoint
  54. POST /oauth/mfa/challenge
  55. params:
  56. `client_id`
  57. `client_secret`
  58. `mfa_token` - access token to check second step of mfa
  59. `challenge_type` - 'totp' or 'recovery'
  60. `code`
  61. """
  62. def challenge(conn, %{"mfa_token" => mfa_token} = params) do
  63. with {:ok, app} <- Token.Utils.fetch_app(conn),
  64. {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token),
  65. {:ok, _} <- validates_challenge(user, params),
  66. {:ok, token} <- Token.exchange_token(app, auth) do
  67. json(conn, OAuthView.render("token.json", %{user: user, token: token}))
  68. else
  69. _error ->
  70. conn
  71. |> put_status(400)
  72. |> json(%{error: "Invalid code"})
  73. end
  74. end
  75. # Verify TOTP Code
  76. defp validates_challenge(user, %{"challenge_type" => "totp", "code" => code} = _) do
  77. TOTPAuthenticator.verify(code, user)
  78. end
  79. # Verify Recovery Code
  80. defp validates_challenge(user, %{"challenge_type" => "recovery", "code" => code} = _) do
  81. TOTPAuthenticator.verify_recovery_code(user, code)
  82. end
  83. defp validates_challenge(_, _), do: {:error, :unsupported_challenge_type}
  84. end