1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.MFA.TOTP do
7 This module represents functions to create secrets for
8 TOTP Application as well as validate them with a time based token.
12 @config_ns [:instance, :multi_factor_authentication, :totp]
15 https://github.com/google/google-authenticator/wiki/Key-Uri-Format
17 def provisioning_uri(secret, label, opts \\ []) do
21 issuer: Keyword.get(opts, :issuer, default_issuer()),
22 digits: Keyword.get(opts, :digits, default_digits()),
23 period: Keyword.get(opts, :period, default_period())
25 |> Enum.filter(fn {_, v} -> not is_nil(v) end)
29 %URI{scheme: "otpauth", host: "totp", path: "/" <> label, query: query}
33 defp default_period, do: Config.get(@config_ns ++ [:period])
34 defp default_digits, do: Config.get(@config_ns ++ [:digits])
37 do: Config.get(@config_ns ++ [:issuer], Config.get([:instance, :name]))
39 @doc "Creates a random Base 32 encoded string"
40 def generate_secret do
41 Base.encode32(:crypto.strong_rand_bytes(10))
44 @doc "Generates a valid token based on a secret"
45 def generate_token(secret) do
50 Validates a given token based on a secret.
53 `token_length` default `6`
54 `interval_length` default `30`
57 Returns {:ok, :pass} if the token is valid and
58 {:error, :invalid_token} if it is not.
60 @spec validate_token(String.t(), String.t()) ::
61 {:ok, :pass} | {:error, :invalid_token | :invalid_secret_and_token}
62 def validate_token(secret, token)
63 when is_binary(secret) and is_binary(token) do
65 token_length: default_digits(),
66 interval_length: default_period()
69 validate_token(secret, token, opts)
72 def validate_token(_, _), do: {:error, :invalid_secret_and_token}
74 @doc "See `validate_token/2`"
75 @spec validate_token(String.t(), String.t(), Keyword.t()) ::
76 {:ok, :pass} | {:error, :invalid_token | :invalid_secret_and_token}
77 def validate_token(secret, token, options)
78 when is_binary(secret) and is_binary(token) do
79 case :pot.valid_totp(token, secret, options) do
81 false -> {:error, :invalid_token}
85 def validate_token(_, _, _), do: {:error, :invalid_secret_and_token}