diff --git a/config/config.exs b/config/config.exs index de2b591b..c9a7b34c 100644 --- a/config/config.exs +++ b/config/config.exs @@ -20,6 +20,7 @@ config :philomena, :pow, user: Philomena.Users.User, repo: Philomena.Repo, web_module: PhilomenaWeb, + users_context: Philomena.Users, extensions: [PowResetPassword, PowLockout, PowPersistentSession], controller_callbacks: Pow.Extension.Phoenix.ControllerCallbacks, mailer_backend: PhilomenaWeb.PowMailer diff --git a/lib/philomena/slug.ex b/lib/philomena/slug.ex new file mode 100644 index 00000000..57ed96e1 --- /dev/null +++ b/lib/philomena/slug.ex @@ -0,0 +1,15 @@ +defmodule Philomena.Slug do + def slug(string) when is_binary(string) do + string + |> String.replace("-", "-dash-") + |> String.replace("/", "-fwslash-") + |> String.replace("\\", "-bwslash-") + |> String.replace(":", "-colon-") + |> String.replace(".", "-dot-") + |> String.replace("+", "-plus-") + |> URI.encode() + |> String.replace("%20", "+") + end + + def slug(_string), do: "" +end \ No newline at end of file diff --git a/lib/philomena/users.ex b/lib/philomena/users.ex index 3d472820..c2dca60e 100644 --- a/lib/philomena/users.ex +++ b/lib/philomena/users.ex @@ -77,22 +77,6 @@ defmodule Philomena.Users do |> Repo.update() end - @doc """ - Deletes a User. - - ## Examples - - iex> delete_user(user) - {:ok, %User{}} - - iex> delete_user(user) - {:error, %Ecto.Changeset{}} - - """ - def delete_user(%User{} = user) do - Repo.delete(user) - end - @doc """ Returns an `%Ecto.Changeset{}` for tracking user changes. @@ -105,4 +89,16 @@ defmodule Philomena.Users do def change_user(%User{} = user) do User.changeset(user, %{}) end + + @impl Pow.Ecto.Context + def delete(user) do + {:error, User.changeset(user, %{})} + end + + @impl Pow.Ecto.Context + def create(params) do + %User{} + |> User.creation_changeset(params) + |> Repo.insert() + end end diff --git a/lib/philomena/users/user.ex b/lib/philomena/users/user.ex index 279f6057..60fecd01 100644 --- a/lib/philomena/users/user.ex +++ b/lib/philomena/users/user.ex @@ -1,5 +1,6 @@ defmodule Philomena.Users.User do alias Philomena.Users.Password + alias Philomena.Slug use Ecto.Schema @@ -117,6 +118,20 @@ defmodule Philomena.Users.User do |> validate_required([]) end + def creation_changeset(user, attrs) do + user + |> pow_changeset(attrs) + |> pow_extension_changeset(attrs) + |> cast(attrs, [:name]) + |> validate_required([:name]) + |> put_api_key() + |> put_slug() + |> unique_constraint(:name, name: :index_users_on_name) + |> unique_constraint(:slug, name: :index_users_on_slug) + |> unique_constraint(:email, name: :index_users_on_email) + |> unique_constraint(:authentication_token, name: :index_users_on_authentication_token) + end + def filter_changeset(user, filter) do change(user) |> put_change(:current_filter_id, filter.id) @@ -157,21 +172,20 @@ defmodule Philomena.Users.User do def totp_changeset(changeset, params, backup_codes) do changeset = change(changeset, %{}) user = changeset.data - token = extract_token(params) case user.otp_required_for_login do true -> # User wants to disable TOTP changeset |> pow_password_changeset(params) - |> consume_totp_token_changeset(token) + |> consume_totp_token_changeset(params) |> disable_totp_changeset() _falsy -> # User wants to enable TOTP changeset |> pow_password_changeset(params) - |> consume_totp_token_changeset(token) + |> consume_totp_token_changeset(params) |> enable_totp_changeset(backup_codes) end end @@ -227,7 +241,10 @@ defmodule Philomena.Users.User do user |> change(%{ otp_required_for_login: false, - otp_backup_codes: [] + otp_backup_codes: [], + encrypted_otp_secret: nil, + encrypted_otp_secret_iv: nil, + encrypted_otp_secret_salt: nil }) end @@ -237,6 +254,19 @@ defmodule Philomena.Users.User do defp extract_token(_params), do: "" + defp put_api_key(changeset) do + key = :crypto.strong_rand_bytes(15) |> Base.url_encode64() + + change(changeset, authentication_token: key) + end + + defp put_slug(changeset) do + name = get_field(changeset, :name) + + changeset + |> put_change(:slug, Slug.slug(name)) + end + defp totp_valid?(user, token), do: :pot.valid_totp(token, totp_secret(user), window: 1) diff --git a/lib/philomena_web/router.ex b/lib/philomena_web/router.ex index 2f89b022..dd92a863 100644 --- a/lib/philomena_web/router.ex +++ b/lib/philomena_web/router.ex @@ -30,15 +30,10 @@ defmodule PhilomenaWeb.Router do scope "/" do pipe_through [:browser, :ensure_totp] - pow_session_routes() + pow_routes() pow_extension_routes() end - scope "/", Pow.Phoenix, as: "pow" do - pipe_through [:browser, :protected, :ensure_totp] - resources "/registration", RegistrationController, singleton: true, only: [:edit, :update] - end - scope "/", PhilomenaWeb do pipe_through [:browser, :protected] diff --git a/lib/philomena_web/templates/registration/totp/edit.html.slime b/lib/philomena_web/templates/registration/totp/edit.html.slime index 71da5060..1e579a9c 100644 --- a/lib/philomena_web/templates/registration/totp/edit.html.slime +++ b/lib/philomena_web/templates/registration/totp/edit.html.slime @@ -6,6 +6,38 @@ h1 Two Factor Authentication p Oops, something went wrong! Please check the errors below. = if @current_user.otp_required_for_login do + = if !@changeset.action and get_flash(@conn, :totp_backup_codes) do + .dnp-warning + h4 Important - Save The Below Codes + p + ' The backup codes shown in the green box below are necessary to + ' regain access to your account in the event of you losing access + ' to your authenticator app (such as loss, theft, or damage to your + ' phone). It is extremely important that you write them down and + ' store them in a safe, secure place. If you lose access to you + ' authenticator app and do not have one or more of the above codes, + ' we will be unable to help you regain access to your account. + br + .block.block--fixed.block--success.layout--narrow + h2 Two Factor Authentication Enabled + p + ' You've sucessfully enabled two-factor authentication on your + ' account. From now on you'll be asked for the 6 digit code each + ' time you log in. + p + ' In case you lose your device or uninstall the application, you + ' will need one of the following backup codes to access to your + ' account: + ul + = for code <- get_flash(@conn, :totp_backup_codes) do + li = code + br + p + ' Make sure to write these down (preferably on paper) and store them + ' in a safe location, otherwise you may + strong<> permanently lose access + ' to your account. + p ' Two factor authentication is currently strong> enabled diff --git a/lib/philomena_web/views/app_view.ex b/lib/philomena_web/views/app_view.ex index 6aa4a462..7aeb94dc 100644 --- a/lib/philomena_web/views/app_view.ex +++ b/lib/philomena_web/views/app_view.ex @@ -67,7 +67,7 @@ defmodule PhilomenaWeb.AppView do end end - def button_to(text, route, args) do + def button_to(text, route, args \\ []) do method = Keyword.get(args, :method, "get") class = Keyword.get(args, :class, nil) data = Keyword.get(args, :data, [])