This commit is contained in:
byte[] 2019-11-13 00:28:02 -05:00
parent f627677cb6
commit 7784c09b3e
4 changed files with 41 additions and 25 deletions

View file

@ -4,7 +4,8 @@ defmodule Philomena.Users.User do
use Ecto.Schema use Ecto.Schema
use Pow.Ecto.Schema, use Pow.Ecto.Schema,
password_hash_methods: {&Password.hash_pwd_salt/1, &Password.verify_pass/2} password_hash_methods: {&Password.hash_pwd_salt/1, &Password.verify_pass/2},
password_min_length: 6
use Pow.Extension.Ecto.Schema, use Pow.Extension.Ecto.Schema,
extensions: [PowResetPassword, PowPersistentSession] extensions: [PowResetPassword, PowPersistentSession]
@ -128,44 +129,43 @@ defmodule Philomena.Users.User do
}) })
end end
def consume_totp_token_changeset(user, token) do def consume_totp_token_changeset(changeset, params) do
changeset = change(changeset, %{})
user = changeset.data
token = extract_token(params)
cond do cond do
totp_valid?(user, token) -> totp_valid?(user, token) ->
user changeset
|> change(%{consumed_timestep: token}) |> change(%{consumed_timestep: String.to_integer(token)})
backup_code_valid?(user, token) -> backup_code_valid?(user, token) ->
user changeset
|> change(%{otp_backup_codes: remove_backup_code(user, token)}) |> change(%{otp_backup_codes: remove_backup_code(user, token)})
true -> true ->
user changeset
|> add_error(:consumed_timestep, "invalid token") |> add_error(:consumed_timestep, "invalid token")
end end
end end
def totp_changeset(user, params, backup_codes) do def totp_changeset(changeset, params, backup_codes) do
token = changeset = change(changeset, %{})
case params do user = changeset.data
%{"user" => %{"twofactor_token" => t}} -> token = extract_token(params)
to_string(t)
_ ->
""
end
case user.otp_required_for_login do case user.otp_required_for_login do
true -> true ->
# User wants to disable TOTP # User wants to disable TOTP
user changeset
|> pow_current_password_changeset(params) |> pow_password_changeset(params)
|> consume_totp_token_changeset(token) |> consume_totp_token_changeset(token)
|> disable_totp_changeset() |> disable_totp_changeset()
false -> _falsy ->
# User wants to enable TOTP # User wants to enable TOTP
user changeset
|> pow_current_password_changeset(params) |> pow_password_changeset(params)
|> consume_totp_token_changeset(token) |> consume_totp_token_changeset(token)
|> enable_totp_changeset(backup_codes) |> enable_totp_changeset(backup_codes)
end end
@ -226,6 +226,12 @@ defmodule Philomena.Users.User do
}) })
end end
defp extract_token(%{"user" => %{"twofactor_token" => t}}),
do: to_string(t)
defp extract_token(_params),
do: ""
defp totp_valid?(user, token), defp totp_valid?(user, token),
do: :pot.valid_totp(token, totp_secret(user), window: 60) do: :pot.valid_totp(token, totp_secret(user), window: 60)

View file

@ -34,18 +34,20 @@ defmodule PhilomenaWeb.Router do
end end
scope "/", PhilomenaWeb do scope "/", PhilomenaWeb do
pipe_through [:browser, :ensure_totp] pipe_through [:browser, :protected]
# Additional routes for TOTP # Additional routes for TOTP
scope "/registration", Registration, as: :registration do scope "/registration", Registration, as: :registration do
pipe_through :protected
resources "/totp", TotpController, only: [:edit, :update], singleton: true resources "/totp", TotpController, only: [:edit, :update], singleton: true
end end
scope "/session", Session, as: :session do scope "/session", Session, as: :session do
pipe_through :protected
resources "/totp", TotpController, only: [:new, :create], singleton: true resources "/totp", TotpController, only: [:new, :create], singleton: true
end end
end
scope "/", PhilomenaWeb do
pipe_through [:browser, :ensure_totp]
get "/", ActivityController, :index get "/", ActivityController, :index

View file

@ -37,7 +37,7 @@ header.header
span.hide-limited-desktop< Filters span.hide-limited-desktop< Filters
.dropdown.header__dropdown .dropdown.header__dropdown
a.header__link.header__link-user href="/" a.header__link.header__link-user href="/"
/= user_avatar(@current_user, 'avatar--28px'.freeze, @current_user.name) = render PhilomenaWeb.UserAttributionView, "_user_avatar.html", object: %{user: @current_user}, class: "avatar--28px"
span.header__link-user__dropdown-arrow.hide-mobile data-click-preventdefault="true" span.header__link-user__dropdown-arrow.hide-mobile data-click-preventdefault="true"
nav.dropdown__content.dropdown__content-right.hide-mobile.js-burger-links nav.dropdown__content.dropdown__content-right.hide-mobile.js-burger-links
a.header__link href="/profiles" a.header__link href="/profiles"

View file

@ -0,0 +1,8 @@
h1 Two Factor Authentication
= form_for @changeset, Routes.session_totp_path(@conn, :create), [as: :user, method: "post"], fn f ->
.field
h4 Please enter your 2FA code
= text_input f, :twofactor_token, class: "input", placeholder: "6-digit code", required: true, autofocus: true, autocomplete: "off"
= submit "Sign in", class: "button"