defmodule MaatFeatherWeb.AuthController do @moduledoc """ Controller for authentication. """ use MaatFeatherWeb, :controller alias MaatFeather.Users alias MaatFeather.Users.User alias MaatFeather.Auth.{Token, TOTP} alias MaatFeatherWeb.UserView action_fallback MaatFeatherWeb.FallbackController @doc """ Signin a user to the system. ## Request ```json { "credentials": { "email": "elioenaiferrari@gmail.com", "password": "123456", "totp_token": "556475" // if user has MFA enabled } } ``` ## Response ```json { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjE0MzgyNjAsImlzcyI6Im1hYXRfZmVhdGhlciIsImtpZCI6IkJBMTg5QzNGNjZDODJCNjIiLCJzdWIiOiIyRDJEMkQyRDJENDI0NTQ3NDk0RTIwNTA1NTQyNEM0OTQzMjA0QjQ1NTkyRDJEMkQyRDJEMEE0RDQ5NDc2NjRENDEzMDQ3NDM1MzcxNDc1MzQ5NjIzMzQ0NTE0NTQyNDE1MTU1NDE0MTM0NDc0RTQxNDQ0MzQyNjk1MTRCNDI2NzUxNDQ1MjU1NTc1MTQ4NjIzNDY0NzI2NjM3MzQ1MzMyNjk2NzY1NEY0RjYxNjM3NDRCNkM1ODBBMkI2NDUyNTA2OTRFMzQ1MzczNDIyRjU4MzA2RjcyNDQ2ODY3NzY1MTcxNTQ2NzQxMkI0MTdBN0E0Rjc0NEI0NzJCNzQzMjJGNEM3NzQ5NTg0RDUwNDc1MDU2NTI0NzM5NDY2QjU2Njk2NDZDNjI2MjU0MzY0NDM2Mzc3NjY5MkIwQTU2NkU1MzJGMzMzNzU2NkM2OTUxNTc2MTRDNEE1NDcxNkM0QjUzMzA1Nzc2NkU0MjZBNEY2RTRBNDQ2MTZBNTAzMjc3Mzc1MzMwNjY3MDMyN0E0MzU4NUE1MjY3NjE0RjM2MkY0RTRBMzM1QTYzNjMzNTRFNEQ3QTRBNjc0QTQyMEE2ODMxMzA0QjUxNjkyQjUzNkY3NzM4NkM0QTM3Njg2NTQ5NTE0OTQ0NDE1MTQxNDIwQTJEMkQyRDJEMkQ0NTRFNDQyMDUwNTU0MjRDNDk0MzIwNEI0NTU5MkQyRDJEMkQyRDBBMEEiLCJ0eXAiOiJhY2Nlc3MifQ.YMyQdeXdnvoeGhQR7auLwk4PHWD9oM2I_qQ5YGsbEl0G4Vf-pC7mhvl3JnLuvd_F7nMCVvJ-s_sfPbs-XzthknqAkfLZ3uf9yGCEQHxFQ56ex3rMKnTHsOTpyc5tLP2OP2i4JXmfbDr2D4DwynL6egLzxB41c1_WbrJ3Q3khKWOAxHSsyhN21QbepQrZmVOUZZXekslUUlJL9M79d4b1YWWnEb3daRinfHG6fDXp5dLmcSz8cBdVfNoewRqxpxJfFJesFsslox6p88ZWBTJo9-kmud5eWMgpor0yC4cdUoTfAfSn0cZWPkKME4DFk1FVvzXrs4-G_iMp2AoQsdHO8w" } ``` """ def signin(conn, %{"credentials" => credentials_params}) do with %User{is_verified: true} = user <- Users.get_user_by!(email: credentials_params["email"]), true <- Argon2.verify_pass(credentials_params["password"], user.password_hash) do unless user.has_mfa do {:ok, access_token, _claims} = Token.encode(user) conn |> put_status(:ok) |> json(%{access_token: access_token}) else if TOTP.is_valid?(user.secret_key, credentials_params["totp_token"]) do {:ok, access_token, _claims} = Token.encode(user) conn |> put_status(:ok) |> json(%{access_token: access_token}) else {:error, :unauthorized} end end else _ -> {:error, :unauthorized} end end @doc """ Refresh an access token. ## Response ```json { "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjE0MzgwODIsImlzcyI6Im1hYXRfZmVhdGhlciIsImtpZCI6IkNFNjI0Nzc0RjJFQjk2QjgiLCJzdWIiOiIyRDJEMkQyRDJENDI0NTQ3NDk0RTIwNTA1NTQyNEM0OTQzMjA0QjQ1NTkyRDJEMkQyRDJEMEE0RDQ5NDc2NjRENDEzMDQ3NDM1MzcxNDc1MzQ5NjIzMzQ0NTE0NTQyNDE1MTU1NDE0MTM0NDc0RTQxNDQ0MzQyNjk1MTRCNDI2NzUxNDQ1MjU1NTc1MTQ4NjIzNDY0NzI2NjM3MzQ1MzMyNjk2NzY1NEY0RjYxNjM3NDRCNkM1ODBBMkI2NDUyNTA2OTRFMzQ1MzczNDIyRjU4MzA2RjcyNDQ2ODY3NzY1MTcxNTQ2NzQxMkI0MTdBN0E0Rjc0NEI0NzJCNzQzMjJGNEM3NzQ5NTg0RDUwNDc1MDU2NTI0NzM5NDY2QjU2Njk2NDZDNjI2MjU0MzY0NDM2Mzc3NjY5MkIwQTU2NkU1MzJGMzMzNzU2NkM2OTUxNTc2MTRDNEE1NDcxNkM0QjUzMzA1Nzc2NkU0MjZBNEY2RTRBNDQ2MTZBNTAzMjc3Mzc1MzMwNjY3MDMyN0E0MzU4NUE1MjY3NjE0RjM2MkY0RTRBMzM1QTYzNjMzNTRFNEQ3QTRBNjc0QTQyMEE2ODMxMzA0QjUxNjkyQjUzNkY3NzM4NkM0QTM3Njg2NTQ5NTE0OTQ0NDE1MTQxNDIwQTJEMkQyRDJEMkQ0NTRFNDQyMDUwNTU0MjRDNDk0MzIwNEI0NTU5MkQyRDJEMkQyRDBBMEEiLCJ0eXAiOiJyZWZyZXNoIn0.fKxIwfti40QAiJsE9nN0_QjXhzsczvJoFoBaPZPikRNeaAXkjDrhNw2JRw71bB2WqBNlyojYEgjJvkqRe02MFenioaz_NOsewp3EGGBW3efPdTkJb9osHzTztfT8_E3fT8bsiFUxNrgsgc6ToQt2BX227ctJMjQZOK3BU6FqSQvUykulrjSAJi5C62bABxW9dMBGUiiMs1Jq7o-b9xLOEKN4Mor3o3C2brz9ifhMwFR1phWgpNosmunvnzNuKABgkdzqYSkgWz6KKQ_2NMUvNZcXa0Defs5_-Ait9Zda2yBXELXhAKmT9_vRSe_GMrJBVQZFurOFgTl64euqwu-OPQ" } ``` """ def refresh_token(%Plug.Conn{assigns: %{access_token: access_token}} = conn, _params) do with {:ok, {_old_token, _old_claims}, {new_token, _new_claims}} <- Token.refresh(access_token) do conn |> put_status(:ok) |> json(%{refresh_token: new_token}) else {:error, message} -> conn |> put_status(:unauthorized) |> json(%{errors: %{message: message}}) end end def refresh_token(_conn, _params), do: {:error, :unauthorized} @doc """ Get totp qrcode. ## Response ```json { "qrcode": "\n address}}} = conn, _params) do case Users.get_user!(address) do nil -> {:error, :not_found} user -> {qrcode, uri} = TOTP.gen_qrcode!(user.email, user.secret_key) conn |> put_status(:ok) |> json(%{qrcode: qrcode, uri: uri}) end end @doc """ Get user profile. ## Response ```json { "data": { "address": "2D2D2D2D2D424547494E205055424C4943204B45592D2D2D2D2D0A4D4947664D413047435371475349623344514542415155414134474E4144434269514B4267514452555751486234647266373453326967654F4F6163744B6C580A2B645250694E345373422F58306F724468677651715467412B417A7A4F744B472B74322F4C7749584D50475056524739466B5669646C6262543644363776692B0A566E532F3337566C695157614C4A54716C4B533057766E426A4F6E4A44616A5032773753306670327A43585A5267614F362F4E4A335A6363354E4D7A4A674A420A6831304B51692B536F77386C4A37686549514944415141420A2D2D2D2D2D454E44205055424C4943204B45592D2D2D2D2D0A0A", "email": "elioenaiferrari@gmail.com", "role": "user", "username": "Elix" } } ``` """ def profile( %Plug.Conn{assigns: %{user: %{"sub" => address}}} = conn, _params ) do case Users.get_user!(address) do nil -> {:error, :not_found} user -> conn |> put_view(UserView) |> put_status(:ok) |> render("show.json", %{user: user}) end end def profile(_conn, _params), do: {:error, :unauthorized} @doc """ Account verification. ## Response ```json { "message": "Account verified. You can now login." } ``` """ def account_verification(conn, %{"one_time_token" => one_time_token}) do with %User{} = user <- Users.get_user_by!(one_time_token: one_time_token), {:ok, _} <- Users.update_user(user, %{ is_verified: true, one_time_token: :crypto.strong_rand_bytes(16) |> Base.encode16() }) do conn |> put_status(:ok) |> json(%{ message: "Account verified. You can now login." }) else _ -> {:error, :internal_server_error} end end end