Files
sukaato-site/lib/sukaato_web/controllers/page_controller.ex

240 lines
12 KiB
Elixir

defmodule SukaatoWeb.PageController do
alias SukaatoWeb.Theme
alias Sukaato.{Auth, StandardQueries, User}
alias QRCode.Render
import Ecto.Query
use SukaatoWeb, :controller
@rel_proj_root "../../.."
@site_config_file Path.expand(@rel_proj_root <> "/site.toml", __DIR__)
@site_config Toml.decode_file(@site_config_file)
@admin_username elem(@site_config, 1)["site"]["username"]
@page_links [
%PageLink{name: "Infernus", uri: "/"},
%PageLink{name: "Talisman", uri: "/pubkey"},
%PageLink{name: "Evokation", uri: "/contact"},
%PageLink{name: "Prophets", uri: "/thinkers"}
# @TODO place link as anchor element href attribute value inside template element for ALL pages
# %PageLink{name: "Passage", uri: "/login"}
]
@tables %{
user: User
}
def index(conn, _params) do
Theme.update(elem(@site_config, 1)["site"]["theme"])
render(conn, :index, layout: false, config: elem(@site_config, 1), page_name: "index", pages: @page_links)
end
def pubkey(conn, params) do
Theme.update(elem(@site_config, 1)["site"]["theme"])
user = if Map.has_key?(params, "user"), do: params["user"], else: @admin_username
id = if Map.has_key?(params, "id"), do: params["id"], else: ""
result = StandardQueries.get_pubkeys(user, id)
data = if tuple_size(result) >= 2, do: elem(result, 1), else: []
gpg_id = if length(data) > 0, do: Enum.map(data, fn d -> elem(d, 0) end), else: [""]
content = if length(data) > 0, do: Enum.map(data, fn d -> elem(d, 1) end), else: [""]
qr_embed = {Path.expand("../../../priv/static/images/sigil.svg", __DIR__), 200}
qr_settings = %Render.SvgSettings{image: qr_embed, structure: :readable, background_opacity: 0.0, scale: 4}
# @TODO account for case wherein pubkey_content is a list
if length(content) == 1 do
qr = if Enum.at(content, 0) != "", do: QRCode.create(Enum.at(content, 0)), else: ""
qr = QRCode.render(qr, :svg, qr_settings)
id_qr = Enum.zip([gpg_id, [qr], content])
render(conn, :pubkey, layout: false, config: elem(@site_config, 1), pages: @page_links, user: user, qr: id_qr)
else
# content = if length(content) > 0, do: Enum.filter(content, fn c -> c != "" end), else: []
qr = if length(content) > 1, do: Enum.map(content, fn c -> QRCode.create(c) end), else: [""]
qr = Enum.map(qr, fn q -> QRCode.render(q, :svg, qr_settings) end)
id_qr = Enum.zip([gpg_id, qr, content])
id_qr = Enum.filter(id_qr, fn q -> elem(q, 1) != "" end)
render(conn, :pubkey, layout: false, config: elem(@site_config, 1), pages: @page_links, user: user, qr: id_qr)
end
end
def think(conn, params) do
Theme.update(elem(@site_config, 1)["site"]["theme"])
username = if Map.has_key?(params, "user"), do: params["user"], else: @admin_username
thinker_data = File.read(Path.expand(@rel_proj_root <> "/priv/static/files/users/" <> username <> "/thinkers.json", __DIR__))
thinker_data = if elem(thinker_data, 0) == :ok, do: elem(thinker_data, 1)
thinker_data = elem(Jason.decode(thinker_data, keys: :atoms), 1)
render(conn, :thinkers, layout: false, config: elem(@site_config, 1), pages: @page_links, page_name: "thinkers", thinkers: thinker_data)
end
def contact(conn, params) do
Theme.update(elem(@site_config, 1)["site"]["theme"])
affil = if Map.has_key?(params, "user"), do: StandardQueries.get_inboxes(params["user"]), else: StandardQueries.get_inboxes(@admin_username)
affil = elem(affil, 1)
render(conn, :contact, layout: false, config: elem(@site_config, 1), pages: @page_links, affil: affil)
end
def two_fa(conn, _params) do
pass_token = get_session(conn, "pass_token")
twofa_token = get_session(conn, "2fa_token")
# IO.inspect(conn)
if is_nil(pass_token) do
redirect(conn, to: ~p"/login")
# status = put_status(conn, 404)
# ext = ".heex"
# render(status, ErrorView, "404.html" <> ext)
else
user_id = Enum.at(String.split(pass_token, ":", parts: 2), 0)
if is_nil(twofa_token) do
twofact = Auth.get_credentials(conn, ["code", "chall_resp"])
query = from u in @tables.user, select: {u.totp_active, u.totp_enabled}, where: u.username == ^user_id or u.email == ^user_id
totp_requisite = Auth.is_authflow_required?(user_id, :all?, (query))
query = from u in @tables.user, select: {u.fido_active, u.fido_enabled}, where: u.username == ^user_id or u.email == ^user_id
fido_requisite = Auth.is_authflow_required?(user_id, :all?, (query))
if is_nil(twofact) do
render(conn, :twofact, layout: false, config: elem(@site_config, 1), pages: @page_links, attempted: false, totp: totp_requisite, fido: fido_requisite)
else
result = if Map.has_key?(twofact, "code"), do: Auth.totp_bouncer(conn, user_id, twofact["code"]), else: {conn, !totp_requisite, nil}
conn = elem(result, 0)
authenticated = elem(result, 1)
IO.inspect(authenticated)
result = if Map.has_key?(twofact, "chall_resp"), do: Auth.fido_bouncer(conn, user_id, twofact["chall_resp"]), else: {conn, !fido_requisite, nil}
conn = elem(result, 0)
IO.inspect(elem(result, 1))
authenticated = authenticated and elem(result, 1)
IO.inspect(authenticated)
if authenticated do
# conn = elem(result, 0)
conn = if is_nil(twofa_token), do: conn, else: delete_session(conn, "2fa_token")
conn = if is_nil(twofa_token), do: conn, else: delete_session(conn, "pass_token")
conn = Auth.generate_user_session(conn, user_id, true, "login_token")
redirect(conn, to: ~p"/login")
else
render(conn, :twofact, layout: false, config: elem(@site_config, 1), pages: @page_links, attempted: true, totp: totp_requisite, fido: fido_requisite)
end
end
else
conn = if is_nil(twofa_token), do: conn, else: delete_session(conn, "2fa_token")
conn = if is_nil(twofa_token), do: conn, else: delete_session(conn, "pass_token")
conn = Auth.generate_user_session(conn, user_id, true, "login_token")
redirect(conn, to: ~p"/login")
end
end
end
def login(conn, _params) do
Theme.update(elem(@site_config, 1)["site"]["theme"])
demand = Auth.get_credentials(conn, ["logout"])
want_logout = if is_nil(demand), do: "false", else: demand["logout"]
current_login_token = get_session(conn, "login_token")
result = if want_logout == "true" and !is_nil(current_login_token), do: StandardQueries.dump_login_token(current_login_token), else: {:ok, %{}}
conn = if want_logout == "true" and elem(result, 0) == :ok, do: delete_session(conn, "login_token"), else: conn
pass_token = get_session(conn, "pass_token")
twofa_token = get_session(conn, "2fa_token")
login_token = get_session(conn, "login_token")
submission_names = [
"Act as Clergy", # @NOTE logging in
"Return to Laity" # @NOTE logging out
]
button_name_options = %{
names: submission_names
}
form_fields = %{
username: "Clerical Title",
password: "Incantation",
code: "Sacred Number"
}
if is_nil(login_token) do
if is_nil(pass_token) do
# PSEUDOCODE
# - Create empty map
# - Attempt to acquire username from conn's body parameters
# - Attempt to place non-nil username in key added to empty map
# - Attempt to acquire password from conn's body parameters
# - Attempt to place non-nil password in key added to empty map
# - If these keys exist in map or their values are not nil, call password authentication function
# -- When password authentication succeeds
# --- Place non-nil value in session key pass_token
# --- When 2fa step is necessary (can be left to redirect route)
# ---- Redirect to 2fa route and associated controller
# --- Otherwise when 2fa step is unnecessary (can be left to redirect route)
# ---- Place non-nil value in session key login_token
# ---- Redirect to this controller's route
# -- When password authentication fails, redirect to this controller's router
user = Auth.get_credentials(conn, ["username", "password"])
result = if is_nil(user), do: nil, else: Auth.password_bouncer(conn, user["username"], user["password"])
if is_nil(result) do
button_choice = Map.put(button_name_options, :choice, 0)
render(conn, :login, layout: false, config: elem(@site_config, 1), pages: @page_links, form_fields: form_fields, button_choice: button_choice, attempted: false)
else
authenticated = elem(result, 1)
if authenticated do
conn = elem(result, 0)
redirect(conn, to: ~p"/login") # -> should result in either logout or two-factor page
else
button_choice = Map.put(button_name_options, :choice, 0)
render(conn, :login, layout: false, config: elem(@site_config, 1), pages: @page_links, form_fields: form_fields, button_choice: button_choice, attempted: true)
end
end
else
if is_nil(twofa_token) do
# PSEUDOCODE
# - When 2fa step is necessary (unless left to 2fa route)
# -- Redirect to 2fa route and associated controller
# - Otherwise when 2fa step is unnecessary (unless left to 2fa route)
# -- Place non-nil value in session key login_token
# -- Redirect to this controller's router
user_id = Enum.at(String.split(pass_token, ":", parts: 2), 0)
twofact_required = Auth.is_authflow_required?(user_id, :any?)
# IO.inspect(twofact_required)
if twofact_required do
redirect(conn, to: ~p"/second_factor")
else
conn = Auth.generate_user_session(conn, user_id, true, "login_token")
conn = delete_session(conn, "pass_token")
# redirect(conn, to: ~p"/login") # -> should result in logout page
login_token = get_session(conn, "login_token")
StandardQueries.absorb_login_token(login_token)
button_choice = Map.put(button_name_options, :choice, 1)
render(conn, :logout, layout: false, config: elem(@site_config, 1), pages: @page_links, form_fields: form_fields, button_choice: button_choice)
end
else
# PSEUDOCODE
# - Remove non-nil value in session key pass_token
# - Place non-nil value in session key login_token
# - Redirect to this controller's router
conn = delete_session(conn, "2fa_token")
conn = delete_session(conn, "pass_token")
user_id = Enum.at(String.split(pass_token, ":", parts: 2), 0)
conn = Auth.generate_user_session(conn, user_id, true, "login_token")
# redirect(conn, to: ~p"/login") # -> should result in logout page
StandardQueries.absorb_login_token(login_token)
button_choice = Map.put(button_name_options, :choice, 1)
render(conn, :logout, layout: false, config: elem(@site_config, 1), pages: @page_links, form_fields: form_fields, button_choice: button_choice)
end
end
else
conn = if is_nil(twofa_token), do: conn, else: delete_session(conn, "2fa_token")
conn = if is_nil(twofa_token), do: conn, else: delete_session(conn, "pass_token")
StandardQueries.absorb_login_token(login_token)
button_choice = Map.put(button_name_options, :choice, 1)
render(conn, :logout, layout: false, config: elem(@site_config, 1), pages: @page_links, form_fields: form_fields, button_choice: button_choice)
end
end
end