logo

pleroma

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

totp.ex (2721B)


  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.TOTP do
  5. @moduledoc """
  6. This module represents functions to create secrets for
  7. TOTP Application as well as validate them with a time based token.
  8. """
  9. alias Pleroma.Config
  10. @config_ns [:instance, :multi_factor_authentication, :totp]
  11. @doc """
  12. https://github.com/google/google-authenticator/wiki/Key-Uri-Format
  13. """
  14. @spec provisioning_uri(String.t(), String.t(), list()) :: String.t()
  15. def provisioning_uri(secret, label, opts \\ []) do
  16. query =
  17. %{
  18. secret: secret,
  19. issuer: Keyword.get(opts, :issuer, default_issuer()),
  20. digits: Keyword.get(opts, :digits, default_digits()),
  21. period: Keyword.get(opts, :period, default_period())
  22. }
  23. |> Enum.filter(fn {_, v} -> not is_nil(v) end)
  24. |> Enum.into(%{})
  25. |> URI.encode_query()
  26. %URI{scheme: "otpauth", host: "totp", path: "/" <> label, query: query}
  27. |> to_string()
  28. end
  29. defp default_period, do: Config.get(@config_ns ++ [:period])
  30. defp default_digits, do: Config.get(@config_ns ++ [:digits])
  31. defp default_issuer,
  32. do: Config.get(@config_ns ++ [:issuer], Config.get([:instance, :name]))
  33. @doc "Creates a random Base 32 encoded string"
  34. def generate_secret do
  35. Base.encode32(:crypto.strong_rand_bytes(10))
  36. end
  37. @doc "Generates a valid token based on a secret"
  38. def generate_token(secret) do
  39. :pot.totp(secret)
  40. end
  41. @doc """
  42. Validates a given token based on a secret.
  43. optional parameters:
  44. `token_length` default `6`
  45. `interval_length` default `30`
  46. `window` default 0
  47. Returns {:ok, :pass} if the token is valid and
  48. {:error, :invalid_token} if it is not.
  49. """
  50. @spec validate_token(String.t(), String.t()) ::
  51. {:ok, :pass} | {:error, :invalid_token | :invalid_secret_and_token}
  52. def validate_token(secret, token)
  53. when is_binary(secret) and is_binary(token) do
  54. opts = [
  55. token_length: default_digits(),
  56. interval_length: default_period()
  57. ]
  58. validate_token(secret, token, opts)
  59. end
  60. def validate_token(_, _), do: {:error, :invalid_secret_and_token}
  61. @doc "See `validate_token/2`"
  62. @spec validate_token(String.t(), String.t(), Keyword.t()) ::
  63. {:ok, :pass} | {:error, :invalid_token | :invalid_secret_and_token}
  64. def validate_token(secret, token, options)
  65. when is_binary(secret) and is_binary(token) do
  66. case :pot.valid_totp(token, secret, options) do
  67. true -> {:ok, :pass}
  68. false -> {:error, :invalid_token}
  69. end
  70. end
  71. def validate_token(_, _, _), do: {:error, :invalid_secret_and_token}
  72. end