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