1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.OAuth.MFAController do
7 The model represents api to use Multi Factor authentications.
10 use Pleroma.Web, :controller
13 alias Pleroma.Web.Auth.TOTPAuthenticator
14 alias Pleroma.Web.OAuth.MFAView, as: View
15 alias Pleroma.Web.OAuth.OAuthController
16 alias Pleroma.Web.OAuth.OAuthView
17 alias Pleroma.Web.OAuth.Token
19 plug(:fetch_session when action in [:show, :verify])
20 plug(:fetch_flash when action in [:show, :verify])
23 Display form to input mfa code or recovery code.
25 def show(conn, %{"mfa_token" => mfa_token} = params) do
26 template = Map.get(params, "challenge_type", "totp")
30 |> render("#{template}.html", %{
32 redirect_uri: params["redirect_uri"],
33 state: params["state"]
38 Verification code and continue authorization.
40 def verify(conn, %{"mfa" => %{"mfa_token" => mfa_token} = mfa_params} = _) do
41 with {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token),
42 {:ok, _} <- validates_challenge(user, mfa_params) do
44 |> OAuthController.after_create_authorization(auth, %{
46 "redirect_uri" => mfa_params["redirect_uri"],
47 "state" => mfa_params["state"]
53 |> put_flash(:error, "Two-factor authentication failed.")
54 |> put_status(:unauthorized)
60 Verification second step of MFA (or recovery) and returns access token.
63 POST /oauth/mfa/challenge
68 `mfa_token` - access token to check second step of mfa
69 `challenge_type` - 'totp' or 'recovery'
73 def challenge(conn, %{"mfa_token" => mfa_token} = params) do
74 with {:ok, app} <- Token.Utils.fetch_app(conn),
75 {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token),
76 {:ok, _} <- validates_challenge(user, params),
77 {:ok, token} <- Token.exchange_token(app, auth) do
78 json(conn, OAuthView.render("token.json", %{user: user, token: token}))
83 |> json(%{error: "Invalid code"})
88 defp validates_challenge(user, %{"challenge_type" => "totp", "code" => code} = _) do
89 TOTPAuthenticator.verify(code, user)
92 # Verify Recovery Code
93 defp validates_challenge(user, %{"challenge_type" => "recovery", "code" => code} = _) do
94 TOTPAuthenticator.verify_recovery_code(user, code)
97 defp validates_challenge(_, _), do: {:error, :unsupported_challenge_type}