diff --git a/lib/kmxgit/user_manager.ex b/lib/kmxgit/user_manager.ex
index cef4b22..c3829a7 100644
--- a/lib/kmxgit/user_manager.ex
+++ b/lib/kmxgit/user_manager.ex
@@ -236,8 +236,8 @@ defmodule Kmxgit.UserManager do
|> Repo.delete()
end
- def change_user(%User{} = user \\ %User{}) do
- User.changeset(user, %{})
+ def change_user(%User{} = user \\ %User{}, params \\ %{}) do
+ User.changeset(user, params)
end
def authenticate_user(login, password) do
@@ -259,10 +259,10 @@ defmodule Kmxgit.UserManager do
end
end
- def otp_init do
+ def totp_init do
Repo.transaction fn ->
Enum.each list_users(), fn u ->
- {:ok, _} = User.otp_changeset(u) |> Repo.update()
+ {:ok, _} = User.totp_changeset(u) |> Repo.update()
end
end
end
@@ -274,7 +274,7 @@ defmodule Kmxgit.UserManager do
## Examples
iex> generate_totp_enrolment_url(user)
"""
- def totp_enrolment_url(%User{email: email, otp_secret: secret}) do
+ def totp_enrolment_url(%User{email: email, totp_secret: secret}) do
"otpauth://totp/kmxgit:#{email}?secret=#{secret}&issuer=kmxgit&algorithm=SHA1&digits=6&period=30"
end
@@ -285,7 +285,7 @@ defmodule Kmxgit.UserManager do
end
def verify_user_totp(user = %User{}, token) do
- User.totp_verify(user, token)
+ User.totp_verify(user, token || 0)
end
def delete_user_totp(user = %User{}) do
diff --git a/lib/kmxgit/user_manager/user.ex b/lib/kmxgit/user_manager/user.ex
index bf99abb..41af245 100644
--- a/lib/kmxgit/user_manager/user.ex
+++ b/lib/kmxgit/user_manager/user.ex
@@ -15,8 +15,8 @@ defmodule Kmxgit.UserManager.User do
field :hashed_password, :string, redact: true
field :is_admin, :boolean, null: false, default: false
field :name, :string
- field :otp_last, :integer, default: 0, redact: true
- field :otp_secret, :string, redact: true
+ field :totp_last, :integer, default: 0, redact: true
+ field :totp_secret, :string, redact: true
has_many :owned_repositories, Repository
field :password, :string, virtual: true, redact: true
field :password_confirmation, :string, virtual: true, redact: true
@@ -47,16 +47,16 @@ defmodule Kmxgit.UserManager.User do
def registration_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:email, :password])
- |> generate_otp_secret()
+ |> generate_totp_secret()
|> validate_email()
|> validate_password(opts)
|> common_changeset()
end
- def otp_changeset(user) do
+ def totp_changeset(user) do
user
|> cast(%{}, [])
- |> generate_otp_secret()
+ |> generate_totp_secret()
|> common_changeset()
end
@@ -177,15 +177,15 @@ defmodule Kmxgit.UserManager.User do
defp common_changeset(changeset) do
changeset
|> cast_assoc(:slug)
- |> validate_required([:deploy_only, :email, :hashed_password, :is_admin, :otp_secret, :slug])
+ |> validate_required([:deploy_only, :email, :hashed_password, :is_admin, :totp_secret, :slug])
|> validate_email()
|> Markdown.validate_markdown(:description)
|> foreign_key_constraint(:owned_repositories, name: :repositories_user_id_fkey)
end
- defp generate_otp_secret(changeset) do
+ defp generate_totp_secret(changeset) do
secret = :crypto.strong_rand_bytes(10) |> Base.encode32()
- put_change(changeset, :otp_secret, secret)
+ put_change(changeset, :totp_secret, secret)
end
def changeset(user, attrs \\ %{}) do
@@ -205,7 +205,7 @@ defmodule Kmxgit.UserManager.User do
def admin_create_user_changeset(user, attrs \\ %{}, opts \\ []) do
user
|> cast(attrs, [:deploy_only, :description, :email, :is_admin, :name, :password, :ssh_keys])
- |> generate_otp_secret()
+ |> generate_totp_secret()
|> validate_email()
|> maybe_validate_password(opts)
|> common_changeset()
@@ -246,27 +246,27 @@ defmodule Kmxgit.UserManager.User do
end
end
- def totp_verify(%__MODULE__{otp_secret: secret}, token) do
+ def totp_verify(%__MODULE__{totp_secret: secret}, token) do
:pot.valid_totp(token, secret, [window: 1, addwindow: 1])
end
def totp_changeset(user, :delete) do
user
- |> cast(%{otp_last: 0}, [:otp_last])
+ |> cast(%{totp_last: 0}, [:totp_last])
end
def totp_changeset(user, params) do
user
- |> cast(params, [:otp_last])
- |> verify_otp_last()
+ |> cast(params, [:totp_last])
+ |> verify_totp_last()
end
- defp verify_otp_last(changeset) do
- otp_last = Integer.to_string get_field(changeset, :otp_last)
+ defp verify_totp_last(changeset) do
+ otp_last = changeset |> get_field(:otp_last) |> Integer.to_string()
if totp_verify(changeset.data, otp_last) do
changeset
else
changeset
- |> add_error(:otp_last, "invalid token")
+ |> add_error(:totp_last, "invalid token")
end
end
end
diff --git a/lib/kmxgit_web/controllers/repository_controller.ex b/lib/kmxgit_web/controllers/repository_controller.ex
index 6704ab0..bacf007 100644
--- a/lib/kmxgit_web/controllers/repository_controller.ex
+++ b/lib/kmxgit_web/controllers/repository_controller.ex
@@ -281,11 +281,12 @@ defmodule KmxgitWeb.RepositoryController do
defp git_put_log1(git, repo, branch, path) do
slug = Repository.full_slug(repo)
- {:ok, log1} = if path do
- GitManager.log1_file(slug, path, branch)
- else
- GitManager.log1(slug, branch)
- end
+ log1 = case if path, do: GitManager.log1_file(slug, path, branch), else: GitManager.log1(slug, branch) do
+ {:ok, log1} -> log1
+ {:error, err} ->
+ IO.inspect(err)
+ nil
+ end
%{git | log1: log1}
end
diff --git a/lib/kmxgit_web/controllers/user_controller.ex b/lib/kmxgit_web/controllers/user_controller.ex
index 2d29c97..b6d36ed 100644
--- a/lib/kmxgit_web/controllers/user_controller.ex
+++ b/lib/kmxgit_web/controllers/user_controller.ex
@@ -79,7 +79,7 @@ defmodule KmxgitWeb.UserController do
if params["login"] == User.login(current_user) do
user = current_user
changeset = UserManager.change_user(user)
- |> Ecto.Changeset.put_change(:otp_last, "")
+ |> Ecto.Changeset.put_change(:totp_last, "")
totp_enrolment_qrcode_src = totp_enrolment_qrcode_src(user)
conn
|> assign(:changeset, changeset)
diff --git a/lib/kmxgit_web/controllers/user_session_controller.ex b/lib/kmxgit_web/controllers/user_session_controller.ex
index 6cf2b9c..7713644 100644
--- a/lib/kmxgit_web/controllers/user_session_controller.ex
+++ b/lib/kmxgit_web/controllers/user_session_controller.ex
@@ -2,21 +2,34 @@ defmodule KmxgitWeb.UserSessionController do
use KmxgitWeb, :controller
alias Kmxgit.UserManager
+ alias Kmxgit.UserManager.User
alias KmxgitWeb.UserAuth
def new(conn, _params) do
render(conn, "new.html", error_message: nil)
end
- def create(conn, %{"user" => user_params}) do
- %{"login" => login, "password" => password, "otp" => otp} = user_params
- if user = UserManager.get_user_by_login_and_password(login, password) do
- if user.otp_last == 0 || UserManager.verify_user_totp(user, otp) do
+ def create(conn, params = %{"user" => user_params}) do
+ user_id = conn |> get_session(:check_totp_for)
+ user = if user_id do
+ {id, ""} = Integer.parse(user_id)
+ UserManager.get_user(id)
+ else
+ %{"login" => login, "password" => password} = user_params
+ UserManager.get_user_by_login_and_password(login, password)
+ end
+ totp = user_params["totp"]
+ if user do
+ if user.totp_last == 0 || totp && UserManager.verify_user_totp(user, totp) do
UserAuth.log_in_user(conn, user, user_params)
else
+ changeset = UserManager.change_user(%User{}, user_params)
conn
+ |> put_session(:check_totp_for, Integer.to_string(user.id))
+ |> assign(:changeset, changeset)
|> assign(:error_message, "Invalid token")
- |> render("new.html")
+ |> assign(:totp, totp)
+ |> render("totp.html")
end
else
# In order to prevent user enumeration attacks, don't disclose whether the email is registered.
diff --git a/lib/kmxgit_web/templates/page/user_agreement.html.heex b/lib/kmxgit_web/templates/page/user_agreement.html.heex
index 7072bcc..e258761 100644
--- a/lib/kmxgit_web/templates/page/user_agreement.html.heex
+++ b/lib/kmxgit_web/templates/page/user_agreement.html.heex
@@ -2,7 +2,9 @@
<h1>User agreement</h1>
- Please upload only legal source code and assets.
+ Please upload only legal source code and assets for which you have the
+ copyright or written permission of the author.
+
<h2>Violations</h2>
diff --git a/lib/kmxgit_web/templates/user/edit.html.heex b/lib/kmxgit_web/templates/user/edit.html.heex
index 7928466..4e6ba30 100644
--- a/lib/kmxgit_web/templates/user/edit.html.heex
+++ b/lib/kmxgit_web/templates/user/edit.html.heex
@@ -119,7 +119,7 @@
<hr/>
<h2><%= gettext "Two factor authentification (2FA)" %></h2>
- <%= if @user.otp_last != 0 do %>
+ <%= if @user.totp_last != 0 do %>
<p>
<%= gettext "2FA enabled (TOTP)" %>
</p>
diff --git a/lib/kmxgit_web/templates/user_session/new.html.heex b/lib/kmxgit_web/templates/user_session/new.html.heex
index aaa1edb..c49ae2a 100644
--- a/lib/kmxgit_web/templates/user_session/new.html.heex
+++ b/lib/kmxgit_web/templates/user_session/new.html.heex
@@ -20,12 +20,6 @@
<%= error_tag f, :password %>
</div>
- <div class="mb-3">
- <%= label f, :otp, gettext("TOTP (Google Authenticator)"), class: "form-label" %>
- <%= number_input f, :otp, class: "form-control" %>
- <%= error_tag f, :otp %>
- </div>
-
<div class="mb-3 form-check">
<%= checkbox f, :remember_me, class: "form-check-input" %>
<%= label f, :remember_me, "Keep me logged in for 60 days", class: "form-check-label" %>
diff --git a/lib/kmxgit_web/templates/user_session/totp.html.heex b/lib/kmxgit_web/templates/user_session/totp.html.heex
new file mode 100644
index 0000000..875ce39
--- /dev/null
+++ b/lib/kmxgit_web/templates/user_session/totp.html.heex
@@ -0,0 +1,28 @@
+<div class="container-fluid">
+ <h1><%= gettext "Log in (2FA)" %></h1>
+
+ <.form let={f} for={@changeset} action={Routes.user_session_path(@conn, :create)} as={:user}>
+ <%= if @totp do %>
+ <div class="alert alert-danger">
+ <p><%= @error_message %></p>
+ </div>
+ <% end %>
+
+ <div class="mb-3">
+ <%= label f, :totp, gettext("TOTP (Google Authenticator)"), class: "form-label" %>
+ <%= number_input f, :totp, class: "form-control" %>
+ <%= error_tag f, :totp %>
+ </div>
+
+ <div class="mb-3 form-check">
+ <%= checkbox f, :remember_me, class: "form-check-input" %>
+ <%= label f, :remember_me, "Keep me logged in for 60 days", class: "form-check-label" %>
+ </div>
+
+ <%= render "recaptcha.html", assigns %>
+
+ <div>
+ <%= submit gettext("Submit"), class: "btn btn-primary" %>
+ </div>
+ </.form>
+</div>
diff --git a/mix.exs b/mix.exs
index 6547332..b193804 100644
--- a/mix.exs
+++ b/mix.exs
@@ -34,11 +34,9 @@ defmodule Kmxgit.MixProject do
defp deps do
[
{:bcrypt_elixir, "~> 2.0"},
- {:dart_sass, "~> 0.2", runtime: Mix.env() == :dev},
{:earmark, "~> 1.4.5"},
{:ecto_sql, "~> 3.6"},
{:elixir_auth_google, "~> 1.6.2"},
- {:esbuild, "~> 0.2", runtime: Mix.env() == :dev},
{:floki, ">= 0.30.0", only: :test},
{:gen_smtp, "~> 1.1"},
{:gettext, "~> 0.18"},
diff --git a/priv/repo/migrations/20220111151254_change_otp_to_totp.exs b/priv/repo/migrations/20220111151254_change_otp_to_totp.exs
new file mode 100644
index 0000000..cdadb44
--- /dev/null
+++ b/priv/repo/migrations/20220111151254_change_otp_to_totp.exs
@@ -0,0 +1,8 @@
+defmodule Kmxgit.Repo.Migrations.ChangeOtpToTotp do
+ use Ecto.Migration
+
+ def change do
+ rename table(:users), :otp_last, to: :totp_last
+ rename table(:users), :otp_secret, to: :totp_secret
+ end
+end