logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git

mfa_controller.ex (2887B)


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