Skip to content

Commit

Permalink
add vendor_session_controller
Browse files Browse the repository at this point in the history
  • Loading branch information
ksevelyar committed Aug 21, 2023
1 parent 895c4cc commit f966d4d
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 100 deletions.
4 changes: 0 additions & 4 deletions lib/market/accounts/vendor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ defmodule Market.Accounts.Vendor do
changeset
|> validate_required([:password])
|> validate_length(:password, min: 12, max: 72)
# Examples of additional password validation:
# |> validate_format(:password, ~r/[a-z]/, message: "at least one lower case character")
# |> validate_format(:password, ~r/[A-Z]/, message: "at least one upper case character")
# |> validate_format(:password, ~r/[!?@#$%^&*_0-9]/, message: "at least one digit or punctuation character")
|> maybe_hash_password(opts)
end

Expand Down
28 changes: 28 additions & 0 deletions lib/market_web/controllers/vendor_session_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule MarketWeb.VendorSessionController do
use MarketWeb, :controller

alias Market.Accounts
alias MarketWeb.VendorAuth

action_fallback MarketWeb.FallbackController

def create(conn, %{"vendor" => vendor_params}) do
%{"email" => email, "password" => password} = vendor_params

if vendor = Accounts.get_vendor_by_email_and_password(email, password) do
conn
|> VendorAuth.log_in_vendor(vendor, vendor_params)
|> put_status(:created)
|> render(:show, vendor: vendor)
else
# In order to prevent user enumeration attacks, don't disclose whether the email is registered.
render(conn, :error, error_message: "Invalid email or password")
end
end

def delete(conn, _params) do
conn
|> VendorAuth.log_out_vendor()
|> send_resp(:no_content, "")
end
end
9 changes: 9 additions & 0 deletions lib/market_web/controllers/vendor_session_json.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule MarketWeb.VendorSessionJSON do
def show(%{vendor: vendor}) do
%{id: vendor.id}
end

def error(_) do
%{errors: "🐗"}
end
end
35 changes: 3 additions & 32 deletions lib/market_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ defmodule MarketWeb.Router do
pipe_through :api

post "/vendors/register", VendorRegistrationController, :create

post "/vendors/log_in", VendorSessionController, :create
delete "/vendors/log_out", VendorSessionController, :delete
end

# Enable Swoosh mailbox preview in development
Expand All @@ -19,36 +22,4 @@ defmodule MarketWeb.Router do
forward "/mailbox", Plug.Swoosh.MailboxPreview
end
end

## Authentication routes
# scope "/", MarketWeb do
# pipe_through [:browser, :redirect_if_vendor_is_authenticated]
#
# get "/vendors/register", VendorRegistrationController, :new
# post "/vendors/register", VendorRegistrationController, :create
# get "/vendors/log_in", VendorSessionController, :new
# post "/vendors/log_in", VendorSessionController, :create
# get "/vendors/reset_password", VendorResetPasswordController, :new
# post "/vendors/reset_password", VendorResetPasswordController, :create
# get "/vendors/reset_password/:token", VendorResetPasswordController, :edit
# put "/vendors/reset_password/:token", VendorResetPasswordController, :update
# end
#
# scope "/", MarketWeb do
# pipe_through [:browser, :require_authenticated_vendor]
#
# get "/vendors/settings", VendorSettingsController, :edit
# put "/vendors/settings", VendorSettingsController, :update
# get "/vendors/settings/confirm_email/:token", VendorSettingsController, :confirm_email
# end
#
# scope "/", MarketWeb do
# pipe_through [:browser]
#
# delete "/vendors/log_out", VendorSessionController, :delete
# get "/vendors/confirm", VendorConfirmationController, :new
# post "/vendors/confirm", VendorConfirmationController, :create
# get "/vendors/confirm/:token", VendorConfirmationController, :edit
# post "/vendors/confirm/:token", VendorConfirmationController, :update
# end
end
65 changes: 1 addition & 64 deletions lib/market_web/vendor_auth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ defmodule MarketWeb.VendorAuth do
use MarketWeb, :verified_routes

import Plug.Conn
import Phoenix.Controller

alias Market.Accounts

Expand All @@ -13,18 +12,6 @@ defmodule MarketWeb.VendorAuth do
@remember_me_cookie "_market_web_vendor_remember_me"
@remember_me_options [sign: true, max_age: @max_age, same_site: "Lax"]

@doc """
Logs the vendor in.
It renews the session ID and clears the whole session
to avoid fixation attacks. See the renew_session
function to customize this behaviour.
It also sets a `:live_socket_id` key in the session,
so LiveView sessions are identified and automatically
disconnected on log out. The line can be safely removed
if you are not using LiveView.
"""
def log_in_vendor(conn, vendor, params \\ %{}) do
token = Accounts.generate_vendor_session_token(vendor)

Expand All @@ -42,40 +29,16 @@ defmodule MarketWeb.VendorAuth do
conn
end

# This function renews the session ID and erases the whole
# session to avoid fixation attacks. If there is any data
# in the session you may want to preserve after log in/log out,
# you must explicitly fetch the session data before clearing
# and then immediately set it after clearing, for example:
#
# defp renew_session(conn) do
# preferred_locale = get_session(conn, :preferred_locale)
#
# conn
# |> configure_session(renew: true)
# |> clear_session()
# |> put_session(:preferred_locale, preferred_locale)
# end
#
defp renew_session(conn) do
conn
|> configure_session(renew: true)
|> clear_session()
end

@doc """
Logs the vendor out.
It clears all session data for safety. See renew_session.
"""
def log_out_vendor(conn) do
vendor_token = get_session(conn, :vendor_token)
vendor_token && Accounts.delete_vendor_session_token(vendor_token)

if live_socket_id = get_session(conn, :live_socket_id) do
MarketWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
end

conn
|> renew_session()
|> delete_resp_cookie(@remember_me_cookie)
Expand Down Expand Up @@ -105,33 +68,7 @@ defmodule MarketWeb.VendorAuth do
end
end

@doc """
Used for routes that require the vendor to be authenticated.
If you want to enforce the vendor email is confirmed before
they use the application at all, here would be a good place.
"""
def require_authenticated_vendor(conn, _opts) do
if conn.assigns[:current_vendor] do
conn
else
conn
|> put_flash(:error, "You must log in to access this page.")
|> maybe_store_return_to()
end
end

defp put_token_in_session(conn, token) do
conn
|> put_session(:vendor_token, token)
|> put_session(:live_socket_id, "vendors_sessions:#{Base.url_encode64(token)}")
end

defp maybe_store_return_to(%{method: "GET"} = conn) do
put_session(conn, :vendor_return_to, current_path(conn))
put_session(conn, :vendor_token, token)
end

defp maybe_store_return_to(conn), do: conn

defp signed_in_path(_conn), do: ~p"/"
end
68 changes: 68 additions & 0 deletions test/market_web/controllers/vendor_session_controller_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
defmodule MarketWeb.VendorSessionControllerTest do
use MarketWeb.ConnCase, async: true

import Market.AccountsFixtures

setup do
%{vendor: vendor_fixture()}
end

describe "POST /vendors/log_in" do
test "logs the vendor in", %{conn: conn, vendor: vendor} do
conn =
conn
|> init_test_session([])
|> post(~p"/vendors/log_in", %{
"vendor" => %{"email" => vendor.email, "password" => valid_vendor_password()}
})

assert get_session(conn, :vendor_token)

# Now do a logged in request and assert on the menu
# conn = get(conn, ~p"/")
# response = html_response(conn, 200)
# assert response =~ vendor.email
# assert response =~ ~p"/vendors/settings"
# assert response =~ ~p"/vendors/log_out"
end

test "logs the vendor in with remember me", %{conn: conn, vendor: vendor} do
conn = init_test_session(conn, [])

conn = post(conn, ~p"/vendors/log_in", %{
"vendor" => %{
"email" => vendor.email,
"password" => valid_vendor_password(),
"remember_me" => "true"
}
})

assert conn.resp_cookies["_market_web_vendor_remember_me"]
end

test "emits error message with invalid credentials", %{conn: conn, vendor: vendor} do
conn =
conn
|> init_test_session([])
|> post(~p"/vendors/log_in", %{
"vendor" => %{"email" => vendor.email, "password" => "invalid_password"}
})

response = json_response(conn, 200)

Check warning on line 51 in test/market_web/controllers/vendor_session_controller_test.exs

View workflow job for this annotation

GitHub Actions / OTP 25 / Elixir 1.14

variable "response" is unused (if the variable is not meant to be used, prefix it with an underscore)
end
end

describe "DELETE /vendors/log_out" do
test "logs the vendor out", %{conn: conn, vendor: vendor} do
conn =
conn |> init_test_session([]) |> log_in_vendor(vendor) |> delete(~p"/vendors/log_out")

refute get_session(conn, :vendor_token)
end

test "succeeds even if the vendor is not logged in", %{conn: conn} do
conn = conn |> init_test_session([]) |> delete(~p"/vendors/log_out")
refute get_session(conn, :vendor_token)
end
end
end
13 changes: 13 additions & 0 deletions test/support/conn_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,17 @@ defmodule MarketWeb.ConnCase do
Market.DataCase.setup_sandbox(tags)
{:ok, conn: Phoenix.ConnTest.build_conn()}
end

def register_and_log_in_vendor(%{conn: conn}) do
vendor = Market.AccountsFixtures.vendor_fixture()
%{conn: log_in_vendor(conn, vendor), vendor: vendor}
end

def log_in_vendor(conn, vendor) do
token = Market.Accounts.generate_vendor_session_token(vendor)

conn
|> Phoenix.ConnTest.init_test_session(%{})
|> Plug.Conn.put_session(:vendor_token, token)
end
end

0 comments on commit f966d4d

Please sign in to comment.