diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 13397e7a4..ff93d0ace 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,7 +33,7 @@ jobs: uses: actions/setup-elixir@v1 with: elixir-version: "1.10.3" - otp-version: "23.0.1" + otp-version: "23.0.2" - name: Install Dependencies run: mix deps.get --only test - name: Setup Database diff --git a/.tool-versions b/.tool-versions index f0f2d6a4a..d8606fb6f 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ elixir 1.10.3-otp-23 -erlang 23.0.1 +erlang 23.0.2 nodejs 10.20.1 diff --git a/apps/fg_http/lib/fg_http/rules.ex b/apps/fg_http/lib/fg_http/rules.ex index 0a1b57d23..364cf890f 100644 --- a/apps/fg_http/lib/fg_http/rules.ex +++ b/apps/fg_http/lib/fg_http/rules.ex @@ -8,14 +8,6 @@ defmodule FgHttp.Rules do alias FgHttp.Rules.Rule - def list_rules(device_id) do - Repo.all(from r in Rule, where: r.device_id == ^device_id) - end - - def list_rules do - Repo.all(Rule) - end - def get_rule!(id), do: Repo.get!(Rule, id) def create_rule(attrs \\ %{}) do diff --git a/apps/fg_http/lib/fg_http/users.ex b/apps/fg_http/lib/fg_http/users.ex index 49ddb0b1a..0e6a09b7f 100644 --- a/apps/fg_http/lib/fg_http/users.ex +++ b/apps/fg_http/lib/fg_http/users.ex @@ -8,10 +8,6 @@ defmodule FgHttp.Users do alias FgHttp.Users.User - def list_users do - Repo.all(User) - end - def get_user!(email: email) do Repo.get_by!(User, email: email) end diff --git a/apps/fg_http/lib/fg_http/users/password_reset.ex b/apps/fg_http/lib/fg_http/users/password_reset.ex index 11cc51c78..20c979828 100644 --- a/apps/fg_http/lib/fg_http/users/password_reset.ex +++ b/apps/fg_http/lib/fg_http/users/password_reset.ex @@ -34,6 +34,7 @@ defmodule FgHttp.Users.PasswordReset do password_reset |> cast(attrs, [:email, :reset_sent_at, :reset_token]) |> validate_required([:email]) + |> validate_format(:email, ~r/@/) |> generate_reset_token() |> validate_required([:reset_token]) |> unique_constraint(:reset_token) diff --git a/apps/fg_http/lib/fg_http/users/session.ex b/apps/fg_http/lib/fg_http/users/session.ex index ff701f423..1c79e5807 100644 --- a/apps/fg_http/lib/fg_http/users/session.ex +++ b/apps/fg_http/lib/fg_http/users/session.ex @@ -52,8 +52,4 @@ defmodule FgHttp.Users.Session do add_error(changeset, :password, "invalid: #{error_msg}") end end - - defp authenticate_user(changeset) do - changeset - end end diff --git a/apps/fg_http/lib/fg_http/users/user.ex b/apps/fg_http/lib/fg_http/users/user.ex index e79599eb3..80810289a 100644 --- a/apps/fg_http/lib/fg_http/users/user.ex +++ b/apps/fg_http/lib/fg_http/users/user.ex @@ -85,6 +85,11 @@ defmodule FgHttp.Users.User do user |> cast(attrs, [:email]) |> validate_required([:email]) + |> validate_format(:email, ~r/@/) + end + + def update_changeset(user, %{} = attrs) do + changeset(user, attrs) end def changeset(user, attrs) do diff --git a/apps/fg_http/lib/fg_http/util/fg_crypto.ex b/apps/fg_http/lib/fg_http/util/fg_crypto.ex new file mode 100644 index 000000000..d02b15e97 --- /dev/null +++ b/apps/fg_http/lib/fg_http/util/fg_crypto.ex @@ -0,0 +1,11 @@ +defmodule FgHttp.Util.FgCrypto do + @moduledoc """ + Utilities for working with crypto functions + """ + + def rand_string(length \\ 16) do + :crypto.strong_rand_bytes(length) + |> Base.url_encode64() + |> binary_part(0, length) + end +end diff --git a/apps/fg_http/lib/fg_http_web/views/error_helpers.ex b/apps/fg_http/lib/fg_http_web/error_helpers.ex similarity index 77% rename from apps/fg_http/lib/fg_http_web/views/error_helpers.ex rename to apps/fg_http/lib/fg_http_web/error_helpers.ex index 81eb567a9..2c58540f4 100644 --- a/apps/fg_http/lib/fg_http_web/views/error_helpers.ex +++ b/apps/fg_http/lib/fg_http_web/error_helpers.ex @@ -4,6 +4,19 @@ defmodule FgHttpWeb.ErrorHelpers do """ use Phoenix.HTML + import Ecto.Changeset, only: [traverse_errors: 2] + + def aggregated_errors(changeset) do + traverse_errors(changeset, fn {msg, opts} -> + Enum.reduce(opts, msg, fn {key, value}, acc -> + String.replace(acc, "%{#{key}}", to_string(value)) + end) + end) + |> Enum.reduce("", fn {key, value}, acc -> + joined_errors = Enum.join(value, "; ") + "#{acc}#{key}: #{joined_errors}\n" + end) + end @doc """ Generates tag for inlined form input errors. diff --git a/apps/fg_http/lib/fg_http_web/live/new_device_live.ex b/apps/fg_http/lib/fg_http_web/live/new_device_live.ex index 8a8137b67..dae31b3c0 100644 --- a/apps/fg_http/lib/fg_http_web/live/new_device_live.ex +++ b/apps/fg_http/lib/fg_http_web/live/new_device_live.ex @@ -8,20 +8,22 @@ defmodule FgHttpWeb.NewDeviceLive do alias FgHttp.Devices.Device alias FgHttpWeb.Router.Helpers, as: Routes - def mount(_params, %{"current_user_id" => user_id}, socket) do - if connected?(socket), do: wait_for_device_connect(socket) + # Number of seconds before simulating a device connect + @mocked_timer 3000 - device = %Device{id: "1", user_id: user_id} + def mount(_params, %{"user_id" => user_id}, socket) do + if connected?(socket) do + # Send a mock device connect + :timer.send_after(@mocked_timer, self(), {:pubkey, "foobar"}) + end + + device = %Device{user_id: user_id} {:ok, assign(socket, :device, device)} end # XXX: Receive other device details to create an intelligent name def handle_info({:pubkey, pubkey}, socket) do - device = %Device{public_key: pubkey} + device = %{socket.assigns.device | public_key: pubkey} {:noreply, assign(socket, :device, device)} end - - defp wait_for_device_connect(_socket) do - :timer.send_after(3000, self(), {:pubkey, "foobar"}) - end end diff --git a/apps/fg_http/lib/fg_http_web/templates/device/new.html.eex b/apps/fg_http/lib/fg_http_web/templates/device/new.html.eex index fe3fea83a..02a288ea5 100644 --- a/apps/fg_http/lib/fg_http_web/templates/device/new.html.eex +++ b/apps/fg_http/lib/fg_http_web/templates/device/new.html.eex @@ -6,7 +6,7 @@ <%= aggregated_errors(@changeset) %> <% end %> -<%= live_render(@conn, FgHttpWeb.NewDeviceLive, session: %{"current_user_id" => @session.id}) %> +<%= live_render(@conn, FgHttpWeb.NewDeviceLive, session: %{"user_id" => @session.id}) %>

<%= link "Back", to: Routes.device_path(@conn, :index) %> diff --git a/apps/fg_http/lib/fg_http_web/templates/user/edit.html.eex b/apps/fg_http/lib/fg_http_web/templates/user/edit.html.eex index eb2dd7f2c..7b74c95c9 100644 --- a/apps/fg_http/lib/fg_http_web/templates/user/edit.html.eex +++ b/apps/fg_http/lib/fg_http_web/templates/user/edit.html.eex @@ -5,7 +5,7 @@ <% end %> -

+
Edit Account
<%= label(f, :email, class: "db fw6 lh-copy f6") %> diff --git a/apps/fg_http/lib/fg_http_web/templates/user/new.html.eex b/apps/fg_http/lib/fg_http_web/templates/user/new.html.eex index 866217ad0..e4019a9d3 100644 --- a/apps/fg_http/lib/fg_http_web/templates/user/new.html.eex +++ b/apps/fg_http/lib/fg_http_web/templates/user/new.html.eex @@ -1,27 +1,36 @@

Sign Up

-<%= form_for @changeset, Routes.user_path(@conn, :new), fn f -> %> +<%= form_for @changeset, Routes.user_path(@conn, :create), fn f -> %> <%= if @changeset.action do %>

Oops, something went wrong! Please check the errors below.

<% end %> - - -
- - - -
- - - - <%= submit "Submit" %> +
+ Sign Up +
+ <%= label(f, :email, class: "db fw6 lh-copy f6") %> + <%= text_input(f, :email, class: "pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100") %> + <%= error_tag f, :email %> +
+
+ <%= label(f, :password, class: "db fw6 lh-copy f6") %> + <%= password_input(f, :password, class: "pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100") %> + <%= error_tag f, :password %> +
+
+ <%= label(f, :password_confirmation, class: "db fw6 lh-copy f6") %> + <%= password_input(f, :password_confirmation, class: "pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100") %> + <%= error_tag f, :password_confirmation %> +
+
+ <%= label(f, :current_password, class: "db fw6 lh-copy f6") %> + <%= password_input(f, :current_password, class: "pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100") %> + <%= error_tag f, :current_password %> +
+
+
+ <%= submit "Sign Up", class: "b ph3 pv2 input-reset ba b--black bg-transparent grow pointer f6 dib" %> +
<% end %> diff --git a/apps/fg_http/lib/fg_http_web/views/device_view.ex b/apps/fg_http/lib/fg_http_web/views/device_view.ex index c21ec491f..2963163b4 100644 --- a/apps/fg_http/lib/fg_http_web/views/device_view.ex +++ b/apps/fg_http/lib/fg_http_web/views/device_view.ex @@ -1,16 +1,3 @@ defmodule FgHttpWeb.DeviceView do use FgHttpWeb, :view - import Ecto.Changeset, only: [traverse_errors: 2] - - def aggregated_errors(changeset) do - traverse_errors(changeset, fn {msg, opts} -> - Enum.reduce(opts, msg, fn {key, value}, acc -> - String.replace(acc, "%{#{key}}", to_string(value)) - end) - end) - |> Enum.reduce("", fn {key, value}, acc -> - joined_errors = Enum.join(value, "; ") - "#{acc}#{key}: #{joined_errors}\n" - end) - end end diff --git a/apps/fg_http/mix.exs b/apps/fg_http/mix.exs index 5752d766e..26f933c00 100644 --- a/apps/fg_http/mix.exs +++ b/apps/fg_http/mix.exs @@ -46,6 +46,7 @@ defmodule FgHttp.MixProject do [ {:phoenix, "~> 1.5.1"}, {:excoveralls, "~> 0.13", only: :test}, + {:floki, ">= 0.0.0", only: :test}, {:argon2_elixir, "~> 2.0"}, {:phoenix_pubsub, "~> 2.0"}, {:phoenix_ecto, "~> 4.0"}, diff --git a/apps/fg_http/test/fg_http/password_resets_test.exs b/apps/fg_http/test/fg_http/password_resets_test.exs index f911aa43e..38622a16e 100644 --- a/apps/fg_http/test/fg_http/password_resets_test.exs +++ b/apps/fg_http/test/fg_http/password_resets_test.exs @@ -6,8 +6,8 @@ defmodule FgHttp.PasswordResetsTest do describe "password_resets" do alias FgHttp.Users.PasswordReset - @valid_attrs %{email: "test"} - @invalid_attrs %{email: ""} + @valid_attrs %{email: "test@test"} + @invalid_attrs %{email: "invalid"} test "get_password_reset!/1 returns the password_reset with given token" do token = Fixtures.password_reset(%{reset_sent_at: DateTime.utc_now()}).reset_token diff --git a/apps/fg_http/test/fg_http/util/fg_crypto_test.exs b/apps/fg_http/test/fg_http/util/fg_crypto_test.exs new file mode 100644 index 000000000..eb97c67b2 --- /dev/null +++ b/apps/fg_http/test/fg_http/util/fg_crypto_test.exs @@ -0,0 +1,17 @@ +defmodule FgHttp.Util.FgCryptoTest do + use ExUnit.Case, async: true + + alias FgHttp.Util.FgCrypto + + describe "rand_string" do + test "it returns a string of default length" do + assert 16 == String.length(FgCrypto.rand_string()) + end + + test "it returns a string of proper length" do + for length <- [1, 32, 32_768] do + assert length == String.length(FgCrypto.rand_string(length)) + end + end + end +end diff --git a/apps/fg_http/test/fg_http_web/controllers/device_controller_test.exs b/apps/fg_http/test/fg_http_web/controllers/device_controller_test.exs index 0bc88925d..53f44ebec 100644 --- a/apps/fg_http/test/fg_http_web/controllers/device_controller_test.exs +++ b/apps/fg_http/test/fg_http_web/controllers/device_controller_test.exs @@ -1,8 +1,72 @@ -defmodule FgHttpWeb.DeviceControllerTest do - use FgHttpWeb.ConnCase, async: true - +defmodule FgHttpWeb.DeviceControllerTestHelpers do alias FgHttp.Fixtures + def create_device(_) do + device = Fixtures.device() + {:ok, device: device} + end +end + +defmodule FgHttpWeb.DeviceControllerUnauthedTest do + use FgHttpWeb.ConnCase, async: true + import FgHttpWeb.DeviceControllerTestHelpers + + @create_attrs %{public_key: "foobar"} + @update_attrs %{name: "some updated name"} + + describe "index" do + test "redirects to new session", %{unauthed_conn: conn} do + test_conn = get(conn, Routes.device_path(conn, :index)) + assert redirected_to(test_conn) == Routes.session_path(test_conn, :new) + end + end + + describe "new device" do + test "redirects to new session", %{unauthed_conn: conn} do + test_conn = get(conn, Routes.device_path(conn, :new)) + assert redirected_to(test_conn) == Routes.session_path(test_conn, :new) + end + end + + describe "create device" do + test "redirects to new session", %{unauthed_conn: conn} do + test_conn = post(conn, Routes.device_path(conn, :create), device: @create_attrs) + assert redirected_to(test_conn) == Routes.session_path(test_conn, :new) + end + end + + describe "edit device" do + setup [:create_device] + + test "redirects to new session", %{unauthed_conn: conn, device: device} do + test_conn = get(conn, Routes.device_path(conn, :edit, device)) + assert redirected_to(test_conn) == Routes.session_path(test_conn, :new) + end + end + + describe "update device" do + setup [:create_device] + + test "redirects to new session", %{unauthed_conn: conn, device: device} do + test_conn = put(conn, Routes.device_path(conn, :update, device), device: @update_attrs) + assert redirected_to(test_conn) == Routes.session_path(test_conn, :new) + end + end + + describe "delete device" do + setup [:create_device] + + test "redirects to new session", %{unauthed_conn: conn, device: device} do + test_conn = delete(conn, Routes.device_path(conn, :delete, device)) + assert redirected_to(test_conn) == Routes.session_path(test_conn, :new) + end + end +end + +defmodule FgHttpWeb.DeviceControllerAuthedTest do + use FgHttpWeb.ConnCase, async: true + import FgHttpWeb.DeviceControllerTestHelpers + @create_attrs %{public_key: "foobar"} @update_attrs %{name: "some updated name"} @invalid_attrs %{public_key: nil} @@ -28,8 +92,8 @@ defmodule FgHttpWeb.DeviceControllerTest do end test "renders errors when data is invalid", %{authed_conn: conn} do - conn = post(conn, Routes.device_path(conn, :create), device: @invalid_attrs) - assert html_response(conn, 200) =~ "public_key: can't be blank" + test_conn = post(conn, Routes.device_path(conn, :create), device: @invalid_attrs) + assert html_response(test_conn, 200) =~ "public_key: can't be blank" end end @@ -37,8 +101,8 @@ defmodule FgHttpWeb.DeviceControllerTest do setup [:create_device] test "renders form for editing chosen device", %{authed_conn: conn, device: device} do - conn = get(conn, Routes.device_path(conn, :edit, device)) - assert html_response(conn, 200) =~ "Edit Device" + test_conn = get(conn, Routes.device_path(conn, :edit, device)) + assert html_response(test_conn, 200) =~ "Edit Device" end end @@ -71,9 +135,4 @@ defmodule FgHttpWeb.DeviceControllerTest do end end end - - defp create_device(_) do - device = Fixtures.device() - {:ok, device: device} - end end diff --git a/apps/fg_http/test/fg_http_web/controllers/password_reset_controller_test.exs b/apps/fg_http/test/fg_http_web/controllers/password_reset_controller_test.exs index 051054a94..8e2eef9be 100644 --- a/apps/fg_http/test/fg_http_web/controllers/password_reset_controller_test.exs +++ b/apps/fg_http/test/fg_http_web/controllers/password_reset_controller_test.exs @@ -3,7 +3,7 @@ defmodule FgHttpWeb.PasswordResetControllerTest do alias FgHttp.Fixtures - @valid_create_attrs %{email: "test"} + @valid_create_attrs %{email: "test@test"} @invalid_create_attrs %{email: "doesnt-exist"} describe "new password_reset" do diff --git a/apps/fg_http/test/fg_http_web/controllers/session_controller_test.exs b/apps/fg_http/test/fg_http_web/controllers/session_controller_test.exs index d3fcfa6bc..23ed1eba8 100644 --- a/apps/fg_http/test/fg_http_web/controllers/session_controller_test.exs +++ b/apps/fg_http/test/fg_http_web/controllers/session_controller_test.exs @@ -3,8 +3,8 @@ defmodule FgHttpWeb.SessionControllerTest do alias FgHttp.{Fixtures, Repo, Users.User} - @valid_attrs %{email: "test", password: "test"} - @invalid_attrs %{email: "test", password: "wrong"} + @valid_attrs %{email: "test@test", password: "test"} + @invalid_attrs %{email: "test@test", password: "wrong"} describe "new when a user is already signed in" do test "redirects to authenticated root", %{authed_conn: conn} do @@ -15,11 +15,17 @@ defmodule FgHttpWeb.SessionControllerTest do end describe "new when a user is not signed in" do - test "renders sign in form", %{unauthed_conn: conn} do + test "renders sign in form for new session path", %{unauthed_conn: conn} do test_conn = get(conn, Routes.session_path(conn, :new)) assert html_response(test_conn, 200) =~ "Sign In" end + + test "renders sign in form for root path", %{unauthed_conn: conn} do + test_conn = get(conn, "/") + + assert html_response(test_conn, 200) =~ "Sign In" + end end describe "create when user exists" do diff --git a/apps/fg_http/test/fg_http_web/controllers/user_controller_test.exs b/apps/fg_http/test/fg_http_web/controllers/user_controller_test.exs index b660e5a4f..0d8364c7a 100644 --- a/apps/fg_http/test/fg_http_web/controllers/user_controller_test.exs +++ b/apps/fg_http/test/fg_http_web/controllers/user_controller_test.exs @@ -4,34 +4,42 @@ defmodule FgHttpWeb.UserControllerTest do alias FgHttp.Users.Session @valid_create_attrs %{ - email: "fixure", + email: "valid@test", password: "password", password_confirmation: "password" } @invalid_create_attrs %{ - email: "fixture", + email: "test@test", password: "password", password_confirmation: "wrong_password" } @valid_update_attrs %{ - email: "new-email", + email: "test@test", password: "new_password", password_confirmation: "new_password" } + @valid_email_attrs %{email: "test@test"} + @invalid_email_attrs %{email: "invalid"} + @empty_update_password_attrs %{ + email: "", + password: "", + password_confirmation: "", + current_password: "" + } @valid_update_password_attrs %{ - email: "fixture", + email: "test@test", password: "new_password", password_confirmation: "new_password", current_password: "test" } @invalid_update_password_attrs %{ - email: "fixture", + email: "test@test", password: "new_password", password_confirmation: "new_password", current_password: "wrong current password" } @invalid_update_attrs %{ - email: "new-email", + email: "test@test", password: "new_password", password_confirmation: "wrong_password" } @@ -87,9 +95,25 @@ defmodule FgHttpWeb.UserControllerTest do assert html_response(test_conn, 200) =~ "is invalid: invalid password" end + + test "does nothing when password params are empty", %{authed_conn: conn} do + test_conn = put(conn, Routes.user_path(conn, :update), user: @empty_update_password_attrs) + end end describe "update" do + test "updates email", %{authed_conn: conn} do + test_conn = put(conn, Routes.user_path(conn, :update), user: @valid_email_attrs) + + assert redirected_to(test_conn) == Routes.user_path(test_conn, :show) + end + + test "renders error when email is invalid", %{authed_conn: conn} do + test_conn = put(conn, Routes.user_path(conn, :update), user: @invalid_email_attrs) + + assert html_response(test_conn, 200) =~ "has invalid format" + end + test "updates user when params are valid", %{authed_conn: conn} do test_conn = put(conn, Routes.user_path(conn, :update), user: @valid_update_attrs) diff --git a/apps/fg_http/test/fg_http_web/live/new_device_live_test.exs b/apps/fg_http/test/fg_http_web/live/new_device_live_test.exs new file mode 100644 index 000000000..2db3b6fb8 --- /dev/null +++ b/apps/fg_http/test/fg_http_web/live/new_device_live_test.exs @@ -0,0 +1,19 @@ +defmodule FgHttpWeb.NewDeviceLiveTest do + use FgHttpWeb.ConnCase, async: true + import Phoenix.LiveViewTest + @endpoint FgHttpWeb.Endpoint + + test "disconnected", %{authed_conn: conn} do + conn = get(conn, Routes.device_path(conn, :new)) + + assert html_response(conn, 200) =~ "New Device" + end + + test "mount and handle_info/2", %{authed_conn: conn} do + {:ok, view, html} = live_isolated(conn, FgHttpWeb.NewDeviceLive) + assert html =~ "When we receive a connection from your device, we'll prompt" + assert render(view) =~ "When we receive a connection from your device, we'll prompt" + send(view.pid, {:pubkey, "test pubkey"}) + assert render(view) =~ "test pubkey" + end +end diff --git a/apps/fg_http/test/support/fixtures.ex b/apps/fg_http/test/support/fixtures.ex index 9b564f35b..9d93ff967 100644 --- a/apps/fg_http/test/support/fixtures.ex +++ b/apps/fg_http/test/support/fixtures.ex @@ -5,13 +5,13 @@ defmodule FgHttp.Fixtures do alias FgHttp.{Devices, PasswordResets, Repo, Rules, Sessions, Users, Users.User} def user(attrs \\ %{}) do - case Repo.get_by(User, email: "test") do + case Repo.get_by(User, email: "test@test") do nil -> - attrs = - attrs - |> Enum.into(%{email: "test", password: "test", password_confirmation: "test"}) + {:ok, user} = + %{email: "test@test", password: "test", password_confirmation: "test"} + |> Map.merge(attrs) + |> Users.create_user() - {:ok, user} = Users.create_user(attrs) user %User{} = user -> diff --git a/docs/css/style.css b/docs/css/style.css index 6a530b042..6f4f354ed 100644 --- a/docs/css/style.css +++ b/docs/css/style.css @@ -1,5 +1,80 @@ +/* + * fg-green: 6ece00; + * fg-blue: 006ece; + * fg-purple: 6000ce; + * fg-orange: ce6000; + * fg-yellow: cec700; + * fg-red: ce0006; + */ +a { + text-decoration: none; + color: white; +} +a:visited { + text-decoration: none; + color: white; +} + +img.logo { + position: fixed; + left: 2ch; + width: 5ch; + height: 5ch; +} + +nav { + margin: auto; + padding-top: 0; + height: 6ch; +} + +ul.nav-links { + display: flex; + flex-direction: row; + flex-wrap: wrap; + max-width: 70ch; + list-style-type: none; + margin: 0 auto; + padding: 0; + justify-content: space-between; +} + +ul.nav-links li a { + display: block; + padding: 2ch 0; +} + +body { + background-color: #006ece; + margin: 0; + padding: 0; +} + main { + margin: 0; + padding: 0; + width: 100%; +} + +main section { + background-color: white; +} +div.content { max-width: 70ch; padding: 2ch; margin: auto; } + +footer { + color: white; + margin: auto; + padding-top: 0; + height: 6ch; + height: +} + +footer div { + margin: 0 auto; + padding: 2ch 0; + max-width: 70ch; +} diff --git a/docs/img/logo.svg b/docs/img/logo.svg new file mode 100644 index 000000000..23551001d --- /dev/null +++ b/docs/img/logo.svg @@ -0,0 +1,68 @@ + +image/svg+xml diff --git a/docs/index.html b/docs/index.html index e62008b67..5a8c66122 100644 --- a/docs/index.html +++ b/docs/index.html @@ -7,36 +7,46 @@
- -
+
+ + +
-

FireGuard

+
+

FireGuard

- -

Introduction

+ +

Introduction

- -

Installation

+ +

Installation

- -

Usage

+ +

Usage

- -

Contributing

+ +

Contributing

+
diff --git a/mix.lock b/mix.lock index a8a3875b9..95b1ef09d 100644 --- a/mix.lock +++ b/mix.lock @@ -10,7 +10,7 @@ "credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"}, "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"}, "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, - "ecto": {:hex, :ecto, "3.4.4", "a2c881e80dc756d648197ae0d936216c0308370332c5e77a2325a10293eef845", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4bd3ad62abc3b21fb629f0f7a3dab23a192fca837d257dd08449fba7373561"}, + "ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"}, "ecto_network": {:hex, :ecto_network, "1.3.0", "1e77fa37c20e0f6a426d3862732f3317b0fa4c18f123d325f81752a491d7304e", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 0.0.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.14.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "053a5e46ef2837e8ea5ea97c82fa0f5494699209eddd764e663c85f11b2865bd"}, "ecto_sql": {:hex, :ecto_sql, "3.4.4", "d28bac2d420f708993baed522054870086fd45016a9d09bb2cd521b9c48d32ea", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "edb49af715dd72f213b66adfd0f668a43c17ed510b5d9ac7528569b23af57fe8"}, @@ -18,8 +18,10 @@ "ex_machina": {:hex, :ex_machina, "2.4.0", "09a34c5d371bfb5f78399029194a8ff67aff340ebe8ba19040181af35315eabb", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "a20bc9ddc721b33ea913b93666c5d0bdca5cbad7a67540784ae277228832d72c"}, "excoveralls": {:hex, :excoveralls, "0.13.0", "4e1b7cc4e0351d8d16e9be21b0345a7e165798ee5319c7800b9138ce17e0b38e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "fe2a56c8909564e2e6764765878d7d5e141f2af3bc8ff3b018a68ee2a218fced"}, "file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"}, + "floki": {:hex, :floki, "0.26.0", "4df88977e2e357c6720e1b650f613444bfb48c5acfc6a0c646ab007d08ad13bf", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e7b66ce7feef5518a9cd9fc7b52dd62a64028bd9cb6d6ad282a0f0fc90a4ae52"}, "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"}, "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, + "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"}, "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, @@ -29,11 +31,11 @@ "phoenix": {:hex, :phoenix, "1.5.3", "bfe0404e48ea03dfe17f141eff34e1e058a23f15f109885bbdcf62be303b49ff", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8e16febeb9640d8b33895a691a56481464b82836d338bb3a23125cd7b6157c25"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"}, - "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.2", "38d94c30df5e2ef11000697a4fbe2b38d0fbf79239d492ff1be87bbc33bc3a84", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "a3dec3d28ddb5476c96a7c8a38ea8437923408bc88da43e5c45d97037b396280"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.4", "940c0344b1d66a2e46eef02af3a70e0c5bb45a4db0bf47917add271b76cd3914", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "38f9308357dea4cc77f247e216da99fcb0224e05ada1469167520bed4cb8cccd"}, "phoenix_live_view": {:hex, :phoenix_live_view, "0.12.1", "42f591c781edbf9fab921319076b7ac635d43aa23e6748d2644563326236d7e4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.4.16 or ~> 1.5.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "585321e98df1cd5943e370b9784e950a37ca073744eb534660c9048967c52ab6"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, - "plug": {:hex, :plug, "1.10.2", "0079345cfdf9e17da3858b83eb46bc54beb91554c587b96438f55c1477af5a86", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7898d0eb4767efb3b925fd7f9d1870d15e66e9c33b89c58d8d2ad89aa75ab3c1"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.2.2", "7a09aa5d10e79b92d332a288f21cc49406b1b994cbda0fde76160e7f4cc890ea", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e82364b29311dbad3753d588febd7e5ef05062cd6697d8c231e0e007adab3727"}, + "plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.3.0", "149a50e05cb73c12aad6506a371cd75750c0b19a32f81866e1a323dda9e0e99d", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bc595a1870cef13f9c1e03df56d96804db7f702175e4ccacdb8fc75c02a7b97e"}, "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, "postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},