defmodule PlausibleWeb.TwoFactor.Session do @moduledoc """ Functions for managing session data related to Two-Factor Authentication. """ import Plug.Conn alias Plausible.Auth @remember_2fa_cookie "remember_2fa" @remember_2fa_days 30 @remember_2fa_seconds @remember_2fa_days * 24 * 60 * 60 @session_2fa_cookie "session_2fa" @session_2fa_seconds 5 * 60 @spec set_2fa_user(Plug.Conn.t(), Auth.User.t()) :: Plug.Conn.t() def set_2fa_user(conn, %Auth.User{} = user) do put_resp_cookie(conn, @session_2fa_cookie, %{current_2fa_user_id: user.id}, domain: domain(), secure: secure_cookie?(), encrypt: true, max_age: @session_2fa_seconds, same_site: "Lax" ) end @spec get_2fa_user(Plug.Conn.t()) :: {:ok, Auth.User.t()} | {:error, :not_found} def get_2fa_user(conn) do conn = fetch_cookies(conn, encrypted: [@session_2fa_cookie]) session_2fa = conn.cookies[@session_2fa_cookie] with id when is_integer(id) <- session_2fa[:current_2fa_user_id], %Auth.User{} = user <- Plausible.Users.with_subscription(id) do {:ok, user} else _ -> {:error, :not_found} end end @spec clear_2fa_user(Plug.Conn.t()) :: Plug.Conn.t() def clear_2fa_user(conn) do delete_resp_cookie(conn, @session_2fa_cookie, domain: domain(), secure: secure_cookie?(), encrypt: true, max_age: @session_2fa_seconds, same_site: "Lax" ) end @spec remember_2fa_days() :: non_neg_integer() def remember_2fa_days(), do: @remember_2fa_days @spec remember_2fa?(Plug.Conn.t(), Auth.User.t()) :: boolean() def remember_2fa?(conn, user) do conn = fetch_cookies(conn, encrypted: [@remember_2fa_cookie]) not is_nil(user.totp_token) and conn.cookies[@remember_2fa_cookie] == user.totp_token end @spec maybe_set_remember_2fa(Plug.Conn.t(), Auth.User.t(), String.t() | nil) :: Plug.Conn.t() def maybe_set_remember_2fa(conn, user, "true") do put_resp_cookie(conn, @remember_2fa_cookie, user.totp_token, domain: domain(), secure: secure_cookie?(), encrypt: true, max_age: @remember_2fa_seconds, same_site: "Lax" ) end def maybe_set_remember_2fa(conn, _, _) do clear_remember_2fa(conn) end @spec clear_remember_2fa(Plug.Conn.t()) :: Plug.Conn.t() def clear_remember_2fa(conn) do delete_resp_cookie(conn, @remember_2fa_cookie, domain: domain(), secure: secure_cookie?(), encrypt: true, max_age: @remember_2fa_seconds, same_site: "Lax" ) end defp domain(), do: PlausibleWeb.Endpoint.host() defp secure_cookie?() do :plausible |> Application.fetch_env!(PlausibleWeb.Endpoint) |> Keyword.fetch!(:secure_cookie) end end