diff --git a/lib/kmxgit/user_manager.ex b/lib/kmxgit/user_manager.ex
index ccc2c2e..cef4b22 100644
--- a/lib/kmxgit/user_manager.ex
+++ b/lib/kmxgit/user_manager.ex
@@ -284,6 +284,16 @@ defmodule Kmxgit.UserManager do
|> Repo.update()
end
+ def verify_user_totp(user = %User{}, token) do
+ User.totp_verify(user, token)
+ end
+
+ def delete_user_totp(user = %User{}) do
+ user
+ |> User.totp_changeset(:delete)
+ |> Repo.update()
+ end
+
def admin_user_present? do
if Repo.one(from user in User,
where: [is_admin: true],
diff --git a/lib/kmxgit/user_manager/user.ex b/lib/kmxgit/user_manager/user.ex
index 081b597..bf99abb 100644
--- a/lib/kmxgit/user_manager/user.ex
+++ b/lib/kmxgit/user_manager/user.ex
@@ -215,10 +215,6 @@ defmodule Kmxgit.UserManager.User do
user.name || login(user)
end
- def login(user) do
- user.slug.slug || raise ArgumentError, "no slug for user"
- end
-
def ssh_keys_with_env(user) do
(user.ssh_keys || "")
|> String.split("\n")
@@ -241,12 +237,12 @@ defmodule Kmxgit.UserManager.User do
def login(user) do
if user do
if user.slug do
- user.slug.slug
+ user.slug.slug || raise ArgumentError, "no slug for user !"
else
- nil
+ raise ArgumentError, "no slug for user"
end
else
- nil
+ raise ArgumentError, "no slug for nil user"
end
end
@@ -254,6 +250,10 @@ defmodule Kmxgit.UserManager.User do
:pot.valid_totp(token, secret, [window: 1, addwindow: 1])
end
+ def totp_changeset(user, :delete) do
+ user
+ |> cast(%{otp_last: 0}, [:otp_last])
+ end
def totp_changeset(user, params) do
user
|> cast(params, [:otp_last])
diff --git a/lib/kmxgit_web/controllers/user_controller.ex b/lib/kmxgit_web/controllers/user_controller.ex
index 800c841..2d29c97 100644
--- a/lib/kmxgit_web/controllers/user_controller.ex
+++ b/lib/kmxgit_web/controllers/user_controller.ex
@@ -79,10 +79,11 @@ 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, "")
totp_enrolment_qrcode_src = totp_enrolment_qrcode_src(user)
conn
|> assign(:changeset, changeset)
- |> assign(:page_title, gettext("Activate TOTP for user %{login}", login: User.login(user)))
+ |> assign(:page_title, gettext("Enrol TOTP for user %{login}", login: User.login(user)))
|> assign(:totp_enrolment_qrcode_src, totp_enrolment_qrcode_src)
|> assign(:user, user)
|> render("totp.html")
@@ -99,13 +100,13 @@ defmodule KmxgitWeb.UserController do
case UserManager.update_user_totp(user, params["user"]) do
{:ok, user} ->
conn
- |> put_flash(:info, "2FA (TOTP) was successfuly activated")
+ |> put_flash(:info, "Enroled 2FA (TOTP) successfuly.")
|> redirect(to: Routes.slug_path(conn, :show, User.login(user)))
{:error, changeset} ->
totp_enrolment_qrcode_src = totp_enrolment_qrcode_src(user)
conn
|> assign(:changeset, changeset)
- |> assign(:page_title, gettext("Activate TOTP for user %{login}", login: User.login(user)))
+ |> assign(:page_title, gettext("Enrol TOTP for user %{login}", login: User.login(user)))
|> assign(:totp_enrolment_qrcode_src, totp_enrolment_qrcode_src)
|> assign(:user, user)
|> render("totp.html")
@@ -115,6 +116,26 @@ defmodule KmxgitWeb.UserController do
end
end
+ def totp_delete(conn, params) do
+ current_user = conn.assigns.current_user
+ if params["login"] == User.login(current_user) do
+ user = current_user
+ case UserManager.delete_user_totp(user) do
+ {:ok, user} ->
+ conn
+ |> put_flash(:info, "Removed 2FA (TOTP) successfuly.")
+ |> redirect(to: Routes.slug_path(conn, :show, User.login(user)))
+ {:error, changeset} ->
+ IO.inspect(changeset)
+ conn
+ |> put_flash(:error, "Failed to remove 2FA (TOTP).")
+ |> redirect(to: Routes.user_path(conn, :edit, User.login(user)))
+ end
+ else
+ not_found(conn)
+ end
+ end
+
def delete(conn, params) do
current_user = conn.assigns.current_user
if params["login"] == current_user.slug.slug do
diff --git a/lib/kmxgit_web/controllers/user_session_controller.ex b/lib/kmxgit_web/controllers/user_session_controller.ex
index e46f9ef..6cf2b9c 100644
--- a/lib/kmxgit_web/controllers/user_session_controller.ex
+++ b/lib/kmxgit_web/controllers/user_session_controller.ex
@@ -9,13 +9,20 @@ defmodule KmxgitWeb.UserSessionController do
end
def create(conn, %{"user" => user_params}) do
- %{"login" => login, "password" => password} = user_params
-
+ %{"login" => login, "password" => password, "otp" => otp} = user_params
if user = UserManager.get_user_by_login_and_password(login, password) do
- UserAuth.log_in_user(conn, user, user_params)
+ if user.otp_last == 0 || UserManager.verify_user_totp(user, otp) do
+ UserAuth.log_in_user(conn, user, user_params)
+ else
+ conn
+ |> assign(:error_message, "Invalid token")
+ |> render("new.html")
+ end
else
# In order to prevent user enumeration attacks, don't disclose whether the email is registered.
- render(conn, "new.html", error_message: "Invalid email or password")
+ conn
+ |> assign(:error_message, "Invalid email or password")
+ |> render("new.html")
end
end
def create(conn, _params) do
diff --git a/lib/kmxgit_web/router.ex b/lib/kmxgit_web/router.ex
index 53782ec..4f347b9 100644
--- a/lib/kmxgit_web/router.ex
+++ b/lib/kmxgit_web/router.ex
@@ -81,6 +81,7 @@ defmodule KmxgitWeb.Router do
put "/user/:login", UserController, :update
get "/user/:login/totp", UserController, :totp
put "/user/:login/totp", UserController, :totp_update
+ delete "/user/:login/totp", UserController, :totp_delete
get "/repository/:owner/*slug", RepositoryController, :edit
put "/repository/:owner/*slug", RepositoryController, :update
end
diff --git a/lib/kmxgit_web/templates/user/edit.html.heex b/lib/kmxgit_web/templates/user/edit.html.heex
index 3af7fe8..7928466 100644
--- a/lib/kmxgit_web/templates/user/edit.html.heex
+++ b/lib/kmxgit_web/templates/user/edit.html.heex
@@ -120,8 +120,22 @@
<h2><%= gettext "Two factor authentification (2FA)" %></h2>
<%= if @user.otp_last != 0 do %>
- <%= gettext "2FA enabled (TOTP)" %>
+ <p>
+ <%= gettext "2FA enabled (TOTP)" %>
+ </p>
+ <p>
+ <%= link gettext("Disable TOTP (Google Authenticator)"), to: Routes.user_path(@conn, :totp_delete, User.login(@user)), method: :delete, data: [confirm: gettext("Are you sure you want to disable TOTP (Google Authenticator) for %{site} ?", site: "kmxgit")], class: "btn btn-danger" %>
+ </p>
<% else %>
- <%= link gettext("Enable TOTP (Google Authenticator)"), to: Routes.user_path(@conn, :totp, User.login(@user)), class: "btn btn-danger" %>
+ <p>
+ <%= link gettext("Enable TOTP (Google Authenticator)"), to: Routes.user_path(@conn, :totp, User.login(@user)), class: "btn btn-danger" %>
+ </p>
<% end %>
+
+ <br />
+
+ <hr/>
+
+ <br/>
+ <br/>
</div>
diff --git a/lib/kmxgit_web/templates/user_session/new.html.heex b/lib/kmxgit_web/templates/user_session/new.html.heex
index c49ae2a..aaa1edb 100644
--- a/lib/kmxgit_web/templates/user_session/new.html.heex
+++ b/lib/kmxgit_web/templates/user_session/new.html.heex
@@ -20,6 +20,12 @@
<%= 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" %>