diff --git a/apps/fg_http/lib/fg_http/devices/device.ex b/apps/fg_http/lib/fg_http/devices/device.ex index 178e64445..1e93550ed 100644 --- a/apps/fg_http/lib/fg_http/devices/device.ex +++ b/apps/fg_http/lib/fg_http/devices/device.ex @@ -17,7 +17,7 @@ defmodule FgHttp.Devices.Device do has_many :rules, Rule belongs_to :user, User - timestamps() + timestamps(type: :utc_datetime_usec) end @doc false diff --git a/apps/fg_http/lib/fg_http/email.ex b/apps/fg_http/lib/fg_http/email.ex index b0daba792..c2f93be9d 100644 --- a/apps/fg_http/lib/fg_http/email.ex +++ b/apps/fg_http/lib/fg_http/email.ex @@ -3,13 +3,14 @@ defmodule FgHttp.Email do Handles Email for the app """ - import Bamboo.{Email, Phoenix} + use Bamboo.Phoenix, view: FgHttpWeb.EmailView alias FgHttp.Users.PasswordReset @from "noreply@#{Application.get_env(:fg_http, FgHttpWeb.Endpoint)[:url][:host]}" defp base_email(to) do new_email() + |> put_html_layout({FgHttpWeb.LayoutView, "email.html"}) |> from(@from) |> to(to) end @@ -17,6 +18,7 @@ defmodule FgHttp.Email do def password_reset(%PasswordReset{} = password_reset) do base_email(password_reset.email) |> subject("FireGuard password reset") - |> put_html_layout({FgHttpWeb.LayoutView, "email.html.eex"}) + |> assign(:reset_token, password_reset.reset_token) + |> render(:password_reset) end end diff --git a/apps/fg_http/lib/fg_http/password_resets.ex b/apps/fg_http/lib/fg_http/password_resets.ex index a1c7bc382..eab8dddae 100644 --- a/apps/fg_http/lib/fg_http/password_resets.ex +++ b/apps/fg_http/lib/fg_http/password_resets.ex @@ -17,7 +17,7 @@ defmodule FgHttp.PasswordResets do def get_password_reset!(reset_token: reset_token) do validity_secs = -1 * PasswordReset.token_validity_secs() - now = DateTime.truncate(DateTime.utc_now(), :second) + now = DateTime.utc_now() query = from p in PasswordReset, diff --git a/apps/fg_http/lib/fg_http/rules/rule.ex b/apps/fg_http/lib/fg_http/rules/rule.ex index f5b7fe1c0..6be9a535e 100644 --- a/apps/fg_http/lib/fg_http/rules/rule.ex +++ b/apps/fg_http/lib/fg_http/rules/rule.ex @@ -18,7 +18,7 @@ defmodule FgHttp.Rules.Rule do belongs_to :device, Device - timestamps() + timestamps(type: :utc_datetime_usec) end @doc false diff --git a/apps/fg_http/lib/fg_http/sessions.ex b/apps/fg_http/lib/fg_http/sessions.ex index e5160fa36..0f34da247 100644 --- a/apps/fg_http/lib/fg_http/sessions.ex +++ b/apps/fg_http/lib/fg_http/sessions.ex @@ -37,8 +37,8 @@ defmodule FgHttp.Sessions do {:error, %Ecto.Changeset{}} """ - def create_session(%{email: email} = attrs) do - get_session!(email: email) + def create_session(%Session{} = session, %{} = attrs) do + session |> Session.create_changeset(attrs) |> Repo.update() end diff --git a/apps/fg_http/lib/fg_http/users/password_reset.ex b/apps/fg_http/lib/fg_http/users/password_reset.ex index 1b438ca1a..4444648a3 100644 --- a/apps/fg_http/lib/fg_http/users/password_reset.ex +++ b/apps/fg_http/lib/fg_http/users/password_reset.ex @@ -12,7 +12,7 @@ defmodule FgHttp.Users.PasswordReset do @token_validity_secs 86_400 schema "users" do - field :reset_sent_at, :utc_datetime + field :reset_sent_at, :utc_datetime_usec field :password_hash, :string field :password, :string, virtual: true field :password_confirmation, :string, virtual: true @@ -40,6 +40,8 @@ defmodule FgHttp.Users.PasswordReset do |> generate_reset_token() |> validate_required([:reset_token]) |> unique_constraint(:reset_token) + |> set_reset_sent_at() + |> validate_required([:reset_sent_at]) end @doc false @@ -80,4 +82,11 @@ defmodule FgHttp.Users.PasswordReset do end defp clear_token_fields(changeset), do: changeset + + defp set_reset_sent_at(%Ecto.Changeset{valid?: true} = changeset) do + changeset + |> put_change(:reset_sent_at, DateTime.utc_now()) + end + + defp set_reset_sent_at(changeset), do: changeset end diff --git a/apps/fg_http/lib/fg_http/users/session.ex b/apps/fg_http/lib/fg_http/users/session.ex index 46f405c78..c44ef2021 100644 --- a/apps/fg_http/lib/fg_http/users/session.ex +++ b/apps/fg_http/lib/fg_http/users/session.ex @@ -11,7 +11,7 @@ defmodule FgHttp.Users.Session do schema "users" do field :email, :string field :password, :string, virtual: true - field :last_signed_in_at, :utc_datetime + field :last_signed_in_at, :utc_datetime_usec end def create_changeset(session, attrs \\ %{}) do @@ -23,7 +23,7 @@ defmodule FgHttp.Users.Session do end defp set_last_signed_in_at(%Ecto.Changeset{valid?: true} = changeset) do - last_signed_in_at = DateTime.truncate(DateTime.utc_now(), :second) + last_signed_in_at = DateTime.utc_now() change(changeset, last_signed_in_at: last_signed_in_at) end diff --git a/apps/fg_http/lib/fg_http/users/user.ex b/apps/fg_http/lib/fg_http/users/user.ex index bd1b35108..b8b3dcf25 100644 --- a/apps/fg_http/lib/fg_http/users/user.ex +++ b/apps/fg_http/lib/fg_http/users/user.ex @@ -11,8 +11,8 @@ defmodule FgHttp.Users.User do schema "users" do field :email, :string - field :confirmed_at, :utc_datetime - field :last_signed_in_at, :utc_datetime + field :confirmed_at, :utc_datetime_usec + field :last_signed_in_at, :utc_datetime_usec field :password_hash, :string # VIRTUAL FIELDS @@ -22,7 +22,7 @@ defmodule FgHttp.Users.User do has_many :devices, Device, on_delete: :delete_all - timestamps() + timestamps(type: :utc_datetime_usec) end @doc false diff --git a/apps/fg_http/lib/fg_http_web/controllers/session_controller.ex b/apps/fg_http/lib/fg_http_web/controllers/session_controller.ex index f79e45e7b..5b42e7e65 100644 --- a/apps/fg_http/lib/fg_http_web/controllers/session_controller.ex +++ b/apps/fg_http/lib/fg_http_web/controllers/session_controller.ex @@ -3,7 +3,7 @@ defmodule FgHttpWeb.SessionController do Implements the CRUD for a Session """ - alias FgHttp.Sessions + alias FgHttp.{Sessions, Users.Session} use FgHttpWeb, :controller plug FgHttpWeb.Plugs.RedirectAuthenticated when action in [:new] @@ -15,22 +15,30 @@ defmodule FgHttpWeb.SessionController do end # POST /sessions - def create(conn, %{"session" => session_params}) do - case Sessions.create_session(session_params) do - {:ok, session} -> - conn - |> clear_session() - |> put_session(:user_id, session.id) - |> assign(:session, session) - |> put_flash(:info, "Session created successfully") - |> redirect(to: Routes.device_path(conn, :index)) + def create(conn, %{"session" => %{"email" => email} = session_params}) do + case Sessions.get_session!(email: email) do + %Session{} = session -> + case Sessions.create_session(session, session_params) do + {:ok, session} -> + conn + |> clear_session() + |> put_session(:user_id, session.id) + |> assign(:session, session) + |> put_flash(:info, "Session created successfully") + |> redirect(to: Routes.device_path(conn, :index)) - {:error, changeset} -> + {:error, changeset} -> + conn + |> clear_session() + |> assign(:session, nil) + |> put_flash(:error, "Error creating session.") + |> render("new.html", changeset: changeset) + end + + nil -> conn - |> clear_session() - |> assign(:session, nil) - |> put_flash(:error, "Error creating session.") - |> render("new.html", changeset: changeset) + |> put_flash(:error, "Email not found.") + |> render("new.html") end end diff --git a/apps/fg_http/lib/fg_http_web/plugs/session_loader.ex b/apps/fg_http/lib/fg_http_web/plugs/session_loader.ex index 23e2481ae..0c1a66857 100644 --- a/apps/fg_http/lib/fg_http_web/plugs/session_loader.ex +++ b/apps/fg_http/lib/fg_http_web/plugs/session_loader.ex @@ -32,8 +32,7 @@ defmodule FgHttpWeb.Plugs.SessionLoader do defp unauthed(conn) do conn - |> clear_session() - |> put_flash(:error, "There was an error loading your session. Please sign in again") + |> put_flash(:error, "Please sign in to access that page.") |> redirect(to: Routes.session_path(conn, :new)) |> halt() end diff --git a/apps/fg_http/lib/fg_http_web/router.ex b/apps/fg_http/lib/fg_http_web/router.ex index 2d11994e6..ff9841bb3 100644 --- a/apps/fg_http/lib/fg_http_web/router.ex +++ b/apps/fg_http/lib/fg_http_web/router.ex @@ -25,7 +25,8 @@ defmodule FgHttpWeb.Router do scope "/", FgHttpWeb do pipe_through :browser - resources "/password_resets", PasswordResetController, only: [:edit, :update, :new, :create] + resources "/password_resets", PasswordResetController, only: [:update, :new, :create] + get "/password_resets/:reset_token", PasswordResetController, :edit resources "/user", UserController, singleton: true, only: [:show, :edit, :update, :delete] resources "/users", UserController, only: [:new, :create] @@ -38,7 +39,7 @@ defmodule FgHttpWeb.Router do resources "/sessions", SessionController, only: [:new, :create, :delete] - get "/", DeviceController, :index + get "/", SessionController, :new end # Other scopes may use custom stacks. diff --git a/apps/fg_http/lib/fg_http_web/templates/email/password_reset.html.eex b/apps/fg_http/lib/fg_http_web/templates/email/password_reset.html.eex index dc21995df..cd028a7f6 100644 --- a/apps/fg_http/lib/fg_http_web/templates/email/password_reset.html.eex +++ b/apps/fg_http/lib/fg_http_web/templates/email/password_reset.html.eex @@ -1,3 +1,5 @@ -

- <%= password_reset_url(@password_reset) %> -

+<%= link( + "Click here to reset your password.", + to: Routes.password_reset_url(FgHttpWeb.Endpoint, :edit, @reset_token) + ) +%> diff --git a/apps/fg_http/lib/fg_http_web/templates/email/password_reset.text.eex b/apps/fg_http/lib/fg_http_web/templates/email/password_reset.text.eex new file mode 100644 index 000000000..48eee5c2b --- /dev/null +++ b/apps/fg_http/lib/fg_http_web/templates/email/password_reset.text.eex @@ -0,0 +1,3 @@ +Copy and paste the following URL into your browser to reset your password: + +<%= Routes.password_reset_url(FgHttpWeb.Endpoint, :edit, @reset_token) %> diff --git a/apps/fg_http/lib/fg_http_web/templates/layout/email.html.eex b/apps/fg_http/lib/fg_http_web/templates/layout/email.html.eex index 36eb9b7c1..0fee29ac1 100644 --- a/apps/fg_http/lib/fg_http_web/templates/layout/email.html.eex +++ b/apps/fg_http/lib/fg_http_web/templates/layout/email.html.eex @@ -1,8 +1,8 @@ - "> + "> - <%= render @view_module, @view_template, assigns %> + <%= @inner_content %> diff --git a/apps/fg_http/lib/fg_http_web/templates/session/new.html.eex b/apps/fg_http/lib/fg_http_web/templates/session/new.html.eex index ffa04c532..0dc5d9030 100644 --- a/apps/fg_http/lib/fg_http_web/templates/session/new.html.eex +++ b/apps/fg_http/lib/fg_http_web/templates/session/new.html.eex @@ -20,8 +20,8 @@ <%= submit "Sign in", class: "b ph3 pv2 input-reset ba b--black bg-transparent grow pointer f6 dib" %>
- <%= link("Sign up", to: Routes.user_path(@conn, :new), class: "f6 link dim black") %> - <%= link("Forgot your password?", to: Routes.password_reset_path(@conn, :new), class: "f6 link dim black") %> + <%= link("Sign up", to: Routes.user_path(@conn, :new), class: "f6 link dim black fl") %> + <%= link("Forgot your password?", to: Routes.password_reset_path(@conn, :new), class: "f6 link dim black fr") %>
<% end) %> diff --git a/apps/fg_http/lib/fg_http_web/views/email_view.ex b/apps/fg_http/lib/fg_http_web/views/email_view.ex index f09c1f7f4..b49856b3b 100644 --- a/apps/fg_http/lib/fg_http_web/views/email_view.ex +++ b/apps/fg_http/lib/fg_http_web/views/email_view.ex @@ -1,10 +1,3 @@ defmodule FgHttpWeb.EmailView do use FgHttpWeb, :view - - def password_reset_url(password_reset) do - link( - "Click here to reset your password.", - Routes.password_reset_path(:update, password_reset.reset_token) - ) - end end diff --git a/apps/fg_http/lib/fg_http_web/views/layout_view.ex b/apps/fg_http/lib/fg_http_web/views/layout_view.ex index 27136b0fc..fe4d8cbc6 100644 --- a/apps/fg_http/lib/fg_http_web/views/layout_view.ex +++ b/apps/fg_http/lib/fg_http_web/views/layout_view.ex @@ -4,9 +4,9 @@ defmodule FgHttpWeb.LayoutView do def render_flash(conn) do ~E"""
- <%= if get_flash(conn, :error) do %> -
- <%= get_flash(conn, :error) %> + <%= if get_flash(conn, :info) do %> +
+ <%= get_flash(conn, :info) %>
<% end %> <%= if get_flash(conn, :error) do %> diff --git a/apps/fg_http/priv/repo/migrations/20200225005454_create_users.exs b/apps/fg_http/priv/repo/migrations/20200225005454_create_users.exs index e998a3f5b..128623ff3 100644 --- a/apps/fg_http/priv/repo/migrations/20200225005454_create_users.exs +++ b/apps/fg_http/priv/repo/migrations/20200225005454_create_users.exs @@ -4,13 +4,13 @@ defmodule FgHttp.Repo.Migrations.CreateUsers do def change do create table(:users) do add :email, :string - add :confirmed_at, :utc_datetime + add :confirmed_at, :utc_datetime_usec add :password_hash, :string - add :last_signed_in_at, :utc_datetime + add :last_signed_in_at, :utc_datetime_usec add :reset_token, :string - add :reset_sent_at, :utc_datetime + add :reset_sent_at, :utc_datetime_usec - timestamps() + timestamps(type: :utc_datetime_usec) end create unique_index(:users, [:email]) diff --git a/apps/fg_http/priv/repo/migrations/20200228145810_create_devices.exs b/apps/fg_http/priv/repo/migrations/20200228145810_create_devices.exs index 4dbff63c9..d1c8d6b8e 100644 --- a/apps/fg_http/priv/repo/migrations/20200228145810_create_devices.exs +++ b/apps/fg_http/priv/repo/migrations/20200228145810_create_devices.exs @@ -9,7 +9,7 @@ defmodule FgHttp.Repo.Migrations.CreateDevices do add :last_ip, :inet add :user_id, references(:users, on_delete: :delete_all), null: false - timestamps() + timestamps(type: :utc_datetime_usec) end create index(:devices, [:user_id]) diff --git a/apps/fg_http/priv/repo/migrations/20200228154815_create_rules.exs b/apps/fg_http/priv/repo/migrations/20200228154815_create_rules.exs index eda009fb9..6ecb22209 100644 --- a/apps/fg_http/priv/repo/migrations/20200228154815_create_rules.exs +++ b/apps/fg_http/priv/repo/migrations/20200228154815_create_rules.exs @@ -14,7 +14,7 @@ defmodule FgHttp.Repo.Migrations.CreateRules do add :port, :string add :device_id, references(:devices, on_delete: :delete_all), null: false - timestamps() + timestamps(type: :utc_datetime_usec) end create index(:rules, [:device_id]) diff --git a/apps/fg_http/test/fg_http/password_resets_test.exs b/apps/fg_http/test/fg_http/password_resets_test.exs index 46c3561b0..f911aa43e 100644 --- a/apps/fg_http/test/fg_http/password_resets_test.exs +++ b/apps/fg_http/test/fg_http/password_resets_test.exs @@ -22,7 +22,7 @@ defmodule FgHttp.PasswordResetsTest do PasswordResets.create_password_reset(Fixtures.password_reset(), @valid_attrs) # reset_sent_at should be nil after creation - assert is_nil(password_reset.reset_sent_at) + assert !is_nil(password_reset.reset_sent_at) assert password_reset.reset_token assert password_reset.email == email diff --git a/apps/fg_http/test/fg_http_web/controllers/password_reset_controller_test.exs b/apps/fg_http/test/fg_http_web/controllers/password_reset_controller_test.exs index d5c232e36..051054a94 100644 --- a/apps/fg_http/test/fg_http_web/controllers/password_reset_controller_test.exs +++ b/apps/fg_http/test/fg_http_web/controllers/password_reset_controller_test.exs @@ -36,13 +36,11 @@ defmodule FgHttpWeb.PasswordResetControllerTest do describe "edit password_reset" do setup [:create_password_reset] - test "renders password change form", %{unauthed_conn: conn, password_reset: password_reset} do - params = [{:reset_token, password_reset.reset_token}] - + test "renders form", %{unauthed_conn: conn, password_reset: password_reset} do conn = get( conn, - Routes.password_reset_path(conn, :edit, password_reset.id, params) + Routes.password_reset_path(conn, :edit, password_reset.reset_token) ) assert html_response(conn, 200) =~ "Edit Password" diff --git a/apps/fg_http/test/support/fixtures.ex b/apps/fg_http/test/support/fixtures.ex index 23b100d3c..9bca64f70 100644 --- a/apps/fg_http/test/support/fixtures.ex +++ b/apps/fg_http/test/support/fixtures.ex @@ -30,7 +30,10 @@ defmodule FgHttp.Fixtures do end def session(_attrs \\ %{}) do - {:ok, session} = Sessions.create_session(%{email: user().email, password: "test"}) + email = user().email + record = Sessions.get_session!(email: email) + create_params = %{email: email, password: "test"} + {:ok, session} = Sessions.create_session(record, create_params) session end diff --git a/config/dev.exs b/config/dev.exs index aa586d0c2..190a68bcf 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -18,7 +18,7 @@ config :fg_http, FgHttp.Mailer, adapter: Bamboo.LocalAdapter # watchers to your application. For example, we use it # with webpack to recompile .js and .css sources. config :fg_http, FgHttpWeb.Endpoint, - http: [host: "localhost", port: 4000], + http: [port: 4000], debug_errors: true, code_reloader: true, check_origin: false,