diff --git a/lib/clacks/actor.ex b/lib/clacks/actor.ex
index 0402431..f2a0b32 100644
--- a/lib/clacks/actor.ex
+++ b/lib/clacks/actor.ex
@@ -29,7 +29,7 @@ defmodule Clacks.Actor do
@spec get_by_nickname(nickname :: String.t()) :: t() | nil
def get_by_nickname(nickname) do
- Repo.one(from a in __MODULE__, where: a.nickname == ^nickname)
+ Repo.get_by(__MODULE__, nickname: nickname)
end
@spec get_by_ap_id(ap_id :: String.t(), force_refetch :: boolean()) :: t() | nil
@@ -43,7 +43,7 @@ defmodule Clacks.Actor do
@spec get_cached_by_ap_id(ap_id :: String.t()) :: t() | nil
def get_cached_by_ap_id(ap_id) do
- Repo.one(from a in __MODULE__, where: a.ap_id == ^ap_id)
+ Repo.get_by(__MODULE__, ap_id: ap_id)
end
@spec fetch(ap_id :: String.t()) :: t() | nil
diff --git a/lib/clacks/user.ex b/lib/clacks/user.ex
index 576286f..73b75c7 100644
--- a/lib/clacks/user.ex
+++ b/lib/clacks/user.ex
@@ -1,6 +1,7 @@
defmodule Clacks.User do
use Ecto.Schema
import Ecto.Changeset
+ alias Clacks.Repo
@type t() :: %__MODULE__{}
@@ -40,4 +41,8 @@ defmodule Clacks.User do
) do
change(changeset, Bcrypt.add_hash(password))
end
+
+ def get_by_username(username) do
+ Repo.get_by(__MODULE__, username: username)
+ end
end
diff --git a/lib/clacks_web/controllers/login_controller.ex b/lib/clacks_web/controllers/login_controller.ex
new file mode 100644
index 0000000..b00a194
--- /dev/null
+++ b/lib/clacks_web/controllers/login_controller.ex
@@ -0,0 +1,42 @@
+defmodule ClacksWeb.LoginController do
+ use ClacksWeb, :controller
+ alias Clacks.User
+ alias ClacksWeb.Router.Helpers, as: Routes
+ alias ClacksWeb.Endpoint
+
+ def login(conn, params) do
+ render(conn, "login.html", %{
+ continue: Map.get(params, "continue")
+ })
+ end
+
+ def login_post(conn, %{"username" => username, "password" => password} = params) do
+ user = User.get_by_username(username)
+
+ case Bcrypt.check_pass(user, password) do
+ {:ok, user} ->
+ user_token = Phoenix.Token.sign(Endpoint, "user token", user.id)
+ redirect_uri = Map.get(params, "continue") || "/"
+
+ conn
+ |> put_session(:user_token, user_token)
+ |> redirect(to: redirect_uri)
+
+ {:error, _reason} ->
+ conn
+ |> put_flash(:error, "Invalid username or password.")
+ |> redirect(to: Routes.login_path(Endpoint, :login))
+ end
+ end
+
+ def login_post(conn, _params) do
+ redirect(conn, to: Routes.login_path(Endpoint, :login))
+ end
+
+ def logout(conn, _params) do
+ conn
+ |> clear_session()
+ |> put_flash(:info, "Logged out.")
+ |> redirect(to: "/")
+ end
+end
diff --git a/lib/clacks_web/plug/web_authenticate.ex b/lib/clacks_web/plug/web_authenticate.ex
new file mode 100644
index 0000000..79e84a3
--- /dev/null
+++ b/lib/clacks_web/plug/web_authenticate.ex
@@ -0,0 +1,41 @@
+defmodule ClacksWeb.Plug.WebAuthenticate do
+ import Plug.Conn
+ alias Clacks.{Repo, User}
+ alias ClacksWeb.Router.Helpers, as: Routes
+ alias ClacksWeb.Endpoint
+
+ def init(%{on_failure: on_failure_action} = opts)
+ when on_failure_action in [:redirect_to_login, :pass],
+ do: opts
+
+ def init(_), do: %{on_failure: :redirect_to_login}
+
+ def call(conn, %{on_failure: on_failure_action}) do
+ user_token = get_session(conn, :user_token)
+
+ case Phoenix.Token.verify(Endpoint, "user token", user_token, max_age: 7 * 24 * 60 * 60) do
+ {:error, _reason} ->
+ on_failure(conn, on_failure_action)
+
+ {:ok, user_id} ->
+ case Repo.get(User, user_id) do
+ nil ->
+ on_failure(conn, on_failure_action)
+
+ user ->
+ user = Repo.preload(user, :actor)
+ assign(conn, :user, user)
+ end
+ end
+ end
+
+ defp on_failure(conn, :redirect_to_login) do
+ conn
+ |> Phoenix.Controller.redirect(to: Routes.login_path(Endpoint, :login))
+ |> halt()
+ end
+
+ defp on_failure(conn, :pass) do
+ conn
+ end
+end
diff --git a/lib/clacks_web/router.ex b/lib/clacks_web/router.ex
index 761ec8d..dd0a8e1 100644
--- a/lib/clacks_web/router.ex
+++ b/lib/clacks_web/router.ex
@@ -7,6 +7,11 @@ defmodule ClacksWeb.Router do
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
+ plug ClacksWeb.Plug.WebAuthenticate, on_failure: :pass
+ end
+
+ pipeline :browser_authenticated do
+ plug ClacksWeb.Plug.WebAuthenticate, on_failure: :redirect_to_login
end
pipeline :activitypub do
@@ -15,6 +20,15 @@ defmodule ClacksWeb.Router do
scope "/", ClacksWeb do
pipe_through :browser
+
+ get "/login", LoginController, :login
+ post "/login", LoginController, :login_post
+ post "/logout", LoginController, :logout_post
+ end
+
+ scope "/", ClacksWeb do
+ pipe_through :browser
+ pipe_through :browser_authenticated
end
scope "/", ClacksWeb do
diff --git a/lib/clacks_web/templates/login/login.html.eex b/lib/clacks_web/templates/login/login.html.eex
new file mode 100644
index 0000000..46db376
--- /dev/null
+++ b/lib/clacks_web/templates/login/login.html.eex
@@ -0,0 +1,13 @@
+<%= form_tag Routes.login_path(@conn, :login_post), method: :post do %>
+ <%= if @continue do %>
+
+ <% end %>
+
+
+
+
+
+
+
+ <%= submit "Log In" %>
+<% end %>
diff --git a/lib/clacks_web/views/login_view.ex b/lib/clacks_web/views/login_view.ex
new file mode 100644
index 0000000..33453a4
--- /dev/null
+++ b/lib/clacks_web/views/login_view.ex
@@ -0,0 +1,3 @@
+defmodule ClacksWeb.PageView do
+ use ClacksWeb, :view
+end
diff --git a/lib/clacks_web/views/page_view.ex b/lib/clacks_web/views/page_view.ex
index 33453a4..9d58594 100644
--- a/lib/clacks_web/views/page_view.ex
+++ b/lib/clacks_web/views/page_view.ex
@@ -1,3 +1,3 @@
-defmodule ClacksWeb.PageView do
+defmodule ClacksWeb.LoginView do
use ClacksWeb, :view
end