logo

pleroma

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

mfa.ex (3990B)


  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.MFA do
  5. @moduledoc """
  6. The MFA context.
  7. """
  8. alias Pleroma.User
  9. alias Pleroma.MFA.BackupCodes
  10. alias Pleroma.MFA.Changeset
  11. alias Pleroma.MFA.Settings
  12. alias Pleroma.MFA.TOTP
  13. @doc """
  14. Returns MFA methods the user has enabled.
  15. ## Examples
  16. iex> Pleroma.MFA.supported_method(User)
  17. "totp, u2f"
  18. """
  19. @spec supported_methods(User.t()) :: String.t()
  20. def supported_methods(user) do
  21. settings = fetch_settings(user)
  22. Settings.mfa_methods()
  23. |> Enum.reduce([], fn m, acc ->
  24. if method_enabled?(m, settings) do
  25. acc ++ [m]
  26. else
  27. acc
  28. end
  29. end)
  30. |> Enum.join(",")
  31. end
  32. @doc "Checks that user enabled MFA"
  33. def require?(user) do
  34. fetch_settings(user).enabled
  35. end
  36. @doc """
  37. Display MFA settings of user
  38. """
  39. def mfa_settings(user) do
  40. settings = fetch_settings(user)
  41. Settings.mfa_methods()
  42. |> Enum.map(fn m -> [m, method_enabled?(m, settings)] end)
  43. |> Enum.into(%{enabled: settings.enabled}, fn [a, b] -> {a, b} end)
  44. end
  45. @doc false
  46. def fetch_settings(%User{} = user) do
  47. user.multi_factor_authentication_settings || %Settings{}
  48. end
  49. @doc "clears backup codes"
  50. def invalidate_backup_code(%User{} = user, hash_code) do
  51. %{backup_codes: codes} = fetch_settings(user)
  52. user
  53. |> Changeset.cast_backup_codes(codes -- [hash_code])
  54. |> User.update_and_set_cache()
  55. end
  56. @doc "generates backup codes"
  57. @spec generate_backup_codes(User.t()) :: {:ok, list(binary)} | {:error, String.t()}
  58. def generate_backup_codes(%User{} = user) do
  59. with codes <- BackupCodes.generate(),
  60. hashed_codes <- Enum.map(codes, &Pleroma.Password.Pbkdf2.hash_pwd_salt/1),
  61. changeset <- Changeset.cast_backup_codes(user, hashed_codes),
  62. {:ok, _} <- User.update_and_set_cache(changeset) do
  63. {:ok, codes}
  64. else
  65. {:error, msg} ->
  66. {:error, msg}
  67. end
  68. end
  69. @doc """
  70. Generates secret key and set delivery_type to 'app' for TOTP method.
  71. """
  72. @spec setup_totp(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
  73. def setup_totp(user) do
  74. user
  75. |> Changeset.setup_totp(%{secret: TOTP.generate_secret(), delivery_type: "app"})
  76. |> User.update_and_set_cache()
  77. end
  78. @doc """
  79. Confirms the TOTP method for user.
  80. `attrs`:
  81. `password` - current user password
  82. `code` - TOTP token
  83. """
  84. @spec confirm_totp(User.t(), map()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t() | atom()}
  85. def confirm_totp(%User{} = user, attrs) do
  86. with settings <- user.multi_factor_authentication_settings.totp,
  87. {:ok, :pass} <- TOTP.validate_token(settings.secret, attrs["code"]) do
  88. user
  89. |> Changeset.confirm_totp()
  90. |> User.update_and_set_cache()
  91. end
  92. end
  93. @doc """
  94. Disables the TOTP method for user.
  95. `attrs`:
  96. `password` - current user password
  97. """
  98. @spec disable_totp(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
  99. def disable_totp(%User{} = user) do
  100. user
  101. |> Changeset.disable_totp()
  102. |> Changeset.disable()
  103. |> User.update_and_set_cache()
  104. end
  105. @doc """
  106. Force disables all MFA methods for user.
  107. """
  108. @spec disable(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
  109. def disable(%User{} = user) do
  110. user
  111. |> Changeset.disable_totp()
  112. |> Changeset.disable(true)
  113. |> User.update_and_set_cache()
  114. end
  115. @doc """
  116. Checks if the user has MFA method enabled.
  117. """
  118. def method_enabled?(method, settings) do
  119. with {:ok, %{confirmed: true} = _} <- Map.fetch(settings, method) do
  120. true
  121. else
  122. _ -> false
  123. end
  124. end
  125. @doc """
  126. Checks if the user has enabled at least one MFA method.
  127. """
  128. def enabled?(settings) do
  129. Settings.mfa_methods()
  130. |> Enum.map(fn m -> method_enabled?(m, settings) end)
  131. |> Enum.any?()
  132. end
  133. end