diff --git a/.gitignore b/.gitignore
index 992ce9ff778bef3c3bedf5ef5636100a86279809..41b1bd8b624263ad70d1172bbb7f8db979b69525 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,4 +35,6 @@ npm-debug.log
 # Local config env variables
 .env
 
-.idea
+# Intellij generated code
+/.idea/
+volt.iml
diff --git a/config/config.exs b/config/config.exs
index 6a063bb550c9175f9c4f6366924ce6664a9e22ff..19040e055e23bec5c376a02909e3e13f2efbd23c 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -13,6 +13,7 @@ config :volt,
 # Configures the endpoint
 config :volt, VoltWeb.Endpoint,
   url: [host: "localhost"],
+  http: [port: 4000, timeout: 15],
   render_errors: [view: VoltWeb.ErrorView, accepts: ~w(html json), layout: false],
   pubsub_server: Volt.PubSub,
   live_view: [signing_salt: "1oLL9rdz"]
diff --git a/config/test.exs b/config/test.exs
index 61c7f77107fce73a078f692af3ec6ea9e45fa7cb..910fedd6fce32665187b3c4a9bd62ff0b7b93129 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -1,5 +1,8 @@
 import Config
 
+# Only in tests, remove the complexity from the password hashing algorithm
+config :pbkdf2_elixir, :rounds, 1
+
 # Configure your database
 #
 # The MIX_TEST_PARTITION environment variable can be used
diff --git a/env.template b/env.template
index 607d06ed1c3a30712a8aa0ec35b6fd23826f6371..78aafd2478a29450a8d5a3b11f70fca776a50691 100644
--- a/env.template
+++ b/env.template
@@ -5,3 +5,4 @@
 # export POSTGRES_USER_TEST="REPLACE_ME"
 # export POSTGRES_PASSWORD_TEST="REPLACE_ME"
 # export POSTGRES_HOST_TEST="REPLACE_ME"
+# export BING_MAP_KEY="REPLACE_ME"
\ No newline at end of file
diff --git a/features/config.exs b/features/config.exs
index 87d61ea395e82845327122b0087fa19d7a810489..32b4c11476069ef5e55fdbd8c1980f61d77115bd 100644
--- a/features/config.exs
+++ b/features/config.exs
@@ -1,7 +1,15 @@
 defmodule WhiteBreadConfig do
   use WhiteBread.SuiteConfiguration
 
-  suite name:          "All",
-        context:       WhiteBreadContext,
-        feature_paths: ["features/"]
+  suite name:          "Courier",
+        context:       WhiteBreadContext.Courier,
+        feature_paths: ["features/contexts/courier/*.feature"]
+
+  suite name:          "Customer",
+        context:       WhiteBreadContext.Customer,
+        feature_paths: ["features/contexts/customer/*.feature"]
+
+  suite name:          "Restaurant",
+        context:       WhiteBreadContext.Restaurant,
+        feature_paths: ["features/contexts/restaurant/*.feature"]
 end
diff --git a/features/contexts/courier/courier_context.exs b/features/contexts/courier/courier_context.exs
new file mode 100644
index 0000000000000000000000000000000000000000..6fc71e3f5b3e0b9d0f34f3d6ab3736217a0add65
--- /dev/null
+++ b/features/contexts/courier/courier_context.exs
@@ -0,0 +1,87 @@
+defmodule WhiteBreadContext.Courier do
+  use WhiteBread.Context
+  use Hound.Helpers
+  alias Volt.{Repo, Accounts.Restaurant, Accounts.Customer, Accounts.Courier}
+
+  feature_starting_state fn  ->
+    Application.ensure_all_started(:hound)
+    %{}
+  end
+  scenario_starting_state fn _state ->
+    Hound.start_session
+    Ecto.Adapters.SQL.Sandbox.checkout(Volt.Repo)
+    Ecto.Adapters.SQL.Sandbox.mode(Volt.Repo, {:shared, self()})
+
+    %{}
+  end
+  scenario_finalize fn _status, _state ->
+    Hound.end_session
+    Ecto.Adapters.SQL.Sandbox.checkin(Volt.Repo)
+  end
+
+  given_ ~r/^that no user is logged in$/, fn state ->
+    {:ok, state}
+  end
+
+  and_ ~r/^my email is "(?<email>[^"]+)" $/,
+  fn state, %{email: email} ->
+    {:ok, state |> Map.put(:email, email)}
+  end
+
+  and_ ~r/^my password is "(?<password>[^"]+)"$/,
+  fn state, %{password: password} ->
+    {:ok, state |> Map.put(:password, password)}
+  end
+
+  and_ ~r/^my name is "(?<first_name>[^"]+)", "(?<last_name>[^"]+)"$/,
+  fn state, %{first_name: first_name, last_name: last_name} ->
+    {:ok, state |> Map.put(:first_name, first_name) |> Map.put(:last_name, last_name)}
+  end
+
+  and_ ~r/^my phone number is "(?<phone_number>[^"]+)"$/,
+  fn state, %{phone_number: phone_number} ->
+    {:ok, state |> Map.put(:phone_number, phone_number)}
+  end
+
+  and_ ~r/^my status is "(?<courier_status>[^"]+)"$/,
+  fn state, %{courier_status: courier_status} ->
+    {:ok, state |> Map.put(:courier_status, courier_status)}
+  end
+
+  and_ ~r/^I open the welcome page$/, fn state ->
+    navigate_to "/"
+    {:ok, state}
+  end
+
+  and_ ~r/^I click the register as a courier button$/, fn state ->
+    click({:id, "courier_register"})
+    {:ok, state}
+  end
+
+  and_ ~r/^I enter my information$/, fn state ->
+    fill_field({:id, "email"}, state[:email])
+    fill_field({:id, "password"}, state[:password])
+    fill_field({:id, "first_name"}, state[:first_name])
+    fill_field({:id, "last_name"}, state[:last_name])
+    fill_field({:id, "phone_number"}, state[:phone_number])
+    fill_field({:id, "courier_status"}, state[:courier_status])
+    {:ok, state}
+  end
+
+  when_ ~r/^I summit the register button$/, fn state ->
+    click({:id, "submit_button"})
+    {:ok, state}
+  end
+
+  then_ ~r/^I should receive a confirmation message$/, fn state ->
+    assert visible_in_page? ~r/Customer created successfully./
+    {:ok, state}
+  end
+
+  and_ ~r/^the following courier already exist$/, fn state, %{table_data: table} ->
+    table
+    |> Enum.map(fn courier -> Courier.changeset(%Courier{}, courier) end)
+    |> Enum.each(fn changeset -> Repo.insert!(changeset) end)
+    {:ok, state}
+  end
+end
diff --git a/features/courier_registration.feature b/features/contexts/courier/courier_registration.feature
similarity index 100%
rename from features/courier_registration.feature
rename to features/contexts/courier/courier_registration.feature
diff --git a/features/contexts/white_bread_context.exs b/features/contexts/customer/customer_context.exs
similarity index 50%
rename from features/contexts/white_bread_context.exs
rename to features/contexts/customer/customer_context.exs
index e3074f6300c1e3f41d9a322f44efe4e0ece768cc..a4fe26d069858b41a3593e96c1106051747f5e6b 100644
--- a/features/contexts/white_bread_context.exs
+++ b/features/contexts/customer/customer_context.exs
@@ -1,6 +1,7 @@
-defmodule WhiteBreadContext do
+defmodule WhiteBreadContext.Customer do
   use WhiteBread.Context
   use Hound.Helpers
+  import Volt.AccountsFixtures
   alias Volt.{Repo, Accounts.Restaurant, Accounts.Customer, Accounts.Courier}
 
   feature_starting_state fn  ->
@@ -19,37 +20,6 @@ defmodule WhiteBreadContext do
     Ecto.Adapters.SQL.Sandbox.checkin(Volt.Repo)
   end
 
-  ## -- RESTAURANT -- ##
-
-  and_ ~r/^I open restaurant registration page$/, fn state ->
-    navigate_to "/restaurant/new"
-    {:ok, state}
-  end
-
-  and_ ~r/^I enter the restaurant information$/, fn state ->
-    fill_field({:id, "restaurant_email"}, "giorgio_cuisine@restaurant.gov")
-    fill_field({:id, "restaurant_password"}, "qwerty123")
-    fill_field({:id, "restaurant_first_name"}, "Giorgio")
-    fill_field({:id, "restaurant_last_name"}, "Shumaylovi")
-    fill_field({:id, "restaurant_phone_number"}, "+37253584669")
-    fill_field({:id, "restaurant_name"}, "Shit's Bussin")
-    fill_field({:id, "restaurant_address"}, "Joe Mama 69")
-    fill_field({:id, "restaurant_city"}, "Tartu")
-    fill_field({:id, "restaurant_zip_code"}, "51004")
-    fill_field({:id, "restaurant_price_level"}, 1)
-    {:ok, state}
-  end
-
-  when_ ~r/^I submit the form$/, fn state ->
-    submit_element({:id, "submit"})
-    {:ok, state}
-  end
-
-  then_ ~r/^I should be redirected to the restaurant dashboard$/, fn state ->
-    {:ok, state}
-  end
-
-  ## -- CUSTOMER -- ##
   given_ ~r/^that no user is logged in$/, fn state ->
     {:ok, state}
   end
@@ -125,70 +95,19 @@ defmodule WhiteBreadContext do
 
   and_ ~r/^the following customer already exist$/, fn state, %{table_data: table} ->
     table
-    |> Enum.map(fn customer -> Customer.changeset(%Customer{}, customer) end)
-    |> Enum.each(fn changeset -> Repo.insert!(changeset) end)
-    {:ok, state}
-  end
-
-  ## -- COURIER -- ##
-  given_ ~r/^that no user is logged in$/, fn state ->
-    {:ok, state}
-  end
-
-  and_ ~r/^my email is "(?<email>[^"]+)" and password is "(?<password>[^"]+)"$/,
-  fn state, %{email: email, password: password} ->
-    {:ok, state |> Map.put(:email, email) |> Map.put(:password, password)}
-  end
-
-  and_ ~r/^my name is "(?<first_name>[^"]+)", "(?<last_name>[^"]+)"$/,
-  fn state, %{first_name: first_name, last_name: last_name} ->
-    {:ok, state |> Map.put(:first_name, first_name) |> Map.put(:last_name, last_name)}
-  end
-
-  and_ ~r/^my phone number is "(?<phone_number>[^"]+)"$/,
-  fn state, %{phone_number: phone_number} ->
-    {:ok, state |> Map.put(:phone_number, phone_number)}
-  end
-
-  and_ ~r/^my courier_status is "(?<courier_status>[^"]+)"$/,
-  fn state, %{courier_status: courier_status} ->
-    {:ok, state |> Map.put(:courier_status, courier_status)}
-  end
-
-  and_ ~r/^I open the welcome page$/, fn state ->
-    navigate_to "/"
+    |> Enum.map(fn customer -> customer_fixture(customer) end)
     {:ok, state}
   end
 
-  and_ ~r/^I click the register as courier button$/, fn state ->
-    click({:id, "courier_register"})
-    {:ok, state}
-  end
+  # use current location/Map
 
-  and_ ~r/^I enter my information$/, fn state ->
-    fill_field({:id, "email"}, state[:email])
-    fill_field({:id, "password"}, state[:password])
-    fill_field({:id, "first_name"}, state[:first_name])
-    fill_field({:id, "last_name"}, state[:last_name])
-    fill_field({:id, "phone_number"}, state[:phone_number])
-    ill_field({:id, "courier_status"}, state[:courier_status])
-    {:ok, state}
-  end
-
-  when_ ~r/^I summit the register button$/, fn state ->
-    click({:id, "submit_button"})
+  when_ ~r/^I click on `use current location` checkbox$/, fn state ->
+    click({:id, "use_current_location_check"})
     {:ok, state}
   end
 
-  then_ ~r/^I should receive a confirmation message$/, fn state ->
-    assert visible_in_page? ~r/Customer created successfully./
-    {:ok, state}
-  end
-
-  and_ ~r/^the following courier already exist$/, fn state, %{table_data: table} ->
-    table
-    |> Enum.map(fn courier -> Courier.changeset(%Courier{}, courier) end)
-    |> Enum.each(fn changeset -> Repo.insert!(changeset) end)
+  then_ ~r/^Address, city and postal code fields are filled$/, fn state ->
+    assert inner_text({:id, "city"}) !=  nil
     {:ok, state}
   end
 end
diff --git a/features/customer_register.feature b/features/contexts/customer/customer_registration.feature
similarity index 78%
rename from features/customer_register.feature
rename to features/contexts/customer/customer_registration.feature
index 89159500b93d5a23beb274fbbf173dba6034f006..e7e7d344303cf03ad076b71636f21e446f6e14b0 100644
--- a/features/customer_register.feature
+++ b/features/contexts/customer/customer_registration.feature
@@ -1,6 +1,6 @@
-Feature: Customer Register
-    As a Customer 
-    I want to be able to self-register 
+Feature: Customer Registration
+    As a Customer
+    I want to be able to self-register
     so that I can be able to order food from Volt.
 
     Scenario: Customer register (with confirmation)
@@ -16,12 +16,12 @@ Feature: Customer Register
         And I enter my information
         When I summit the register button
         Then I should receive a confirmation message
-    
+
     Scenario: Customer register (rejection with invalid field)
         Given that no user is logged in
-        And my email is "wrong email" and password is "my111good*password"
+        And my email is "email@example.com" and password is "my111good*password"
         And my name is "Romain", "BARRERE"
-        And my phone number is "+33641941064"
+        And my phone number is "zessfzef'"
         And my address is "Raatuse 22", "51009", "Tartu"
         And my birth date is "1999-03-12"
         And my bank card is "wrong card"
@@ -30,7 +30,7 @@ Feature: Customer Register
         And I enter my information
         When I summit the register button
         Then I should receive a rejection message
-    
+
     Scenario: Customer register (rejection with already taken information)
         Given that no user is logged in
         And the following customer already exist
@@ -47,3 +47,10 @@ Feature: Customer Register
         And I enter my information
         When I summit the register button
         Then I should receive a rejection message
+
+    Scenario: Customer register when `use current location` checkbox is checked, then address, city and postal code are filled
+        Given that no user is logged in
+        And I open the welcome page
+        And I click the register as customer button
+        When I click on `use current location` checkbox
+        Then Address, city and postal code fields are filled
diff --git a/features/contexts/restaurant/restaurant_add_item.feature b/features/contexts/restaurant/restaurant_add_item.feature
new file mode 100644
index 0000000000000000000000000000000000000000..6a772082348a5b6a0aca9afa9c443d9c210aeaaa
--- /dev/null
+++ b/features/contexts/restaurant/restaurant_add_item.feature
@@ -0,0 +1,16 @@
+Feature: Restaurant Add Item
+    As a restaurant worker 
+    I want to be able to add items to the menu 
+    so that potential customers can see the items we have for offer.
+
+
+  Scenario: Restaurant add item (with confirmation)
+    Given that I want to add this item: "Pizza", "Cheese", "10.99"
+    And I open restaurant registration page
+    And I enter the restaurant information
+    When I submit the form
+    Then I open the page to add an item
+    And I fill the form
+    When I submit the form
+    Then I should have a confirmation that item has been created
+    
\ No newline at end of file
diff --git a/features/contexts/restaurant/restaurant_context.exs b/features/contexts/restaurant/restaurant_context.exs
new file mode 100644
index 0000000000000000000000000000000000000000..cccc08a9750d590204ff18d914b4436ad453fdf2
--- /dev/null
+++ b/features/contexts/restaurant/restaurant_context.exs
@@ -0,0 +1,78 @@
+defmodule WhiteBreadContext.Restaurant do
+  use WhiteBread.Context
+  use Hound.Helpers
+  import Volt.AccountsFixtures
+  alias Volt.{Repo, Accounts.Restaurant, Accounts.Customer, Accounts.Courier}
+  import VoltWeb.RestaurantAuth
+
+  feature_starting_state fn  ->
+    Application.ensure_all_started(:hound)
+    %{}
+  end
+  scenario_starting_state fn _state ->
+    Hound.start_session
+    Ecto.Adapters.SQL.Sandbox.checkout(Volt.Repo)
+    Ecto.Adapters.SQL.Sandbox.mode(Volt.Repo, {:shared, self()})
+
+    %{}
+  end
+  scenario_finalize fn _status, _state ->
+    Hound.end_session
+    Ecto.Adapters.SQL.Sandbox.checkin(Volt.Repo)
+  end
+
+  and_ ~r/^I open restaurant registration page$/, fn state ->
+    navigate_to "/restaurants/register"
+    {:ok, state}
+  end
+
+  and_ ~r/^I enter the restaurant information$/, fn state ->
+    fill_field({:id, "restaurant_email"}, "giorgio_cuisine@restaurant.gov")
+    fill_field({:id, "restaurant_password"}, "qwerty123uyghyiu")
+    fill_field({:id, "restaurant_first_name"}, "Giorgio")
+    fill_field({:id, "restaurant_last_name"}, "Shumaylovi")
+    fill_field({:id, "restaurant_phone_number"}, "+37253584669")
+    fill_field({:id, "restaurant_name"}, "Shit's Bussin")
+    fill_field({:id, "restaurant_address"}, "Joe Mama 69")
+    fill_field({:id, "restaurant_city"}, "Tartu")
+    fill_field({:id, "restaurant_zip_code"}, "51004")
+    fill_field({:id, "restaurant_price_level"}, 1)
+    {:ok, state}
+  end
+
+  when_ ~r/^I submit the form$/, fn state ->
+    submit_element({:id, "submit"})
+    {:ok, state}
+  end
+
+  then_ ~r/^I should be redirected to the restaurant dashboard$/, fn state ->
+    {:ok, state}
+  end
+
+  given_ ~r/^that a restaurant worker is connected$/, fn state ->
+    log_in_restaurant(state, restaurant_fixture())
+    {:ok, state}
+  end
+
+  given_ ~r/^that I want to add this item: "(?<name>[^"]+)", "(?<description>[^"]+)", "(?<unit_price>[^"]+)"$/,
+  fn state, %{name: name, description: description, unit_price: unit_price} ->
+    {:ok, state |> Map.put(:name, name) |> Map.put(:description, description) |> Map.put(:unit_price, unit_price)}
+  end
+
+  then_ ~r/^I open the page to add an item$/, fn state ->
+    navigate_to "/restaurants/additem"
+    {:ok, state}
+  end
+
+  and_ ~r/^I fill the form$/, fn state ->
+    fill_field({:id, "item_name"}, state[:name])
+    fill_field({:id, "item_description"}, state[:description])
+    fill_field({:id, "item_unit_price"}, state[:unit_price])
+    {:ok, state}
+  end
+
+  then_ ~r/^I should have a confirmation that item has been created$/, fn state ->
+    assert visible_in_page? ~r/Item created successfully./
+    {:ok, state}
+  end
+end
diff --git a/features/restaurant/restaurant_registration.feature b/features/contexts/restaurant/restaurant_registration.feature
similarity index 100%
rename from features/restaurant/restaurant_registration.feature
rename to features/contexts/restaurant/restaurant_registration.feature
diff --git a/lib/volt/accounts.ex b/lib/volt/accounts.ex
index 575ec7337e3c3eb01c189045758663a9850dace3..e789836c71c91b42ad17886871dbbfe4dbb41bc4 100644
--- a/lib/volt/accounts.ex
+++ b/lib/volt/accounts.ex
@@ -21,276 +21,1284 @@ defmodule Volt.Accounts do
     Repo.all(Restaurant)
   end
 
+  alias Volt.Accounts.Courier
+
   @doc """
-  Gets a single restaurant.
+  Returns the list of couriers.
 
-  Raises `Ecto.NoResultsError` if the Restaurant does not exist.
+  ## Examples
+
+      iex> list_couriers()
+      [%Courier{}, ...]
+
+  """
+  def list_couriers do
+    Repo.all(Courier)
+  end
+
+  @doc """
+  Gets a single courier.
+
+  Raises `Ecto.NoResultsError` if the Courier does not exist.
 
   ## Examples
 
-      iex> get_restaurant!(123)
-      %Restaurant{}
+      iex> get_courier!(123)
+      %Courier{}
 
-      iex> get_restaurant!(456)
+      iex> get_courier!(456)
       ** (Ecto.NoResultsError)
 
   """
-  def get_restaurant!(id), do: Repo.get!(Restaurant, id)
+  def get_courier!(id), do: Repo.get!(Courier, id)
+
+  alias Volt.Accounts.Customer
 
   @doc """
-  Creates a restaurant.
+  Returns the list of customers.
 
   ## Examples
 
-      iex> create_restaurant(%{field: value})
-      {:ok, %Restaurant{}}
+      iex> list_customers()
+      [%Customer{}, ...]
 
-      iex> create_restaurant(%{field: bad_value})
-      {:error, %Ecto.Changeset{}}
+  """
+  def list_customers do
+    Repo.all(Customer)
+  end
+
+  alias Volt.Accounts.{Courier, CourierToken, CourierNotifier}
+
+  ## Database getters
+
+  @doc """
+  Gets a courier by email.
+
+  ## Examples
+
+      iex> get_courier_by_email("foo@example.com")
+      %Courier{}
+
+      iex> get_courier_by_email("unknown@example.com")
+      nil
 
   """
-  def create_restaurant(attrs \\ %{}) do
-    %Restaurant{}
-    |> Restaurant.changeset(attrs)
-    |> Repo.insert()
+  def get_courier_by_email(email) when is_binary(email) do
+    Repo.get_by(Courier, email: email)
   end
 
   @doc """
-  Updates a restaurant.
+  Gets a courier by email and password.
 
   ## Examples
 
-      iex> update_restaurant(restaurant, %{field: new_value})
-      {:ok, %Restaurant{}}
+      iex> get_courier_by_email_and_password("foo@example.com", "correct_password")
+      %Courier{}
 
-      iex> update_restaurant(restaurant, %{field: bad_value})
-      {:error, %Ecto.Changeset{}}
+      iex> get_courier_by_email_and_password("foo@example.com", "invalid_password")
+      nil
 
   """
-  def update_restaurant(%Restaurant{} = restaurant, attrs) do
-    restaurant
-    |> Restaurant.changeset(attrs)
-    |> Repo.update()
+  def get_courier_by_email_and_password(email, password)
+      when is_binary(email) and is_binary(password) do
+    courier = Repo.get_by(Courier, email: email)
+    if Courier.valid_password?(courier, password), do: courier
   end
 
+  ## Courier registration
+
   @doc """
-  Deletes a restaurant.
+  Registers a courier.
 
   ## Examples
 
-      iex> delete_restaurant(restaurant)
-      {:ok, %Restaurant{}}
+      iex> register_courier(%{field: value})
+      {:ok, %Courier{}}
 
-      iex> delete_restaurant(restaurant)
+      iex> register_courier(%{field: bad_value})
       {:error, %Ecto.Changeset{}}
 
   """
-  def delete_restaurant(%Restaurant{} = restaurant) do
-    Repo.delete(restaurant)
+  def register_courier(attrs) do
+    %Courier{}
+    |> Courier.registration_changeset(attrs)
+    |> Repo.insert()
   end
 
   @doc """
-  Returns an `%Ecto.Changeset{}` for tracking restaurant changes.
+  Returns an `%Ecto.Changeset{}` for tracking courier changes.
 
   ## Examples
 
-      iex> change_restaurant(restaurant)
-      %Ecto.Changeset{data: %Restaurant{}}
+      iex> change_courier_registration(courier)
+      %Ecto.Changeset{data: %Courier{}}
 
   """
-  def change_restaurant(%Restaurant{} = restaurant, attrs \\ %{}) do
-    Restaurant.changeset(restaurant, attrs)
+  def change_courier_registration(%Courier{} = courier, attrs \\ %{}) do
+    Courier.registration_changeset(courier, attrs, hash_password: false)
   end
 
-  alias Volt.Accounts.Courier
+  ## Settings
 
   @doc """
-  Returns the list of couriers.
+  Returns an `%Ecto.Changeset{}` for changing the courier email.
 
   ## Examples
 
-      iex> list_couriers()
-      [%Courier{}, ...]
+      iex> change_courier_email(courier)
+      %Ecto.Changeset{data: %Courier{}}
 
   """
-  def list_couriers do
-    Repo.all(Courier)
+  def change_courier_email(courier, attrs \\ %{}) do
+    Courier.email_changeset(courier, attrs)
   end
 
   @doc """
-  Gets a single courier.
+  Emulates that the email will change without actually changing
+  it in the database.
 
-  Raises `Ecto.NoResultsError` if the Courier does not exist.
+  ## Examples
+
+      iex> apply_courier_email(courier, "valid password", %{email: ...})
+      {:ok, %Courier{}}
+
+      iex> apply_courier_email(courier, "invalid password", %{email: ...})
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def apply_courier_email(courier, password, attrs) do
+    courier
+    |> Courier.email_changeset(attrs)
+    |> Courier.validate_current_password(password)
+    |> Ecto.Changeset.apply_action(:update)
+  end
+
+  @doc """
+  Updates the courier email using the given token.
+
+  If the token matches, the courier email is updated and the token is deleted.
+  The confirmed_at date is also updated to the current time.
+  """
+  def update_courier_email(courier, token) do
+    context = "change:#{courier.email}"
+
+    with {:ok, query} <- CourierToken.verify_change_email_token_query(token, context),
+         %CourierToken{sent_to: email} <- Repo.one(query),
+         {:ok, _} <- Repo.transaction(courier_email_multi(courier, email, context)) do
+      :ok
+    else
+      _ -> :error
+    end
+  end
+
+  defp courier_email_multi(courier, email, context) do
+    changeset =
+      courier
+      |> Courier.email_changeset(%{email: email})
+      |> Courier.confirm_changeset()
+
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:courier, changeset)
+    |> Ecto.Multi.delete_all(:tokens, CourierToken.courier_and_contexts_query(courier, [context]))
+  end
+
+  @doc """
+  Delivers the update email instructions to the given courier.
 
   ## Examples
 
-      iex> get_courier!(123)
-      %Courier{}
+      iex> deliver_courier_update_email_instructions(courier, current_email, &Routes.courier_update_email_url(conn, :edit, &1))
+      {:ok, %{to: ..., body: ...}}
 
-      iex> get_courier!(456)
-      ** (Ecto.NoResultsError)
+  """
+  def deliver_courier_update_email_instructions(%Courier{} = courier, current_email, update_email_url_fun)
+      when is_function(update_email_url_fun, 1) do
+    {encoded_token, courier_token} = CourierToken.build_email_token(courier, "change:#{current_email}")
+
+    Repo.insert!(courier_token)
+    CourierNotifier.deliver_courier_update_email_instructions(courier, update_email_url_fun.(encoded_token))
+  end
+
+  @doc """
+  Returns an `%Ecto.Changeset{}` for changing the courier password.
+
+  ## Examples
+
+      iex> change_courier_password(courier)
+      %Ecto.Changeset{data: %Courier{}}
 
   """
-  def get_courier!(id), do: Repo.get!(Courier, id)
+  def change_courier_password(courier, attrs \\ %{}) do
+    Courier.password_changeset(courier, attrs, hash_password: false)
+  end
 
   @doc """
-  Creates a courier.
+  Updates the courier password.
 
   ## Examples
 
-      iex> create_courier(%{field: value})
+      iex> update_courier_password(courier, "valid password", %{password: ...})
       {:ok, %Courier{}}
 
-      iex> create_courier(%{field: bad_value})
+      iex> update_courier_password(courier, "invalid password", %{password: ...})
       {:error, %Ecto.Changeset{}}
 
   """
-  def create_courier(attrs \\ %{}) do
-    %Courier{}
-    |> Courier.changeset(attrs)
-    |> Repo.insert()
+  def update_courier_password(courier, password, attrs) do
+    changeset =
+      courier
+      |> Courier.password_changeset(attrs)
+      |> Courier.validate_current_password(password)
+
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:courier, changeset)
+    |> Ecto.Multi.delete_all(:tokens, CourierToken.courier_and_contexts_query(courier, :all))
+    |> Repo.transaction()
+    |> case do
+      {:ok, %{courier: courier}} -> {:ok, courier}
+      {:error, :courier, changeset, _} -> {:error, changeset}
+    end
+  end
+
+  @doc """
+  Returns an `%Ecto.Changeset{}` for changing the courier profile.
+
+  ## Examples
+
+      iex> change_courier_profile(courier)
+      %Ecto.Changeset{data: %Courier{}}
+
+  """
+  def change_courier_profile(courier, attrs \\ %{}) do
+    Courier.profile_changeset(courier, attrs)
   end
 
   @doc """
-  Updates a courier.
+  Updates the courier profile.
 
   ## Examples
 
-      iex> update_courier(courier, %{field: new_value})
+      iex> update_courier_profile(courier, "valid password", %{password: ...})
       {:ok, %Courier{}}
 
-      iex> update_courier(courier, %{field: bad_value})
+      iex> update_courier_profile(courier, "invalid password", %{password: ...})
       {:error, %Ecto.Changeset{}}
 
   """
-  def update_courier(%Courier{} = courier, attrs) do
-    courier
-    |> Courier.changeset(attrs)
-    |> Repo.update()
+  def update_courier_profile(courier, password, attrs) do
+    changeset =
+      courier
+      |> Courier.profile_changeset(attrs)
+      |> Courier.validate_current_password(password)
+
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:courier, changeset)
+    |> Ecto.Multi.delete_all(:tokens, CourierToken.courier_and_contexts_query(courier, :all))
+    |> Repo.transaction()
+    |> case do
+         {:ok, %{courier: courier}} -> {:ok, courier}
+         {:error, :courier, changeset, _} -> {:error, changeset}
+       end
+  end
+
+  ## Session
+
+  @doc """
+  Generates a session token.
+  """
+  def generate_courier_session_token(courier) do
+    {token, courier_token} = CourierToken.build_session_token(courier)
+    Repo.insert!(courier_token)
+    token
+  end
+
+  @doc """
+  Gets the courier with the given signed token.
+  """
+  def get_courier_by_session_token(token) do
+    {:ok, query} = CourierToken.verify_session_token_query(token)
+    Repo.one(query)
+  end
+
+  @doc """
+  Deletes the signed token with the given context.
+  """
+  def delete_courier_session_token(token) do
+    Repo.delete_all(CourierToken.token_and_context_query(token, "session"))
+    :ok
+  end
+
+  ## Confirmation
+
+  @doc """
+  Delivers the confirmation email instructions to the given courier.
+
+  ## Examples
+
+      iex> deliver_courier_confirmation_instructions(courier, &Routes.courier_confirmation_url(conn, :edit, &1))
+      {:ok, %{to: ..., body: ...}}
+
+      iex> deliver_courier_confirmation_instructions(confirmed_courier, &Routes.courier_confirmation_url(conn, :edit, &1))
+      {:error, :already_confirmed}
+
+  """
+  def deliver_courier_confirmation_instructions(%Courier{} = courier, confirmation_url_fun)
+      when is_function(confirmation_url_fun, 1) do
+    if courier.confirmed_at do
+      {:error, :already_confirmed}
+    else
+      {encoded_token, courier_token} = CourierToken.build_email_token(courier, "confirm")
+      Repo.insert!(courier_token)
+      CourierNotifier.deliver_confirmation_instructions(courier, confirmation_url_fun.(encoded_token))
+    end
+  end
+
+  @doc """
+  Confirms a courier by the given token.
+
+  If the token matches, the courier account is marked as confirmed
+  and the token is deleted.
+  """
+  def confirm_courier(token) do
+    with {:ok, query} <- CourierToken.verify_email_token_query(token, "confirm"),
+         %Courier{} = courier <- Repo.one(query),
+         {:ok, %{courier: courier}} <- Repo.transaction(confirm_courier_multi(courier)) do
+      {:ok, courier}
+    else
+      _ -> :error
+    end
+  end
+
+  defp confirm_courier_multi(courier) do
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:courier, Courier.confirm_changeset(courier))
+    |> Ecto.Multi.delete_all(:tokens, CourierToken.courier_and_contexts_query(courier, ["confirm"]))
+  end
+
+  ## Reset password
+
+  @doc """
+  Delivers the reset password email to the given courier.
+
+  ## Examples
+
+      iex> deliver_courier_reset_password_instructions(courier, &Routes.courier_reset_password_url(conn, :edit, &1))
+      {:ok, %{to: ..., body: ...}}
+
+  """
+  def deliver_courier_reset_password_instructions(%Courier{} = courier, reset_password_url_fun)
+      when is_function(reset_password_url_fun, 1) do
+    {encoded_token, courier_token} = CourierToken.build_email_token(courier, "reset_password")
+    Repo.insert!(courier_token)
+    CourierNotifier.deliver_reset_password_instructions(courier, reset_password_url_fun.(encoded_token))
+  end
+
+  @doc """
+  Gets the courier by reset password token.
+
+  ## Examples
+
+      iex> get_courier_by_reset_password_token("validtoken")
+      %Courier{}
+
+      iex> get_courier_by_reset_password_token("invalidtoken")
+      nil
+
+  """
+  def get_courier_by_reset_password_token(token) do
+    with {:ok, query} <- CourierToken.verify_email_token_query(token, "reset_password"),
+         %Courier{} = courier <- Repo.one(query) do
+      courier
+    else
+      _ -> nil
+    end
   end
 
   @doc """
-  Deletes a courier.
+  Resets the courier password.
 
   ## Examples
 
-      iex> delete_courier(courier)
+      iex> reset_courier_password(courier, %{password: "new long password", password_confirmation: "new long password"})
       {:ok, %Courier{}}
 
-      iex> delete_courier(courier)
+      iex> reset_courier_password(courier, %{password: "valid", password_confirmation: "not the same"})
       {:error, %Ecto.Changeset{}}
 
   """
-  def delete_courier(%Courier{} = courier) do
-    Repo.delete(courier)
+  def reset_courier_password(courier, attrs) do
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:courier, Courier.password_changeset(courier, attrs))
+    |> Ecto.Multi.delete_all(:tokens, CourierToken.courier_and_contexts_query(courier, :all))
+    |> Repo.transaction()
+    |> case do
+      {:ok, %{courier: courier}} -> {:ok, courier}
+      {:error, :courier, changeset, _} -> {:error, changeset}
+    end
   end
 
+  alias Volt.Accounts.{Restaurant, RestaurantToken, RestaurantNotifier}
+
+  ## Database getters
+
   @doc """
-  Returns an `%Ecto.Changeset{}` for tracking courier changes.
+  Gets a restaurant by email.
 
   ## Examples
 
-      iex> change_courier(courier)
-      %Ecto.Changeset{data: %Courier{}}
+      iex> get_restaurant_by_email("foo@example.com")
+      %Restaurant{}
+
+      iex> get_restaurant_by_email("unknown@example.com")
+      nil
 
   """
-  def change_courier(%Courier{} = courier, attrs \\ %{}) do
-    Courier.changeset(courier, attrs)
+  def get_restaurant_by_email(email) when is_binary(email) do
+    Repo.get_by(Restaurant, email: email)
   end
 
-  alias Volt.Accounts.Customer
-
   @doc """
-  Returns the list of customers.
+  Gets a restaurant by email and password.
 
   ## Examples
 
-      iex> list_customers()
-      [%Customer{}, ...]
+      iex> get_restaurant_by_email_and_password("foo@example.com", "correct_password")
+      %Restaurant{}
+
+      iex> get_restaurant_by_email_and_password("foo@example.com", "invalid_password")
+      nil
 
   """
-  def list_customers do
-    Repo.all(Customer)
+  def get_restaurant_by_email_and_password(email, password)
+      when is_binary(email) and is_binary(password) do
+    restaurant = Repo.get_by(Restaurant, email: email)
+    if Restaurant.valid_password?(restaurant, password), do: restaurant
   end
 
   @doc """
-  Gets a single customer.
+  Gets a single restaurant.
 
-  Raises `Ecto.NoResultsError` if the Customer does not exist.
+  Raises `Ecto.NoResultsError` if the Restaurant does not exist.
 
   ## Examples
 
-      iex> get_customer!(123)
-      %Customer{}
+      iex> get_restaurant!(123)
+      %Restaurant{}
 
-      iex> get_customer!(456)
+      iex> get_restaurant!(456)
       ** (Ecto.NoResultsError)
 
   """
-  def get_customer!(id), do: Repo.get!(Customer, id)
+  def get_restaurant!(id), do: Repo.get!(Restaurant, id)
+
+  ## Restaurant registration
 
   @doc """
-  Creates a customer.
+  Registers a restaurant.
 
   ## Examples
 
-      iex> create_customer(%{field: value})
-      {:ok, %Customer{}}
+      iex> register_restaurant(%{field: value})
+      {:ok, %Restaurant{}}
 
-      iex> create_customer(%{field: bad_value})
+      iex> register_restaurant(%{field: bad_value})
       {:error, %Ecto.Changeset{}}
 
   """
-  def create_customer(attrs \\ %{}) do
-    %Customer{}
-    |> Customer.changeset(attrs)
+  def register_restaurant(attrs) do
+    %Restaurant{}
+    |> Restaurant.registration_changeset(attrs)
     |> Repo.insert()
   end
 
   @doc """
-  Updates a customer.
+  Returns an `%Ecto.Changeset{}` for tracking restaurant changes.
 
   ## Examples
 
-      iex> update_customer(customer, %{field: new_value})
-      {:ok, %Customer{}}
+      iex> change_restaurant_registration(restaurant)
+      %Ecto.Changeset{data: %Restaurant{}}
 
-      iex> update_customer(customer, %{field: bad_value})
-      {:error, %Ecto.Changeset{}}
+  """
+  def change_restaurant_registration(%Restaurant{} = restaurant, attrs \\ %{}) do
+    Restaurant.registration_changeset(restaurant, attrs, hash_password: false)
+  end
+
+  ## Settings
+
+  @doc """
+  Returns an `%Ecto.Changeset{}` for changing the restaurant email.
+
+  ## Examples
+
+      iex> change_restaurant_email(restaurant)
+      %Ecto.Changeset{data: %Restaurant{}}
 
   """
-  def update_customer(%Customer{} = customer, attrs) do
-    customer
-    |> Customer.changeset(attrs)
-    |> Repo.update()
+  def change_restaurant_email(restaurant, attrs \\ %{}) do
+    Restaurant.email_changeset(restaurant, attrs)
   end
 
   @doc """
-  Deletes a customer.
+  # TODO: email should be changed for real
+  Emulates that the email will change without actually changing
+  it in the database.
 
   ## Examples
 
-      iex> delete_customer(customer)
-      {:ok, %Customer{}}
+      iex> apply_restaurant_email(restaurant, "valid password", %{email: ...})
+      {:ok, %Restaurant{}}
 
-      iex> delete_customer(customer)
+      iex> apply_restaurant_email(restaurant, "invalid password", %{email: ...})
       {:error, %Ecto.Changeset{}}
 
   """
-  def delete_customer(%Customer{} = customer) do
-    Repo.delete(customer)
+  def apply_restaurant_email(restaurant, password, attrs) do
+    restaurant
+    |> Restaurant.email_changeset(attrs)
+    |> Restaurant.validate_current_password(password)
+    |> Ecto.Changeset.apply_action(:update)
   end
 
   @doc """
-  Returns an `%Ecto.Changeset{}` for tracking customer changes.
+  Updates the restaurant email using the given token.
+
+  If the token matches, the restaurant email is updated and the token is deleted.
+  The confirmed_at date is also updated to the current time.
+  """
+  def update_restaurant_email(restaurant, token) do
+    context = "change:#{restaurant.email}"
+
+    with {:ok, query} <- RestaurantToken.verify_change_email_token_query(token, context),
+         %RestaurantToken{sent_to: email} <- Repo.one(query),
+         {:ok, _} <- Repo.transaction(restaurant_email_multi(restaurant, email, context)) do
+      :ok
+    else
+      _ -> :error
+    end
+  end
+
+  defp restaurant_email_multi(restaurant, email, context) do
+    changeset =
+      restaurant
+      |> Restaurant.email_changeset(%{email: email})
+      |> Restaurant.confirm_changeset()
+
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:restaurant, changeset)
+    |> Ecto.Multi.delete_all(:tokens, RestaurantToken.restaurant_and_contexts_query(restaurant, [context]))
+  end
+
+  @doc """
+  Delivers the update email instructions to the given restaurant.
 
   ## Examples
 
-      iex> change_customer(customer)
-      %Ecto.Changeset{data: %Customer{}}
+      iex> deliver_restaurant_update_email_instructions(restaurant, current_email, &Routes.restaurant_update_email_url(conn, :edit, &1))
+      {:ok, %{to: ..., body: ...}}
 
   """
-  def change_customer(%Customer{} = customer, attrs \\ %{}) do
-    Customer.changeset(customer, attrs)
+  def deliver_restaurant_update_email_instructions(%Restaurant{} = restaurant, current_email, update_email_url_fun)
+      when is_function(update_email_url_fun, 1) do
+    {encoded_token, restaurant_token} = RestaurantToken.build_email_token(restaurant, "change:#{current_email}")
+
+    Repo.insert!(restaurant_token)
+    RestaurantNotifier.deliver_restaurant_update_email_instructions(restaurant, update_email_url_fun.(encoded_token))
   end
+
+  @doc """
+  Returns an `%Ecto.Changeset{}` for changing the restaurant profile.
+
+  ## Examples
+
+      iex> change_restaurant_profile(restaurant)
+      %Ecto.Changeset{data: %Restaurant{}}
+
+  """
+  def change_restaurant_profile(restaurant, attrs \\ %{}) do
+    Restaurant.profile_changeset(restaurant, attrs)
+  end
+
+  @doc """
+  Updates the restaurant profile.
+
+  ## Examples
+
+      iex> update_restaurant_profile(restaurant, "valid password", %{password: ...})
+      {:ok, %Restaurant{}}
+
+      iex> update_restaurant_profile(restaurant, "invalid password", %{password: ...})
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def update_restaurant_profile(restaurant, password, attrs) do
+    changeset =
+      restaurant
+      |> Restaurant.profile_changeset(attrs)
+      |> Restaurant.validate_current_password(password)
+
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:restaurant, changeset)
+    |> Ecto.Multi.delete_all(:tokens, RestaurantToken.restaurant_and_contexts_query(restaurant, :all))
+    |> Repo.transaction()
+    |> case do
+         {:ok, %{restaurant: restaurant}} -> {:ok, restaurant}
+         {:error, :restaurant, changeset, _} -> {:error, changeset}
+       end
+  end
+
+  @doc """
+  Returns an `%Ecto.Changeset{}` for changing the restaurant password.
+
+  ## Examples
+
+      iex> change_restaurant_password(restaurant)
+      %Ecto.Changeset{data: %Restaurant{}}
+
+  """
+  def change_restaurant_password(restaurant, attrs \\ %{}) do
+    Restaurant.password_changeset(restaurant, attrs, hash_password: false)
+  end
+
+  @doc """
+  Updates the restaurant password.
+
+  ## Examples
+
+      iex> update_restaurant_password(restaurant, "valid password", %{password: ...})
+      {:ok, %Restaurant{}}
+
+      iex> update_restaurant_password(restaurant, "invalid password", %{password: ...})
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def update_restaurant_password(restaurant, password, attrs) do
+    changeset =
+      restaurant
+      |> Restaurant.password_changeset(attrs)
+      |> Restaurant.validate_current_password(password)
+
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:restaurant, changeset)
+    |> Ecto.Multi.delete_all(:tokens, RestaurantToken.restaurant_and_contexts_query(restaurant, :all))
+    |> Repo.transaction()
+    |> case do
+      {:ok, %{restaurant: restaurant}} -> {:ok, restaurant}
+      {:error, :restaurant, changeset, _} -> {:error, changeset}
+    end
+  end
+
+  ## Session
+
+  @doc """
+  Generates a session token.
+  """
+  def generate_restaurant_session_token(restaurant) do
+    {token, restaurant_token} = RestaurantToken.build_session_token(restaurant)
+    Repo.insert!(restaurant_token)
+    token
+  end
+
+  @doc """
+  Gets the restaurant with the given signed token.
+  """
+  def get_restaurant_by_session_token(token) do
+    {:ok, query} = RestaurantToken.verify_session_token_query(token)
+    Repo.one(query)
+  end
+
+  @doc """
+  Deletes the signed token with the given context.
+  """
+  def delete_restaurant_session_token(token) do
+    Repo.delete_all(RestaurantToken.token_and_context_query(token, "session"))
+    :ok
+  end
+
+  ## Confirmation
+
+  @doc """
+  Delivers the confirmation email instructions to the given restaurant.
+
+  ## Examples
+
+      iex> deliver_restaurant_confirmation_instructions(restaurant, &Routes.restaurant_confirmation_url(conn, :edit, &1))
+      {:ok, %{to: ..., body: ...}}
+
+      iex> deliver_restaurant_confirmation_instructions(confirmed_restaurant, &Routes.restaurant_confirmation_url(conn, :edit, &1))
+      {:error, :already_confirmed}
+
+  """
+  def deliver_restaurant_confirmation_instructions(%Restaurant{} = restaurant, confirmation_url_fun)
+      when is_function(confirmation_url_fun, 1) do
+    if restaurant.confirmed_at do
+      {:error, :already_confirmed}
+    else
+      {encoded_token, restaurant_token} = RestaurantToken.build_email_token(restaurant, "confirm")
+      Repo.insert!(restaurant_token)
+      RestaurantNotifier.deliver_confirmation_instructions(restaurant, confirmation_url_fun.(encoded_token))
+    end
+  end
+
+  @doc """
+  Confirms a restaurant by the given token.
+
+  If the token matches, the restaurant account is marked as confirmed
+  and the token is deleted.
+  """
+  def confirm_restaurant(token) do
+    with {:ok, query} <- RestaurantToken.verify_email_token_query(token, "confirm"),
+         %Restaurant{} = restaurant <- Repo.one(query),
+         {:ok, %{restaurant: restaurant}} <- Repo.transaction(confirm_restaurant_multi(restaurant)) do
+      {:ok, restaurant}
+    else
+      _ -> :error
+    end
+  end
+
+  defp confirm_restaurant_multi(restaurant) do
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:restaurant, Restaurant.confirm_changeset(restaurant))
+    |> Ecto.Multi.delete_all(:tokens, RestaurantToken.restaurant_and_contexts_query(restaurant, ["confirm"]))
+  end
+
+  ## Reset password
+
+  @doc """
+  Delivers the reset password email to the given restaurant.
+
+  ## Examples
+
+      iex> deliver_restaurant_reset_password_instructions(restaurant, &Routes.restaurant_reset_password_url(conn, :edit, &1))
+      {:ok, %{to: ..., body: ...}}
+
+  """
+  def deliver_restaurant_reset_password_instructions(%Restaurant{} = restaurant, reset_password_url_fun)
+      when is_function(reset_password_url_fun, 1) do
+    {encoded_token, restaurant_token} = RestaurantToken.build_email_token(restaurant, "reset_password")
+    Repo.insert!(restaurant_token)
+    RestaurantNotifier.deliver_reset_password_instructions(restaurant, reset_password_url_fun.(encoded_token))
+  end
+
+  @doc """
+  Gets the restaurant by reset password token.
+
+  ## Examples
+
+      iex> get_restaurant_by_reset_password_token("validtoken")
+      %Restaurant{}
+
+      iex> get_restaurant_by_reset_password_token("invalidtoken")
+      nil
+
+  """
+  def get_restaurant_by_reset_password_token(token) do
+    with {:ok, query} <- RestaurantToken.verify_email_token_query(token, "reset_password"),
+         %Restaurant{} = restaurant <- Repo.one(query) do
+      restaurant
+    else
+      _ -> nil
+    end
+  end
+
+  @doc """
+  Resets the restaurant password.
+
+  ## Examples
+
+      iex> reset_restaurant_password(restaurant, %{password: "new long password", password_confirmation: "new long password"})
+      {:ok, %Restaurant{}}
+
+      iex> reset_restaurant_password(restaurant, %{password: "valid", password_confirmation: "not the same"})
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def reset_restaurant_password(restaurant, attrs) do
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:restaurant, Restaurant.password_changeset(restaurant, attrs))
+    |> Ecto.Multi.delete_all(:tokens, RestaurantToken.restaurant_and_contexts_query(restaurant, :all))
+    |> Repo.transaction()
+    |> case do
+      {:ok, %{restaurant: restaurant}} -> {:ok, restaurant}
+      {:error, :restaurant, changeset, _} -> {:error, changeset}
+    end
+  end
+
+  alias Volt.Accounts.{Customer, CustomerToken, CustomerNotifier}
+
+  ## Database getters
+
+  @doc """
+  Gets a customer by email.
+
+  ## Examples
+
+      iex> get_customer_by_email("foo@example.com")
+      %Customer{}
+
+      iex> get_customer_by_email("unknown@example.com")
+      nil
+
+  """
+  def get_customer_by_email(email) when is_binary(email) do
+    Repo.get_by(Customer, email: email)
+  end
+
+  @doc """
+  Gets a customer by email and password.
+
+  ## Examples
+
+      iex> get_customer_by_email_and_password("foo@example.com", "correct_password")
+      %Customer{}
+
+      iex> get_customer_by_email_and_password("foo@example.com", "invalid_password")
+      nil
+
+  """
+  def get_customer_by_email_and_password(email, password)
+      when is_binary(email) and is_binary(password) do
+    customer = Repo.get_by(Customer, email: email)
+    if Customer.valid_password?(customer, password), do: customer
+  end
+
+  @doc """
+  Gets a single customer.
+
+  Raises `Ecto.NoResultsError` if the Customer does not exist.
+
+  ## Examples
+
+      iex> get_customer!(123)
+      %Customer{}
+
+      iex> get_customer!(456)
+      ** (Ecto.NoResultsError)
+
+  """
+  def get_customer!(id), do: Repo.get!(Customer, id)
+
+  ## Customer registration
+
+  @doc """
+  Registers a customer.
+
+  ## Examples
+
+      iex> register_customer(%{field: value})
+      {:ok, %Customer{}}
+
+      iex> register_customer(%{field: bad_value})
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def register_customer(attrs) do
+    %Customer{}
+    |> Customer.registration_changeset(attrs)
+    |> Repo.insert()
+  end
+
+  @doc """
+  Returns an `%Ecto.Changeset{}` for tracking customer changes.
+
+  ## Examples
+
+      iex> change_customer_registration(customer)
+      %Ecto.Changeset{data: %Customer{}}
+
+  """
+  def change_customer_registration(%Customer{} = customer, attrs \\ %{}) do
+    Customer.registration_changeset(customer, attrs, hash_password: false)
+  end
+
+  ## Settings
+
+  @doc """
+  Returns an `%Ecto.Changeset{}` for changing the customer profile.
+
+  ## Examples
+
+      iex> change_customer_profile(customer)
+      %Ecto.Changeset{data: %Customer{}}
+
+  """
+  def change_customer_profile(customer, attrs \\ %{}) do
+    Customer.profile_changeset(customer, attrs)
+  end
+
+  @doc """
+  Updates the customer profile.
+
+  ## Examples
+
+      iex> update_customer_profile(customer, "valid password", %{password: ...})
+      {:ok, %Customer{}}
+
+      iex> update_customer_profile(customer, "invalid password", %{password: ...})
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def update_customer_profile(customer, password, attrs) do
+    changeset =
+      customer
+      |> Customer.profile_changeset(attrs)
+      |> Customer.validate_current_password(password)
+
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:customer, changeset)
+    |> Ecto.Multi.delete_all(:tokens, CustomerToken.customer_and_contexts_query(customer, :all))
+    |> Repo.transaction()
+    |> case do
+         {:ok, %{customer: customer}} -> {:ok, customer}
+         {:error, :customer, changeset, _} -> {:error, changeset}
+       end
+  end
+
+  @doc """
+  Returns an `%Ecto.Changeset{}` for changing the customer email.
+
+  ## Examples
+
+      iex> change_customer_email(customer)
+      %Ecto.Changeset{data: %Customer{}}
+
+  """
+  def change_customer_email(customer, attrs \\ %{}) do
+    Customer.email_changeset(customer, attrs)
+  end
+
+  @doc """
+  Emulates that the email will change without actually changing
+  it in the database.
+
+  ## Examples
+
+      iex> apply_customer_email(customer, "valid password", %{email: ...})
+      {:ok, %Customer{}}
+
+      iex> apply_customer_email(customer, "invalid password", %{email: ...})
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def apply_customer_email(customer, password, attrs) do
+    customer
+    |> Customer.email_changeset(attrs)
+    |> Customer.validate_current_password(password)
+    |> Ecto.Changeset.apply_action(:update)
+  end
+
+  @doc """
+  Updates the customer email using the given token.
+
+  If the token matches, the customer email is updated and the token is deleted.
+  The confirmed_at date is also updated to the current time.
+  """
+  def update_customer_email(customer, token) do
+    context = "change:#{customer.email}"
+
+    with {:ok, query} <- CustomerToken.verify_change_email_token_query(token, context),
+         %CustomerToken{sent_to: email} <- Repo.one(query),
+         {:ok, _} <- Repo.transaction(customer_email_multi(customer, email, context)) do
+      :ok
+    else
+      _ -> :error
+    end
+  end
+
+  defp customer_email_multi(customer, email, context) do
+    changeset =
+      customer
+      |> Customer.email_changeset(%{email: email})
+      |> Customer.confirm_changeset()
+
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:customer, changeset)
+    |> Ecto.Multi.delete_all(:tokens, CustomerToken.customer_and_contexts_query(customer, [context]))
+  end
+
+  @doc """
+  Delivers the update email instructions to the given customer.
+
+  ## Examples
+
+      iex> deliver_customer_update_email_instructions(customer, current_email, &Routes.customer_update_email_url(conn, :edit, &1))
+      {:ok, %{to: ..., body: ...}}
+
+  """
+  def deliver_customer_update_email_instructions(%Customer{} = customer, current_email, update_email_url_fun)
+      when is_function(update_email_url_fun, 1) do
+    {encoded_token, customer_token} = CustomerToken.build_email_token(customer, "change:#{current_email}")
+
+    Repo.insert!(customer_token)
+    CustomerNotifier.deliver_customer_update_email_instructions(customer, update_email_url_fun.(encoded_token))
+  end
+
+  @doc """
+  Returns an `%Ecto.Changeset{}` for changing the customer password.
+
+  ## Examples
+
+      iex> change_customer_password(customer)
+      %Ecto.Changeset{data: %Customer{}}
+
+  """
+  def change_customer_password(customer, attrs \\ %{}) do
+    Customer.password_changeset(customer, attrs, hash_password: false)
+  end
+
+  @doc """
+  Updates the customer password.
+
+  ## Examples
+
+      iex> update_customer_password(customer, "valid password", %{password: ...})
+      {:ok, %Customer{}}
+
+      iex> update_customer_password(customer, "invalid password", %{password: ...})
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def update_customer_password(customer, password, attrs) do
+    changeset =
+      customer
+      |> Customer.password_changeset(attrs)
+      |> Customer.validate_current_password(password)
+
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:customer, changeset)
+    |> Ecto.Multi.delete_all(:tokens, CustomerToken.customer_and_contexts_query(customer, :all))
+    |> Repo.transaction()
+    |> case do
+      {:ok, %{customer: customer}} -> {:ok, customer}
+      {:error, :customer, changeset, _} -> {:error, changeset}
+    end
+  end
+
+  ## Session
+
+  @doc """
+  Generates a session token.
+  """
+  def generate_customer_session_token(customer) do
+    {token, customer_token} = CustomerToken.build_session_token(customer)
+    Repo.insert!(customer_token)
+    token
+  end
+
+  @doc """
+  Gets the customer with the given signed token.
+  """
+  def get_customer_by_session_token(token) do
+    {:ok, query} = CustomerToken.verify_session_token_query(token)
+    Repo.one(query)
+  end
+
+  @doc """
+  Deletes the signed token with the given context.
+  """
+  def delete_customer_session_token(token) do
+    Repo.delete_all(CustomerToken.token_and_context_query(token, "session"))
+    :ok
+  end
+
+  ## Confirmation
+
+  @doc """
+  Delivers the confirmation email instructions to the given customer.
+
+  ## Examples
+
+      iex> deliver_customer_confirmation_instructions(customer, &Routes.customer_confirmation_url(conn, :edit, &1))
+      {:ok, %{to: ..., body: ...}}
+
+      iex> deliver_customer_confirmation_instructions(confirmed_customer, &Routes.customer_confirmation_url(conn, :edit, &1))
+      {:error, :already_confirmed}
+
+  """
+  def deliver_customer_confirmation_instructions(%Customer{} = customer, confirmation_url_fun)
+      when is_function(confirmation_url_fun, 1) do
+    if customer.confirmed_at do
+      {:error, :already_confirmed}
+    else
+      {encoded_token, customer_token} = CustomerToken.build_email_token(customer, "confirm")
+      Repo.insert!(customer_token)
+      CustomerNotifier.deliver_confirmation_instructions(customer, confirmation_url_fun.(encoded_token))
+    end
+  end
+
+  @doc """
+  Confirms a customer by the given token.
+
+  If the token matches, the customer account is marked as confirmed
+  and the token is deleted.
+  """
+  def confirm_customer(token) do
+    with {:ok, query} <- CustomerToken.verify_email_token_query(token, "confirm"),
+         %Customer{} = customer <- Repo.one(query),
+         {:ok, %{customer: customer}} <- Repo.transaction(confirm_customer_multi(customer)) do
+      {:ok, customer}
+    else
+      _ -> :error
+    end
+  end
+
+  defp confirm_customer_multi(customer) do
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:customer, Customer.confirm_changeset(customer))
+    |> Ecto.Multi.delete_all(:tokens, CustomerToken.customer_and_contexts_query(customer, ["confirm"]))
+  end
+
+  ## Reset password
+
+  @doc """
+  Delivers the reset password email to the given customer.
+
+  ## Examples
+
+      iex> deliver_customer_reset_password_instructions(customer, &Routes.customer_reset_password_url(conn, :edit, &1))
+      {:ok, %{to: ..., body: ...}}
+
+  """
+  def deliver_customer_reset_password_instructions(%Customer{} = customer, reset_password_url_fun)
+      when is_function(reset_password_url_fun, 1) do
+    {encoded_token, customer_token} = CustomerToken.build_email_token(customer, "reset_password")
+    Repo.insert!(customer_token)
+    CustomerNotifier.deliver_reset_password_instructions(customer, reset_password_url_fun.(encoded_token))
+  end
+
+  @doc """
+  Gets the customer by reset password token.
+
+  ## Examples
+
+      iex> get_customer_by_reset_password_token("validtoken")
+      %Customer{}
+
+      iex> get_customer_by_reset_password_token("invalidtoken")
+      nil
+
+  """
+  def get_customer_by_reset_password_token(token) do
+    with {:ok, query} <- CustomerToken.verify_email_token_query(token, "reset_password"),
+         %Customer{} = customer <- Repo.one(query) do
+      customer
+    else
+      _ -> nil
+    end
+  end
+
+  @doc """
+  Resets the customer password.
+
+  ## Examples
+
+      iex> reset_customer_password(customer, %{password: "new long password", password_confirmation: "new long password"})
+      {:ok, %Customer{}}
+
+      iex> reset_customer_password(customer, %{password: "valid", password_confirmation: "not the same"})
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def reset_customer_password(customer, attrs) do
+    Ecto.Multi.new()
+    |> Ecto.Multi.update(:customer, Customer.password_changeset(customer, attrs))
+    |> Ecto.Multi.delete_all(:tokens, CustomerToken.customer_and_contexts_query(customer, :all))
+    |> Repo.transaction()
+    |> case do
+      {:ok, %{customer: customer}} -> {:ok, customer}
+      {:error, :customer, changeset, _} -> {:error, changeset}
+    end
+  end
+
+  """
+  TODO: Revise old tests
+
+  def create_restaurant(attrs \\ %{}) do
+    %Restaurant{}
+    |> Restaurant.changeset(attrs)
+    |> Repo.insert()
+  end
+
+
+  def update_restaurant(%Restaurant{} = restaurant, attrs) do
+    restaurant
+    |> Restaurant.changeset(attrs)
+    |> Repo.update()
+  end
+
+  def delete_restaurant(%Restaurant{} = restaurant) do
+    Repo.delete(restaurant)
+  end
+
+
+  def change_restaurant(%Restaurant{} = restaurant, attrs \\ %{}) do
+    Restaurant.changeset(restaurant, attrs)
+  end
+
+  alias Volt.Accounts.Courier
+
+
+  def list_couriers do
+    Repo.all(Courier)
+  end
+
+
+  def get_courier!(id), do: Repo.get!(Courier, id)
+
+
+  def create_courier(attrs \\ %{}) do
+    %Courier{}
+    |> Courier.changeset(attrs)
+    |> Repo.insert()
+  end
+
+
+  def update_courier(%Courier{} = courier, attrs) do
+    courier
+    |> Courier.changeset(attrs)
+    |> Repo.update()
+  end
+
+
+  def delete_courier(%Courier{} = courier) do
+    Repo.delete(courier)
+  end
+
+
+  def change_courier(%Courier{} = courier, attrs \\ %{}) do
+    Courier.changeset(courier, attrs)
+  end
+
+  alias Volt.Accounts.Customer
+
+
+  def list_customers do
+    Repo.all(Customer)
+  end
+
+
+  def create_customer(attrs \\ %{}) do
+    %Customer{}
+    |> Customer.changeset(attrs)
+    |> Repo.insert()
+  end
+
+
+  def update_customer(%Customer{} = customer, attrs) do
+    customer
+    |> Customer.changeset(attrs)
+    |> Repo.update()
+  end
+
+
+  def delete_customer(%Customer{} = customer) do
+    Repo.delete(customer)
+  end
+
+
+  def change_customer(%Customer{} = customer, attrs \\ %{}) do
+    Customer.changeset(customer, attrs)
+  end
+  """
+
 end
diff --git a/lib/volt/accounts/courier.ex b/lib/volt/accounts/courier.ex
index 605288baaef7a49c9229c2c19832efaba3ea632a..582c830aef94dfa9ac75a3d8c006b183e57f93a2 100644
--- a/lib/volt/accounts/courier.ex
+++ b/lib/volt/accounts/courier.ex
@@ -7,21 +7,153 @@ defmodule Volt.Accounts.Courier do
     field :email, :string
     field :first_name, :string
     field :last_name, :string
-    field :password, :string
     field :phone_number, :string
+    field :password, :string, virtual: true, redact: true
+    field :hashed_password, :string, redact: true
+    field :confirmed_at, :naive_datetime
 
     timestamps()
   end
 
-  @doc false
-  def changeset(courier, attrs) do
+  @doc """
+  A courier changeset for registration.
+
+  It is important to validate the length of both email and password.
+  Otherwise databases may truncate the email without warnings, which
+  could lead to unpredictable or insecure behaviour. Long passwords may
+  also be very expensive to hash for certain algorithms.
+
+  ## Options
+
+    * `:hash_password` - Hashes the password so it can be stored securely
+      in the database and ensures the password field is cleared to prevent
+      leaks in the logs. If password hashing is not needed and clearing the
+      password field is not desired (like when using this changeset for
+      validations on a LiveView form), this option can be set to `false`.
+      Defaults to `true`.
+  """
+  def registration_changeset(courier, attrs, opts \\ []) do
     courier
-    |> cast(attrs, [:email, :password, :first_name, :last_name, :phone_number, :courier_status])
-    |> validate_required([:email, :password, :first_name, :last_name, :phone_number, :courier_status])
-    |> validate_format(:phone_number, ~r/^\+(\d{1,3}?)\d{10}$/, message: "Invalid phone number")
-    |> validate_format(:email, ~r/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/, message: "Invalid email address")
-    |> validate_length(:password, min: 8)
-    |> unique_constraint(:email, name: :courier_unique_email_constraint)
+    |> cast(attrs, [:email, :password, :first_name, :last_name, :phone_number])
+    |> validate_profile()
+    |> validate_email()
+    |> validate_password(opts)
+  end
+
+  defp validate_profile(changeset) do
+    changeset
+    |> validate_required([:first_name, :last_name, :phone_number])
+    |> validate_format(:phone_number, ~r/^\+(\d{1,3})?\d{10}$/, message: "Invalid phone number. Should start with plus and from 10 to 13 digits")
     |> unique_constraint(:phone_number, name: :courier_unique_phone_number_constraint)
   end
+
+  defp validate_email(changeset) do
+    changeset
+    |> validate_required([:email])
+    |> validate_format(:email, ~r/^[^\s]+@[^\s]+$/, message: "must have the @ sign and no spaces")
+    |> validate_length(:email, max: 160)
+    |> unsafe_validate_unique(:email, Volt.Repo)
+    |> unique_constraint(:email)
+  end
+
+  defp validate_password(changeset, opts) do
+    changeset
+    |> validate_required([:password])
+    |> validate_length(:password, min: 12, max: 72)
+    # |> 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
+
+  defp maybe_hash_password(changeset, opts) do
+    hash_password? = Keyword.get(opts, :hash_password, true)
+    password = get_change(changeset, :password)
+
+    if hash_password? && password && changeset.valid? do
+      changeset
+      |> put_change(:hashed_password, Pbkdf2.hash_pwd_salt(password))
+      |> delete_change(:password)
+    else
+      changeset
+    end
+  end
+
+  @doc """
+  A courier changeset for changing the email.
+
+  It requires the email to change otherwise an error is added.
+  """
+  def email_changeset(courier, attrs) do
+    courier
+    |> cast(attrs, [:email])
+    |> validate_email()
+    |> case do
+      %{changes: %{email: _}} = changeset -> changeset
+      %{} = changeset -> add_error(changeset, :email, "did not change")
+    end
+  end
+
+  @doc """
+  A courier changeset for changing the password.
+
+  ## Options
+
+    * `:hash_password` - Hashes the password so it can be stored securely
+      in the database and ensures the password field is cleared to prevent
+      leaks in the logs. If password hashing is not needed and clearing the
+      password field is not desired (like when using this changeset for
+      validations on a LiveView form), this option can be set to `false`.
+      Defaults to `true`.
+  """
+  def password_changeset(courier, attrs, opts \\ []) do
+    courier
+    |> cast(attrs, [:password])
+    |> validate_confirmation(:password, message: "does not match password")
+    |> validate_password(opts)
+  end
+
+  @doc """
+  A courier changeset for changing the profile.
+  """
+  def profile_changeset(courier, attrs) do
+    courier
+    |> cast(attrs, [:first_name, :last_name, :phone_number])
+    |> validate_profile()
+  end
+
+  @doc """
+  Confirms the account by setting `confirmed_at`.
+  """
+  def confirm_changeset(courier) do
+    now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
+    change(courier, confirmed_at: now)
+  end
+
+  @doc """
+  Verifies the password.
+
+  If there is no courier or the courier doesn't have a password, we call
+  `Pbkdf2.no_user_verify/0` to avoid timing attacks.
+  """
+  def valid_password?(%Volt.Accounts.Courier{hashed_password: hashed_password}, password)
+      when is_binary(hashed_password) and byte_size(password) > 0 do
+    Pbkdf2.verify_pass(password, hashed_password)
+  end
+
+  def valid_password?(_, _) do
+    Pbkdf2.no_user_verify()
+    false
+  end
+
+  @doc """
+  Validates the current password otherwise adds an error to the changeset.
+  """
+  def validate_current_password(changeset, password) do
+    if valid_password?(changeset.data, password) do
+      changeset
+    else
+      add_error(changeset, :current_password, "is not valid")
+    end
+  end
 end
diff --git a/lib/volt/accounts/courier_notifier.ex b/lib/volt/accounts/courier_notifier.ex
new file mode 100644
index 0000000000000000000000000000000000000000..2a400e8b29c7339b3652f58e74f444e84837b75d
--- /dev/null
+++ b/lib/volt/accounts/courier_notifier.ex
@@ -0,0 +1,79 @@
+defmodule Volt.Accounts.CourierNotifier do
+  import Swoosh.Email
+
+  alias Volt.Mailer
+
+  # Delivers the email using the application mailer.
+  defp deliver(recipient, subject, body) do
+    email =
+      new()
+      |> to(recipient)
+      |> from({"Volt", "contact@example.com"})
+      |> subject(subject)
+      |> text_body(body)
+
+    with {:ok, _metadata} <- Mailer.deliver(email) do
+      {:ok, email}
+    end
+  end
+
+  @doc """
+  Deliver instructions to confirm account.
+  """
+  def deliver_confirmation_instructions(courier, url) do
+    deliver(courier.email, "Confirmation instructions", """
+
+    ==============================
+
+    Hi #{courier.email},
+
+    You can confirm your account by visiting the URL below:
+
+    #{url}
+
+    If you didn't create an account with us, please ignore this.
+
+    ==============================
+    """)
+  end
+
+  @doc """
+  Deliver instructions to reset a courier password.
+  """
+  def deliver_reset_password_instructions(courier, url) do
+    deliver(courier.email, "Reset password instructions", """
+
+    ==============================
+
+    Hi #{courier.email},
+
+    You can reset your password by visiting the URL below:
+
+    #{url}
+
+    If you didn't request this change, please ignore this.
+
+    ==============================
+    """)
+  end
+
+  @doc """
+  Deliver instructions to update a courier email.
+  """
+  def deliver_courier_update_email_instructions(courier, url) do
+    deliver(courier.email, "Update email instructions", """
+
+    ==============================
+
+    Hi #{courier.email},
+
+    You can change your email by visiting the URL below:
+
+    #{url}
+
+    If you didn't request this change, please ignore this.
+
+    ==============================
+    """)
+  end
+end
diff --git a/lib/volt/accounts/courier_token.ex b/lib/volt/accounts/courier_token.ex
new file mode 100644
index 0000000000000000000000000000000000000000..3262541e1f2da0f36b93e4f9fb0667af86b73278
--- /dev/null
+++ b/lib/volt/accounts/courier_token.ex
@@ -0,0 +1,179 @@
+defmodule Volt.Accounts.CourierToken do
+  use Ecto.Schema
+  import Ecto.Query
+  alias Volt.Accounts.CourierToken
+
+  @hash_algorithm :sha256
+  @rand_size 32
+
+  # It is very important to keep the reset password token expiry short,
+  # since someone with access to the email may take over the account.
+  @reset_password_validity_in_days 1
+  @confirm_validity_in_days 7
+  @change_email_validity_in_days 7
+  @session_validity_in_days 60
+
+  schema "couriers_tokens" do
+    field :token, :binary
+    field :context, :string
+    field :sent_to, :string
+    belongs_to :courier, Volt.Accounts.Courier
+
+    timestamps(updated_at: false)
+  end
+
+  @doc """
+  Generates a token that will be stored in a signed place,
+  such as session or cookie. As they are signed, those
+  tokens do not need to be hashed.
+
+  The reason why we store session tokens in the database, even
+  though Phoenix already provides a session cookie, is because
+  Phoenix' default session cookies are not persisted, they are
+  simply signed and potentially encrypted. This means they are
+  valid indefinitely, unless you change the signing/encryption
+  salt.
+
+  Therefore, storing them allows individual courier
+  sessions to be expired. The token system can also be extended
+  to store additional data, such as the device used for logging in.
+  You could then use this information to display all valid sessions
+  and devices in the UI and allow users to explicitly expire any
+  session they deem invalid.
+  """
+  def build_session_token(courier) do
+    token = :crypto.strong_rand_bytes(@rand_size)
+    {token, %CourierToken{token: token, context: "session", courier_id: courier.id}}
+  end
+
+  @doc """
+  Checks if the token is valid and returns its underlying lookup query.
+
+  The query returns the courier found by the token, if any.
+
+  The token is valid if it matches the value in the database and it has
+  not expired (after @session_validity_in_days).
+  """
+  def verify_session_token_query(token) do
+    query =
+      from token in token_and_context_query(token, "session"),
+        join: courier in assoc(token, :courier),
+        where: token.inserted_at > ago(@session_validity_in_days, "day"),
+        select: courier
+
+    {:ok, query}
+  end
+
+  @doc """
+  Builds a token and its hash to be delivered to the courier's email.
+
+  The non-hashed token is sent to the courier email while the
+  hashed part is stored in the database. The original token cannot be reconstructed,
+  which means anyone with read-only access to the database cannot directly use
+  the token in the application to gain access. Furthermore, if the user changes
+  their email in the system, the tokens sent to the previous email are no longer
+  valid.
+
+  Users can easily adapt the existing code to provide other types of delivery methods,
+  for example, by phone numbers.
+  """
+  def build_email_token(courier, context) do
+    build_hashed_token(courier, context, courier.email)
+  end
+
+  defp build_hashed_token(courier, context, sent_to) do
+    token = :crypto.strong_rand_bytes(@rand_size)
+    hashed_token = :crypto.hash(@hash_algorithm, token)
+
+    {Base.url_encode64(token, padding: false),
+     %CourierToken{
+       token: hashed_token,
+       context: context,
+       sent_to: sent_to,
+       courier_id: courier.id
+     }}
+  end
+
+  @doc """
+  Checks if the token is valid and returns its underlying lookup query.
+
+  The query returns the courier found by the token, if any.
+
+  The given token is valid if it matches its hashed counterpart in the
+  database and the user email has not changed. This function also checks
+  if the token is being used within a certain period, depending on the
+  context. The default contexts supported by this function are either
+  "confirm", for account confirmation emails, and "reset_password",
+  for resetting the password. For verifying requests to change the email,
+  see `verify_change_email_token_query/2`.
+  """
+  def verify_email_token_query(token, context) do
+    case Base.url_decode64(token, padding: false) do
+      {:ok, decoded_token} ->
+        hashed_token = :crypto.hash(@hash_algorithm, decoded_token)
+        days = days_for_context(context)
+
+        query =
+          from token in token_and_context_query(hashed_token, context),
+            join: courier in assoc(token, :courier),
+            where: token.inserted_at > ago(^days, "day") and token.sent_to == courier.email,
+            select: courier
+
+        {:ok, query}
+
+      :error ->
+        :error
+    end
+  end
+
+  defp days_for_context("confirm"), do: @confirm_validity_in_days
+  defp days_for_context("reset_password"), do: @reset_password_validity_in_days
+
+  @doc """
+  Checks if the token is valid and returns its underlying lookup query.
+
+  The query returns the courier found by the token, if any.
+
+  This is used to validate requests to change the courier
+  email. It is different from `verify_email_token_query/2` precisely because
+  `verify_email_token_query/2` validates the email has not changed, which is
+  the starting point by this function.
+
+  The given token is valid if it matches its hashed counterpart in the
+  database and if it has not expired (after @change_email_validity_in_days).
+  The context must always start with "change:".
+  """
+  def verify_change_email_token_query(token, "change:" <> _ = context) do
+    case Base.url_decode64(token, padding: false) do
+      {:ok, decoded_token} ->
+        hashed_token = :crypto.hash(@hash_algorithm, decoded_token)
+
+        query =
+          from token in token_and_context_query(hashed_token, context),
+            where: token.inserted_at > ago(@change_email_validity_in_days, "day")
+
+        {:ok, query}
+
+      :error ->
+        :error
+    end
+  end
+
+  @doc """
+  Returns the token struct for the given token value and context.
+  """
+  def token_and_context_query(token, context) do
+    from CourierToken, where: [token: ^token, context: ^context]
+  end
+
+  @doc """
+  Gets all tokens for the given courier for the given contexts.
+  """
+  def courier_and_contexts_query(courier, :all) do
+    from t in CourierToken, where: t.courier_id == ^courier.id
+  end
+
+  def courier_and_contexts_query(courier, [_ | _] = contexts) do
+    from t in CourierToken, where: t.courier_id == ^courier.id and t.context in ^contexts
+  end
+end
diff --git a/lib/volt/accounts/customer.ex b/lib/volt/accounts/customer.ex
index 2cbd5977272d985dd7d5e60c6798c3f8c126ec46..b009ef15ecb1b620d1e255a458d43467db5f62b4 100644
--- a/lib/volt/accounts/customer.ex
+++ b/lib/volt/accounts/customer.ex
@@ -12,23 +12,155 @@ defmodule Volt.Accounts.Customer do
     field :email, :string
     field :first_name, :string
     field :last_name, :string
-    field :password, :string
     field :phone_number, :string
+    field :password, :string, virtual: true, redact: true
+    field :hashed_password, :string, redact: true
+    field :confirmed_at, :naive_datetime
 
     timestamps()
   end
 
-  @doc false
-  def changeset(customer, attrs) do
+  @doc """
+  A customer changeset for registration.
+
+  It is important to validate the length of both email and password.
+  Otherwise databases may truncate the email without warnings, which
+  could lead to unpredictable or insecure behaviour. Long passwords may
+  also be very expensive to hash for certain algorithms.
+
+  ## Options
+
+    * `:hash_password` - Hashes the password so it can be stored securely
+      in the database and ensures the password field is cleared to prevent
+      leaks in the logs. If password hashing is not needed and clearing the
+      password field is not desired (like when using this changeset for
+      validations on a LiveView form), this option can be set to `false`.
+      Defaults to `true`.
+  """
+  def registration_changeset(customer, attrs, opts \\ []) do
     customer
     |> cast(attrs, [:email, :password, :first_name, :last_name, :phone_number, :birth_date, :address, :zip_code, :city, :card_number, :balance])
-    |> validate_required([:email, :password, :first_name, :last_name, :phone_number, :birth_date, :address, :zip_code, :city, :card_number, :balance])
-    |> validate_format(:phone_number, ~r/^\+(\d{1,3}?)\d{10}$/, message: "Invalid phone number")
+    |> validate_profile()
+    |> validate_email()
+    |> validate_password(opts)
+  end
+
+  defp validate_profile(changeset) do
+    changeset
+    |> validate_required([:first_name, :last_name, :phone_number, :birth_date, :address, :zip_code, :city, :card_number, :balance])
+    |> validate_format(:phone_number, ~r/^\+(\d{1,3})?\d{10}$/, message: "Invalid phone number. Should start with plus and from 10 to 13 digits")
     |> validate_format(:card_number, ~r/[0-9]{13,}/, message: "Card number must be at least 13 digits")
-    |> validate_format(:email, ~r/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/, message: "Invalid email address")
-    |> validate_length(:password, min: 8)
     |> validate_format(:zip_code, ~r/^\d{5}$/, message: "Zip code must be 5 digits")
-    |> unique_constraint(:email, name: :customer_unique_email_constraint)
     |> unique_constraint(:phone_number, name: :customer_unique_phone_number_constraint)
   end
+
+  defp validate_email(changeset) do
+    changeset
+    |> validate_required([:email])
+    |> validate_format(:email, ~r/^[^\s]+@[^\s]+$/, message: "must have the @ sign and no spaces")
+    |> validate_length(:email, max: 160)
+    |> unsafe_validate_unique(:email, Volt.Repo)
+    |> unique_constraint(:email)
+  end
+
+  defp validate_password(changeset, opts) do
+    changeset
+    |> validate_required([:password])
+    |> validate_length(:password, min: 12, max: 72)
+    # |> 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
+
+  defp maybe_hash_password(changeset, opts) do
+    hash_password? = Keyword.get(opts, :hash_password, true)
+    password = get_change(changeset, :password)
+
+    if hash_password? && password && changeset.valid? do
+      changeset
+      |> put_change(:hashed_password, Pbkdf2.hash_pwd_salt(password))
+      |> delete_change(:password)
+    else
+      changeset
+    end
+  end
+
+  @doc """
+  A customer changeset for changing the email.
+
+  It requires the email to change otherwise an error is added.
+  """
+  def email_changeset(customer, attrs) do
+    customer
+    |> cast(attrs, [:email])
+    |> validate_email()
+    |> case do
+      %{changes: %{email: _}} = changeset -> changeset
+      %{} = changeset -> add_error(changeset, :email, "did not change")
+    end
+  end
+
+  @doc """
+  A customer changeset for changing the password.
+
+  ## Options
+
+    * `:hash_password` - Hashes the password so it can be stored securely
+      in the database and ensures the password field is cleared to prevent
+      leaks in the logs. If password hashing is not needed and clearing the
+      password field is not desired (like when using this changeset for
+      validations on a LiveView form), this option can be set to `false`.
+      Defaults to `true`.
+  """
+  def password_changeset(customer, attrs, opts \\ []) do
+    customer
+    |> cast(attrs, [:password])
+    |> validate_confirmation(:password, message: "does not match password")
+    |> validate_password(opts)
+  end
+
+  @doc """
+  A customer changeset for changing the profile.
+  """
+  def profile_changeset(customer, attrs) do
+    customer
+    |> cast(attrs, [:first_name, :last_name, :phone_number, :birth_date, :address, :zip_code, :city, :card_number, :balance])
+    |> validate_profile()
+  end
+
+  @doc """
+  Confirms the account by setting `confirmed_at`.
+  """
+  def confirm_changeset(customer) do
+    now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
+    change(customer, confirmed_at: now)
+  end
+
+  @doc """
+  Verifies the password.
+
+  If there is no customer or the customer doesn't have a password, we call
+  `Pbkdf2.no_user_verify/0` to avoid timing attacks.
+  """
+  def valid_password?(%Volt.Accounts.Customer{hashed_password: hashed_password}, password)
+      when is_binary(hashed_password) and byte_size(password) > 0 do
+    Pbkdf2.verify_pass(password, hashed_password)
+  end
+
+  def valid_password?(_, _) do
+    Pbkdf2.no_user_verify()
+    false
+  end
+
+  @doc """
+  Validates the current password otherwise adds an error to the changeset.
+  """
+  def validate_current_password(changeset, password) do
+    if valid_password?(changeset.data, password) do
+      changeset
+    else
+      add_error(changeset, :current_password, "is not valid")
+    end
+  end
 end
diff --git a/lib/volt/accounts/customer_notifier.ex b/lib/volt/accounts/customer_notifier.ex
new file mode 100644
index 0000000000000000000000000000000000000000..12e8aac12e3bc2281ac29b134acc352e1f4d4da6
--- /dev/null
+++ b/lib/volt/accounts/customer_notifier.ex
@@ -0,0 +1,79 @@
+defmodule Volt.Accounts.CustomerNotifier do
+  import Swoosh.Email
+
+  alias Volt.Mailer
+
+  # Delivers the email using the application mailer.
+  defp deliver(recipient, subject, body) do
+    email =
+      new()
+      |> to(recipient)
+      |> from({"Volt", "contact@example.com"})
+      |> subject(subject)
+      |> text_body(body)
+
+    with {:ok, _metadata} <- Mailer.deliver(email) do
+      {:ok, email}
+    end
+  end
+
+  @doc """
+  Deliver instructions to confirm account.
+  """
+  def deliver_confirmation_instructions(customer, url) do
+    deliver(customer.email, "Confirmation instructions", """
+
+    ==============================
+
+    Hi #{customer.email},
+
+    You can confirm your account by visiting the URL below:
+
+    #{url}
+
+    If you didn't create an account with us, please ignore this.
+
+    ==============================
+    """)
+  end
+
+  @doc """
+  Deliver instructions to reset a customer password.
+  """
+  def deliver_reset_password_instructions(customer, url) do
+    deliver(customer.email, "Reset password instructions", """
+
+    ==============================
+
+    Hi #{customer.email},
+
+    You can reset your password by visiting the URL below:
+
+    #{url}
+
+    If you didn't request this change, please ignore this.
+
+    ==============================
+    """)
+  end
+
+  @doc """
+  Deliver instructions to update a customer email.
+  """
+  def deliver_customer_update_email_instructions(customer, url) do
+    deliver(customer.email, "Update email instructions", """
+
+    ==============================
+
+    Hi #{customer.email},
+
+    You can change your email by visiting the URL below:
+
+    #{url}
+
+    If you didn't request this change, please ignore this.
+
+    ==============================
+    """)
+  end
+end
diff --git a/lib/volt/accounts/customer_token.ex b/lib/volt/accounts/customer_token.ex
new file mode 100644
index 0000000000000000000000000000000000000000..e8573e9d702d5eb808e10f8af44641dd4948b6a8
--- /dev/null
+++ b/lib/volt/accounts/customer_token.ex
@@ -0,0 +1,179 @@
+defmodule Volt.Accounts.CustomerToken do
+  use Ecto.Schema
+  import Ecto.Query
+  alias Volt.Accounts.CustomerToken
+
+  @hash_algorithm :sha256
+  @rand_size 32
+
+  # It is very important to keep the reset password token expiry short,
+  # since someone with access to the email may take over the account.
+  @reset_password_validity_in_days 1
+  @confirm_validity_in_days 7
+  @change_email_validity_in_days 7
+  @session_validity_in_days 60
+
+  schema "customers_tokens" do
+    field :token, :binary
+    field :context, :string
+    field :sent_to, :string
+    belongs_to :customer, Volt.Accounts.Customer
+
+    timestamps(updated_at: false)
+  end
+
+  @doc """
+  Generates a token that will be stored in a signed place,
+  such as session or cookie. As they are signed, those
+  tokens do not need to be hashed.
+
+  The reason why we store session tokens in the database, even
+  though Phoenix already provides a session cookie, is because
+  Phoenix' default session cookies are not persisted, they are
+  simply signed and potentially encrypted. This means they are
+  valid indefinitely, unless you change the signing/encryption
+  salt.
+
+  Therefore, storing them allows individual customer
+  sessions to be expired. The token system can also be extended
+  to store additional data, such as the device used for logging in.
+  You could then use this information to display all valid sessions
+  and devices in the UI and allow users to explicitly expire any
+  session they deem invalid.
+  """
+  def build_session_token(customer) do
+    token = :crypto.strong_rand_bytes(@rand_size)
+    {token, %CustomerToken{token: token, context: "session", customer_id: customer.id}}
+  end
+
+  @doc """
+  Checks if the token is valid and returns its underlying lookup query.
+
+  The query returns the customer found by the token, if any.
+
+  The token is valid if it matches the value in the database and it has
+  not expired (after @session_validity_in_days).
+  """
+  def verify_session_token_query(token) do
+    query =
+      from token in token_and_context_query(token, "session"),
+        join: customer in assoc(token, :customer),
+        where: token.inserted_at > ago(@session_validity_in_days, "day"),
+        select: customer
+
+    {:ok, query}
+  end
+
+  @doc """
+  Builds a token and its hash to be delivered to the customer's email.
+
+  The non-hashed token is sent to the customer email while the
+  hashed part is stored in the database. The original token cannot be reconstructed,
+  which means anyone with read-only access to the database cannot directly use
+  the token in the application to gain access. Furthermore, if the user changes
+  their email in the system, the tokens sent to the previous email are no longer
+  valid.
+
+  Users can easily adapt the existing code to provide other types of delivery methods,
+  for example, by phone numbers.
+  """
+  def build_email_token(customer, context) do
+    build_hashed_token(customer, context, customer.email)
+  end
+
+  defp build_hashed_token(customer, context, sent_to) do
+    token = :crypto.strong_rand_bytes(@rand_size)
+    hashed_token = :crypto.hash(@hash_algorithm, token)
+
+    {Base.url_encode64(token, padding: false),
+     %CustomerToken{
+       token: hashed_token,
+       context: context,
+       sent_to: sent_to,
+       customer_id: customer.id
+     }}
+  end
+
+  @doc """
+  Checks if the token is valid and returns its underlying lookup query.
+
+  The query returns the customer found by the token, if any.
+
+  The given token is valid if it matches its hashed counterpart in the
+  database and the user email has not changed. This function also checks
+  if the token is being used within a certain period, depending on the
+  context. The default contexts supported by this function are either
+  "confirm", for account confirmation emails, and "reset_password",
+  for resetting the password. For verifying requests to change the email,
+  see `verify_change_email_token_query/2`.
+  """
+  def verify_email_token_query(token, context) do
+    case Base.url_decode64(token, padding: false) do
+      {:ok, decoded_token} ->
+        hashed_token = :crypto.hash(@hash_algorithm, decoded_token)
+        days = days_for_context(context)
+
+        query =
+          from token in token_and_context_query(hashed_token, context),
+            join: customer in assoc(token, :customer),
+            where: token.inserted_at > ago(^days, "day") and token.sent_to == customer.email,
+            select: customer
+
+        {:ok, query}
+
+      :error ->
+        :error
+    end
+  end
+
+  defp days_for_context("confirm"), do: @confirm_validity_in_days
+  defp days_for_context("reset_password"), do: @reset_password_validity_in_days
+
+  @doc """
+  Checks if the token is valid and returns its underlying lookup query.
+
+  The query returns the customer found by the token, if any.
+
+  This is used to validate requests to change the customer
+  email. It is different from `verify_email_token_query/2` precisely because
+  `verify_email_token_query/2` validates the email has not changed, which is
+  the starting point by this function.
+
+  The given token is valid if it matches its hashed counterpart in the
+  database and if it has not expired (after @change_email_validity_in_days).
+  The context must always start with "change:".
+  """
+  def verify_change_email_token_query(token, "change:" <> _ = context) do
+    case Base.url_decode64(token, padding: false) do
+      {:ok, decoded_token} ->
+        hashed_token = :crypto.hash(@hash_algorithm, decoded_token)
+
+        query =
+          from token in token_and_context_query(hashed_token, context),
+            where: token.inserted_at > ago(@change_email_validity_in_days, "day")
+
+        {:ok, query}
+
+      :error ->
+        :error
+    end
+  end
+
+  @doc """
+  Returns the token struct for the given token value and context.
+  """
+  def token_and_context_query(token, context) do
+    from CustomerToken, where: [token: ^token, context: ^context]
+  end
+
+  @doc """
+  Gets all tokens for the given customer for the given contexts.
+  """
+  def customer_and_contexts_query(customer, :all) do
+    from t in CustomerToken, where: t.customer_id == ^customer.id
+  end
+
+  def customer_and_contexts_query(customer, [_ | _] = contexts) do
+    from t in CustomerToken, where: t.customer_id == ^customer.id and t.context in ^contexts
+  end
+end
diff --git a/lib/volt/accounts/restaurant.ex b/lib/volt/accounts/restaurant.ex
index 517fdbec725730c6f6cf6340ad4e72498f552a10..097593ec2e45ffdf3368be610342c0d823efafd5 100644
--- a/lib/volt/accounts/restaurant.ex
+++ b/lib/volt/accounts/restaurant.ex
@@ -12,17 +12,156 @@ defmodule Volt.Accounts.Restaurant do
     field :last_name, :string
     field :name, :string
     field :opening_time, :time
-    field :password, :string
+    field :password, :string, virtual: true, redact: true
+    field :hashed_password, :string, redact: true
+    field :confirmed_at, :naive_datetime
     field :phone_number, :string
     field :price_level, :integer
+    has_many :items, Volt.Sales.Item
 
     timestamps()
   end
 
-  @doc false
-  def changeset(restaurant, attrs) do
+  @doc """
+  A restaurant changeset for registration.
+
+  It is important to validate the length of both email and password.
+  Otherwise databases may truncate the email without warnings, which
+  could lead to unpredictable or insecure behaviour. Long passwords may
+  also be very expensive to hash for certain algorithms.
+
+  ## Options
+
+    * `:hash_password` - Hashes the password so it can be stored securely
+      in the database and ensures the password field is cleared to prevent
+      leaks in the logs. If password hashing is not needed and clearing the
+      password field is not desired (like when using this changeset for
+      validations on a LiveView form), this option can be set to `false`.
+      Defaults to `true`.
+  """
+  def registration_changeset(restaurant, attrs, opts \\ []) do
     restaurant
     |> cast(attrs, [:email, :password, :first_name, :last_name, :phone_number, :name, :address, :city, :zip_code, :price_level, :opening_time, :closing_time])
-    |> validate_required([:email, :password, :first_name, :last_name, :phone_number, :name, :address, :price_level, :opening_time, :closing_time, :zip_code, :city])
+    |> validate_profile()
+    |> validate_email()
+    |> validate_password(opts)
+  end
+
+  defp validate_profile(changeset) do
+    changeset
+    |> validate_required([:first_name, :last_name, :phone_number, :name, :address, :price_level, :opening_time, :closing_time, :zip_code, :city])
+    |> validate_format(:phone_number, ~r/^\+(\d{1,3})?\d{10}$/, message: "Invalid phone number. Should start with plus and from 10 to 13 digits")
+    |> validate_format(:zip_code, ~r/^\d{5}$/, message: "Zip code must be 5 digits")
+    |> unique_constraint(:phone_number, name: :customer_unique_phone_number_constraint)
+  end
+
+  defp validate_email(changeset) do
+    changeset
+    |> validate_required([:email])
+    |> validate_format(:email, ~r/^[^\s]+@[^\s]+$/, message: "must have the @ sign and no spaces")
+    |> validate_length(:email, max: 160)
+    |> unsafe_validate_unique(:email, Volt.Repo)
+    |> unique_constraint(:email)
+  end
+
+  defp validate_password(changeset, opts) do
+    changeset
+    |> validate_required([:password])
+    |> validate_length(:password, min: 12, max: 72)
+    # |> 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
+
+  defp maybe_hash_password(changeset, opts) do
+    hash_password? = Keyword.get(opts, :hash_password, true)
+    password = get_change(changeset, :password)
+
+    if hash_password? && password && changeset.valid? do
+      changeset
+      |> put_change(:hashed_password, Pbkdf2.hash_pwd_salt(password))
+      |> delete_change(:password)
+    else
+      changeset
+    end
+  end
+
+  @doc """
+  A restaurant changeset for changing the email.
+
+  It requires the email to change otherwise an error is added.
+  """
+  def email_changeset(restaurant, attrs) do
+    restaurant
+    |> cast(attrs, [:email])
+    |> validate_email()
+    |> case do
+      %{changes: %{email: _}} = changeset -> changeset
+      %{} = changeset -> add_error(changeset, :email, "did not change")
+    end
+  end
+
+  @doc """
+  A restaurant changeset for changing the password.
+
+  ## Options
+
+    * `:hash_password` - Hashes the password so it can be stored securely
+      in the database and ensures the password field is cleared to prevent
+      leaks in the logs. If password hashing is not needed and clearing the
+      password field is not desired (like when using this changeset for
+      validations on a LiveView form), this option can be set to `false`.
+      Defaults to `true`.
+  """
+  def password_changeset(restaurant, attrs, opts \\ []) do
+    restaurant
+    |> cast(attrs, [:password])
+    |> validate_confirmation(:password, message: "does not match password")
+    |> validate_password(opts)
+  end
+
+  @doc """
+  A restaurant changeset for changing the profile.
+  """
+  def profile_changeset(restaurant, attrs) do
+    restaurant
+    |> cast(attrs, [:first_name, :last_name, :phone_number, :name, :address, :city, :zip_code, :price_level, :opening_time, :closing_time])
+    |> validate_profile()
+  end
+
+  @doc """
+  Confirms the account by setting `confirmed_at`.
+  """
+  def confirm_changeset(restaurant) do
+    now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
+    change(restaurant, confirmed_at: now)
+  end
+
+  @doc """
+  Verifies the password.
+
+  If there is no restaurant or the restaurant doesn't have a password, we call
+  `Pbkdf2.no_user_verify/0` to avoid timing attacks.
+  """
+  def valid_password?(%Volt.Accounts.Restaurant{hashed_password: hashed_password}, password)
+      when is_binary(hashed_password) and byte_size(password) > 0 do
+    Pbkdf2.verify_pass(password, hashed_password)
+  end
+
+  def valid_password?(_, _) do
+    Pbkdf2.no_user_verify()
+    false
+  end
+
+  @doc """
+  Validates the current password otherwise adds an error to the changeset.
+  """
+  def validate_current_password(changeset, password) do
+    if valid_password?(changeset.data, password) do
+      changeset
+    else
+      add_error(changeset, :current_password, "is not valid")
+    end
   end
 end
diff --git a/lib/volt/accounts/restaurant_notifier.ex b/lib/volt/accounts/restaurant_notifier.ex
new file mode 100644
index 0000000000000000000000000000000000000000..bec4c96104a22e3b0cf7e7b5462729b6903bb568
--- /dev/null
+++ b/lib/volt/accounts/restaurant_notifier.ex
@@ -0,0 +1,79 @@
+defmodule Volt.Accounts.RestaurantNotifier do
+  import Swoosh.Email
+
+  alias Volt.Mailer
+
+  # Delivers the email using the application mailer.
+  defp deliver(recipient, subject, body) do
+    email =
+      new()
+      |> to(recipient)
+      |> from({"Volt", "contact@example.com"})
+      |> subject(subject)
+      |> text_body(body)
+
+    with {:ok, _metadata} <- Mailer.deliver(email) do
+      {:ok, email}
+    end
+  end
+
+  @doc """
+  Deliver instructions to confirm account.
+  """
+  def deliver_confirmation_instructions(restaurant, url) do
+    deliver(restaurant.email, "Confirmation instructions", """
+
+    ==============================
+
+    Hi #{restaurant.email},
+
+    You can confirm your account by visiting the URL below:
+
+    #{url}
+
+    If you didn't create an account with us, please ignore this.
+
+    ==============================
+    """)
+  end
+
+  @doc """
+  Deliver instructions to reset a restaurant password.
+  """
+  def deliver_reset_password_instructions(restaurant, url) do
+    deliver(restaurant.email, "Reset password instructions", """
+
+    ==============================
+
+    Hi #{restaurant.email},
+
+    You can reset your password by visiting the URL below:
+
+    #{url}
+
+    If you didn't request this change, please ignore this.
+
+    ==============================
+    """)
+  end
+
+  @doc """
+  Deliver instructions to update a restaurant email.
+  """
+  def deliver_restaurant_update_email_instructions(restaurant, url) do
+    deliver(restaurant.email, "Update email instructions", """
+
+    ==============================
+
+    Hi #{restaurant.email},
+
+    You can change your email by visiting the URL below:
+
+    #{url}
+
+    If you didn't request this change, please ignore this.
+
+    ==============================
+    """)
+  end
+end
diff --git a/lib/volt/accounts/restaurant_token.ex b/lib/volt/accounts/restaurant_token.ex
new file mode 100644
index 0000000000000000000000000000000000000000..796d2fad765ac0babcb7520e5b8e95dd35a3a8d5
--- /dev/null
+++ b/lib/volt/accounts/restaurant_token.ex
@@ -0,0 +1,179 @@
+defmodule Volt.Accounts.RestaurantToken do
+  use Ecto.Schema
+  import Ecto.Query
+  alias Volt.Accounts.RestaurantToken
+
+  @hash_algorithm :sha256
+  @rand_size 32
+
+  # It is very important to keep the reset password token expiry short,
+  # since someone with access to the email may take over the account.
+  @reset_password_validity_in_days 1
+  @confirm_validity_in_days 7
+  @change_email_validity_in_days 7
+  @session_validity_in_days 60
+
+  schema "restaurants_tokens" do
+    field :token, :binary
+    field :context, :string
+    field :sent_to, :string
+    belongs_to :restaurant, Volt.Accounts.Restaurant
+
+    timestamps(updated_at: false)
+  end
+
+  @doc """
+  Generates a token that will be stored in a signed place,
+  such as session or cookie. As they are signed, those
+  tokens do not need to be hashed.
+
+  The reason why we store session tokens in the database, even
+  though Phoenix already provides a session cookie, is because
+  Phoenix' default session cookies are not persisted, they are
+  simply signed and potentially encrypted. This means they are
+  valid indefinitely, unless you change the signing/encryption
+  salt.
+
+  Therefore, storing them allows individual restaurant
+  sessions to be expired. The token system can also be extended
+  to store additional data, such as the device used for logging in.
+  You could then use this information to display all valid sessions
+  and devices in the UI and allow users to explicitly expire any
+  session they deem invalid.
+  """
+  def build_session_token(restaurant) do
+    token = :crypto.strong_rand_bytes(@rand_size)
+    {token, %RestaurantToken{token: token, context: "session", restaurant_id: restaurant.id}}
+  end
+
+  @doc """
+  Checks if the token is valid and returns its underlying lookup query.
+
+  The query returns the restaurant found by the token, if any.
+
+  The token is valid if it matches the value in the database and it has
+  not expired (after @session_validity_in_days).
+  """
+  def verify_session_token_query(token) do
+    query =
+      from token in token_and_context_query(token, "session"),
+        join: restaurant in assoc(token, :restaurant),
+        where: token.inserted_at > ago(@session_validity_in_days, "day"),
+        select: restaurant
+
+    {:ok, query}
+  end
+
+  @doc """
+  Builds a token and its hash to be delivered to the restaurant's email.
+
+  The non-hashed token is sent to the restaurant email while the
+  hashed part is stored in the database. The original token cannot be reconstructed,
+  which means anyone with read-only access to the database cannot directly use
+  the token in the application to gain access. Furthermore, if the user changes
+  their email in the system, the tokens sent to the previous email are no longer
+  valid.
+
+  Users can easily adapt the existing code to provide other types of delivery methods,
+  for example, by phone numbers.
+  """
+  def build_email_token(restaurant, context) do
+    build_hashed_token(restaurant, context, restaurant.email)
+  end
+
+  defp build_hashed_token(restaurant, context, sent_to) do
+    token = :crypto.strong_rand_bytes(@rand_size)
+    hashed_token = :crypto.hash(@hash_algorithm, token)
+
+    {Base.url_encode64(token, padding: false),
+     %RestaurantToken{
+       token: hashed_token,
+       context: context,
+       sent_to: sent_to,
+       restaurant_id: restaurant.id
+     }}
+  end
+
+  @doc """
+  Checks if the token is valid and returns its underlying lookup query.
+
+  The query returns the restaurant found by the token, if any.
+
+  The given token is valid if it matches its hashed counterpart in the
+  database and the user email has not changed. This function also checks
+  if the token is being used within a certain period, depending on the
+  context. The default contexts supported by this function are either
+  "confirm", for account confirmation emails, and "reset_password",
+  for resetting the password. For verifying requests to change the email,
+  see `verify_change_email_token_query/2`.
+  """
+  def verify_email_token_query(token, context) do
+    case Base.url_decode64(token, padding: false) do
+      {:ok, decoded_token} ->
+        hashed_token = :crypto.hash(@hash_algorithm, decoded_token)
+        days = days_for_context(context)
+
+        query =
+          from token in token_and_context_query(hashed_token, context),
+            join: restaurant in assoc(token, :restaurant),
+            where: token.inserted_at > ago(^days, "day") and token.sent_to == restaurant.email,
+            select: restaurant
+
+        {:ok, query}
+
+      :error ->
+        :error
+    end
+  end
+
+  defp days_for_context("confirm"), do: @confirm_validity_in_days
+  defp days_for_context("reset_password"), do: @reset_password_validity_in_days
+
+  @doc """
+  Checks if the token is valid and returns its underlying lookup query.
+
+  The query returns the restaurant found by the token, if any.
+
+  This is used to validate requests to change the restaurant
+  email. It is different from `verify_email_token_query/2` precisely because
+  `verify_email_token_query/2` validates the email has not changed, which is
+  the starting point by this function.
+
+  The given token is valid if it matches its hashed counterpart in the
+  database and if it has not expired (after @change_email_validity_in_days).
+  The context must always start with "change:".
+  """
+  def verify_change_email_token_query(token, "change:" <> _ = context) do
+    case Base.url_decode64(token, padding: false) do
+      {:ok, decoded_token} ->
+        hashed_token = :crypto.hash(@hash_algorithm, decoded_token)
+
+        query =
+          from token in token_and_context_query(hashed_token, context),
+            where: token.inserted_at > ago(@change_email_validity_in_days, "day")
+
+        {:ok, query}
+
+      :error ->
+        :error
+    end
+  end
+
+  @doc """
+  Returns the token struct for the given token value and context.
+  """
+  def token_and_context_query(token, context) do
+    from RestaurantToken, where: [token: ^token, context: ^context]
+  end
+
+  @doc """
+  Gets all tokens for the given restaurant for the given contexts.
+  """
+  def restaurant_and_contexts_query(restaurant, :all) do
+    from t in RestaurantToken, where: t.restaurant_id == ^restaurant.id
+  end
+
+  def restaurant_and_contexts_query(restaurant, [_ | _] = contexts) do
+    from t in RestaurantToken, where: t.restaurant_id == ^restaurant.id and t.context in ^contexts
+  end
+end
diff --git a/lib/volt/sales.ex b/lib/volt/sales.ex
new file mode 100644
index 0000000000000000000000000000000000000000..70423d2ccec793bedf115c0a0ff775926218e634
--- /dev/null
+++ b/lib/volt/sales.ex
@@ -0,0 +1,104 @@
+defmodule Volt.Sales do
+  @moduledoc """
+  The Sales context.
+  """
+
+  import Ecto.Query, warn: false
+  alias Volt.Repo
+
+  alias Volt.Sales.Item
+
+  @doc """
+  Returns the list of items.
+
+  ## Examples
+
+      iex> list_items()
+      [%Item{}, ...]
+
+  """
+  def list_items do
+    Repo.all(Item)
+  end
+
+  @doc """
+  Gets a single item.
+
+  Raises `Ecto.NoResultsError` if the Item does not exist.
+
+  ## Examples
+
+      iex> get_item!(123)
+      %Item{}
+
+      iex> get_item!(456)
+      ** (Ecto.NoResultsError)
+
+  """
+  def get_item!(id), do: Repo.get!(Item, id)
+
+  @doc """
+  Creates a item.
+
+  ## Examples
+
+      iex> create_item(%{field: value})
+      {:ok, %Item{}}
+
+      iex> create_item(%{field: bad_value})
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def create_item(attrs \\ %{}) do
+    %Item{}
+    |> Item.changeset(attrs)
+    |> Repo.insert()
+  end
+
+  @doc """
+  Updates a item.
+
+  ## Examples
+
+      iex> update_item(item, %{field: new_value})
+      {:ok, %Item{}}
+
+      iex> update_item(item, %{field: bad_value})
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def update_item(%Item{} = item, attrs) do
+    item
+    |> Item.changeset(attrs)
+    |> Repo.update()
+  end
+
+  @doc """
+  Deletes a item.
+
+  ## Examples
+
+      iex> delete_item(item)
+      {:ok, %Item{}}
+
+      iex> delete_item(item)
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def delete_item(%Item{} = item) do
+    Repo.delete(item)
+  end
+
+  @doc """
+  Returns an `%Ecto.Changeset{}` for tracking item changes.
+
+  ## Examples
+
+      iex> change_item(item)
+      %Ecto.Changeset{data: %Item{}}
+
+  """
+  def change_item(%Item{} = item, attrs \\ %{}) do
+    Item.changeset(item, attrs)
+  end
+end
diff --git a/lib/volt/sales/item.ex b/lib/volt/sales/item.ex
new file mode 100644
index 0000000000000000000000000000000000000000..d22e6cf99a83745853af301c595bab1f998c5c51
--- /dev/null
+++ b/lib/volt/sales/item.ex
@@ -0,0 +1,20 @@
+defmodule Volt.Sales.Item do
+  use Ecto.Schema
+  import Ecto.Changeset
+
+  schema "items" do
+    field :description, :string
+    field :name, :string
+    field :unit_price, :string
+    belongs_to :restaurant, Volt.Accounts.Restaurant
+
+    timestamps()
+  end
+
+  @doc false
+  def changeset(item, attrs) do
+    item
+    |> cast(attrs, [:name, :description, :unit_price])
+    |> validate_required([:name, :unit_price])
+  end
+end
diff --git a/lib/volt_web/controllers/courier_auth.ex b/lib/volt_web/controllers/courier_auth.ex
new file mode 100644
index 0000000000000000000000000000000000000000..50b90357cfcf71193b6f0935b47e232f4bf3bab2
--- /dev/null
+++ b/lib/volt_web/controllers/courier_auth.ex
@@ -0,0 +1,149 @@
+defmodule VoltWeb.CourierAuth do
+  import Plug.Conn
+  import Phoenix.Controller
+
+  alias Volt.Accounts
+  alias VoltWeb.Router.Helpers, as: Routes
+
+  # Make the remember me cookie valid for 60 days.
+  # If you want bump or reduce this value, also change
+  # the token expiry itself in CourierToken.
+  @max_age 60 * 60 * 24 * 60
+  @remember_me_cookie "_volt_web_courier_remember_me"
+  @remember_me_options [sign: true, max_age: @max_age, same_site: "Lax"]
+
+  @doc """
+  Logs the courier 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_courier(conn, courier, params \\ %{}) do
+    token = Accounts.generate_courier_session_token(courier)
+    courier_return_to = get_session(conn, :courier_return_to)
+
+    conn
+    |> renew_session()
+    |> put_session(:courier_token, token)
+    |> put_session(:live_socket_id, "couriers_sessions:#{Base.url_encode64(token)}")
+    |> maybe_write_remember_me_cookie(token, params)
+    |> redirect(to: courier_return_to || signed_in_path(conn))
+  end
+
+  defp maybe_write_remember_me_cookie(conn, token, %{"remember_me" => "true"}) do
+    put_resp_cookie(conn, @remember_me_cookie, token, @remember_me_options)
+  end
+
+  defp maybe_write_remember_me_cookie(conn, _token, _params) 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 courier out.
+
+  It clears all session data for safety. See renew_session.
+  """
+  def log_out_courier(conn) do
+    courier_token = get_session(conn, :courier_token)
+    courier_token && Accounts.delete_courier_session_token(courier_token)
+
+    if live_socket_id = get_session(conn, :live_socket_id) do
+      VoltWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
+    end
+
+    conn
+    |> renew_session()
+    |> delete_resp_cookie(@remember_me_cookie)
+    |> redirect(to: "/")
+  end
+
+  @doc """
+  Authenticates the courier by looking into the session
+  and remember me token.
+  """
+  def fetch_current_courier(conn, _opts) do
+    {courier_token, conn} = ensure_courier_token(conn)
+    courier = courier_token && Accounts.get_courier_by_session_token(courier_token)
+    assign(conn, :current_courier, courier)
+  end
+
+  defp ensure_courier_token(conn) do
+    if courier_token = get_session(conn, :courier_token) do
+      {courier_token, conn}
+    else
+      conn = fetch_cookies(conn, signed: [@remember_me_cookie])
+
+      if courier_token = conn.cookies[@remember_me_cookie] do
+        {courier_token, put_session(conn, :courier_token, courier_token)}
+      else
+        {nil, conn}
+      end
+    end
+  end
+
+  @doc """
+  Used for routes that require the courier to not be authenticated.
+  """
+  def redirect_if_courier_is_authenticated(conn, _opts) do
+    if conn.assigns[:current_courier] do
+      conn
+      |> redirect(to: signed_in_path(conn))
+      |> halt()
+    else
+      conn
+    end
+  end
+
+  @doc """
+  Used for routes that require the courier to be authenticated.
+
+  If you want to enforce the courier email is confirmed before
+  they use the application at all, here would be a good place.
+  """
+  def require_authenticated_courier(conn, _opts) do
+    if conn.assigns[:current_courier] do
+      conn
+    else
+      conn
+      |> put_flash(:error, "You must log in to access this page.")
+      |> maybe_store_return_to()
+      |> redirect(to: Routes.courier_session_path(conn, :new))
+      |> halt()
+    end
+  end
+
+  defp maybe_store_return_to(%{method: "GET"} = conn) do
+    put_session(conn, :courier_return_to, current_path(conn))
+  end
+
+  defp maybe_store_return_to(conn), do: conn
+
+  defp signed_in_path(_conn), do: "/couriers/dashboard"
+end
diff --git a/lib/volt_web/controllers/courier_confirmation_controller.ex b/lib/volt_web/controllers/courier_confirmation_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..50a24f5a0b927ae4c264b680cd7c3e735ba8680a
--- /dev/null
+++ b/lib/volt_web/controllers/courier_confirmation_controller.ex
@@ -0,0 +1,56 @@
+defmodule VoltWeb.CourierConfirmationController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+
+  def new(conn, _params) do
+    render(conn, "new.html")
+  end
+
+  def create(conn, %{"courier" => %{"email" => email}}) do
+    if courier = Accounts.get_courier_by_email(email) do
+      Accounts.deliver_courier_confirmation_instructions(
+        courier,
+        &Routes.courier_confirmation_url(conn, :edit, &1)
+      )
+    end
+
+    conn
+    |> put_flash(
+      :info,
+      "If your email is in our system and it has not been confirmed yet, " <>
+        "you will receive an email with instructions shortly."
+    )
+    |> redirect(to: "/")
+  end
+
+  def edit(conn, %{"token" => token}) do
+    render(conn, "edit.html", token: token)
+  end
+
+  # Do not log in the courier after confirmation to avoid a
+  # leaked token giving the courier access to the account.
+  def update(conn, %{"token" => token}) do
+    case Accounts.confirm_courier(token) do
+      {:ok, _} ->
+        conn
+        |> put_flash(:info, "Courier confirmed successfully.")
+        |> redirect(to: "/")
+
+      :error ->
+        # If there is a current courier and the account was already confirmed,
+        # then odds are that the confirmation link was already visited, either
+        # by some automation or by the courier themselves, so we redirect without
+        # a warning message.
+        case conn.assigns do
+          %{current_courier: %{confirmed_at: confirmed_at}} when not is_nil(confirmed_at) ->
+            redirect(conn, to: "/")
+
+          %{} ->
+            conn
+            |> put_flash(:error, "Courier confirmation link is invalid or it has expired.")
+            |> redirect(to: "/")
+        end
+    end
+  end
+end
diff --git a/lib/volt_web/controllers/courier_controller.ex b/lib/volt_web/controllers/courier_controller.ex
deleted file mode 100644
index 0d093fce59a3ad32f0fd41e10aa3314570c5bd92..0000000000000000000000000000000000000000
--- a/lib/volt_web/controllers/courier_controller.ex
+++ /dev/null
@@ -1,62 +0,0 @@
-defmodule VoltWeb.CourierController do
-  use VoltWeb, :controller
-
-  alias Volt.Accounts
-  alias Volt.Accounts.Courier
-
-  def index(conn, _params) do
-    couriers = Accounts.list_couriers()
-    render(conn, "index.html", couriers: couriers)
-  end
-
-  def new(conn, _params) do
-    changeset = Accounts.change_courier(%Courier{})
-    render(conn, "new.html", changeset: changeset)
-  end
-
-  def create(conn, %{"courier" => courier_params}) do
-    case Accounts.create_courier(courier_params) do
-      {:ok, courier} ->
-        conn
-        |> put_flash(:info, "Courier created successfully.")
-        |> redirect(to: Routes.courier_path(conn, :show, courier))
-
-      {:error, %Ecto.Changeset{} = changeset} ->
-        render(conn, "new.html", changeset: changeset)
-    end
-  end
-
-  def show(conn, %{"id" => id}) do
-    courier = Accounts.get_courier!(id)
-    render(conn, "show.html", courier: courier)
-  end
-
-  def edit(conn, %{"id" => id}) do
-    courier = Accounts.get_courier!(id)
-    changeset = Accounts.change_courier(courier)
-    render(conn, "edit.html", courier: courier, changeset: changeset)
-  end
-
-  def update(conn, %{"id" => id, "courier" => courier_params}) do
-    courier = Accounts.get_courier!(id)
-
-    case Accounts.update_courier(courier, courier_params) do
-      {:ok, courier} ->
-        conn
-        |> put_flash(:info, "Courier updated successfully.")
-        |> redirect(to: Routes.courier_path(conn, :show, courier))
-
-      {:error, %Ecto.Changeset{} = changeset} ->
-        render(conn, "edit.html", courier: courier, changeset: changeset)
-    end
-  end
-
-  def delete(conn, %{"id" => id}) do
-    courier = Accounts.get_courier!(id)
-    {:ok, _courier} = Accounts.delete_courier(courier)
-
-    conn
-    |> put_flash(:info, "Courier deleted successfully.")
-    |> redirect(to: Routes.courier_path(conn, :index))
-  end
-end
diff --git a/lib/volt_web/controllers/courier_dashboard_controller.ex b/lib/volt_web/controllers/courier_dashboard_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..34b7bbb41ce4335dd32ea7ac1795634283063832
--- /dev/null
+++ b/lib/volt_web/controllers/courier_dashboard_controller.ex
@@ -0,0 +1,7 @@
+defmodule VoltWeb.CourierDashboardController do
+  use VoltWeb, :controller
+
+  def index(conn, _params) do
+    render(conn, "index.html")
+  end
+end
diff --git a/lib/volt_web/controllers/courier_registration_controller.ex b/lib/volt_web/controllers/courier_registration_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..a1e1f623faca27298581d82122b01a80c2b808c0
--- /dev/null
+++ b/lib/volt_web/controllers/courier_registration_controller.ex
@@ -0,0 +1,30 @@
+defmodule VoltWeb.CourierRegistrationController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+  alias Volt.Accounts.Courier
+  alias VoltWeb.CourierAuth
+
+  def new(conn, _params) do
+    changeset = Accounts.change_courier_registration(%Courier{})
+    render(conn, "new.html", changeset: changeset)
+  end
+
+  def create(conn, %{"courier" => courier_params}) do
+    case Accounts.register_courier(courier_params) do
+      {:ok, courier} ->
+        {:ok, _} =
+          Accounts.deliver_courier_confirmation_instructions(
+            courier,
+            &Routes.courier_confirmation_url(conn, :edit, &1)
+          )
+
+        conn
+        |> put_flash(:info, "Courier created successfully.")
+        |> CourierAuth.log_in_courier(courier)
+
+      {:error, %Ecto.Changeset{} = changeset} ->
+        render(conn, "new.html", changeset: changeset)
+    end
+  end
+end
diff --git a/lib/volt_web/controllers/courier_reset_password_controller.ex b/lib/volt_web/controllers/courier_reset_password_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..dd32253070a4f3be032e3a042645aa904cc82e5a
--- /dev/null
+++ b/lib/volt_web/controllers/courier_reset_password_controller.ex
@@ -0,0 +1,58 @@
+defmodule VoltWeb.CourierResetPasswordController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+
+  plug :get_courier_by_reset_password_token when action in [:edit, :update]
+
+  def new(conn, _params) do
+    render(conn, "new.html")
+  end
+
+  def create(conn, %{"courier" => %{"email" => email}}) do
+    if courier = Accounts.get_courier_by_email(email) do
+      Accounts.deliver_courier_reset_password_instructions(
+        courier,
+        &Routes.courier_reset_password_url(conn, :edit, &1)
+      )
+    end
+
+    conn
+    |> put_flash(
+      :info,
+      "If your email is in our system, you will receive instructions to reset your password shortly."
+    )
+    |> redirect(to: "/")
+  end
+
+  def edit(conn, _params) do
+    render(conn, "edit.html", changeset: Accounts.change_courier_password(conn.assigns.courier))
+  end
+
+  # Do not log in the courier after reset password to avoid a
+  # leaked token giving the courier access to the account.
+  def update(conn, %{"courier" => courier_params}) do
+    case Accounts.reset_courier_password(conn.assigns.courier, courier_params) do
+      {:ok, _} ->
+        conn
+        |> put_flash(:info, "Password reset successfully.")
+        |> redirect(to: Routes.courier_session_path(conn, :new))
+
+      {:error, changeset} ->
+        render(conn, "edit.html", changeset: changeset)
+    end
+  end
+
+  defp get_courier_by_reset_password_token(conn, _opts) do
+    %{"token" => token} = conn.params
+
+    if courier = Accounts.get_courier_by_reset_password_token(token) do
+      conn |> assign(:courier, courier) |> assign(:token, token)
+    else
+      conn
+      |> put_flash(:error, "Reset password link is invalid or it has expired.")
+      |> redirect(to: "/")
+      |> halt()
+    end
+  end
+end
diff --git a/lib/volt_web/controllers/courier_session_controller.ex b/lib/volt_web/controllers/courier_session_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..33cd154dc41675e6ffc88d1b2e8c1e4e12b37ccb
--- /dev/null
+++ b/lib/volt_web/controllers/courier_session_controller.ex
@@ -0,0 +1,27 @@
+defmodule VoltWeb.CourierSessionController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+  alias VoltWeb.CourierAuth
+
+  def new(conn, _params) do
+    render(conn, "new.html", error_message: nil)
+  end
+
+  def create(conn, %{"courier" => courier_params}) do
+    %{"email" => email, "password" => password} = courier_params
+
+    if courier = Accounts.get_courier_by_email_and_password(email, password) do
+      CourierAuth.log_in_courier(conn, courier, courier_params)
+    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")
+    end
+  end
+
+  def delete(conn, _params) do
+    conn
+    |> put_flash(:info, "Logged out successfully.")
+    |> CourierAuth.log_out_courier()
+  end
+end
diff --git a/lib/volt_web/controllers/courier_settings_controller.ex b/lib/volt_web/controllers/courier_settings_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..9b6844452cd9626ba3e4ba8e343dc9ad90d89cb4
--- /dev/null
+++ b/lib/volt_web/controllers/courier_settings_controller.ex
@@ -0,0 +1,91 @@
+defmodule VoltWeb.CourierSettingsController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+  alias VoltWeb.CourierAuth
+
+  plug :assign_changesets
+
+  def edit(conn, _params) do
+    render(conn, "edit.html")
+  end
+
+  def update(conn, %{"action" => "update_email"} = params) do
+    %{"current_password" => password, "courier" => courier_params} = params
+    courier = conn.assigns.current_courier
+
+    case Accounts.apply_courier_email(courier, password, courier_params) do
+      {:ok, applied_courier} ->
+        Accounts.deliver_courier_update_email_instructions(
+          applied_courier,
+          courier.email,
+          &Routes.courier_settings_url(conn, :confirm_email, &1)
+        )
+
+        conn
+        |> put_flash(
+          :info,
+          "A link to confirm your email change has been sent to the new address."
+        )
+        |> redirect(to: Routes.courier_settings_path(conn, :edit))
+
+      {:error, changeset} ->
+        render(conn, "edit.html", email_changeset: changeset)
+    end
+  end
+
+  def update(conn, %{"action" => "update_password"} = params) do
+    %{"current_password" => password, "courier" => courier_params} = params
+    courier = conn.assigns.current_courier
+
+    case Accounts.update_courier_password(courier, password, courier_params) do
+      {:ok, courier} ->
+        conn
+        |> put_flash(:info, "Password updated successfully.")
+        |> put_session(:courier_return_to, Routes.courier_settings_path(conn, :edit))
+        |> CourierAuth.log_in_courier(courier)
+
+      {:error, changeset} ->
+        render(conn, "edit.html", password_changeset: changeset)
+    end
+  end
+
+  def update(conn, %{"action" => "update_profile"} = params) do
+    %{"current_password" => password, "courier" => courier_params} = params
+    courier = conn.assigns.current_courier
+
+    case Accounts.update_courier_profile(courier, password, courier_params) do
+      {:ok, courier} ->
+        conn
+        |> put_flash(:info, "Profile updated successfully.")
+        |> put_session(:courier_return_to, Routes.courier_settings_path(conn, :edit))
+        |> CourierAuth.log_in_courier(courier)
+
+      {:error, changeset} ->
+        render(conn, "edit.html", profile_changeset: changeset)
+    end
+  end
+
+  def confirm_email(conn, %{"token" => token}) do
+    case Accounts.update_courier_email(conn.assigns.current_courier, token) do
+      :ok ->
+        conn
+        |> put_flash(:info, "Email changed successfully.")
+        |> redirect(to: Routes.courier_settings_path(conn, :edit))
+
+      :error ->
+        conn
+        |> put_flash(:error, "Email change link is invalid or it has expired.")
+        |> redirect(to: Routes.courier_settings_path(conn, :edit))
+    end
+  end
+
+  defp assign_changesets(conn, _opts) do
+    courier = conn.assigns.current_courier
+
+    conn
+    |> assign(:email_changeset, Accounts.change_courier_email(courier))
+    |> assign(:password_changeset, Accounts.change_courier_password(courier))
+    |> assign(:profile_changeset, Accounts.change_courier_profile(courier))
+  end
+end
diff --git a/lib/volt_web/controllers/customer_auth.ex b/lib/volt_web/controllers/customer_auth.ex
new file mode 100644
index 0000000000000000000000000000000000000000..e3f1af2afd6c35f1cc274c0474425b1ab9f4ddeb
--- /dev/null
+++ b/lib/volt_web/controllers/customer_auth.ex
@@ -0,0 +1,149 @@
+defmodule VoltWeb.CustomerAuth do
+  import Plug.Conn
+  import Phoenix.Controller
+
+  alias Volt.Accounts
+  alias VoltWeb.Router.Helpers, as: Routes
+
+  # Make the remember me cookie valid for 60 days.
+  # If you want bump or reduce this value, also change
+  # the token expiry itself in CustomerToken.
+  @max_age 60 * 60 * 24 * 60
+  @remember_me_cookie "_volt_web_customer_remember_me"
+  @remember_me_options [sign: true, max_age: @max_age, same_site: "Lax"]
+
+  @doc """
+  Logs the customer 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_customer(conn, customer, params \\ %{}) do
+    token = Accounts.generate_customer_session_token(customer)
+    customer_return_to = get_session(conn, :customer_return_to)
+
+    conn
+    |> renew_session()
+    |> put_session(:customer_token, token)
+    |> put_session(:live_socket_id, "customers_sessions:#{Base.url_encode64(token)}")
+    |> maybe_write_remember_me_cookie(token, params)
+    |> redirect(to: customer_return_to || signed_in_path(conn))
+  end
+
+  defp maybe_write_remember_me_cookie(conn, token, %{"remember_me" => "true"}) do
+    put_resp_cookie(conn, @remember_me_cookie, token, @remember_me_options)
+  end
+
+  defp maybe_write_remember_me_cookie(conn, _token, _params) 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 customer out.
+
+  It clears all session data for safety. See renew_session.
+  """
+  def log_out_customer(conn) do
+    customer_token = get_session(conn, :customer_token)
+    customer_token && Accounts.delete_customer_session_token(customer_token)
+
+    if live_socket_id = get_session(conn, :live_socket_id) do
+      VoltWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
+    end
+
+    conn
+    |> renew_session()
+    |> delete_resp_cookie(@remember_me_cookie)
+    |> redirect(to: "/")
+  end
+
+  @doc """
+  Authenticates the customer by looking into the session
+  and remember me token.
+  """
+  def fetch_current_customer(conn, _opts) do
+    {customer_token, conn} = ensure_customer_token(conn)
+    customer = customer_token && Accounts.get_customer_by_session_token(customer_token)
+    assign(conn, :current_customer, customer)
+  end
+
+  defp ensure_customer_token(conn) do
+    if customer_token = get_session(conn, :customer_token) do
+      {customer_token, conn}
+    else
+      conn = fetch_cookies(conn, signed: [@remember_me_cookie])
+
+      if customer_token = conn.cookies[@remember_me_cookie] do
+        {customer_token, put_session(conn, :customer_token, customer_token)}
+      else
+        {nil, conn}
+      end
+    end
+  end
+
+  @doc """
+  Used for routes that require the customer to not be authenticated.
+  """
+  def redirect_if_customer_is_authenticated(conn, _opts) do
+    if conn.assigns[:current_customer] do
+      conn
+      |> redirect(to: signed_in_path(conn))
+      |> halt()
+    else
+      conn
+    end
+  end
+
+  @doc """
+  Used for routes that require the customer to be authenticated.
+
+  If you want to enforce the customer email is confirmed before
+  they use the application at all, here would be a good place.
+  """
+  def require_authenticated_customer(conn, _opts) do
+    if conn.assigns[:current_customer] do
+      conn
+    else
+      conn
+      |> put_flash(:error, "You must log in to access this page.")
+      |> maybe_store_return_to()
+      |> redirect(to: Routes.customer_session_path(conn, :new))
+      |> halt()
+    end
+  end
+
+  defp maybe_store_return_to(%{method: "GET"} = conn) do
+    put_session(conn, :customer_return_to, current_path(conn))
+  end
+
+  defp maybe_store_return_to(conn), do: conn
+
+  defp signed_in_path(_conn), do: "/customers/dashboard"
+end
diff --git a/lib/volt_web/controllers/customer_confirmation_controller.ex b/lib/volt_web/controllers/customer_confirmation_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..e0881fd9592571c1a3a214523943bd92478a0066
--- /dev/null
+++ b/lib/volt_web/controllers/customer_confirmation_controller.ex
@@ -0,0 +1,56 @@
+defmodule VoltWeb.CustomerConfirmationController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+
+  def new(conn, _params) do
+    render(conn, "new.html")
+  end
+
+  def create(conn, %{"customer" => %{"email" => email}}) do
+    if customer = Accounts.get_customer_by_email(email) do
+      Accounts.deliver_customer_confirmation_instructions(
+        customer,
+        &Routes.customer_confirmation_url(conn, :edit, &1)
+      )
+    end
+
+    conn
+    |> put_flash(
+      :info,
+      "If your email is in our system and it has not been confirmed yet, " <>
+        "you will receive an email with instructions shortly."
+    )
+    |> redirect(to: "/")
+  end
+
+  def edit(conn, %{"token" => token}) do
+    render(conn, "edit.html", token: token)
+  end
+
+  # Do not log in the customer after confirmation to avoid a
+  # leaked token giving the customer access to the account.
+  def update(conn, %{"token" => token}) do
+    case Accounts.confirm_customer(token) do
+      {:ok, _} ->
+        conn
+        |> put_flash(:info, "Customer confirmed successfully.")
+        |> redirect(to: "/")
+
+      :error ->
+        # If there is a current customer and the account was already confirmed,
+        # then odds are that the confirmation link was already visited, either
+        # by some automation or by the customer themselves, so we redirect without
+        # a warning message.
+        case conn.assigns do
+          %{current_customer: %{confirmed_at: confirmed_at}} when not is_nil(confirmed_at) ->
+            redirect(conn, to: "/")
+
+          %{} ->
+            conn
+            |> put_flash(:error, "Customer confirmation link is invalid or it has expired.")
+            |> redirect(to: "/")
+        end
+    end
+  end
+end
diff --git a/lib/volt_web/controllers/customer_controller.ex b/lib/volt_web/controllers/customer_controller.ex
deleted file mode 100644
index 038a17b54f936b9eda0002e988732e9eda30fb67..0000000000000000000000000000000000000000
--- a/lib/volt_web/controllers/customer_controller.ex
+++ /dev/null
@@ -1,62 +0,0 @@
-defmodule VoltWeb.CustomerController do
-  use VoltWeb, :controller
-
-  alias Volt.Accounts
-  alias Volt.Accounts.Customer
-
-  def index(conn, _params) do
-    customers = Accounts.list_customers()
-    render(conn, "index.html", customers: customers)
-  end
-
-  def new(conn, _params) do
-    changeset = Accounts.change_customer(%Customer{})
-    render(conn, "new.html", changeset: changeset)
-  end
-
-  def create(conn, %{"customer" => customer_params}) do
-    case Accounts.create_customer(customer_params) do
-      {:ok, customer} ->
-        conn
-        |> put_flash(:info, "Customer created successfully.")
-        |> redirect(to: Routes.customer_path(conn, :show, customer))
-
-      {:error, %Ecto.Changeset{} = changeset} ->
-        render(conn, "new.html", changeset: changeset)
-    end
-  end
-
-  def show(conn, %{"id" => id}) do
-    customer = Accounts.get_customer!(id)
-    render(conn, "show.html", customer: customer)
-  end
-
-  def edit(conn, %{"id" => id}) do
-    customer = Accounts.get_customer!(id)
-    changeset = Accounts.change_customer(customer)
-    render(conn, "edit.html", customer: customer, changeset: changeset)
-  end
-
-  def update(conn, %{"id" => id, "customer" => customer_params}) do
-    customer = Accounts.get_customer!(id)
-
-    case Accounts.update_customer(customer, customer_params) do
-      {:ok, customer} ->
-        conn
-        |> put_flash(:info, "Customer updated successfully.")
-        |> redirect(to: Routes.customer_path(conn, :show, customer))
-
-      {:error, %Ecto.Changeset{} = changeset} ->
-        render(conn, "edit.html", customer: customer, changeset: changeset)
-    end
-  end
-
-  def delete(conn, %{"id" => id}) do
-    customer = Accounts.get_customer!(id)
-    {:ok, _customer} = Accounts.delete_customer(customer)
-
-    conn
-    |> put_flash(:info, "Customer deleted successfully.")
-    |> redirect(to: Routes.customer_path(conn, :index))
-  end
-end
diff --git a/lib/volt_web/controllers/customer_dashboard_controller.ex b/lib/volt_web/controllers/customer_dashboard_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..aa5c64955e082fd81860c0621d538b4a2dcf6c1e
--- /dev/null
+++ b/lib/volt_web/controllers/customer_dashboard_controller.ex
@@ -0,0 +1,33 @@
+defmodule VoltWeb.CustomerDashboardController do
+  use VoltWeb, :controller
+  import Volt.Geolocation
+  import Ecto.Query, only: [from: 2]
+  alias Volt.Accounts.Restaurant
+  alias Volt.Repo
+
+  defmodule Restaurant_summary do
+    defstruct [:id, :name, :dist, :price_level]
+  end
+
+  defp customer_restaurants(conn) do
+    usr = conn.assigns.current_customer
+    current_user_address = usr.address <> ", " <> usr.zip_code <> ", " <> usr.city
+    restaurants =
+      from(rest in Restaurant, select: {rest.id, rest.name, rest.address, rest.zip_code, rest.city, rest.price_level})
+      |> Repo.all()
+      |> Enum.map(fn {id, name, address, zip_code, city, price_level} ->
+          rest_address = address <> ", " <> zip_code <> ", " <> city
+          [dist, _] = distance(rest_address, current_user_address)
+          %{id: id, name: name, dist: dist, price_level: price_level}
+        end)
+      |> Enum.sort_by(fn(rest) -> {rest.dist} end)
+      |> Enum.map(fn %{id: id, name: name, dist: dist, price_level: price_level} ->
+          %Restaurant_summary{id: id, name: name, dist: dist, price_level: price_level}
+        end)
+    restaurants
+  end
+
+  def index(conn, _params) do
+    render(conn, "index.html", restaurants: customer_restaurants(conn))
+  end
+end
diff --git a/lib/volt_web/controllers/customer_registration_controller.ex b/lib/volt_web/controllers/customer_registration_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..5e465d35d29b2029469e250901419d25218a28c3
--- /dev/null
+++ b/lib/volt_web/controllers/customer_registration_controller.ex
@@ -0,0 +1,30 @@
+defmodule VoltWeb.CustomerRegistrationController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+  alias Volt.Accounts.Customer
+  alias VoltWeb.CustomerAuth
+
+  def new(conn, _params) do
+    changeset = Accounts.change_customer_registration(%Customer{})
+    render(conn, "new.html", changeset: changeset)
+  end
+
+  def create(conn, %{"customer" => customer_params}) do
+    case Accounts.register_customer(customer_params) do
+      {:ok, customer} ->
+        {:ok, _} =
+          Accounts.deliver_customer_confirmation_instructions(
+            customer,
+            &Routes.customer_confirmation_url(conn, :edit, &1)
+          )
+
+        conn
+        |> put_flash(:info, "Customer created successfully.")
+        |> CustomerAuth.log_in_customer(customer)
+
+      {:error, %Ecto.Changeset{} = changeset} ->
+        render(conn, "new.html", changeset: changeset)
+    end
+  end
+end
diff --git a/lib/volt_web/controllers/customer_reset_password_controller.ex b/lib/volt_web/controllers/customer_reset_password_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..4942962c75e3dcf2f9d3b4cd69d0f2649f5802a8
--- /dev/null
+++ b/lib/volt_web/controllers/customer_reset_password_controller.ex
@@ -0,0 +1,58 @@
+defmodule VoltWeb.CustomerResetPasswordController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+
+  plug :get_customer_by_reset_password_token when action in [:edit, :update]
+
+  def new(conn, _params) do
+    render(conn, "new.html")
+  end
+
+  def create(conn, %{"customer" => %{"email" => email}}) do
+    if customer = Accounts.get_customer_by_email(email) do
+      Accounts.deliver_customer_reset_password_instructions(
+        customer,
+        &Routes.customer_reset_password_url(conn, :edit, &1)
+      )
+    end
+
+    conn
+    |> put_flash(
+      :info,
+      "If your email is in our system, you will receive instructions to reset your password shortly."
+    )
+    |> redirect(to: "/")
+  end
+
+  def edit(conn, _params) do
+    render(conn, "edit.html", changeset: Accounts.change_customer_password(conn.assigns.customer))
+  end
+
+  # Do not log in the customer after reset password to avoid a
+  # leaked token giving the customer access to the account.
+  def update(conn, %{"customer" => customer_params}) do
+    case Accounts.reset_customer_password(conn.assigns.customer, customer_params) do
+      {:ok, _} ->
+        conn
+        |> put_flash(:info, "Password reset successfully.")
+        |> redirect(to: Routes.customer_session_path(conn, :new))
+
+      {:error, changeset} ->
+        render(conn, "edit.html", changeset: changeset)
+    end
+  end
+
+  defp get_customer_by_reset_password_token(conn, _opts) do
+    %{"token" => token} = conn.params
+
+    if customer = Accounts.get_customer_by_reset_password_token(token) do
+      conn |> assign(:customer, customer) |> assign(:token, token)
+    else
+      conn
+      |> put_flash(:error, "Reset password link is invalid or it has expired.")
+      |> redirect(to: "/")
+      |> halt()
+    end
+  end
+end
diff --git a/lib/volt_web/controllers/customer_session_controller.ex b/lib/volt_web/controllers/customer_session_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..183654693e96b3281f66dfdb15008890c1aec683
--- /dev/null
+++ b/lib/volt_web/controllers/customer_session_controller.ex
@@ -0,0 +1,27 @@
+defmodule VoltWeb.CustomerSessionController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+  alias VoltWeb.CustomerAuth
+
+  def new(conn, _params) do
+    render(conn, "new.html", error_message: nil)
+  end
+
+  def create(conn, %{"customer" => customer_params}) do
+    %{"email" => email, "password" => password} = customer_params
+
+    if customer = Accounts.get_customer_by_email_and_password(email, password) do
+      CustomerAuth.log_in_customer(conn, customer, customer_params)
+    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")
+    end
+  end
+
+  def delete(conn, _params) do
+    conn
+    |> put_flash(:info, "Logged out successfully.")
+    |> CustomerAuth.log_out_customer()
+  end
+end
diff --git a/lib/volt_web/controllers/customer_settings_controller.ex b/lib/volt_web/controllers/customer_settings_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..f56f1340a258ebc2647506e138ede27b293ae2c6
--- /dev/null
+++ b/lib/volt_web/controllers/customer_settings_controller.ex
@@ -0,0 +1,91 @@
+defmodule VoltWeb.CustomerSettingsController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+  alias VoltWeb.CustomerAuth
+
+  plug :assign_changesets
+
+  def edit(conn, _params) do
+    render(conn, "edit.html")
+  end
+
+  def update(conn, %{"action" => "update_email"} = params) do
+    %{"current_password" => password, "customer" => customer_params} = params
+    customer = conn.assigns.current_customer
+
+    case Accounts.apply_customer_email(customer, password, customer_params) do
+      {:ok, applied_customer} ->
+        Accounts.deliver_customer_update_email_instructions(
+          applied_customer,
+          customer.email,
+          &Routes.customer_settings_url(conn, :confirm_email, &1)
+        )
+
+        conn
+        |> put_flash(
+          :info,
+          "A link to confirm your email change has been sent to the new address."
+        )
+        |> redirect(to: Routes.customer_settings_path(conn, :edit))
+
+      {:error, changeset} ->
+        render(conn, "edit.html", email_changeset: changeset)
+    end
+  end
+
+  def update(conn, %{"action" => "update_password"} = params) do
+    %{"current_password" => password, "customer" => customer_params} = params
+    customer = conn.assigns.current_customer
+
+    case Accounts.update_customer_password(customer, password, customer_params) do
+      {:ok, customer} ->
+        conn
+        |> put_flash(:info, "Password updated successfully.")
+        |> put_session(:customer_return_to, Routes.customer_settings_path(conn, :edit))
+        |> CustomerAuth.log_in_customer(customer)
+
+      {:error, changeset} ->
+        render(conn, "edit.html", password_changeset: changeset)
+    end
+  end
+
+  def update(conn, %{"action" => "update_profile"} = params) do
+    %{"current_password" => password, "customer" => customer_params} = params
+    customer = conn.assigns.current_customer
+
+    case Accounts.update_customer_profile(customer, password, customer_params) do
+      {:ok, customer} ->
+        conn
+        |> put_flash(:info, "Profile updated successfully.")
+        |> put_session(:customer_return_to, Routes.customer_settings_path(conn, :edit))
+        |> CustomerAuth.log_in_customer(customer)
+
+      {:error, changeset} ->
+        render(conn, "edit.html", profile_changeset: changeset)
+    end
+  end
+
+  def confirm_email(conn, %{"token" => token}) do
+    case Accounts.update_customer_email(conn.assigns.current_customer, token) do
+      :ok ->
+        conn
+        |> put_flash(:info, "Email changed successfully.")
+        |> redirect(to: Routes.customer_settings_path(conn, :edit))
+
+      :error ->
+        conn
+        |> put_flash(:error, "Email change link is invalid or it has expired.")
+        |> redirect(to: Routes.customer_settings_path(conn, :edit))
+    end
+  end
+
+  defp assign_changesets(conn, _opts) do
+    customer = conn.assigns.current_customer
+
+    conn
+    |> assign(:email_changeset, Accounts.change_customer_email(customer))
+    |> assign(:password_changeset, Accounts.change_customer_password(customer))
+    |> assign(:profile_changeset, Accounts.change_customer_profile(customer))
+  end
+end
diff --git a/lib/volt_web/controllers/restaurant_auth.ex b/lib/volt_web/controllers/restaurant_auth.ex
new file mode 100644
index 0000000000000000000000000000000000000000..7e62a928792ec5c6e5df94b98d5f407500808791
--- /dev/null
+++ b/lib/volt_web/controllers/restaurant_auth.ex
@@ -0,0 +1,151 @@
+defmodule VoltWeb.RestaurantAuth do
+  import Plug.Conn
+  import Phoenix.Controller
+
+  alias Volt.Accounts
+  alias VoltWeb.Router.Helpers, as: Routes
+
+  # Make the remember me cookie valid for 60 days.
+  # If you want bump or reduce this value, also change
+  # the token expiry itself in RestaurantToken.
+  @max_age 60 * 60 * 24 * 60
+  @remember_me_cookie "_volt_web_restaurant_remember_me"
+  @remember_me_options [sign: true, max_age: @max_age, same_site: "Lax"]
+
+  @doc """
+  Logs the restaurant 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_restaurant(conn, restaurant, params \\ %{}) do
+    token = Accounts.generate_restaurant_session_token(restaurant)
+    restaurant_return_to = get_session(conn, :restaurant_return_to)
+
+    # restaurant_return_to = get_session(conn, Routes.restaurant_dashboard_path(conn, :show))
+
+    conn
+    |> renew_session()
+    |> put_session(:restaurant_token, token)
+    |> put_session(:live_socket_id, "restaurants_sessions:#{Base.url_encode64(token)}")
+    |> maybe_write_remember_me_cookie(token, params)
+    |> redirect(to: restaurant_return_to || signed_in_path(conn))
+  end
+
+  defp maybe_write_remember_me_cookie(conn, token, %{"remember_me" => "true"}) do
+    put_resp_cookie(conn, @remember_me_cookie, token, @remember_me_options)
+  end
+
+  defp maybe_write_remember_me_cookie(conn, _token, _params) 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 restaurant out.
+
+  It clears all session data for safety. See renew_session.
+  """
+  def log_out_restaurant(conn) do
+    restaurant_token = get_session(conn, :restaurant_token)
+    restaurant_token && Accounts.delete_restaurant_session_token(restaurant_token)
+
+    if live_socket_id = get_session(conn, :live_socket_id) do
+      VoltWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
+    end
+
+    conn
+    |> renew_session()
+    |> delete_resp_cookie(@remember_me_cookie)
+    |> redirect(to: "/")
+  end
+
+  @doc """
+  Authenticates the restaurant by looking into the session
+  and remember me token.
+  """
+  def fetch_current_restaurant(conn, _opts) do
+    {restaurant_token, conn} = ensure_restaurant_token(conn)
+    restaurant = restaurant_token && Accounts.get_restaurant_by_session_token(restaurant_token)
+    assign(conn, :current_restaurant, restaurant)
+  end
+
+  defp ensure_restaurant_token(conn) do
+    if restaurant_token = get_session(conn, :restaurant_token) do
+      {restaurant_token, conn}
+    else
+      conn = fetch_cookies(conn, signed: [@remember_me_cookie])
+
+      if restaurant_token = conn.cookies[@remember_me_cookie] do
+        {restaurant_token, put_session(conn, :restaurant_token, restaurant_token)}
+      else
+        {nil, conn}
+      end
+    end
+  end
+
+  @doc """
+  Used for routes that require the restaurant to not be authenticated.
+  """
+  def redirect_if_restaurant_is_authenticated(conn, _opts) do
+    if conn.assigns[:current_restaurant] do
+      conn
+      |> redirect(to: signed_in_path(conn))
+      |> halt()
+    else
+      conn
+    end
+  end
+
+  @doc """
+  Used for routes that require the restaurant to be authenticated.
+
+  If you want to enforce the restaurant email is confirmed before
+  they use the application at all, here would be a good place.
+  """
+  def require_authenticated_restaurant(conn, _opts) do
+    if conn.assigns[:current_restaurant] do
+      conn
+    else
+      conn
+      |> put_flash(:error, "You must log in to access this page.")
+      |> maybe_store_return_to()
+      |> redirect(to: Routes.restaurant_session_path(conn, :new))
+      |> halt()
+    end
+  end
+
+  defp maybe_store_return_to(%{method: "GET"} = conn) do
+    put_session(conn, :restaurant_return_to, current_path(conn))
+  end
+
+  defp maybe_store_return_to(conn), do: conn
+
+  defp signed_in_path(_conn), do: "/restaurants/dashboard"
+end
diff --git a/lib/volt_web/controllers/restaurant_confirmation_controller.ex b/lib/volt_web/controllers/restaurant_confirmation_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..09cdabd6bedec8974da5bd29461319d6bf530a4a
--- /dev/null
+++ b/lib/volt_web/controllers/restaurant_confirmation_controller.ex
@@ -0,0 +1,56 @@
+defmodule VoltWeb.RestaurantConfirmationController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+
+  def new(conn, _params) do
+    render(conn, "new.html")
+  end
+
+  def create(conn, %{"restaurant" => %{"email" => email}}) do
+    if restaurant = Accounts.get_restaurant_by_email(email) do
+      Accounts.deliver_restaurant_confirmation_instructions(
+        restaurant,
+        &Routes.restaurant_confirmation_url(conn, :edit, &1)
+      )
+    end
+
+    conn
+    |> put_flash(
+      :info,
+      "If your email is in our system and it has not been confirmed yet, " <>
+        "you will receive an email with instructions shortly."
+    )
+    |> redirect(to: "/")
+  end
+
+  def edit(conn, %{"token" => token}) do
+    render(conn, "edit.html", token: token)
+  end
+
+  # Do not log in the restaurant after confirmation to avoid a
+  # leaked token giving the restaurant access to the account.
+  def update(conn, %{"token" => token}) do
+    case Accounts.confirm_restaurant(token) do
+      {:ok, _} ->
+        conn
+        |> put_flash(:info, "Restaurant confirmed successfully.")
+        |> redirect(to: "/")
+
+      :error ->
+        # If there is a current restaurant and the account was already confirmed,
+        # then odds are that the confirmation link was already visited, either
+        # by some automation or by the restaurant themselves, so we redirect without
+        # a warning message.
+        case conn.assigns do
+          %{current_restaurant: %{confirmed_at: confirmed_at}} when not is_nil(confirmed_at) ->
+            redirect(conn, to: "/")
+
+          %{} ->
+            conn
+            |> put_flash(:error, "Restaurant confirmation link is invalid or it has expired.")
+            |> redirect(to: "/")
+        end
+    end
+  end
+end
diff --git a/lib/volt_web/controllers/restaurant_controller.ex b/lib/volt_web/controllers/restaurant_controller.ex
deleted file mode 100644
index 228a35799e39615e7e40538eb9651c3853200c6e..0000000000000000000000000000000000000000
--- a/lib/volt_web/controllers/restaurant_controller.ex
+++ /dev/null
@@ -1,58 +0,0 @@
-defmodule VoltWeb.RestaurantController do
-  use VoltWeb, :controller
-
-  alias Volt.Accounts
-  alias Volt.Accounts.Restaurant
-
-
-  def new(conn, _params) do
-    changeset = Accounts.change_restaurant(%Restaurant{})
-    render(conn, "new.html", changeset: changeset)
-  end
-
-  def create(conn, %{"restaurant" => restaurant_params}) do
-    case Accounts.create_restaurant(restaurant_params) do
-      {:ok, restaurant} ->
-        conn
-        |> put_flash(:info, "Restaurant created successfully.")
-        |> redirect(to: Routes.restaurant_path(conn, :show, restaurant))
-
-      {:error, %Ecto.Changeset{} = changeset} ->
-        render(conn, "new.html", changeset: changeset)
-    end
-  end
-
-  def show(conn, %{"id" => id}) do
-    restaurant = Accounts.get_restaurant!(id)
-    render(conn, "index.html", restaurant: restaurant)
-  end
-
-  def edit(conn, %{"id" => id}) do
-    restaurant = Accounts.get_restaurant!(id)
-    changeset = Accounts.change_restaurant(restaurant)
-    render(conn, "edit.html", restaurant: restaurant, changeset: changeset)
-  end
-
-  def update(conn, %{"id" => id, "restaurant" => restaurant_params}) do
-    restaurant = Accounts.get_restaurant!(id)
-
-    case Accounts.update_restaurant(restaurant, restaurant_params) do
-      {:ok, _restaurant} ->
-        conn
-        |> put_flash(:info, "Restaurant updated successfully.")
-        |> redirect(to: Routes.restaurant_path(conn, :show, id))
-
-      {:error, %Ecto.Changeset{} = changeset} ->
-        render(conn, "edit.html", restaurant: restaurant, changeset: changeset)
-    end
-  end
-
-  def delete(conn, %{"id" => id}) do
-    restaurant = Accounts.get_restaurant!(id)
-    {:ok, _restaurant} = Accounts.delete_restaurant(restaurant)
-
-    conn
-    |> put_flash(:info, "Restaurant deleted successfully.")
-    |> redirect(to: Routes.restaurant_path(conn, :index))
-  end
-end
diff --git a/lib/volt_web/controllers/restaurant_dashboard_controller.ex b/lib/volt_web/controllers/restaurant_dashboard_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..88c3aede593cd77c6c02467124ff1d4547ea655c
--- /dev/null
+++ b/lib/volt_web/controllers/restaurant_dashboard_controller.ex
@@ -0,0 +1,74 @@
+defmodule VoltWeb.RestaurantDashboardController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+  alias VoltWeb.RestaurantAuth
+
+  plug :assign_email_and_password_changesets
+
+  def index(conn, _params) do
+    render(conn, "index.html")
+  end
+
+  def update(conn, %{"action" => "update_email"} = params) do
+    %{"current_password" => password, "restaurant" => restaurant_params} = params
+    restaurant = conn.assigns.current_restaurant
+
+    case Accounts.apply_restaurant_email(restaurant, password, restaurant_params) do
+      {:ok, applied_restaurant} ->
+        Accounts.deliver_restaurant_update_email_instructions(
+          applied_restaurant,
+          restaurant.email,
+          &Routes.restaurant_settings_url(conn, :confirm_email, &1)
+        )
+
+        conn
+        |> put_flash(
+          :info,
+          "A link to confirm your email change has been sent to the new address."
+        )
+        |> redirect(to: Routes.restaurant_settings_path(conn, :edit))
+
+      {:error, changeset} ->
+        render(conn, "edit.html", email_changeset: changeset)
+    end
+  end
+
+  def update(conn, %{"action" => "update_password"} = params) do
+    %{"current_password" => password, "restaurant" => restaurant_params} = params
+    restaurant = conn.assigns.current_restaurant
+
+    case Accounts.update_restaurant_password(restaurant, password, restaurant_params) do
+      {:ok, restaurant} ->
+        conn
+        |> put_flash(:info, "Password updated successfully.")
+        |> put_session(:restaurant_return_to, Routes.restaurant_settings_path(conn, :edit))
+        |> RestaurantAuth.log_in_restaurant(restaurant)
+
+      {:error, changeset} ->
+        render(conn, "edit.html", password_changeset: changeset)
+    end
+  end
+
+  def confirm_email(conn, %{"token" => token}) do
+    case Accounts.update_restaurant_email(conn.assigns.current_restaurant, token) do
+      :ok ->
+        conn
+        |> put_flash(:info, "Email changed successfully.")
+        |> redirect(to: Routes.restaurant_settings_path(conn, :edit))
+
+      :error ->
+        conn
+        |> put_flash(:error, "Email change link is invalid or it has expired.")
+        |> redirect(to: Routes.restaurant_settings_path(conn, :edit))
+    end
+  end
+
+  defp assign_email_and_password_changesets(conn, _opts) do
+    restaurant = conn.assigns.current_restaurant
+
+    conn
+    |> assign(:email_changeset, Accounts.change_restaurant_email(restaurant))
+    |> assign(:password_changeset, Accounts.change_restaurant_password(restaurant))
+  end
+end
diff --git a/lib/volt_web/controllers/restaurant_item_controller.ex b/lib/volt_web/controllers/restaurant_item_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..21ff8becfdade18ae350a4964bbde801a83cdcda
--- /dev/null
+++ b/lib/volt_web/controllers/restaurant_item_controller.ex
@@ -0,0 +1,86 @@
+defmodule VoltWeb.RestaurantItemController do
+  use VoltWeb, :controller
+
+  import Ecto.Query, only: [from: 2]
+
+  alias Volt.Sales
+  alias Volt.Sales.Item
+  alias Volt.Repo
+
+  def index(conn, _params) do
+    restaurant = conn.assigns.current_restaurant
+    items =
+      from(i in Item, where: i.restaurant_id == ^restaurant.id)
+      |> Repo.all()
+      |> Repo.preload(:restaurant)
+    render(conn, "index.html", items: items)
+  end
+
+  def new(conn, _params) do
+    changeset = Sales.change_item(%Item{})
+    render(conn, "new.html", changeset: changeset)
+  end
+
+  def create(conn, %{"item" => item_params}) do
+    restaurant = conn.assigns.current_restaurant
+
+    item_struct = Ecto.build_assoc(restaurant, :items, Enum.map(item_params, fn({key, value}) -> {String.to_atom(key), value} end))
+    changeset = Item.changeset(item_struct, %{})
+    case Repo.insert(changeset) do
+      {:ok, item} ->
+        conn
+        |> put_flash(:info, "Item created successfully.")
+        |> redirect(to: Routes.restaurant_item_path(conn, :show, item))
+
+      {:error, %Ecto.Changeset{} = changeset} ->
+        render(conn, "new.html", changeset: changeset)
+    end
+  end
+
+  def show(conn, %{"id" => id}) do
+    # item = Sales.get_item!(id) |> Repo.preload(:restaurant)
+    item =
+      from(i in Item, where: i.id == ^id)
+      |> Repo.one()
+      |> Repo.preload(:restaurant)
+    render(conn, "show.html", item: item)
+  end
+
+  def edit(conn, %{"id" => id}) do
+    item =
+      from(i in Item, where: i.id == ^id)
+      |> Repo.one()
+      |> Repo.preload(:restaurant)
+    changeset = Sales.change_item(item)
+    render(conn, "edit.html", item: item, changeset: changeset)
+  end
+
+  def update(conn, %{"id" => id, "item" => item_params}) do
+    item =
+      from(i in Item, where: i.id == ^id)
+      |> Repo.one()
+      |> Repo.preload(:restaurant)
+
+    case Sales.update_item(item, item_params) do
+      {:ok, item} ->
+        conn
+        |> put_flash(:info, "Item updated successfully.")
+        |> redirect(to: Routes.restaurant_item_path(conn, :show, item))
+
+      {:error, %Ecto.Changeset{} = changeset} ->
+        render(conn, "edit.html", item: item, changeset: changeset)
+    end
+  end
+
+  def delete(conn, %{"id" => id}) do
+    item =
+      from(i in Item, where: i.id == ^id)
+      |> Repo.one()
+      |> Repo.preload(:restaurant)
+    {:ok, _item} = Sales.delete_item(item)
+
+    conn
+    |> put_flash(:info, "Item deleted successfully.")
+    |> redirect(to: Routes.restaurant_item_path(conn, :index))
+  end
+end
diff --git a/lib/volt_web/controllers/restaurant_registration_controller.ex b/lib/volt_web/controllers/restaurant_registration_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..32ac23f2edcbb1e366b7251afde13718146b7709
--- /dev/null
+++ b/lib/volt_web/controllers/restaurant_registration_controller.ex
@@ -0,0 +1,30 @@
+defmodule VoltWeb.RestaurantRegistrationController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+  alias Volt.Accounts.Restaurant
+  alias VoltWeb.RestaurantAuth
+
+  def new(conn, _params) do
+    changeset = Accounts.change_restaurant_registration(%Restaurant{})
+    render(conn, "new.html", changeset: changeset)
+  end
+
+  def create(conn, %{"restaurant" => restaurant_params}) do
+    case Accounts.register_restaurant(restaurant_params) do
+      {:ok, restaurant} ->
+        {:ok, _} =
+          Accounts.deliver_restaurant_confirmation_instructions(
+            restaurant,
+            &Routes.restaurant_confirmation_url(conn, :edit, &1)
+          )
+
+        conn
+        |> put_flash(:info, "Restaurant created successfully.")
+        |> RestaurantAuth.log_in_restaurant(restaurant)
+
+      {:error, %Ecto.Changeset{} = changeset} ->
+        render(conn, "new.html", changeset: changeset)
+    end
+  end
+end
diff --git a/lib/volt_web/controllers/restaurant_reset_password_controller.ex b/lib/volt_web/controllers/restaurant_reset_password_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..e85e9730dc12d1e08e210a83c1dcab58a5c191b2
--- /dev/null
+++ b/lib/volt_web/controllers/restaurant_reset_password_controller.ex
@@ -0,0 +1,58 @@
+defmodule VoltWeb.RestaurantResetPasswordController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+
+  plug :get_restaurant_by_reset_password_token when action in [:edit, :update]
+
+  def new(conn, _params) do
+    render(conn, "new.html")
+  end
+
+  def create(conn, %{"restaurant" => %{"email" => email}}) do
+    if restaurant = Accounts.get_restaurant_by_email(email) do
+      Accounts.deliver_restaurant_reset_password_instructions(
+        restaurant,
+        &Routes.restaurant_reset_password_url(conn, :edit, &1)
+      )
+    end
+
+    conn
+    |> put_flash(
+      :info,
+      "If your email is in our system, you will receive instructions to reset your password shortly."
+    )
+    |> redirect(to: "/")
+  end
+
+  def edit(conn, _params) do
+    render(conn, "edit.html", changeset: Accounts.change_restaurant_password(conn.assigns.restaurant))
+  end
+
+  # Do not log in the restaurant after reset password to avoid a
+  # leaked token giving the restaurant access to the account.
+  def update(conn, %{"restaurant" => restaurant_params}) do
+    case Accounts.reset_restaurant_password(conn.assigns.restaurant, restaurant_params) do
+      {:ok, _} ->
+        conn
+        |> put_flash(:info, "Password reset successfully.")
+        |> redirect(to: Routes.restaurant_session_path(conn, :new))
+
+      {:error, changeset} ->
+        render(conn, "edit.html", changeset: changeset)
+    end
+  end
+
+  defp get_restaurant_by_reset_password_token(conn, _opts) do
+    %{"token" => token} = conn.params
+
+    if restaurant = Accounts.get_restaurant_by_reset_password_token(token) do
+      conn |> assign(:restaurant, restaurant) |> assign(:token, token)
+    else
+      conn
+      |> put_flash(:error, "Reset password link is invalid or it has expired.")
+      |> redirect(to: "/")
+      |> halt()
+    end
+  end
+end
diff --git a/lib/volt_web/controllers/restaurant_session_controller.ex b/lib/volt_web/controllers/restaurant_session_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..1e2dba81090b6b38e81b896d57ecca125233b3c7
--- /dev/null
+++ b/lib/volt_web/controllers/restaurant_session_controller.ex
@@ -0,0 +1,27 @@
+defmodule VoltWeb.RestaurantSessionController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+  alias VoltWeb.RestaurantAuth
+
+  def new(conn, _params) do
+    render(conn, "new.html", error_message: nil)
+  end
+
+  def create(conn, %{"restaurant" => restaurant_params}) do
+    %{"email" => email, "password" => password} = restaurant_params
+
+    if restaurant = Accounts.get_restaurant_by_email_and_password(email, password) do
+      RestaurantAuth.log_in_restaurant(conn, restaurant, restaurant_params)
+    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")
+    end
+  end
+
+  def delete(conn, _params) do
+    conn
+    |> put_flash(:info, "Logged out successfully.")
+    |> RestaurantAuth.log_out_restaurant()
+  end
+end
diff --git a/lib/volt_web/controllers/restaurant_settings_controller.ex b/lib/volt_web/controllers/restaurant_settings_controller.ex
new file mode 100644
index 0000000000000000000000000000000000000000..a3c556209ab4739ac792ee7c7d3219fe71f1ab27
--- /dev/null
+++ b/lib/volt_web/controllers/restaurant_settings_controller.ex
@@ -0,0 +1,92 @@
+defmodule VoltWeb.RestaurantSettingsController do
+  use VoltWeb, :controller
+
+  alias Volt.Accounts
+  alias VoltWeb.RestaurantAuth
+
+  plug :assign_changesets
+
+  def edit(conn, _params) do
+    render(conn, "edit.html")
+  end
+
+  def update(conn, %{"action" => "update_email"} = params) do
+    %{"current_password" => password, "restaurant" => restaurant_params} = params
+    restaurant = conn.assigns.current_restaurant
+
+    case Accounts.apply_restaurant_email(restaurant, password, restaurant_params) do
+      {:ok, applied_restaurant} ->
+        Accounts.deliver_restaurant_update_email_instructions(
+          applied_restaurant,
+          restaurant.email,
+          &Routes.restaurant_settings_url(conn, :confirm_email, &1)
+        )
+
+        conn
+        |> put_flash(
+          :info,
+          "A link to confirm your email change has been sent to the new address."
+        )
+        |> redirect(to: Routes.restaurant_settings_path(conn, :edit))
+
+      {:error, changeset} ->
+        render(conn, "edit.html", email_changeset: changeset)
+    end
+  end
+
+  def update(conn, %{"action" => "update_password"} = params) do
+    %{"current_password" => password, "restaurant" => restaurant_params} = params
+    restaurant = conn.assigns.current_restaurant
+
+    case Accounts.update_restaurant_password(restaurant, password, restaurant_params) do
+      {:ok, restaurant} ->
+        conn
+        |> put_flash(:info, "Password updated successfully.")
+        |> put_session(:restaurant_return_to, Routes.restaurant_settings_path(conn, :edit))
+        |> RestaurantAuth.log_in_restaurant(restaurant)
+
+      {:error, changeset} ->
+        render(conn, "edit.html", password_changeset: changeset)
+    end
+  end
+
+  def update(conn, %{"action" => "update_profile"} = params) do
+    %{"current_password" => password, "restaurant" => restaurant_params} = params
+    restaurant = conn.assigns.current_restaurant
+
+    case Accounts.update_restaurant_profile(restaurant, password, restaurant_params) do
+      {:ok, restaurant} ->
+        conn
+        |> put_flash(:info, "Profile updated successfully.")
+        |> put_session(:restaurant_return_to, Routes.restaurant_settings_path(conn, :edit))
+        |> RestaurantAuth.log_in_restaurant(restaurant)
+
+      {:error, changeset} ->
+        render(conn, "edit.html", profile_changeset: changeset)
+    end
+  end
+
+
+  def confirm_email(conn, %{"token" => token}) do
+    case Accounts.update_restaurant_email(conn.assigns.current_restaurant, token) do
+      :ok ->
+        conn
+        |> put_flash(:info, "Email changed successfully.")
+        |> redirect(to: Routes.restaurant_settings_path(conn, :edit))
+
+      :error ->
+        conn
+        |> put_flash(:error, "Email change link is invalid or it has expired.")
+        |> redirect(to: Routes.restaurant_settings_path(conn, :edit))
+    end
+  end
+
+  defp assign_changesets(conn, _opts) do
+    restaurant = conn.assigns.current_restaurant
+
+    conn
+    |> assign(:email_changeset, Accounts.change_restaurant_email(restaurant))
+    |> assign(:password_changeset, Accounts.change_restaurant_password(restaurant))
+    |> assign(:profile_changeset, Accounts.change_restaurant_profile(restaurant))
+  end
+end
diff --git a/lib/volt_web/router.ex b/lib/volt_web/router.ex
index 91baeb5938591198dd1fb66711f7bfde16ddfdfc..b06a12b1d71345d922af0aa75978d11976117b47 100644
--- a/lib/volt_web/router.ex
+++ b/lib/volt_web/router.ex
@@ -1,6 +1,12 @@
 defmodule VoltWeb.Router do
   use VoltWeb, :router
 
+  import VoltWeb.CustomerAuth
+
+  import VoltWeb.RestaurantAuth
+
+  import VoltWeb.CourierAuth
+
   pipeline :browser do
     plug :accepts, ["html"]
     plug :fetch_session
@@ -8,6 +14,9 @@ defmodule VoltWeb.Router do
     plug :put_root_layout, {VoltWeb.LayoutView, :root}
     plug :protect_from_forgery
     plug :put_secure_browser_headers
+    plug :fetch_current_customer
+    plug :fetch_current_restaurant
+    plug :fetch_current_courier
   end
 
   pipeline :api do
@@ -17,9 +26,6 @@ defmodule VoltWeb.Router do
   scope "/", VoltWeb do
     pipe_through :browser
     get "/", PageController, :index
-    resources "/restaurant", RestaurantController
-    resources "/courier", CourierController
-    resources "/customer", CustomerController
   end
 
 
@@ -41,7 +47,6 @@ defmodule VoltWeb.Router do
 
     scope "/" do
       pipe_through :browser
-
       live_dashboard "/dashboard", metrics: VoltWeb.Telemetry
     end
   end
@@ -57,4 +62,116 @@ defmodule VoltWeb.Router do
       forward "/mailbox", Plug.Swoosh.MailboxPreview
     end
   end
+
+
+
+  ## Authentication routes
+
+  scope "/", VoltWeb do
+    pipe_through [:browser, :redirect_if_courier_is_authenticated]
+
+    get "/couriers/register", CourierRegistrationController, :new
+    post "/couriers/register", CourierRegistrationController, :create
+    get "/couriers/log_in", CourierSessionController, :new
+    post "/couriers/log_in", CourierSessionController, :create
+    get "/couriers/reset_password", CourierResetPasswordController, :new
+    post "/couriers/reset_password", CourierResetPasswordController, :create
+    get "/couriers/reset_password/:token", CourierResetPasswordController, :edit
+    put "/couriers/reset_password/:token", CourierResetPasswordController, :update
+  end
+
+  scope "/", VoltWeb do
+    pipe_through [:browser, :require_authenticated_courier]
+
+    get "/couriers/settings", CourierSettingsController, :edit
+    put "/couriers/settings", CourierSettingsController, :update
+    get "/couriers/settings/confirm_email/:token", CourierSettingsController, :confirm_email
+    get "/couriers/dashboard", CourierDashboardController, :index
+  end
+
+  scope "/", VoltWeb do
+    pipe_through [:browser]
+
+    delete "/couriers/log_out", CourierSessionController, :delete
+    get "/couriers/confirm", CourierConfirmationController, :new
+    post "/couriers/confirm", CourierConfirmationController, :create
+    get "/couriers/confirm/:token", CourierConfirmationController, :edit
+    post "/couriers/confirm/:token", CourierConfirmationController, :update
+  end
+
+  ## Authentication routes
+
+  scope "/", VoltWeb do
+    pipe_through [:browser, :redirect_if_restaurant_is_authenticated]
+
+    get "/restaurants/register", RestaurantRegistrationController, :new
+    post "/restaurants/register", RestaurantRegistrationController, :create
+    get "/restaurants/log_in", RestaurantSessionController, :new
+    post "/restaurants/log_in", RestaurantSessionController, :create
+    get "/restaurants/reset_password", RestaurantResetPasswordController, :new
+    post "/restaurants/reset_password", RestaurantResetPasswordController, :create
+    get "/restaurants/reset_password/:token", RestaurantResetPasswordController, :edit
+    put "/restaurants/reset_password/:token", RestaurantResetPasswordController, :update
+  end
+
+  scope "/", VoltWeb do
+    pipe_through [:browser, :require_authenticated_restaurant]
+
+    get "/restaurants/settings", RestaurantSettingsController, :edit
+    put "/restaurants/settings", RestaurantSettingsController, :update
+    get "/restaurants/settings/confirm_email/:token", RestaurantSettingsController, :confirm_email
+    get "/restaurants/dashboard", RestaurantDashboardController, :index
+
+    get "/restaurants/additem", RestaurantItemController, :new
+    post "/restaurants/additem", RestaurantItemController, :create
+    get "/restaurants/menu", RestaurantItemController, :index
+    get "/restaurants/item/:id", RestaurantItemController, :show
+    get "/restaurants/edit_item/:id", RestaurantItemController, :edit
+    put "/restaurants/edit_item/:id", RestaurantItemController, :update
+    delete "/restaurants/delete_item/:id", RestaurantItemController, :delete
+  end
+
+  scope "/", VoltWeb do
+    pipe_through [:browser]
+
+    delete "/restaurants/log_out", RestaurantSessionController, :delete
+    get "/restaurants/confirm", RestaurantConfirmationController, :new
+    post "/restaurants/confirm", RestaurantConfirmationController, :create
+    get "/restaurants/confirm/:token", RestaurantConfirmationController, :edit
+    post "/restaurants/confirm/:token", RestaurantConfirmationController, :update
+  end
+
+  ## Authentication routes
+
+  scope "/", VoltWeb do
+    pipe_through [:browser, :redirect_if_customer_is_authenticated]
+
+    get "/customers/register", CustomerRegistrationController, :new
+    post "/customers/register", CustomerRegistrationController, :create
+    get "/customers/log_in", CustomerSessionController, :new
+    post "/customers/log_in", CustomerSessionController, :create
+    get "/customers/reset_password", CustomerResetPasswordController, :new
+    post "/customers/reset_password", CustomerResetPasswordController, :create
+    get "/customers/reset_password/:token", CustomerResetPasswordController, :edit
+    put "/customers/reset_password/:token", CustomerResetPasswordController, :update
+  end
+
+  scope "/", VoltWeb do
+    pipe_through [:browser, :require_authenticated_customer]
+
+    get "/customers/settings", CustomerSettingsController, :edit
+    put "/customers/settings", CustomerSettingsController, :update
+    get "/customers/settings/confirm_email/:token", CustomerSettingsController, :confirm_email
+    get "/customers/dashboard", CustomerDashboardController, :index
+  end
+
+  scope "/", VoltWeb do
+    pipe_through [:browser]
+
+    delete "/customers/log_out", CustomerSessionController, :delete
+    get "/customers/confirm", CustomerConfirmationController, :new
+    post "/customers/confirm", CustomerConfirmationController, :create
+    get "/customers/confirm/:token", CustomerConfirmationController, :edit
+    post "/customers/confirm/:token", CustomerConfirmationController, :update
+  end
 end
diff --git a/lib/volt_web/services/geolocation.ex b/lib/volt_web/services/geolocation.ex
new file mode 100644
index 0000000000000000000000000000000000000000..588d9a0332fcaf7168a87bf57e2d044d9c01955b
--- /dev/null
+++ b/lib/volt_web/services/geolocation.ex
@@ -0,0 +1,22 @@
+defmodule Volt.Geolocation do
+
+  def find_location(address) do
+    uri = "http://dev.virtualearth.net/REST/v1/Locations?q=1#{URI.encode(address)}%&key=#{get_key()}"
+    response = HTTPoison.get! uri
+    matches = Regex.named_captures(~r/coordinates\D+(?<lat>-?\d+.\d+)\D+(?<long>-?\d+.\d+)/, response.body)
+    [{v1, _}, {v2, _}] = [matches["lat"] |> Float.parse, matches["long"] |> Float.parse]
+    [v1, v2]
+  end
+
+  def distance(origin, destination) do
+    [o1, o2] = find_location(origin)
+    [d1, d2] = find_location(destination)
+    uri = "https://dev.virtualearth.net/REST/v1/Routes/DistanceMatrix?origins=#{o1},#{o2}&destinations=#{d1},#{d2}&travelMode=driving&key=#{get_key()}"
+    response = HTTPoison.get! uri
+    matches = Regex.named_captures(~r/travelD\D+(?<dist>\d+.\d+)\D+(?<dur>\d+.\d+)/,response.body)
+    [{v1, _}, {v2, _}] = [matches["dist"] |> Float.parse, matches["dur"] |> Float.parse]
+    [v1, v2]
+ end
+
+  defp get_key(), do: System.get_env("BING_MAP_KEY")
+end
diff --git a/lib/volt_web/templates/courier/edit.html.heex b/lib/volt_web/templates/courier/edit.html.heex
deleted file mode 100644
index a657afb276376e20bf30c40926352771e41a43d7..0000000000000000000000000000000000000000
--- a/lib/volt_web/templates/courier/edit.html.heex
+++ /dev/null
@@ -1,5 +0,0 @@
-<h1>Edit Courier</h1>
-
-<%= render "form.html", Map.put(assigns, :action, Routes.courier_path(@conn, :update, @courier)) %>
-
-<span><%= link "Back", to: Routes.courier_path(@conn, :index) %></span>
diff --git a/lib/volt_web/templates/courier/index.html.heex b/lib/volt_web/templates/courier/index.html.heex
deleted file mode 100644
index 3236996b5c5c21dc22c0b144ea37edbf53ca95cc..0000000000000000000000000000000000000000
--- a/lib/volt_web/templates/courier/index.html.heex
+++ /dev/null
@@ -1,2 +0,0 @@
-<h1>Courier registration</h1>
-<span><%= link "New Courier", to: Routes.courier_path(@conn, :new) %></span>
diff --git a/lib/volt_web/templates/courier/new.html.heex b/lib/volt_web/templates/courier/new.html.heex
deleted file mode 100644
index aeea11fd16c5c100a012dc4ee1995f1b6868ba0c..0000000000000000000000000000000000000000
--- a/lib/volt_web/templates/courier/new.html.heex
+++ /dev/null
@@ -1,5 +0,0 @@
-<h1>New Courier</h1>
-
-<%= render "form.html", Map.put(assigns, :action, Routes.courier_path(@conn, :create)) %>
-
-<span><%= link "Back", to: Routes.courier_path(@conn, :index) %></span>
diff --git a/lib/volt_web/templates/courier/show.html.heex b/lib/volt_web/templates/courier/show.html.heex
deleted file mode 100644
index 79cb1ce6d454dc38aaecaade72ef4918e8c1db5c..0000000000000000000000000000000000000000
--- a/lib/volt_web/templates/courier/show.html.heex
+++ /dev/null
@@ -1,38 +0,0 @@
-<h1>Show Courier</h1>
-
-<ul>
-
-  <li>
-    <strong>Email:</strong>
-    <%= @courier.email %>
-  </li>
-
-  <li>
-    <strong>Password:</strong>
-    <%= @courier.password %>
-  </li>
-
-  <li>
-    <strong>First name:</strong>
-    <%= @courier.first_name %>
-  </li>
-
-  <li>
-    <strong>Last name:</strong>
-    <%= @courier.last_name %>
-  </li>
-
-  <li>
-    <strong>Phone number:</strong>
-    <%= @courier.phone_number %>
-  </li>
-
-  <li>
-    <strong>Courier status:</strong>
-    <%= @courier.courier_status %>
-  </li>
-
-</ul>
-
-<span><%= link "Edit", to: Routes.courier_path(@conn, :edit, @courier) %></span> |
-<span><%= link "Back", to: Routes.courier_path(@conn, :index) %></span>
diff --git a/lib/volt_web/templates/courier_confirmation/edit.html.heex b/lib/volt_web/templates/courier_confirmation/edit.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..8db32f771a5fcc52e899d1c7b8f4c46c9c0d7026
--- /dev/null
+++ b/lib/volt_web/templates/courier_confirmation/edit.html.heex
@@ -0,0 +1,12 @@
+<h1>Confirm account</h1>
+
+<.form let={_f} for={:courier} action={Routes.courier_confirmation_path(@conn, :update, @token)}>
+  <div>
+    <%= submit "Confirm my account" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.courier_registration_path(@conn, :new) %> |
+  <%= link "Log in", to: Routes.courier_session_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/courier_confirmation/new.html.heex b/lib/volt_web/templates/courier_confirmation/new.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..8251d7d3b4bf3d06969ccd5e0422486d0b386415
--- /dev/null
+++ b/lib/volt_web/templates/courier_confirmation/new.html.heex
@@ -0,0 +1,15 @@
+<h1>Resend confirmation instructions</h1>
+
+<.form let={f} for={:courier} action={Routes.courier_confirmation_path(@conn, :create)}>
+  <%= label f, :email %>
+  <%= email_input f, :email, required: true %>
+
+  <div>
+    <%= submit "Resend confirmation instructions" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.courier_registration_path(@conn, :new) %> |
+  <%= link "Log in", to: Routes.courier_session_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/courier_dashboard/index.html.heex b/lib/volt_web/templates/courier_dashboard/index.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..25a753ca40a383d8f992b48ce369cf7a0113c253
--- /dev/null
+++ b/lib/volt_web/templates/courier_dashboard/index.html.heex
@@ -0,0 +1 @@
+<h1>Courier Dashboard</h1>
\ No newline at end of file
diff --git a/lib/volt_web/templates/courier/form.html.heex b/lib/volt_web/templates/courier_registration/new.html.heex
similarity index 52%
rename from lib/volt_web/templates/courier/form.html.heex
rename to lib/volt_web/templates/courier_registration/new.html.heex
index 2d46828c4e7d7766edc221be98e234196445853d..4d4d1c7a2f3c73d66026e1f4b43a6d849d275233 100644
--- a/lib/volt_web/templates/courier/form.html.heex
+++ b/lib/volt_web/templates/courier_registration/new.html.heex
@@ -1,16 +1,18 @@
-<.form let={f} for={@changeset} action={@action}>
+<h1>Register</h1>
+
+<.form let={f} for={@changeset} action={Routes.courier_registration_path(@conn, :create)}>
   <%= if @changeset.action do %>
     <div class="alert alert-danger">
-      <p>Oops, something went wrong! Please check the errors below.</p>
+      <p>@changeset Oops, something went wrong! Please check the errors below.</p>
     </div>
   <% end %>
 
   <%= label f, :email %>
-  <%= email_input f, :email %>
+  <%= email_input f, :email, required: true %>
   <%= error_tag f, :email %>
 
   <%= label f, :password %>
-  <%= password_input f, :password %>
+  <%= password_input f, :password, required: true %>
   <%= error_tag f, :password %>
 
   <%= label f, :first_name %>
@@ -25,11 +27,12 @@
   <%= telephone_input f, :phone_number %>
   <%= error_tag f, :phone_number %>
 
-  <%= label f, :courier_status %>
-  <%= text_input f, :courier_status %>
-  <%= error_tag f, :courier_status %>
-
   <div>
-    <%= submit "Save" %>
+    <%= submit "Register" %>
   </div>
 </.form>
+
+<p>
+  <%= link "Log in", to: Routes.courier_session_path(@conn, :new) %> |
+  <%= link "Forgot your password?", to: Routes.courier_reset_password_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/courier_reset_password/edit.html.heex b/lib/volt_web/templates/courier_reset_password/edit.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..df5c1a15d57073d606f8ac38bee9bc3c88129dcb
--- /dev/null
+++ b/lib/volt_web/templates/courier_reset_password/edit.html.heex
@@ -0,0 +1,26 @@
+<h1>Reset password</h1>
+
+<.form let={f} for={@changeset} action={Routes.courier_reset_password_path(@conn, :update, @token)}>
+  <%= if @changeset.action do %>
+    <div class="alert alert-danger">
+      <p>Oops, something went wrong! Please check the errors below.</p>
+    </div>
+  <% end %>
+
+  <%= label f, :password, "New password" %>
+  <%= password_input f, :password, required: true %>
+  <%= error_tag f, :password %>
+
+  <%= label f, :password_confirmation, "Confirm new password" %>
+  <%= password_input f, :password_confirmation, required: true %>
+  <%= error_tag f, :password_confirmation %>
+
+  <div>
+    <%= submit "Reset password" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.courier_registration_path(@conn, :new) %> |
+  <%= link "Log in", to: Routes.courier_session_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/courier_reset_password/new.html.heex b/lib/volt_web/templates/courier_reset_password/new.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..192986c9e40dae96c30abf25d4f6612be2c51f94
--- /dev/null
+++ b/lib/volt_web/templates/courier_reset_password/new.html.heex
@@ -0,0 +1,15 @@
+<h1>Forgot your password?</h1>
+
+<.form let={f} for={:courier} action={Routes.courier_reset_password_path(@conn, :create)}>
+  <%= label f, :email %>
+  <%= email_input f, :email, required: true %>
+
+  <div>
+    <%= submit "Send instructions to reset password" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.courier_registration_path(@conn, :new) %> |
+  <%= link "Log in", to: Routes.courier_session_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/courier_session/new.html.heex b/lib/volt_web/templates/courier_session/new.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..e3bbcc2f386aa4ae65e36587c95f43a06b646f14
--- /dev/null
+++ b/lib/volt_web/templates/courier_session/new.html.heex
@@ -0,0 +1,27 @@
+<h1>Log in</h1>
+
+<.form let={f} for={@conn} action={Routes.courier_session_path(@conn, :create)} as={:courier}>
+  <%= if @error_message do %>
+    <div class="alert alert-danger">
+      <p><%= @error_message %></p>
+    </div>
+  <% end %>
+
+  <%= label f, :email %>
+  <%= email_input f, :email, required: true %>
+
+  <%= label f, :password %>
+  <%= password_input f, :password, required: true %>
+
+  <%= label f, :remember_me, "Keep me logged in for 60 days" %>
+  <%= checkbox f, :remember_me %>
+
+  <div>
+    <%= submit "Log in" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.courier_registration_path(@conn, :new) %> |
+  <%= link "Forgot your password?", to: Routes.courier_reset_password_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/courier_settings/edit.html.heex b/lib/volt_web/templates/courier_settings/edit.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..a802e274a3f8edc6057f91e2ac4ebbec688fd187
--- /dev/null
+++ b/lib/volt_web/templates/courier_settings/edit.html.heex
@@ -0,0 +1,85 @@
+<h1>Settings</h1>
+
+<h3>Change email</h3>
+
+<.form let={f} for={@email_changeset} action={Routes.courier_settings_path(@conn, :update)} id="update_email">
+  <%= if @email_changeset.action do %>
+    <div class="alert alert-danger">
+      <p>Oops, something went wrong! Please check the errors below.</p>
+    </div>
+  <% end %>
+
+  <%= hidden_input f, :action, name: "action", value: "update_email" %>
+
+  <%= label f, :email %>
+  <%= email_input f, :email, required: true %>
+  <%= error_tag f, :email %>
+
+  <%= label f, :current_password, for: "current_password_for_email" %>
+  <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_email" %>
+  <%= error_tag f, :current_password %>
+
+  <div>
+    <%= submit "Change email" %>
+  </div>
+</.form>
+
+<h3>Change password</h3>
+
+<.form let={f} for={@password_changeset} action={Routes.courier_settings_path(@conn, :update)} id="update_password">
+  <%= if @password_changeset.action do %>
+    <div class="alert alert-danger">
+      <p>Oops, something went wrong! Please check the errors below.</p>
+    </div>
+  <% end %>
+
+  <%= hidden_input f, :action, name: "action", value: "update_password" %>
+
+  <%= label f, :password, "New password" %>
+  <%= password_input f, :password, required: true %>
+  <%= error_tag f, :password %>
+
+  <%= label f, :password_confirmation, "Confirm new password" %>
+  <%= password_input f, :password_confirmation, required: true %>
+  <%= error_tag f, :password_confirmation %>
+
+  <%= label f, :current_password, for: "current_password_for_password" %>
+  <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_password" %>
+  <%= error_tag f, :current_password %>
+
+  <div>
+    <%= submit "Change password" %>
+  </div>
+</.form>
+
+<h3>Change Profile</h3>
+
+<.form let={f} for={@profile_changeset} action={Routes.courier_settings_path(@conn, :update)} id="update_profile">
+  <%= if @profile_changeset.action do %>
+    <div class="alert alert-danger">
+      <p>Oops, something went wrong! Please check the errors below.</p>
+    </div>
+  <% end %>
+
+  <%= hidden_input f, :action, name: "action", value: "update_profile" %>
+
+  <%= label f, :first_name %>
+  <%= text_input f, :first_name %>
+  <%= error_tag f, :first_name %>
+
+  <%= label f, :last_name %>
+  <%= text_input f, :last_name %>
+  <%= error_tag f, :last_name %>
+
+  <%= label f, :phone_number %>
+  <%= text_input f, :phone_number %>
+  <%= error_tag f, :phone_number %>
+
+  <%= label f, :current_password, for: "current_password_for_password" %>
+  <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_profile" %>
+  <%= error_tag f, :current_password %>
+
+  <div>
+    <%= submit "Change profile" %>
+  </div>
+</.form>
diff --git a/lib/volt_web/templates/customer/edit.html.heex b/lib/volt_web/templates/customer/edit.html.heex
deleted file mode 100644
index 9f9b57d9402c4375ffd85c1c3b73d8daf0c57c85..0000000000000000000000000000000000000000
--- a/lib/volt_web/templates/customer/edit.html.heex
+++ /dev/null
@@ -1,5 +0,0 @@
-<h1>Edit Customer</h1>
-
-<%= render "form.html", Map.put(assigns, :action, Routes.customer_path(@conn, :update, @customer)) %>
-
-<span><%= link "Back", to: Routes.customer_path(@conn, :index) %></span>
diff --git a/lib/volt_web/templates/customer/form.html.heex b/lib/volt_web/templates/customer/form.html.heex
deleted file mode 100644
index c6a7141f257762610938dbbcfdd1ca8a2397e1df..0000000000000000000000000000000000000000
--- a/lib/volt_web/templates/customer/form.html.heex
+++ /dev/null
@@ -1,51 +0,0 @@
-<.form let={f} for={@changeset} action={@action}>
-  <%= if @changeset.action do %>
-    <div class="alert alert-danger">
-      <p>Oops, something went wrong! Please check the errors below.</p>
-    </div>
-  <% end %>
-
-  <%= label f, :email %>
-  <%= text_input f, :email, id: "email" %>
-  <%= error_tag f, :email %>
-
-  <%= label f, :password %>
-  <%= text_input f, :password, id: "password" %>
-  <%= error_tag f, :password %>
-
-  <%= label f, :first_name %>
-  <%= text_input f, :first_name, id: "first_name" %>
-  <%= error_tag f, :first_name %>
-
-  <%= label f, :last_name %>
-  <%= text_input f, :last_name, id: "last_name" %>
-  <%= error_tag f, :last_name %>
-
-  <%= label f, :phone_number %>
-  <%= text_input f, :phone_number, id: "phone_number" %>
-  <%= error_tag f, :phone_number %>
-
-  <%= label f, :birth_date %>
-  <%= date_select f, :birth_date, id: "birth_date" %>
-  <%= error_tag f, :birth_date %>
-
-  <%= label f, :address %>
-  <%= text_input f, :address, id: "address" %>
-  <%= error_tag f, :address %>
-
-  <%= label f, :zip_code %>
-  <%= text_input f, :zip_code, id: "zip_code" %>
-  <%= error_tag f, :zip_code %>
-
-  <%= label f, :city %>
-  <%= text_input f, :city, id: "city" %>
-  <%= error_tag f, :city %>
-
-  <%= label f, :card_number %>
-  <%= text_input f, :card_number, id: "card_number" %>
-  <%= error_tag f, :card_number %>
-
-  <div>
-    <%= submit "Save", id: "submit_button" %>
-  </div>
-</.form>
diff --git a/lib/volt_web/templates/customer/index.html.heex b/lib/volt_web/templates/customer/index.html.heex
deleted file mode 100644
index c5ee19b37caf10a48b37b5a7f933ec9747d0d3f5..0000000000000000000000000000000000000000
--- a/lib/volt_web/templates/customer/index.html.heex
+++ /dev/null
@@ -1,46 +0,0 @@
-<h1>Listing Customers</h1>
-
-<table>
-  <thead>
-    <tr>
-      <th>Email</th>
-      <th>Password</th>
-      <th>First name</th>
-      <th>Last name</th>
-      <th>Phone number</th>
-      <th>Birth date</th>
-      <th>Address</th>
-      <th>Zip code</th>
-      <th>City</th>
-      <th>Card number</th>
-      <th>Balance</th>
-
-      <th></th>
-    </tr>
-  </thead>
-  <tbody>
-<%= for customer <- @customers do %>
-    <tr>
-      <td><%= customer.email %></td>
-      <td><%= customer.password %></td>
-      <td><%= customer.first_name %></td>
-      <td><%= customer.last_name %></td>
-      <td><%= customer.phone_number %></td>
-      <td><%= customer.birth_date %></td>
-      <td><%= customer.address %></td>
-      <td><%= customer.zip_code %></td>
-      <td><%= customer.city %></td>
-      <td><%= customer.card_number %></td>
-      <td><%= customer.balance %></td>
-
-      <td>
-        <span><%= link "Show", to: Routes.customer_path(@conn, :show, customer) %></span>
-        <span><%= link "Edit", to: Routes.customer_path(@conn, :edit, customer) %></span>
-        <span><%= link "Delete", to: Routes.customer_path(@conn, :delete, customer), method: :delete, data: [confirm: "Are you sure?"] %></span>
-      </td>
-    </tr>
-<% end %>
-  </tbody>
-</table>
-
-<span><%= link "New Customer", to: Routes.customer_path(@conn, :new) %></span>
diff --git a/lib/volt_web/templates/customer/new.html.heex b/lib/volt_web/templates/customer/new.html.heex
deleted file mode 100644
index 11e7d93d0e02961212f70cf127682c813ba33796..0000000000000000000000000000000000000000
--- a/lib/volt_web/templates/customer/new.html.heex
+++ /dev/null
@@ -1,5 +0,0 @@
-<h1>New Customer</h1>
-
-<%= render "form.html", Map.put(assigns, :action, Routes.customer_path(@conn, :create)) %>
-
-<span><%= link "Back", to: Routes.page_path(@conn, :index) %></span>
diff --git a/lib/volt_web/templates/customer/show.html.heex b/lib/volt_web/templates/customer/show.html.heex
deleted file mode 100644
index 139580f9540c7df0cc65af1e3a3eac533ac7ea2e..0000000000000000000000000000000000000000
--- a/lib/volt_web/templates/customer/show.html.heex
+++ /dev/null
@@ -1,63 +0,0 @@
-<h1>Show Customer</h1>
-
-<ul>
-
-  <li>
-    <strong>Email:</strong>
-    <%= @customer.email %>
-  </li>
-
-  <li>
-    <strong>Password:</strong>
-    <%= @customer.password %>
-  </li>
-
-  <li>
-    <strong>First name:</strong>
-    <%= @customer.first_name %>
-  </li>
-
-  <li>
-    <strong>Last name:</strong>
-    <%= @customer.last_name %>
-  </li>
-
-  <li>
-    <strong>Phone number:</strong>
-    <%= @customer.phone_number %>
-  </li>
-
-  <li>
-    <strong>Birth date:</strong>
-    <%= @customer.birth_date %>
-  </li>
-
-  <li>
-    <strong>Address:</strong>
-    <%= @customer.address %>
-  </li>
-
-  <li>
-    <strong>Zip code:</strong>
-    <%= @customer.zip_code %>
-  </li>
-
-  <li>
-    <strong>City:</strong>
-    <%= @customer.city %>
-  </li>
-
-  <li>
-    <strong>Card number:</strong>
-    <%= @customer.card_number %>
-  </li>
-
-  <li>
-    <strong>Balance:</strong>
-    <%= @customer.balance %>
-  </li>
-
-</ul>
-
-<span><%= link "Edit", to: Routes.customer_path(@conn, :edit, @customer) %></span> |
-<span><%= link "Back", to: Routes.customer_path(@conn, :index) %></span>
diff --git a/lib/volt_web/templates/customer_confirmation/edit.html.heex b/lib/volt_web/templates/customer_confirmation/edit.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..5dfef6ee934c504010b6d5fb9dcc3c1911432802
--- /dev/null
+++ b/lib/volt_web/templates/customer_confirmation/edit.html.heex
@@ -0,0 +1,12 @@
+<h1>Confirm account</h1>
+
+<.form let={_f} for={:customer} action={Routes.customer_confirmation_path(@conn, :update, @token)}>
+  <div>
+    <%= submit "Confirm my account" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.customer_registration_path(@conn, :new) %> |
+  <%= link "Log in", to: Routes.customer_session_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/customer_confirmation/new.html.heex b/lib/volt_web/templates/customer_confirmation/new.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..9a945375bb5b7dbc7aba3203a3b9028fb5b113ec
--- /dev/null
+++ b/lib/volt_web/templates/customer_confirmation/new.html.heex
@@ -0,0 +1,15 @@
+<h1>Resend confirmation instructions</h1>
+
+<.form let={f} for={:customer} action={Routes.customer_confirmation_path(@conn, :create)}>
+  <%= label f, :email %>
+  <%= email_input f, :email, required: true %>
+
+  <div>
+    <%= submit "Resend confirmation instructions" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.customer_registration_path(@conn, :new) %> |
+  <%= link "Log in", to: Routes.customer_session_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/customer_dashboard/index.html.heex b/lib/volt_web/templates/customer_dashboard/index.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..6dc106af73187637bf8c217304df2775f5d263f6
--- /dev/null
+++ b/lib/volt_web/templates/customer_dashboard/index.html.heex
@@ -0,0 +1,26 @@
+<h1>Listing restaurants</h1>
+
+<table>
+  <thead>
+    <tr>
+      <th>Name</th>
+      <th>Tags</th>
+      <th>Price Level</th>
+      <th>Distance</th>
+      <th></th>
+    </tr>
+  </thead>
+  <tbody>
+<%= for restaurant <- @restaurants do %>
+    <tr>
+      <td><%= restaurant.name %></td>
+      <td> </td>
+      <td><%= restaurant.price_level %></td>
+      <td><%= restaurant.dist %></td>
+      <td>
+        <span><%= link "Order", to: "" %></span>
+      </td>
+    </tr>
+<% end %>
+  </tbody>
+</table>
diff --git a/lib/volt_web/templates/customer_registration/new.html.heex b/lib/volt_web/templates/customer_registration/new.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..19a9b98cc1ab806491d6a85ebf4f297d81d09daa
--- /dev/null
+++ b/lib/volt_web/templates/customer_registration/new.html.heex
@@ -0,0 +1,142 @@
+<h1>Register</h1>
+
+<.form let={f} for={@changeset} action={Routes.customer_registration_path(@conn, :create)}>
+  <%= if @changeset.action do %>
+    <div class="alert alert-danger">
+      <p>Oops, something went wrong! Please check the errors below.</p>
+    </div>
+  <% end %>
+
+  <%= label f, :email %>
+  <%= email_input f, :email, required: true, id: "email" %>
+  <%= error_tag f, :email %>
+
+  <%= label f, :password %>
+  <%= password_input f, :password, required: true, id: "password" %>
+  <%= error_tag f, :password %>
+
+  <%= label f, :first_name %>
+  <%= text_input f, :first_name, id: "first_name" %>
+  <%= error_tag f, :first_name %>
+
+  <%= label f, :last_name %>
+  <%= text_input f, :last_name, id: "last_name" %>
+  <%= error_tag f, :last_name %>
+
+  <%= label f, :phone_number %>
+  <%= text_input f, :phone_number, id: "phone_number" %>
+  <%= error_tag f, :phone_number %>
+
+  <%= label f, :birth_date %>
+  <%= date_select f, :birth_date, id: "birth_date" %>
+  <%= error_tag f, :birth_date %>
+
+  <div title="current location will be taken from center of the map">
+
+      <meta charset="utf-8" />
+      <script type='text/javascript'>
+      var map, searchManager;
+
+      function useCurrentLocation() {
+         // Get the checkbox
+         var checkBox = document.getElementById("use_current_location_check");
+
+         if (checkBox.checked == true){
+           reverseGeocode()
+         } else {
+           document.getElementById("address").value = "";
+           document.getElementById("city").value = "";
+           document.getElementById("zip_code").value = "";
+         }
+      }
+
+      function GetMap() {
+          map = new Microsoft.Maps.Map('#myMap', {});
+
+          //Load the spatial math module
+          Microsoft.Maps.loadModule("Microsoft.Maps.SpatialMath", function () {
+              //Request the user's location
+              navigator.geolocation.getCurrentPosition(function (position) {
+                  var loc = new Microsoft.Maps.Location(position.coords.latitude, position.coords.longitude);
+
+                  //Create an accuracy circle
+                  var path = Microsoft.Maps.SpatialMath.getRegularPolygon(loc, position.coords.accuracy, 36,  Microsoft.Maps.SpatialMath.Meters);
+                  var poly = new Microsoft.Maps.Polygon(path);
+                  map.entities.push(poly);
+
+                  //Add a pushpin at the user's location.
+                  var pin = new Microsoft.Maps.Pushpin(loc);
+                  map.entities.push(pin);
+
+                  //Center the map on the user's location.
+                  map.setView({ center: loc, zoom: 17 });
+              });
+          });
+
+          //Make a request to reverse geocode the center of the map.
+          reverseGeocode();
+      }
+
+      function reverseGeocode() {
+          //If search manager is not defined, load the search module.
+          if (!searchManager) {
+              //Create an instance of the search manager and call the reverseGeocode function again.
+              Microsoft.Maps.loadModule('Microsoft.Maps.Search', function () {
+                  searchManager = new Microsoft.Maps.Search.SearchManager(map);
+              });
+          } else {
+              var searchRequest = {
+                  location: map.getCenter(),
+                  callback: function (r) {
+                      document.getElementById("address").value = r.address.addressLine;
+                      document.getElementById("city").value = r.address.locality;
+                      document.getElementById("zip_code").value = r.address.postalCode;
+                  },
+                  errorCallback: function (e) {
+                      //If there is an error, alert the user about it.
+                      alert("Unable to reverse geocode location.");
+                  }
+              };
+
+              //Make the reverse geocode request.
+              searchManager.reverseGeocode(searchRequest);
+          }
+      }
+      </script>
+
+      <!--- TODO remove hardcoded key --->
+      <script type='text/javascript' src='http://www.bing.com/api/maps/mapcontrol?callback=GetMap&key=Ar7cmpEAx_LoIKFwOuz15AOa6RobbU1CMbgdmPUUJGklvkbKQy1Dz8MwRohxv7kz' async defer></script>
+
+      <body>
+          <div id="myMap" style="position:relative;width:600px;height:400px;"></div>
+      </body>
+
+      Use my current location: <input type="checkbox" id="use_current_location_check" onclick="useCurrentLocation()">
+  </div>
+
+  <%= label f, :address %>
+  <%= text_input f, :address, id: "address" %>
+  <%= error_tag f, :address %>
+
+  <%= label f, :city %>
+  <%= text_input f, :city, id: "city" %>
+  <%= error_tag f, :city %>
+
+  <%= label f, :zip_code %>
+  <%= text_input f, :zip_code, id: "zip_code" %>
+  <%= error_tag f, :zip_code %>
+
+  <%= label f, :card_number %>
+  <%= text_input f, :card_number, id: "card_number" %>
+  <%= error_tag f, :card_number %>
+
+
+  <div>
+    <%= submit "Register", id: "submit_button" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Log in", to: Routes.customer_session_path(@conn, :new) %> |
+  <%= link "Forgot your password?", to: Routes.customer_reset_password_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/customer_reset_password/edit.html.heex b/lib/volt_web/templates/customer_reset_password/edit.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..0f09010879ae84974cd3120bbb43e44538bc6e80
--- /dev/null
+++ b/lib/volt_web/templates/customer_reset_password/edit.html.heex
@@ -0,0 +1,26 @@
+<h1>Reset password</h1>
+
+<.form let={f} for={@changeset} action={Routes.customer_reset_password_path(@conn, :update, @token)}>
+  <%= if @changeset.action do %>
+    <div class="alert alert-danger">
+      <p>Oops, something went wrong! Please check the errors below.</p>
+    </div>
+  <% end %>
+
+  <%= label f, :password, "New password" %>
+  <%= password_input f, :password, required: true %>
+  <%= error_tag f, :password %>
+
+  <%= label f, :password_confirmation, "Confirm new password" %>
+  <%= password_input f, :password_confirmation, required: true %>
+  <%= error_tag f, :password_confirmation %>
+
+  <div>
+    <%= submit "Reset password" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.customer_registration_path(@conn, :new) %> |
+  <%= link "Log in", to: Routes.customer_session_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/customer_reset_password/new.html.heex b/lib/volt_web/templates/customer_reset_password/new.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..0fff4da9b29a52f2b8d0bb89770cd3f50cfb867a
--- /dev/null
+++ b/lib/volt_web/templates/customer_reset_password/new.html.heex
@@ -0,0 +1,15 @@
+<h1>Forgot your password?</h1>
+
+<.form let={f} for={:customer} action={Routes.customer_reset_password_path(@conn, :create)}>
+  <%= label f, :email %>
+  <%= email_input f, :email, required: true %>
+
+  <div>
+    <%= submit "Send instructions to reset password" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.customer_registration_path(@conn, :new) %> |
+  <%= link "Log in", to: Routes.customer_session_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/customer_session/new.html.heex b/lib/volt_web/templates/customer_session/new.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..aeb7c91ceb96f63234530ffe15b4bf1bf8a5141a
--- /dev/null
+++ b/lib/volt_web/templates/customer_session/new.html.heex
@@ -0,0 +1,27 @@
+<h1>Log in</h1>
+
+<.form let={f} for={@conn} action={Routes.customer_session_path(@conn, :create)} as={:customer}>
+  <%= if @error_message do %>
+    <div class="alert alert-danger">
+      <p><%= @error_message %></p>
+    </div>
+  <% end %>
+
+  <%= label f, :email %>
+  <%= email_input f, :email, required: true %>
+
+  <%= label f, :password %>
+  <%= password_input f, :password, required: true %>
+
+  <%= label f, :remember_me, "Keep me logged in for 60 days" %>
+  <%= checkbox f, :remember_me %>
+
+  <div>
+    <%= submit "Log in" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.customer_registration_path(@conn, :new) %> |
+  <%= link "Forgot your password?", to: Routes.customer_reset_password_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/customer_settings/edit.html.heex b/lib/volt_web/templates/customer_settings/edit.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..9e9ff0748480f091e3ab809ccc03d2b4a9ff233a
--- /dev/null
+++ b/lib/volt_web/templates/customer_settings/edit.html.heex
@@ -0,0 +1,105 @@
+<h1>Settings</h1>
+
+<h3>Change email</h3>
+
+<.form let={f} for={@email_changeset} action={Routes.customer_settings_path(@conn, :update)} id="update_email">
+  <%= if @email_changeset.action do %>
+    <div class="alert alert-danger">
+      <p>Oops, something went wrong! Please check the errors below.</p>
+    </div>
+  <% end %>
+
+  <%= hidden_input f, :action, name: "action", value: "update_email" %>
+
+  <%= label f, :email %>
+  <%= email_input f, :email, required: true %>
+  <%= error_tag f, :email %>
+
+  <%= label f, :current_password, for: "current_password_for_email" %>
+  <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_email" %>
+  <%= error_tag f, :current_password %>
+
+  <div>
+    <%= submit "Change email" %>
+  </div>
+</.form>
+
+<h3>Change password</h3>
+
+<.form let={f} for={@password_changeset} action={Routes.customer_settings_path(@conn, :update)} id="update_password">
+  <%= if @password_changeset.action do %>
+    <div class="alert alert-danger">
+      <p>Oops, something went wrong! Please check the errors below.</p>
+    </div>
+  <% end %>
+
+  <%= hidden_input f, :action, name: "action", value: "update_password" %>
+
+  <%= label f, :password, "New password" %>
+  <%= password_input f, :password, required: true %>
+  <%= error_tag f, :password %>
+
+  <%= label f, :password_confirmation, "Confirm new password" %>
+  <%= password_input f, :password_confirmation, required: true %>
+  <%= error_tag f, :password_confirmation %>
+
+  <%= label f, :current_password, for: "current_password_for_password" %>
+  <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_password" %>
+  <%= error_tag f, :current_password %>
+
+  <div>
+    <%= submit "Change password" %>
+  </div>
+</.form>
+
+<h3>Change Profile</h3>
+
+<.form let={f} for={@profile_changeset} action={Routes.customer_settings_path(@conn, :update)} id="update_profile">
+  <%= if @profile_changeset.action do %>
+    <div class="alert alert-danger">
+      <p>Oops, something went wrong! Please check the errors below.</p>
+    </div>
+  <% end %>
+
+  <%= hidden_input f, :action, name: "action", value: "update_profile" %>
+
+  <%= label f, :first_name %>
+  <%= text_input f, :first_name, id: "first_name" %>
+  <%= error_tag f, :first_name %>
+
+  <%= label f, :last_name %>
+  <%= text_input f, :last_name, id: "last_name" %>
+  <%= error_tag f, :last_name %>
+
+  <%= label f, :phone_number %>
+  <%= text_input f, :phone_number, id: "phone_number" %>
+  <%= error_tag f, :phone_number %>
+
+  <%= label f, :birth_date %>
+  <%= date_select f, :birth_date, id: "birth_date" %>
+  <%= error_tag f, :birth_date %>
+
+  <%= label f, :address %>
+  <%= text_input f, :address, id: "address" %>
+  <%= error_tag f, :address %>
+
+  <%= label f, :zip_code %>
+  <%= text_input f, :zip_code, id: "zip_code" %>
+  <%= error_tag f, :zip_code %>
+
+  <%= label f, :city %>
+  <%= text_input f, :city, id: "city" %>
+  <%= error_tag f, :city %>
+
+  <%= label f, :card_number %>
+  <%= text_input f, :card_number, id: "card_number" %>
+  <%= error_tag f, :card_number %>
+
+  <%= label f, :current_password, for: "current_password_for_password" %>
+  <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_password" %>
+  <%= error_tag f, :current_password %>
+
+  <div>
+    <%= submit "Change profile" %>
+  </div>
+</.form>
diff --git a/lib/volt_web/templates/layout/_courier_menu.html.heex b/lib/volt_web/templates/layout/_courier_menu.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..a4ae19798a0c467da06838b684564ff0f285570e
--- /dev/null
+++ b/lib/volt_web/templates/layout/_courier_menu.html.heex
@@ -0,0 +1,8 @@
+<ul>
+<%= if @current_courier do %>
+  <h3>Greetings Courier!</h3>
+  <li><%= @current_courier.email %></li>
+  <li><%= link "Settings", to: Routes.courier_settings_path(@conn, :edit) %></li>
+  <li><%= link "Log out", to: Routes.courier_session_path(@conn, :delete), method: :delete %></li>
+<% end %>
+</ul>
diff --git a/lib/volt_web/templates/layout/_customer_menu.html.heex b/lib/volt_web/templates/layout/_customer_menu.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..0818f7a6aead2ef2e9d9387443828cf953ac3a21
--- /dev/null
+++ b/lib/volt_web/templates/layout/_customer_menu.html.heex
@@ -0,0 +1,8 @@
+<ul>
+<%= if @current_customer do %>
+  <h3>Greetings Customer!</h3>
+  <li><%= @current_customer.email %></li>
+  <li><%= link "Settings", to: Routes.customer_settings_path(@conn, :edit) %></li>
+  <li><%= link "Log out", to: Routes.customer_session_path(@conn, :delete), method: :delete %></li>
+<% end %>
+</ul>
diff --git a/lib/volt_web/templates/layout/_restaurant_menu.html.heex b/lib/volt_web/templates/layout/_restaurant_menu.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..e257b7a75cb0e0b802893ca0ef0cda5b1631e2a4
--- /dev/null
+++ b/lib/volt_web/templates/layout/_restaurant_menu.html.heex
@@ -0,0 +1,8 @@
+<ul>
+<%= if @current_restaurant do %>
+  <h3>Greetings Restaurant Worker!</h3>
+  <li><%= @current_restaurant.email %></li>
+  <li><%= link "Settings", to: Routes.restaurant_settings_path(@conn, :edit) %></li>
+  <li><%= link "Log out", to: Routes.restaurant_session_path(@conn, :delete), method: :delete %></li>
+<% end %>
+</ul>
diff --git a/lib/volt_web/templates/layout/root.html.heex b/lib/volt_web/templates/layout/root.html.heex
index ad6be79717d3f12c78ec3730ad01bd92c84b9a7f..978eb8f395f9f24371e10644fb66dd25379da426 100644
--- a/lib/volt_web/templates/layout/root.html.heex
+++ b/lib/volt_web/templates/layout/root.html.heex
@@ -12,9 +12,29 @@
   <body>
     <header>
       <section class="container">
+         <%= if @current_customer do %>
+        <a href={Routes.customer_dashboard_path(@conn, :index)} class="phx-logo">
+          <img src={Routes.static_path(@conn, "/images/volt_wide_2.png")} alt="Volt logo"/>
+        </a>
+      <% end %>
+      <%= if @current_courier do %>
+        <a href={Routes.courier_dashboard_path(@conn, :index)} class="phx-logo">
+          <img src={Routes.static_path(@conn, "/images/volt_wide_2.png")} alt="Volt logo"/>
+        </a>
+      <% end %>
+      <%= if @current_restaurant do %>
+        <a href={Routes.restaurant_dashboard_path(@conn, :index)} class="phx-logo">
+          <img src={Routes.static_path(@conn, "/images/volt_wide_2.png")} alt="Volt logo"/>
+        </a>
+      <% end %>
+       <%= if !@current_customer and !@current_courier and !@current_restaurant do %>
         <a href={Routes.page_path(@conn, :index)} class="phx-logo">
           <img src={Routes.static_path(@conn, "/images/volt_wide_2.png")} alt="Volt logo"/>
         </a>
+         <% end %>
+        <%= render "_customer_menu.html", assigns %>
+        <%= render "_restaurant_menu.html", assigns %>
+        <%= render "_courier_menu.html", assigns %>
       </section>
     </header>
     <%= @inner_content %>
diff --git a/lib/volt_web/templates/page/index.html.heex b/lib/volt_web/templates/page/index.html.heex
index 2f04ff4503a7c0280b9ce4bd0fbc2291404c6224..7d19891b3375897e29ac7042d14601fb38245671 100644
--- a/lib/volt_web/templates/page/index.html.heex
+++ b/lib/volt_web/templates/page/index.html.heex
@@ -1,20 +1,32 @@
-<section class="row">
-  <article class="column">
-    <h2>Self-registration</h2>
-    <ul>
-      <li>
-        <%= link "Customer", to: Routes.customer_path(@conn, :new), id: "customer_register" %>
-      </li>
-      <li>
-        <%= link "Courier", to: Routes.courier_path(@conn, :new) %>
-      </li>
-      <li>
-        <%= link "Restaurant", to: Routes.restaurant_path(@conn, :new) %>
-      </li>
-    </ul>
-  </article>
-  <article class="column">
-    <h2>Login</h2>
-  
-  </article>
-</section>
+<%= if !@current_customer and !@current_courier and !@current_restaurant do %>
+  <section class="row">
+    <article class="column">
+      <h2>Self-registration</h2>
+      <ul>
+        <li>
+          <%= link "Customer", to: Routes.customer_registration_path(@conn, :new), id: "customer_register" %>
+        </li>
+        <li>
+          <%= link "Courier", to: Routes.courier_registration_path(@conn, :new) %>
+        </li>
+        <li>
+          <%= link "Restaurant", to: Routes.restaurant_registration_path(@conn, :new) %>
+        </li>
+      </ul>
+    </article>
+    <article class="column">
+      <h2>Login</h2>
+      <ul>
+        <li>
+          <%= link "Customer", to: Routes.customer_session_path(@conn, :new) %>
+        </li>
+        <li>
+          <%= link "Courier", to: Routes.courier_session_path(@conn, :new) %>
+        </li>
+        <li>
+          <%= link "Restaurant", to: Routes.restaurant_session_path(@conn, :new) %>
+        </li>
+      </ul>
+    </article>
+  </section>
+<% end %>
diff --git a/lib/volt_web/templates/restaurant/edit.html.heex b/lib/volt_web/templates/restaurant/edit.html.heex
deleted file mode 100644
index 0e1fb863fd1c89a4e871cfdd7f23067e35407e75..0000000000000000000000000000000000000000
--- a/lib/volt_web/templates/restaurant/edit.html.heex
+++ /dev/null
@@ -1,14 +0,0 @@
-<h1>Edit Restaurant</h1>
-
-<%= render "form.html", Map.put(assigns, :action, Routes.restaurant_path(@conn, :update, @restaurant)) %>
-
-
-<%= if @restaurant.id do %>
-<span>
-<%= link "Back", to: Routes.restaurant_path(@conn, :show, @restaurant.id) %>
-</span>
-<% else %>
-<span>
-<%= link "Back", to: Routes.page_path(@conn, :index) %>
-</span>
-<% end %>
diff --git a/lib/volt_web/templates/restaurant/index.html.heex b/lib/volt_web/templates/restaurant/index.html.heex
deleted file mode 100644
index ff22216e66aa68611beed0b4ec6e398cfc825153..0000000000000000000000000000000000000000
--- a/lib/volt_web/templates/restaurant/index.html.heex
+++ /dev/null
@@ -1,36 +0,0 @@
-<h1>Restaurant Dashboard</h1>
-<ul>
-
-  <li>
-    <strong>Email:</strong>
-    <%= @restaurant.email %>
-  </li>
-  <li>
-    <strong>Phone number:</strong>
-    <%= @restaurant.phone_number %>
-  </li>
-
-  <li>
-    <strong>Name:</strong>
-    <%= @restaurant.name %>
-  </li>
-
-  <li>
-    <strong>Address:</strong>
-    <%= @restaurant.address %>
-  </li>
-
-  <li>
-    <strong>Opening time:</strong>
-    <%= @restaurant.opening_time %>
-  </li>
-
-  <li>
-    <strong>Closing time:</strong>
-    <%= @restaurant.closing_time %>
-  </li>
-
-</ul>
-
-<span><%= link "Edit", to: Routes.restaurant_path(@conn, :edit, @restaurant) %></span> |
-<span><%= link "Log out", to: Routes.page_path(@conn, :index) %></span>
diff --git a/lib/volt_web/templates/restaurant/new.html.heex b/lib/volt_web/templates/restaurant/new.html.heex
deleted file mode 100644
index 82ecb216e2369017be7e50f61e6ef99c5176320f..0000000000000000000000000000000000000000
--- a/lib/volt_web/templates/restaurant/new.html.heex
+++ /dev/null
@@ -1,5 +0,0 @@
-<h1>New Restaurant</h1>
-
-<%= render "form.html", Map.put(assigns, :action, Routes.restaurant_path(@conn, :create)) %>
-
-<span><%= link "Back", to: Routes.page_path(@conn, :index) %></span>
diff --git a/lib/volt_web/templates/restaurant/show.html.heex b/lib/volt_web/templates/restaurant/show.html.heex
deleted file mode 100644
index 51b69d5c9780082127c6b046b28432c047fd8c45..0000000000000000000000000000000000000000
--- a/lib/volt_web/templates/restaurant/show.html.heex
+++ /dev/null
@@ -1,67 +0,0 @@
-<h1>Show Restaurant</h1>
-
-<ul>
-
-  <li>
-    <strong>Email:</strong>
-    <%= @restaurant.email %>
-  </li>
-
-  <li>
-    <strong>Password:</strong>
-    <%= @restaurant.password %>
-  </li>
-
-  <li>
-    <strong>First name:</strong>
-    <%= @restaurant.first_name %>
-  </li>
-
-  <li>
-    <strong>Last name:</strong>
-    <%= @restaurant.last_name %>
-  </li>
-
-  <li>
-    <strong>Phone number:</strong>
-    <%= @restaurant.phone_number %>
-  </li>
-
-  <li>
-    <strong>Name:</strong>
-    <%= @restaurant.name %>
-  </li>
-
-  <li>
-    <strong>Address:</strong>
-    <%= @restaurant.address %>
-  </li>
-
-  <li>
-    <strong>City:</strong>
-    <%= @restaurant.city %>
-  </li>
-
-  <li>
-    <strong>ZIP Code:</strong>
-    <%= @restaurant.zip_code %>
-  </li>
-
-  <li>
-    <strong>Price level:</strong>
-    <%= @restaurant.price_level %>
-  </li>
-
-  <li>
-    <strong>Opening time:</strong>
-    <%= @restaurant.opening_time %>
-  </li>
-
-  <li>
-    <strong>Closing time:</strong>
-    <%= @restaurant.closing_time %>
-  </li>
-
-</ul>
-
-<span><%= link "Login", to: Routes.page_path(@conn, :index) %></span>
diff --git a/lib/volt_web/templates/restaurant_confirmation/edit.html.heex b/lib/volt_web/templates/restaurant_confirmation/edit.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..0ed6a092d68e85000e907d6f366c6b0542ae236e
--- /dev/null
+++ b/lib/volt_web/templates/restaurant_confirmation/edit.html.heex
@@ -0,0 +1,12 @@
+<h1>Confirm account</h1>
+
+<.form let={_f} for={:restaurant} action={Routes.restaurant_confirmation_path(@conn, :update, @token)}>
+  <div>
+    <%= submit "Confirm my account" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.restaurant_registration_path(@conn, :new) %> |
+  <%= link "Log in", to: Routes.restaurant_session_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/restaurant_confirmation/new.html.heex b/lib/volt_web/templates/restaurant_confirmation/new.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..d706fa5d308ec6a1c38fcddd1bfa8233c192731a
--- /dev/null
+++ b/lib/volt_web/templates/restaurant_confirmation/new.html.heex
@@ -0,0 +1,15 @@
+<h1>Resend confirmation instructions</h1>
+
+<.form let={f} for={:restaurant} action={Routes.restaurant_confirmation_path(@conn, :create)}>
+  <%= label f, :email %>
+  <%= email_input f, :email, required: true %>
+
+  <div>
+    <%= submit "Resend confirmation instructions" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.restaurant_registration_path(@conn, :new) %> |
+  <%= link "Log in", to: Routes.restaurant_session_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/restaurant_dashboard/index.html.heex b/lib/volt_web/templates/restaurant_dashboard/index.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..d75876d53f404c54e1359c863b4ff6340bf2f538
--- /dev/null
+++ b/lib/volt_web/templates/restaurant_dashboard/index.html.heex
@@ -0,0 +1,3 @@
+<h1>Restaurant Dashboard</h1>
+<span><%= link "Menu", to: Routes.restaurant_item_path(@conn, :index) %></span><br>
+<span><%= link "New Item", to: Routes.restaurant_item_path(@conn, :new) %></span>
diff --git a/lib/volt_web/templates/restaurant_item/edit.html.heex b/lib/volt_web/templates/restaurant_item/edit.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..9100ce5b614c7643f4a280373609cab47a5cc488
--- /dev/null
+++ b/lib/volt_web/templates/restaurant_item/edit.html.heex
@@ -0,0 +1,5 @@
+<h1>Edit Item</h1>
+
+<%= render "form.html", Map.put(assigns, :action, Routes.restaurant_item_path(@conn, :update, @item)) %>
+
+<span><%= link "Back", to: Routes.restaurant_item_path(@conn, :index) %></span>
diff --git a/lib/volt_web/templates/restaurant_item/form.html.heex b/lib/volt_web/templates/restaurant_item/form.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..4e9cc10f8036d04565aaaf0482bb91e14914263f
--- /dev/null
+++ b/lib/volt_web/templates/restaurant_item/form.html.heex
@@ -0,0 +1,23 @@
+<.form let={f} for={@changeset} action={@action}>
+  <%= if @changeset.action do %>
+    <div class="alert alert-danger">
+      <p>Oops, something went wrong! Please check the errors below.</p>
+    </div>
+  <% end %>
+
+  <%= label f, :name %>
+  <%= text_input f, :name, id: "item_name" %>
+  <%= error_tag f, :name %>
+
+  <%= label f, :description %>
+  <%= text_input f, :description, id: "item_description" %>
+  <%= error_tag f, :description %>
+
+  <%= label f, :unit_price %>
+  <%= number_input f, :unit_price, step: "any", id: "item_unit_price" %>
+  <%= error_tag f, :unit_price %>
+
+  <div>
+    <%= submit "Save", id: "submit" %>
+  </div>
+</.form>
diff --git a/lib/volt_web/templates/restaurant_item/index.html.heex b/lib/volt_web/templates/restaurant_item/index.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..dc04557dbbab35e87ba4cefeccf023668a40b32f
--- /dev/null
+++ b/lib/volt_web/templates/restaurant_item/index.html.heex
@@ -0,0 +1,29 @@
+<h1>Menu</h1>
+
+<table>
+  <thead>
+    <tr>
+      <th>Name</th>
+      <th>Description</th>
+      <th>Unit Price</th>
+      <th></th>
+    </tr>
+  </thead>
+  <tbody>
+<%= for item <- @items do %>
+    <tr>
+      <td><%= item.name %></td>
+      <td><%= item.description %></td>
+      <td><%= item.unit_price %></td>
+      <td>
+        <span><%= link "Show", to: Routes.restaurant_item_path(@conn, :show, item) %></span>
+        <span><%= link "Edit", to: Routes.restaurant_item_path(@conn, :edit, item) %></span>
+        <span><%= link "Delete", to: Routes.restaurant_item_path(@conn, :delete, item), method: :delete, data: [confirm: "Are you sure?"] %></span>
+      </td>
+    </tr>
+<% end %>
+  </tbody>
+</table>
+
+<span><%= link "New Item", to: Routes.restaurant_item_path(@conn, :new) %></span><br>
+<span><%= link "Dashboard", to: Routes.restaurant_dashboard_path(@conn, :index) %></span><br>
diff --git a/lib/volt_web/templates/restaurant_item/new.html.heex b/lib/volt_web/templates/restaurant_item/new.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..fcc528bdcdf1d76fea3c9ff83abdf7035e9ddda6
--- /dev/null
+++ b/lib/volt_web/templates/restaurant_item/new.html.heex
@@ -0,0 +1,5 @@
+<h1>New Item</h1>
+
+<%= render "form.html", Map.put(assigns, :action, Routes.restaurant_item_path(@conn, :create)) %>
+
+<span><%= link "Back", to: Routes.restaurant_item_path(@conn, :index) %></span>
diff --git a/lib/volt_web/templates/restaurant_item/show.html.heex b/lib/volt_web/templates/restaurant_item/show.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..f4cbf9c3c774f2a0ef0e2d3b7e98e262dd97b896
--- /dev/null
+++ b/lib/volt_web/templates/restaurant_item/show.html.heex
@@ -0,0 +1,19 @@
+<h1>Show Item</h1>
+
+<ul>
+  <li>
+    <strong>Name:</strong>
+    <%= @item.name %>
+  </li>
+  <li>
+    <strong>Description:</strong>
+    <%= @item.description %>
+  </li>
+  <li>
+    <strong>Unit Price:</strong>
+    <%= @item.unit_price %>
+  </li>
+</ul>
+
+<span><%= link "Edit", to: Routes.restaurant_item_path(@conn, :edit, @item) %></span> |
+<span><%= link "Back", to: Routes.restaurant_item_path(@conn, :index) %></span>
diff --git a/lib/volt_web/templates/restaurant/form.html.heex b/lib/volt_web/templates/restaurant_registration/new.html.heex
similarity index 74%
rename from lib/volt_web/templates/restaurant/form.html.heex
rename to lib/volt_web/templates/restaurant_registration/new.html.heex
index 5e7ffb1e5bb541524c3a62f776920236ad442975..74a001709ac95cb74dbcd1efb209c4419851c410 100644
--- a/lib/volt_web/templates/restaurant/form.html.heex
+++ b/lib/volt_web/templates/restaurant_registration/new.html.heex
@@ -1,4 +1,6 @@
-<.form let={f} for={@changeset} action={@action}>
+<h1>Register</h1>
+
+<.form let={f} for={@changeset} action={Routes.restaurant_registration_path(@conn, :create)}>
   <%= if @changeset.action do %>
     <div class="alert alert-danger">
       <p>Oops, something went wrong! Please check the errors below.</p>
@@ -6,12 +8,11 @@
   <% end %>
 
   <%= label f, :email %>
-  <%= text_input f, :email %>
+  <%= email_input f, :email, required: true %>
   <%= error_tag f, :email %>
 
-
   <%= label f, :password %>
-  <%= text_input f, :password %>
+  <%= password_input f, :password, required: true %>
   <%= error_tag f, :password %>
 
   <%= label f, :first_name %>
@@ -54,7 +55,12 @@
   <%= time_select f, :closing_time %>
   <%= error_tag f, :closing_time %>
 
-  <div id="submit">
-    <%= submit "Save" %>
+  <div>
+    <%= submit "Register", id: "submit" %>
   </div>
 </.form>
+
+<p>
+  <%= link "Log in", to: Routes.restaurant_session_path(@conn, :new) %> |
+  <%= link "Forgot your password?", to: Routes.restaurant_reset_password_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/restaurant_reset_password/edit.html.heex b/lib/volt_web/templates/restaurant_reset_password/edit.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..3a11e0cb02514da3cf4f0fb50fd80b9695c5a27b
--- /dev/null
+++ b/lib/volt_web/templates/restaurant_reset_password/edit.html.heex
@@ -0,0 +1,26 @@
+<h1>Reset password</h1>
+
+<.form let={f} for={@changeset} action={Routes.restaurant_reset_password_path(@conn, :update, @token)}>
+  <%= if @changeset.action do %>
+    <div class="alert alert-danger">
+      <p>Oops, something went wrong! Please check the errors below.</p>
+    </div>
+  <% end %>
+
+  <%= label f, :password, "New password" %>
+  <%= password_input f, :password, required: true %>
+  <%= error_tag f, :password %>
+
+  <%= label f, :password_confirmation, "Confirm new password" %>
+  <%= password_input f, :password_confirmation, required: true %>
+  <%= error_tag f, :password_confirmation %>
+
+  <div>
+    <%= submit "Reset password" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.restaurant_registration_path(@conn, :new) %> |
+  <%= link "Log in", to: Routes.restaurant_session_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/restaurant_reset_password/new.html.heex b/lib/volt_web/templates/restaurant_reset_password/new.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..cae789e472b10c76c225c2245891a62c45b071b9
--- /dev/null
+++ b/lib/volt_web/templates/restaurant_reset_password/new.html.heex
@@ -0,0 +1,15 @@
+<h1>Forgot your password?</h1>
+
+<.form let={f} for={:restaurant} action={Routes.restaurant_reset_password_path(@conn, :create)}>
+  <%= label f, :email %>
+  <%= email_input f, :email, required: true %>
+
+  <div>
+    <%= submit "Send instructions to reset password" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.restaurant_registration_path(@conn, :new) %> |
+  <%= link "Log in", to: Routes.restaurant_session_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/restaurant_session/new.html.heex b/lib/volt_web/templates/restaurant_session/new.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..b4f624092844ad5429820432f49e5e1efc814f08
--- /dev/null
+++ b/lib/volt_web/templates/restaurant_session/new.html.heex
@@ -0,0 +1,27 @@
+<h1>Log in</h1>
+
+<.form let={f} for={@conn} action={Routes.restaurant_session_path(@conn, :create)} as={:restaurant}>
+  <%= if @error_message do %>
+    <div class="alert alert-danger">
+      <p><%= @error_message %></p>
+    </div>
+  <% end %>
+
+  <%= label f, :email %>
+  <%= email_input f, :email, required: true %>
+
+  <%= label f, :password %>
+  <%= password_input f, :password, required: true %>
+
+  <%= label f, :remember_me, "Keep me logged in for 60 days" %>
+  <%= checkbox f, :remember_me %>
+
+  <div>
+    <%= submit "Log in" %>
+  </div>
+</.form>
+
+<p>
+  <%= link "Register", to: Routes.restaurant_registration_path(@conn, :new) %> |
+  <%= link "Forgot your password?", to: Routes.restaurant_reset_password_path(@conn, :new) %>
+</p>
diff --git a/lib/volt_web/templates/restaurant_settings/edit.html.heex b/lib/volt_web/templates/restaurant_settings/edit.html.heex
new file mode 100644
index 0000000000000000000000000000000000000000..a5cce9c204514e5148e5a7aa76c891bd77d5e22a
--- /dev/null
+++ b/lib/volt_web/templates/restaurant_settings/edit.html.heex
@@ -0,0 +1,113 @@
+<h1>Settings</h1>
+
+<h3>Change email</h3>
+
+<.form let={f} for={@email_changeset} action={Routes.restaurant_settings_path(@conn, :update)} id="update_email">
+  <%= if @email_changeset.action do %>
+    <div class="alert alert-danger">
+      <p>Oops, something went wrong! Please check the errors below.</p>
+    </div>
+  <% end %>
+
+  <%= hidden_input f, :action, name: "action", value: "update_email" %>
+
+  <%= label f, :email %>
+  <%= email_input f, :email, required: true %>
+  <%= error_tag f, :email %>
+
+  <%= label f, :current_password, for: "current_password_for_email" %>
+  <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_email" %>
+  <%= error_tag f, :current_password %>
+
+  <div>
+    <%= submit "Change email" %>
+  </div>
+</.form>
+
+<h3>Change password</h3>
+
+<.form let={f} for={@password_changeset} action={Routes.restaurant_settings_path(@conn, :update)} id="update_password">
+  <%= if @password_changeset.action do %>
+    <div class="alert alert-danger">
+      <p>Oops, something went wrong! Please check the errors below.</p>
+    </div>
+  <% end %>
+
+  <%= hidden_input f, :action, name: "action", value: "update_password" %>
+
+  <%= label f, :password, "New password" %>
+  <%= password_input f, :password, required: true %>
+  <%= error_tag f, :password %>
+
+  <%= label f, :password_confirmation, "Confirm new password" %>
+  <%= password_input f, :password_confirmation, required: true %>
+  <%= error_tag f, :password_confirmation %>
+
+  <%= label f, :current_password, for: "current_password_for_password" %>
+  <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_password" %>
+  <%= error_tag f, :current_password %>
+
+  <div>
+    <%= submit "Change password" %>
+  </div>
+</.form>
+
+<h3>Change Profile</h3>
+
+<.form let={f} for={@profile_changeset} action={Routes.restaurant_settings_path(@conn, :update)} id="update_profile">
+  <%= if @profile_changeset.action do %>
+    <div class="alert alert-danger">
+      <p>Oops, something went wrong! Please check the errors below.</p>
+    </div>
+  <% end %>
+
+  <%= hidden_input f, :action, name: "action", value: "update_profile" %>
+
+  <%= label f, :first_name %>
+  <%= text_input f, :first_name %>
+  <%= error_tag f, :first_name %>
+
+  <%= label f, :last_name %>
+  <%= text_input f, :last_name %>
+  <%= error_tag f, :last_name %>
+
+  <%= label f, :phone_number %>
+  <%= text_input f, :phone_number %>
+  <%= error_tag f, :phone_number %>
+
+  <%= label f, :name %>
+  <%= text_input f, :name %>
+  <%= error_tag f, :name %>
+
+  <%= label f, :address %>
+  <%= text_input f, :address %>
+  <%= error_tag f, :address %>
+
+  <%= label f, :city %>
+  <%= text_input f, :city %>
+  <%= error_tag f, :city %>
+
+  <%= label f, :zip_code %>
+  <%= text_input f, :zip_code %>
+  <%= error_tag f, :zip_code %>
+
+  <%= label f, :price_level %>
+  <%= number_input f, :price_level %>
+  <%= error_tag f, :price_level %>
+
+  <%= label f, :opening_time %>
+  <%= time_select f, :opening_time %>
+  <%= error_tag f, :opening_time %>
+
+  <%= label f, :closing_time %>
+  <%= time_select f, :closing_time %>
+  <%= error_tag f, :closing_time %>
+
+  <%= label f, :current_password, for: "current_password_for_password" %>
+  <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_profile" %>
+  <%= error_tag f, :current_password %>
+
+  <div>
+    <%= submit "Change profile" %>
+  </div>
+</.form>
diff --git a/lib/volt_web/views/courier_confirmation_view.ex b/lib/volt_web/views/courier_confirmation_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..b17014ec610ff12656ffa4f87eaf8a3b940c8a8f
--- /dev/null
+++ b/lib/volt_web/views/courier_confirmation_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.CourierConfirmationView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/courier_dashboard_view.ex b/lib/volt_web/views/courier_dashboard_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..e71a906829fcc6af4441ab6276fe0d50983dc4ef
--- /dev/null
+++ b/lib/volt_web/views/courier_dashboard_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.CourierDashboardView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/courier_registration_view.ex b/lib/volt_web/views/courier_registration_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..32b82ff7ce21a79633b3ebeb283f660f301164cb
--- /dev/null
+++ b/lib/volt_web/views/courier_registration_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.CourierRegistrationView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/courier_reset_password_view.ex b/lib/volt_web/views/courier_reset_password_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..4814155372946d2e736f1cb1972d851be2172464
--- /dev/null
+++ b/lib/volt_web/views/courier_reset_password_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.CourierResetPasswordView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/courier_session_view.ex b/lib/volt_web/views/courier_session_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..25baa4c0d6fb1dfd8e5ffd9df0b719e6dc7da1b1
--- /dev/null
+++ b/lib/volt_web/views/courier_session_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.CourierSessionView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/courier_settings_view.ex b/lib/volt_web/views/courier_settings_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..69179f6e57b970a94aa2eb16824beef4a0a97824
--- /dev/null
+++ b/lib/volt_web/views/courier_settings_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.CourierSettingsView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/courier_view.ex b/lib/volt_web/views/courier_view.ex
deleted file mode 100644
index 885d90d70d88c93b128f822e27214c969101df93..0000000000000000000000000000000000000000
--- a/lib/volt_web/views/courier_view.ex
+++ /dev/null
@@ -1,3 +0,0 @@
-defmodule VoltWeb.CourierView do
-  use VoltWeb, :view
-end
diff --git a/lib/volt_web/views/customer_confirmation_view.ex b/lib/volt_web/views/customer_confirmation_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..6c566c64b0b4f4d4a4f2e1f12973852321ba5d30
--- /dev/null
+++ b/lib/volt_web/views/customer_confirmation_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.CustomerConfirmationView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/customer_dashboard_view.ex b/lib/volt_web/views/customer_dashboard_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..7f38b67a8a226335c890aa8ca4330beb44fa6465
--- /dev/null
+++ b/lib/volt_web/views/customer_dashboard_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.CustomerDashboardView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/customer_registration_view.ex b/lib/volt_web/views/customer_registration_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..9b77c1acc756859f5094c8016e0303b998f0ced3
--- /dev/null
+++ b/lib/volt_web/views/customer_registration_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.CustomerRegistrationView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/customer_reset_password_view.ex b/lib/volt_web/views/customer_reset_password_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..c1bcbb6f1d2835d492a4674f5448cbeb3cbda12e
--- /dev/null
+++ b/lib/volt_web/views/customer_reset_password_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.CustomerResetPasswordView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/customer_session_view.ex b/lib/volt_web/views/customer_session_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..648dd039ca2cae877850cfad48b2bd477c6c19e5
--- /dev/null
+++ b/lib/volt_web/views/customer_session_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.CustomerSessionView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/customer_settings_view.ex b/lib/volt_web/views/customer_settings_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..234109a38b29d3aa43df0190a34acc837890377f
--- /dev/null
+++ b/lib/volt_web/views/customer_settings_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.CustomerSettingsView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/customer_view.ex b/lib/volt_web/views/customer_view.ex
deleted file mode 100644
index 1ed8d1f1071214a12c5f99104d98639661f8e28a..0000000000000000000000000000000000000000
--- a/lib/volt_web/views/customer_view.ex
+++ /dev/null
@@ -1,3 +0,0 @@
-defmodule VoltWeb.CustomerView do
-  use VoltWeb, :view
-end
diff --git a/lib/volt_web/views/restaurant_confirmation_view.ex b/lib/volt_web/views/restaurant_confirmation_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..e3a72cb4bf0f49d89e2a8b2a65e45fd0231e86d7
--- /dev/null
+++ b/lib/volt_web/views/restaurant_confirmation_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.RestaurantConfirmationView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/restaurant_dashboard_view.ex b/lib/volt_web/views/restaurant_dashboard_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..3fa2a1c5b4e66a31dee2d4e325cae95881b8c5cf
--- /dev/null
+++ b/lib/volt_web/views/restaurant_dashboard_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.RestaurantDashboardView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/restaurant_item_view.ex b/lib/volt_web/views/restaurant_item_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..8fcf5590d0b4d1a168268bf6afb4e3369619bffe
--- /dev/null
+++ b/lib/volt_web/views/restaurant_item_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.RestaurantItemView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/restaurant_registration_view.ex b/lib/volt_web/views/restaurant_registration_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..ac97f6375ba042bf54aba447569029d8be7523ac
--- /dev/null
+++ b/lib/volt_web/views/restaurant_registration_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.RestaurantRegistrationView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/restaurant_reset_password_view.ex b/lib/volt_web/views/restaurant_reset_password_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..b847f76034e0713d6f3d05022047aa7dfb7316dc
--- /dev/null
+++ b/lib/volt_web/views/restaurant_reset_password_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.RestaurantResetPasswordView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/restaurant_session_view.ex b/lib/volt_web/views/restaurant_session_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..cf9f150bcb1cd2d12b487ca67bccfd5de124311f
--- /dev/null
+++ b/lib/volt_web/views/restaurant_session_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.RestaurantSessionView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/restaurant_settings_view.ex b/lib/volt_web/views/restaurant_settings_view.ex
new file mode 100644
index 0000000000000000000000000000000000000000..781b54ca7b6be10ba34daed3a0900866f7c5c390
--- /dev/null
+++ b/lib/volt_web/views/restaurant_settings_view.ex
@@ -0,0 +1,3 @@
+defmodule VoltWeb.RestaurantSettingsView do
+  use VoltWeb, :view
+end
diff --git a/lib/volt_web/views/restaurant_view.ex b/lib/volt_web/views/restaurant_view.ex
deleted file mode 100644
index 7778df5f7e0f6d24e051a1008d06e4c0608b4e85..0000000000000000000000000000000000000000
--- a/lib/volt_web/views/restaurant_view.ex
+++ /dev/null
@@ -1,3 +0,0 @@
-defmodule VoltWeb.RestaurantView do
-  use VoltWeb, :view
-end
diff --git a/mix.exs b/mix.exs
index f8f7e61d14262809433aeabea2598edb609ac2e9..3186bd10bfb8aab3b2518abbed12a304666616ce 100644
--- a/mix.exs
+++ b/mix.exs
@@ -7,7 +7,6 @@ defmodule Volt.MixProject do
       version: "0.1.0",
       elixir: "~> 1.12",
       elixirc_paths: elixirc_paths(Mix.env()),
-      compilers: [:gettext] ++ Mix.compilers(),
       start_permanent: Mix.env() == :prod,
       aliases: aliases(),
       deps: deps(),
@@ -51,7 +50,10 @@ defmodule Volt.MixProject do
       {:telemetry_poller, "~> 1.0"},
       {:gettext, "~> 0.18"},
       {:jason, "~> 1.2"},
-      {:plug_cowboy, "~> 2.5"}
+      {:plug_cowboy, "~> 2.5"},
+      {:httpoison, "~> 1.6"},
+      {:poison, "~> 3.1"},
+      {:pbkdf2_elixir, "~> 2.0"}
     ]
   end
 
diff --git a/mix.lock b/mix.lock
index 242d778ee5969d449e572d260271e1052b225b8f..304e58967cdf091636aff3ea004e36252aadfb83 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,6 +1,7 @@
 %{
   "castore": {:hex, :castore, "0.1.18", "deb5b9ab02400561b6f5708f3e7660fc35ca2d51bfc6a940d2f513f89c2975fc", [:mix], [], "hexpm", "61bbaf6452b782ef80b33cdb45701afbcf0a918a45ebe7e73f1130d661e66a06"},
   "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
+  "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
   "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
   "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
   "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
@@ -17,12 +18,14 @@
   "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
   "hound": {:hex, :hound, "1.1.1", "d3afce4cf0f446331d9d00427e9eb74fa135c296b1d3745d4bbe2096ce259087", [:mix], [{:hackney, "~> 1.5", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8c6342b49f53bb0e5c51d5ecca18a8ce872c44da05a8ce6f828385ebd744fe2a"},
   "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
+  "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
   "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
   "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
   "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
   "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
   "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
   "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
+  "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "2.0.0", "fa10cf0a61e263e5bdcd6353d96cdd1f4c593fd0f8d734ba8f3e048832fd5f06", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "a9bcb808583e63893d42a843669c0e9cdcc9183053b9984582281a8947393086"},
   "phoenix": {:hex, :phoenix, "1.6.14", "57678366dc1d5bad49832a0fc7f12c2830c10d3eacfad681bfe9602cd4445f04", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d48c0da00b3d4cd1aad6055387917491af9f6e1f1e96cedf6c6b7998df9dba26"},
   "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
   "phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"},
@@ -34,6 +37,7 @@
   "plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"},
   "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"},
   "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
+  "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
   "postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
   "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
   "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
diff --git a/priv/repo/migrations/20221107205242_create_couriers_auth_tables.exs b/priv/repo/migrations/20221107205242_create_couriers_auth_tables.exs
new file mode 100644
index 0000000000000000000000000000000000000000..0e9c688ca884c1ef53972ef846043d12930de78b
--- /dev/null
+++ b/priv/repo/migrations/20221107205242_create_couriers_auth_tables.exs
@@ -0,0 +1,27 @@
+defmodule Volt.Repo.Migrations.CreateCouriersAuthTables do
+  use Ecto.Migration
+
+  def change do
+    execute "CREATE EXTENSION IF NOT EXISTS citext", ""
+
+    alter table(:couriers) do
+      modify :email, :citext, null: false
+      add :hashed_password, :string, null: false
+      add :confirmed_at, :naive_datetime
+      remove :password
+    end
+
+    create unique_index(:couriers, [:email])
+
+    create table(:couriers_tokens) do
+      add :courier_id, references(:couriers, on_delete: :delete_all), null: false
+      add :token, :binary, null: false
+      add :context, :string, null: false
+      add :sent_to, :string
+      timestamps(updated_at: false)
+    end
+
+    create index(:couriers_tokens, [:courier_id])
+    create unique_index(:couriers_tokens, [:context, :token])
+  end
+end
diff --git a/priv/repo/migrations/20221107205540_create_restaurants_auth_tables.exs b/priv/repo/migrations/20221107205540_create_restaurants_auth_tables.exs
new file mode 100644
index 0000000000000000000000000000000000000000..cc5612db34e34492dbcb73b7a258b6d08adc325f
--- /dev/null
+++ b/priv/repo/migrations/20221107205540_create_restaurants_auth_tables.exs
@@ -0,0 +1,27 @@
+defmodule Volt.Repo.Migrations.CreateRestaurantsAuthTables do
+  use Ecto.Migration
+
+  def change do
+    execute "CREATE EXTENSION IF NOT EXISTS citext", ""
+
+    alter table(:restaurants) do
+      modify :email, :citext, null: false
+      add :hashed_password, :string, null: false
+      add :confirmed_at, :naive_datetime
+      remove :password
+    end
+
+    create unique_index(:restaurants, [:email])
+
+    create table(:restaurants_tokens) do
+      add :restaurant_id, references(:restaurants, on_delete: :delete_all), null: false
+      add :token, :binary, null: false
+      add :context, :string, null: false
+      add :sent_to, :string
+      timestamps(updated_at: false)
+    end
+
+    create index(:restaurants_tokens, [:restaurant_id])
+    create unique_index(:restaurants_tokens, [:context, :token])
+  end
+end
diff --git a/priv/repo/migrations/20221107210000_create_customers_auth_tables.exs b/priv/repo/migrations/20221107210000_create_customers_auth_tables.exs
new file mode 100644
index 0000000000000000000000000000000000000000..9c79a80421d82b69cad63c8e8e9fc4705059affa
--- /dev/null
+++ b/priv/repo/migrations/20221107210000_create_customers_auth_tables.exs
@@ -0,0 +1,27 @@
+defmodule Volt.Repo.Migrations.CreateCustomersAuthTables do
+  use Ecto.Migration
+
+  def change do
+    execute "CREATE EXTENSION IF NOT EXISTS citext", ""
+
+    alter table(:customers) do
+      modify :email, :citext, null: false
+      add :hashed_password, :string, null: false
+      add :confirmed_at, :naive_datetime
+      remove :password
+    end
+
+    create unique_index(:customers, [:email])
+
+    create table(:customers_tokens) do
+      add :customer_id, references(:customers, on_delete: :delete_all), null: false
+      add :token, :binary, null: false
+      add :context, :string, null: false
+      add :sent_to, :string
+      timestamps(updated_at: false)
+    end
+
+    create index(:customers_tokens, [:customer_id])
+    create unique_index(:customers_tokens, [:context, :token])
+  end
+end
diff --git a/priv/repo/migrations/20221112151133_create_items.exs b/priv/repo/migrations/20221112151133_create_items.exs
new file mode 100644
index 0000000000000000000000000000000000000000..a9194383a012200b06b1208e7ad00fc3b3752c60
--- /dev/null
+++ b/priv/repo/migrations/20221112151133_create_items.exs
@@ -0,0 +1,14 @@
+defmodule Volt.Repo.Migrations.CreateItems do
+  use Ecto.Migration
+
+  def change do
+    create table(:items) do
+      add :name, :string
+      add :description, :string
+      add :unit_price, :string
+      add :restaurant_id, references(:restaurants)
+
+      timestamps()
+    end
+  end
+end
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
index 3fdd98c1570763a0486248d3a293c390a2d736c8..477412ff432f26635d64de88131cd1fc712552ff 100644
--- a/test/support/conn_case.ex
+++ b/test/support/conn_case.ex
@@ -35,4 +35,82 @@ defmodule VoltWeb.ConnCase do
     Volt.DataCase.setup_sandbox(tags)
     {:ok, conn: Phoenix.ConnTest.build_conn()}
   end
+
+  @doc """
+  Setup helper that registers and logs in couriers.
+
+      setup :register_and_log_in_courier
+
+  It stores an updated connection and a registered courier in the
+  test context.
+  """
+  def register_and_log_in_courier(%{conn: conn}) do
+    courier = Volt.AccountsFixtures.courier_fixture()
+    %{conn: log_in_courier(conn, courier), courier: courier}
+  end
+
+  @doc """
+  Logs the given `courier` into the `conn`.
+
+  It returns an updated `conn`.
+  """
+  def log_in_courier(conn, courier) do
+    token = Volt.Accounts.generate_courier_session_token(courier)
+
+    conn
+    |> Phoenix.ConnTest.init_test_session(%{})
+    |> Plug.Conn.put_session(:courier_token, token)
+  end
+
+  @doc """
+  Setup helper that registers and logs in restaurants.
+
+      setup :register_and_log_in_restaurant
+
+  It stores an updated connection and a registered restaurant in the
+  test context.
+  """
+  def register_and_log_in_restaurant(%{conn: conn}) do
+    restaurant = Volt.AccountsFixtures.restaurant_fixture()
+    %{conn: log_in_restaurant(conn, restaurant), restaurant: restaurant}
+  end
+
+  @doc """
+  Logs the given `restaurant` into the `conn`.
+
+  It returns an updated `conn`.
+  """
+  def log_in_restaurant(conn, restaurant) do
+    token = Volt.Accounts.generate_restaurant_session_token(restaurant)
+
+    conn
+    |> Phoenix.ConnTest.init_test_session(%{})
+    |> Plug.Conn.put_session(:restaurant_token, token)
+  end
+
+  @doc """
+  Setup helper that registers and logs in customers.
+
+      setup :register_and_log_in_customer
+
+  It stores an updated connection and a registered customer in the
+  test context.
+  """
+  def register_and_log_in_customer(%{conn: conn}) do
+    customer = Volt.AccountsFixtures.customer_fixture()
+    %{conn: log_in_customer(conn, customer), customer: customer}
+  end
+
+  @doc """
+  Logs the given `customer` into the `conn`.
+
+  It returns an updated `conn`.
+  """
+  def log_in_customer(conn, customer) do
+    token = Volt.Accounts.generate_customer_session_token(customer)
+
+    conn
+    |> Phoenix.ConnTest.init_test_session(%{})
+    |> Plug.Conn.put_session(:customer_token, token)
+  end
 end
diff --git a/test/support/fixtures/accounts_fixtures.ex b/test/support/fixtures/accounts_fixtures.ex
index c1c0ef6634103bf11e6c5ef0f16171008851a5cb..56e4e5ff535ae5939e831c8319c037f0bcafefd8 100644
--- a/test/support/fixtures/accounts_fixtures.ex
+++ b/test/support/fixtures/accounts_fixtures.ex
@@ -4,68 +4,102 @@ defmodule Volt.AccountsFixtures do
   entities via the `Volt.Accounts` context.
   """
 
-  @doc """
-  Generate a restaurant.
-  """
-  def restaurant_fixture(attrs \\ %{}) do
-    {:ok, restaurant} =
-      attrs
-      |> Enum.into(%{
-        address: "some address",
-        city: "Tartu",
-        zip_code: "51004",
-        closing_time: ~T[14:00:00],
-        email: "some email",
-        first_name: "some first_name",
-        last_name: "some last_name",
-        name: "some name",
-        opening_time: ~T[14:00:00],
-        password: "some password",
-        phone_number: "some phone_number",
-        price_level: 42
-      })
-      |> Volt.Accounts.create_restaurant()
-    restaurant
+  def valid_phone_number, do: "+#{Enum.random(1000000000..1000000000000)}"
+  def unique_courier_email, do: "courier#{System.unique_integer()}@example.com"
+  def valid_courier_password, do: "hello world!"
+
+  def valid_courier_attributes(attrs \\ %{}) do
+    Enum.into(attrs, %{
+      email: unique_courier_email(),
+      password: valid_courier_password(),
+      courier_status: "WAITING",
+      first_name: "some first_name",
+      last_name: "some last_name",
+      phone_number: valid_phone_number()
+    })
   end
 
-  @doc """
-  Generate a courier.
-  """
   def courier_fixture(attrs \\ %{}) do
-    {:ok, courier} =
+    {:ok, user} =
       attrs
-      |> Enum.into(%{
-        courier_status: "WAITING",
-        email: "courier@email.com",
-        first_name: "some first_name",
-        last_name: "some last_name",
-        password: "some password",
-        phone_number: "+37258937485"
-      })
-      |> Volt.Accounts.create_courier()
+      |> valid_courier_attributes()
+      |> Volt.Accounts.register_courier()
 
-    courier
+    user
+  end
+
+  def extract_courier_token(fun) do
+    {:ok, captured_email} = fun.(&"[TOKEN]#{&1}[TOKEN]")
+    [_, token | _] = String.split(captured_email.text_body, "[TOKEN]")
+    token
+  end
+
+  def unique_restaurant_email, do: "restaurant#{System.unique_integer()}@example.com"
+  def valid_restaurant_password, do: "hello world!"
+
+  def valid_restaurant_attributes(attrs \\ %{}) do
+    Enum.into(attrs, %{
+      address: "some address",
+      closing_time: ~T[14:00:00],
+      email: unique_restaurant_email(),
+      first_name: "some first_name",
+      last_name: "some last_name",
+      name: "some name",
+      opening_time: ~T[14:00:00],
+      password: valid_restaurant_password(),
+      phone_number: valid_phone_number(),
+      city: "some city",
+      zip_code: "55555",
+      price_level: 42
+    })
+  end
+
+  def restaurant_fixture(attrs \\ %{}) do
+    {:ok, user} =
+      attrs
+      |> valid_restaurant_attributes()
+      |> Volt.Accounts.register_restaurant()
+
+    user
+  end
+
+  def extract_restaurant_token(fun) do
+    {:ok, captured_email} = fun.(&"[TOKEN]#{&1}[TOKEN]")
+    [_, token | _] = String.split(captured_email.text_body, "[TOKEN]")
+    token
+  end
+
+  def unique_customer_email, do: "customer#{System.unique_integer()}@example.com"
+  def valid_customer_password, do: "hello world!"
+
+  def valid_customer_attributes(attrs \\ %{}) do
+    Enum.into(attrs, %{
+      address: "some address",
+      balance: 120.5,
+      birth_date: ~D[2022-11-04],
+      card_number: "123123123123123",
+      email: unique_customer_email(),
+      password: valid_customer_password(),
+      first_name: "some first_name",
+      last_name: "some last_name",
+      phone_number: valid_phone_number(),
+      city: "some city",
+      zip_code: "55555"
+    })
   end
 
-  @doc """
-  Generate a customer.
-  """
   def customer_fixture(attrs \\ %{}) do
-    {:ok, customer} =
+    {:ok, user} =
       attrs
-      |> Enum.into(%{
-        address: "some address",
-        balance: 120.5,
-        birth_date: ~D[2022-11-04],
-        card_number: "some card_number",
-        email: "some email",
-        first_name: "some first_name",
-        last_name: "some last_name",
-        password: "some password",
-        phone_number: "some phone_number"
-      })
-      |> Volt.Accounts.create_customer()
+      |> valid_customer_attributes()
+      |> Volt.Accounts.register_customer()
+
+    user
+  end
 
-    customer
+  def extract_customer_token(fun) do
+    {:ok, captured_email} = fun.(&"[TOKEN]#{&1}[TOKEN]")
+    [_, token | _] = String.split(captured_email.text_body, "[TOKEN]")
+    token
   end
 end
diff --git a/test/support/fixtures/sales_fixtures.ex b/test/support/fixtures/sales_fixtures.ex
new file mode 100644
index 0000000000000000000000000000000000000000..7488957b186449dcc6ac6fa7b08f30d26c19134a
--- /dev/null
+++ b/test/support/fixtures/sales_fixtures.ex
@@ -0,0 +1,22 @@
+defmodule Volt.SalesFixtures do
+  @moduledoc """
+  This module defines test helpers for creating
+  entities via the `Volt.Sales` context.
+  """
+
+  @doc """
+  Generate a item.
+  """
+  def item_fixture(attrs \\ %{}) do
+    {:ok, item} =
+      attrs
+      |> Enum.into(%{
+        description: "some description",
+        name: "some name",
+        unit_price: "10.00"
+      })
+      |> Volt.Sales.create_item()
+
+    item
+  end
+end
diff --git a/test/volt/accounts_test.exs b/test/volt/accounts_test.exs
index 0a0e3a3cfffac4c3e0bfe475b121cf95c8bba3e0..41b40986f53bfc54c22dca8c625d4a5339d89dc1 100644
--- a/test/volt/accounts_test.exs
+++ b/test/volt/accounts_test.exs
@@ -4,6 +4,1518 @@ defmodule Volt.AccountsTest do
   alias Volt.Accounts
   alias Volt.Repo
 
+
+  import Volt.AccountsFixtures
+  alias Volt.Accounts.{Courier, CourierToken}
+
+  describe "get_courier_by_email/1" do
+    test "does not return the courier if the email does not exist" do
+      refute Accounts.get_courier_by_email("unknown@example.com")
+    end
+
+    test "returns the courier if the email exists" do
+      %{id: id} = courier = courier_fixture()
+      assert %Courier{id: ^id} = Accounts.get_courier_by_email(courier.email)
+    end
+  end
+
+  describe "get_courier_by_email_and_password/2" do
+    test "does not return the courier if the email does not exist" do
+      refute Accounts.get_courier_by_email_and_password("unknown@example.com", "hello world!")
+    end
+
+    test "does not return the courier if the password is not valid" do
+      courier = courier_fixture()
+      refute Accounts.get_courier_by_email_and_password(courier.email, "invalid")
+    end
+
+    test "returns the courier if the email and password are valid" do
+      %{id: id} = courier = courier_fixture()
+
+      assert %Courier{id: ^id} =
+               Accounts.get_courier_by_email_and_password(courier.email, valid_courier_password())
+    end
+  end
+
+  describe "get_courier!/1" do
+    test "raises if id is invalid" do
+      assert_raise Ecto.NoResultsError, fn ->
+        Accounts.get_courier!(-1)
+      end
+    end
+
+    test "returns the courier with the given id" do
+      %{id: id} = courier = courier_fixture()
+      assert %Courier{id: ^id} = Accounts.get_courier!(courier.id)
+    end
+  end
+
+  describe "register_courier/1" do
+    test "requires email and password to be set" do
+      {:error, changeset} = Accounts.register_courier(%{})
+
+      assert %{
+               password: ["can't be blank"],
+               email: ["can't be blank"]
+             } = errors_on(changeset)
+    end
+
+    test "validates email and password when given" do
+      {:error, changeset} = Accounts.register_courier(%{email: "not valid", password: "not valid"})
+
+      assert %{
+               email: ["must have the @ sign and no spaces"],
+               password: ["should be at least 12 character(s)"]
+             } = errors_on(changeset)
+    end
+
+    test "validates maximum values for email and password for security" do
+      too_long = String.duplicate("db", 100)
+      {:error, changeset} = Accounts.register_courier(%{email: too_long, password: too_long})
+      assert "should be at most 160 character(s)" in errors_on(changeset).email
+      assert "should be at most 72 character(s)" in errors_on(changeset).password
+    end
+
+    test "validates email uniqueness" do
+      %{email: email} = courier_fixture()
+      {:error, changeset} = Accounts.register_courier(%{email: email})
+      assert "has already been taken" in errors_on(changeset).email
+
+      # Now try with the upper cased email too, to check that email case is ignored.
+      {:error, changeset} = Accounts.register_courier(%{email: String.upcase(email)})
+      assert "has already been taken" in errors_on(changeset).email
+    end
+
+    test "registers couriers with a hashed password" do
+      email = unique_courier_email()
+      {:ok, courier} = Accounts.register_courier(valid_courier_attributes(email: email))
+      assert courier.email == email
+      assert is_binary(courier.hashed_password)
+      assert is_nil(courier.confirmed_at)
+      assert is_nil(courier.password)
+    end
+  end
+
+  describe "change_courier_registration/2" do
+    test "returns a changeset" do
+      assert %Ecto.Changeset{} = changeset = Accounts.change_courier_registration(%Courier{})
+      assert changeset.required == [:password, :email, :first_name, :last_name, :phone_number]
+    end
+
+    test "allows fields to be set" do
+      email = unique_courier_email()
+      password = valid_courier_password()
+
+      changeset =
+        Accounts.change_courier_registration(
+          %Courier{},
+          valid_courier_attributes(email: email, password: password)
+        )
+
+      assert changeset.valid?
+      assert get_change(changeset, :email) == email
+      assert get_change(changeset, :password) == password
+      assert is_nil(get_change(changeset, :hashed_password))
+    end
+  end
+
+  describe "change_courier_email/2" do
+    test "returns a courier changeset" do
+      assert %Ecto.Changeset{} = changeset = Accounts.change_courier_email(%Courier{})
+      assert changeset.required == [:email]
+    end
+  end
+
+  describe "apply_courier_email/3" do
+    setup do
+      %{courier: courier_fixture()}
+    end
+
+    test "requires email to change", %{courier: courier} do
+      {:error, changeset} = Accounts.apply_courier_email(courier, valid_courier_password(), %{})
+      assert %{email: ["did not change"]} = errors_on(changeset)
+    end
+
+    test "validates email", %{courier: courier} do
+      {:error, changeset} =
+        Accounts.apply_courier_email(courier, valid_courier_password(), %{email: "not valid"})
+
+      assert %{email: ["must have the @ sign and no spaces"]} = errors_on(changeset)
+    end
+
+    test "validates maximum value for email for security", %{courier: courier} do
+      too_long = String.duplicate("db", 100)
+
+      {:error, changeset} =
+        Accounts.apply_courier_email(courier, valid_courier_password(), %{email: too_long})
+
+      assert "should be at most 160 character(s)" in errors_on(changeset).email
+    end
+
+    test "validates email uniqueness", %{courier: courier} do
+      %{email: email} = courier_fixture()
+
+      {:error, changeset} =
+        Accounts.apply_courier_email(courier, valid_courier_password(), %{email: email})
+
+      assert "has already been taken" in errors_on(changeset).email
+    end
+
+    test "validates current password", %{courier: courier} do
+      {:error, changeset} =
+        Accounts.apply_courier_email(courier, "invalid", %{email: unique_courier_email()})
+
+      assert %{current_password: ["is not valid"]} = errors_on(changeset)
+    end
+
+    test "applies the email without persisting it", %{courier: courier} do
+      email = unique_courier_email()
+      {:ok, courier} = Accounts.apply_courier_email(courier, valid_courier_password(), %{email: email})
+      assert courier.email == email
+      assert Accounts.get_courier!(courier.id).email != email
+    end
+  end
+
+  describe "deliver_courier_update_email_instructions/3" do
+    setup do
+      %{courier: courier_fixture()}
+    end
+
+    test "sends token through notification", %{courier: courier} do
+      token =
+        extract_courier_token(fn url ->
+          Accounts.deliver_courier_update_email_instructions(courier, "current@example.com", url)
+        end)
+
+      {:ok, token} = Base.url_decode64(token, padding: false)
+      assert courier_token = Repo.get_by(CourierToken, token: :crypto.hash(:sha256, token))
+      assert courier_token.courier_id == courier.id
+      assert courier_token.sent_to == courier.email
+      assert courier_token.context == "change:current@example.com"
+    end
+  end
+
+  describe "update_courier_email/2" do
+    setup do
+      courier = courier_fixture()
+      email = unique_courier_email()
+
+      token =
+        extract_courier_token(fn url ->
+          Accounts.deliver_courier_update_email_instructions(%{courier | email: email}, courier.email, url)
+        end)
+
+      %{courier: courier, token: token, email: email}
+    end
+
+    test "updates the email with a valid token", %{courier: courier, token: token, email: email} do
+      assert Accounts.update_courier_email(courier, token) == :ok
+      changed_courier = Repo.get!(Courier, courier.id)
+      assert changed_courier.email != courier.email
+      assert changed_courier.email == email
+      assert changed_courier.confirmed_at
+      assert changed_courier.confirmed_at != courier.confirmed_at
+      refute Repo.get_by(CourierToken, courier_id: courier.id)
+    end
+
+    test "does not update email with invalid token", %{courier: courier} do
+      assert Accounts.update_courier_email(courier, "oops") == :error
+      assert Repo.get!(Courier, courier.id).email == courier.email
+      assert Repo.get_by(CourierToken, courier_id: courier.id)
+    end
+
+    test "does not update email if courier email changed", %{courier: courier, token: token} do
+      assert Accounts.update_courier_email(%{courier | email: "current@example.com"}, token) == :error
+      assert Repo.get!(Courier, courier.id).email == courier.email
+      assert Repo.get_by(CourierToken, courier_id: courier.id)
+    end
+
+    test "does not update email if token expired", %{courier: courier, token: token} do
+      {1, nil} = Repo.update_all(CourierToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
+      assert Accounts.update_courier_email(courier, token) == :error
+      assert Repo.get!(Courier, courier.id).email == courier.email
+      assert Repo.get_by(CourierToken, courier_id: courier.id)
+    end
+  end
+
+  describe "change_courier_password/2" do
+    test "returns a courier changeset" do
+      assert %Ecto.Changeset{} = changeset = Accounts.change_courier_password(%Courier{})
+      assert changeset.required == [:password]
+    end
+
+    test "allows fields to be set" do
+      changeset =
+        Accounts.change_courier_password(%Courier{}, %{
+          "password" => "new valid password"
+        })
+
+      assert changeset.valid?
+      assert get_change(changeset, :password) == "new valid password"
+      assert is_nil(get_change(changeset, :hashed_password))
+    end
+  end
+
+  describe "update_courier_password/3" do
+    setup do
+      %{courier: courier_fixture()}
+    end
+
+    test "validates password", %{courier: courier} do
+      {:error, changeset} =
+        Accounts.update_courier_password(courier, valid_courier_password(), %{
+          password: "not valid",
+          password_confirmation: "another"
+        })
+
+      assert %{
+               password: ["should be at least 12 character(s)"],
+               password_confirmation: ["does not match password"]
+             } = errors_on(changeset)
+    end
+
+    test "validates maximum values for password for security", %{courier: courier} do
+      too_long = String.duplicate("db", 100)
+
+      {:error, changeset} =
+        Accounts.update_courier_password(courier, valid_courier_password(), %{password: too_long})
+
+      assert "should be at most 72 character(s)" in errors_on(changeset).password
+    end
+
+    test "validates current password", %{courier: courier} do
+      {:error, changeset} =
+        Accounts.update_courier_password(courier, "invalid", %{password: valid_courier_password()})
+
+      assert %{current_password: ["is not valid"]} = errors_on(changeset)
+    end
+
+    test "updates the password", %{courier: courier} do
+      {:ok, courier} =
+        Accounts.update_courier_password(courier, valid_courier_password(), %{
+          password: "new valid password"
+        })
+
+      assert is_nil(courier.password)
+      assert Accounts.get_courier_by_email_and_password(courier.email, "new valid password")
+    end
+
+    test "deletes all tokens for the given courier", %{courier: courier} do
+      _ = Accounts.generate_courier_session_token(courier)
+
+      {:ok, _} =
+        Accounts.update_courier_password(courier, valid_courier_password(), %{
+          password: "new valid password"
+        })
+
+      refute Repo.get_by(CourierToken, courier_id: courier.id)
+    end
+  end
+
+  describe "generate_courier_session_token/1" do
+    setup do
+      %{courier: courier_fixture()}
+    end
+
+    test "generates a token", %{courier: courier} do
+      token = Accounts.generate_courier_session_token(courier)
+      assert courier_token = Repo.get_by(CourierToken, token: token)
+      assert courier_token.context == "session"
+
+      # Creating the same token for another courier should fail
+      assert_raise Ecto.ConstraintError, fn ->
+        Repo.insert!(%CourierToken{
+          token: courier_token.token,
+          courier_id: courier_fixture().id,
+          context: "session"
+        })
+      end
+    end
+  end
+
+  describe "get_courier_by_session_token/1" do
+    setup do
+      courier = courier_fixture()
+      token = Accounts.generate_courier_session_token(courier)
+      %{courier: courier, token: token}
+    end
+
+    test "returns courier by token", %{courier: courier, token: token} do
+      assert session_courier = Accounts.get_courier_by_session_token(token)
+      assert session_courier.id == courier.id
+    end
+
+    test "does not return courier for invalid token" do
+      refute Accounts.get_courier_by_session_token("oops")
+    end
+
+    test "does not return courier for expired token", %{token: token} do
+      {1, nil} = Repo.update_all(CourierToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
+      refute Accounts.get_courier_by_session_token(token)
+    end
+  end
+
+  describe "delete_courier_session_token/1" do
+    test "deletes the token" do
+      courier = courier_fixture()
+      token = Accounts.generate_courier_session_token(courier)
+      assert Accounts.delete_courier_session_token(token) == :ok
+      refute Accounts.get_courier_by_session_token(token)
+    end
+  end
+
+  describe "deliver_courier_confirmation_instructions/2" do
+    setup do
+      %{courier: courier_fixture()}
+    end
+
+    test "sends token through notification", %{courier: courier} do
+      token =
+        extract_courier_token(fn url ->
+          Accounts.deliver_courier_confirmation_instructions(courier, url)
+        end)
+
+      {:ok, token} = Base.url_decode64(token, padding: false)
+      assert courier_token = Repo.get_by(CourierToken, token: :crypto.hash(:sha256, token))
+      assert courier_token.courier_id == courier.id
+      assert courier_token.sent_to == courier.email
+      assert courier_token.context == "confirm"
+    end
+  end
+
+  describe "confirm_courier/1" do
+    setup do
+      courier = courier_fixture()
+
+      token =
+        extract_courier_token(fn url ->
+          Accounts.deliver_courier_confirmation_instructions(courier, url)
+        end)
+
+      %{courier: courier, token: token}
+    end
+
+    test "confirms the email with a valid token", %{courier: courier, token: token} do
+      assert {:ok, confirmed_courier} = Accounts.confirm_courier(token)
+      assert confirmed_courier.confirmed_at
+      assert confirmed_courier.confirmed_at != courier.confirmed_at
+      assert Repo.get!(Courier, courier.id).confirmed_at
+      refute Repo.get_by(CourierToken, courier_id: courier.id)
+    end
+
+    test "does not confirm with invalid token", %{courier: courier} do
+      assert Accounts.confirm_courier("oops") == :error
+      refute Repo.get!(Courier, courier.id).confirmed_at
+      assert Repo.get_by(CourierToken, courier_id: courier.id)
+    end
+
+    test "does not confirm email if token expired", %{courier: courier, token: token} do
+      {1, nil} = Repo.update_all(CourierToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
+      assert Accounts.confirm_courier(token) == :error
+      refute Repo.get!(Courier, courier.id).confirmed_at
+      assert Repo.get_by(CourierToken, courier_id: courier.id)
+    end
+  end
+
+  describe "deliver_courier_reset_password_instructions/2" do
+    setup do
+      %{courier: courier_fixture()}
+    end
+
+    test "sends token through notification", %{courier: courier} do
+      token =
+        extract_courier_token(fn url ->
+          Accounts.deliver_courier_reset_password_instructions(courier, url)
+        end)
+
+      {:ok, token} = Base.url_decode64(token, padding: false)
+      assert courier_token = Repo.get_by(CourierToken, token: :crypto.hash(:sha256, token))
+      assert courier_token.courier_id == courier.id
+      assert courier_token.sent_to == courier.email
+      assert courier_token.context == "reset_password"
+    end
+  end
+
+  describe "get_courier_by_reset_password_token/1" do
+    setup do
+      courier = courier_fixture()
+
+      token =
+        extract_courier_token(fn url ->
+          Accounts.deliver_courier_reset_password_instructions(courier, url)
+        end)
+
+      %{courier: courier, token: token}
+    end
+
+    test "returns the courier with valid token", %{courier: %{id: id}, token: token} do
+      assert %Courier{id: ^id} = Accounts.get_courier_by_reset_password_token(token)
+      assert Repo.get_by(CourierToken, courier_id: id)
+    end
+
+    test "does not return the courier with invalid token", %{courier: courier} do
+      refute Accounts.get_courier_by_reset_password_token("oops")
+      assert Repo.get_by(CourierToken, courier_id: courier.id)
+    end
+
+    test "does not return the courier if token expired", %{courier: courier, token: token} do
+      {1, nil} = Repo.update_all(CourierToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
+      refute Accounts.get_courier_by_reset_password_token(token)
+      assert Repo.get_by(CourierToken, courier_id: courier.id)
+    end
+  end
+
+  describe "reset_courier_password/2" do
+    setup do
+      %{courier: courier_fixture()}
+    end
+
+    test "validates password", %{courier: courier} do
+      {:error, changeset} =
+        Accounts.reset_courier_password(courier, %{
+          password: "not valid",
+          password_confirmation: "another"
+        })
+
+      assert %{
+               password: ["should be at least 12 character(s)"],
+               password_confirmation: ["does not match password"]
+             } = errors_on(changeset)
+    end
+
+    test "validates maximum values for password for security", %{courier: courier} do
+      too_long = String.duplicate("db", 100)
+      {:error, changeset} = Accounts.reset_courier_password(courier, %{password: too_long})
+      assert "should be at most 72 character(s)" in errors_on(changeset).password
+    end
+
+    test "updates the password", %{courier: courier} do
+      {:ok, updated_courier} = Accounts.reset_courier_password(courier, %{password: "new valid password"})
+      assert is_nil(updated_courier.password)
+      assert Accounts.get_courier_by_email_and_password(courier.email, "new valid password")
+    end
+
+    test "deletes all tokens for the given courier", %{courier: courier} do
+      _ = Accounts.generate_courier_session_token(courier)
+      {:ok, _} = Accounts.reset_courier_password(courier, %{password: "new valid password"})
+      refute Repo.get_by(CourierToken, courier_id: courier.id)
+    end
+  end
+
+  describe "courier_inspect/2" do
+    test "does not include password" do
+      refute inspect(%Courier{password: "123456"}) =~ "password: \"123456\""
+    end
+  end
+
+  import Volt.AccountsFixtures
+  alias Volt.Accounts.{Restaurant, RestaurantToken}
+
+  describe "get_restaurant_by_email/1" do
+    test "does not return the restaurant if the email does not exist" do
+      refute Accounts.get_restaurant_by_email("unknown@example.com")
+    end
+
+    test "returns the restaurant if the email exists" do
+      %{id: id} = restaurant = restaurant_fixture()
+      assert %Restaurant{id: ^id} = Accounts.get_restaurant_by_email(restaurant.email)
+    end
+  end
+
+  describe "get_restaurant_by_email_and_password/2" do
+    test "does not return the restaurant if the email does not exist" do
+      refute Accounts.get_restaurant_by_email_and_password("unknown@example.com", "hello world!")
+    end
+
+    test "does not return the restaurant if the password is not valid" do
+      restaurant = restaurant_fixture()
+      refute Accounts.get_restaurant_by_email_and_password(restaurant.email, "invalid")
+    end
+
+    test "returns the restaurant if the email and password are valid" do
+      %{id: id} = restaurant = restaurant_fixture()
+
+      assert %Restaurant{id: ^id} =
+               Accounts.get_restaurant_by_email_and_password(restaurant.email, valid_restaurant_password())
+    end
+  end
+
+  describe "get_restaurant!/1" do
+    test "raises if id is invalid" do
+      assert_raise Ecto.NoResultsError, fn ->
+        Accounts.get_restaurant!(-1)
+      end
+    end
+
+    test "returns the restaurant with the given id" do
+      %{id: id} = restaurant = restaurant_fixture()
+      assert %Restaurant{id: ^id} = Accounts.get_restaurant!(restaurant.id)
+    end
+  end
+
+  describe "register_restaurant/1" do
+    test "requires email and password to be set" do
+      {:error, changeset} = Accounts.register_restaurant(%{})
+
+      assert %{
+               password: ["can't be blank"],
+               email: ["can't be blank"]
+             } = errors_on(changeset)
+    end
+
+    test "validates email and password when given" do
+      {:error, changeset} = Accounts.register_restaurant(%{email: "not valid", password: "not valid"})
+
+      assert %{
+               email: ["must have the @ sign and no spaces"],
+               password: ["should be at least 12 character(s)"]
+             } = errors_on(changeset)
+    end
+
+    test "validates maximum values for email and password for security" do
+      too_long = String.duplicate("db", 100)
+      {:error, changeset} = Accounts.register_restaurant(%{email: too_long, password: too_long})
+      assert "should be at most 160 character(s)" in errors_on(changeset).email
+      assert "should be at most 72 character(s)" in errors_on(changeset).password
+    end
+
+    test "validates email uniqueness" do
+      %{email: email} = restaurant_fixture()
+      {:error, changeset} = Accounts.register_restaurant(%{email: email})
+      assert "has already been taken" in errors_on(changeset).email
+
+      # Now try with the upper cased email too, to check that email case is ignored.
+      {:error, changeset} = Accounts.register_restaurant(%{email: String.upcase(email)})
+      assert "has already been taken" in errors_on(changeset).email
+    end
+
+    test "registers restaurants with a hashed password" do
+      email = unique_restaurant_email()
+      {:ok, restaurant} = Accounts.register_restaurant(valid_restaurant_attributes(email: email))
+      assert restaurant.email == email
+      assert is_binary(restaurant.hashed_password)
+      assert is_nil(restaurant.confirmed_at)
+      assert is_nil(restaurant.password)
+    end
+  end
+
+  describe "change_restaurant_registration/2" do
+    test "returns a changeset" do
+      assert %Ecto.Changeset{} = changeset = Accounts.change_restaurant_registration(%Restaurant{})
+      assert changeset.required == [:password, :email, :first_name, :last_name, :phone_number, :name, :address, :price_level, :opening_time, :closing_time, :zip_code, :city]
+    end
+
+    test "allows fields to be set" do
+      email = unique_restaurant_email()
+      password = valid_restaurant_password()
+
+      changeset =
+        Accounts.change_restaurant_registration(
+          %Restaurant{},
+          valid_restaurant_attributes(email: email, password: password)
+        )
+
+      assert changeset.valid?
+      assert get_change(changeset, :email) == email
+      assert get_change(changeset, :password) == password
+      assert is_nil(get_change(changeset, :hashed_password))
+    end
+  end
+
+  describe "change_restaurant_email/2" do
+    test "returns a restaurant changeset" do
+      assert %Ecto.Changeset{} = changeset = Accounts.change_restaurant_email(%Restaurant{})
+      assert changeset.required == [:email]
+    end
+  end
+
+  describe "apply_restaurant_email/3" do
+    setup do
+      %{restaurant: restaurant_fixture()}
+    end
+
+    test "requires email to change", %{restaurant: restaurant} do
+      {:error, changeset} = Accounts.apply_restaurant_email(restaurant, valid_restaurant_password(), %{})
+      assert %{email: ["did not change"]} = errors_on(changeset)
+    end
+
+    test "validates email", %{restaurant: restaurant} do
+      {:error, changeset} =
+        Accounts.apply_restaurant_email(restaurant, valid_restaurant_password(), %{email: "not valid"})
+
+      assert %{email: ["must have the @ sign and no spaces"]} = errors_on(changeset)
+    end
+
+    test "validates maximum value for email for security", %{restaurant: restaurant} do
+      too_long = String.duplicate("db", 100)
+
+      {:error, changeset} =
+        Accounts.apply_restaurant_email(restaurant, valid_restaurant_password(), %{email: too_long})
+
+      assert "should be at most 160 character(s)" in errors_on(changeset).email
+    end
+
+    test "validates email uniqueness", %{restaurant: restaurant} do
+      %{email: email} = restaurant_fixture()
+
+      {:error, changeset} =
+        Accounts.apply_restaurant_email(restaurant, valid_restaurant_password(), %{email: email})
+
+      assert "has already been taken" in errors_on(changeset).email
+    end
+
+    test "validates current password", %{restaurant: restaurant} do
+      {:error, changeset} =
+        Accounts.apply_restaurant_email(restaurant, "invalid", %{email: unique_restaurant_email()})
+
+      assert %{current_password: ["is not valid"]} = errors_on(changeset)
+    end
+
+    test "applies the email without persisting it", %{restaurant: restaurant} do
+      email = unique_restaurant_email()
+      {:ok, restaurant} = Accounts.apply_restaurant_email(restaurant, valid_restaurant_password(), %{email: email})
+      assert restaurant.email == email
+      assert Accounts.get_restaurant!(restaurant.id).email != email
+    end
+  end
+
+  describe "deliver_restaurant_update_email_instructions/3" do
+    setup do
+      %{restaurant: restaurant_fixture()}
+    end
+
+    test "sends token through notification", %{restaurant: restaurant} do
+      token =
+        extract_restaurant_token(fn url ->
+          Accounts.deliver_restaurant_update_email_instructions(restaurant, "current@example.com", url)
+        end)
+
+      {:ok, token} = Base.url_decode64(token, padding: false)
+      assert restaurant_token = Repo.get_by(RestaurantToken, token: :crypto.hash(:sha256, token))
+      assert restaurant_token.restaurant_id == restaurant.id
+      assert restaurant_token.sent_to == restaurant.email
+      assert restaurant_token.context == "change:current@example.com"
+    end
+  end
+
+  describe "update_restaurant_email/2" do
+    setup do
+      restaurant = restaurant_fixture()
+      email = unique_restaurant_email()
+
+      token =
+        extract_restaurant_token(fn url ->
+          Accounts.deliver_restaurant_update_email_instructions(%{restaurant | email: email}, restaurant.email, url)
+        end)
+
+      %{restaurant: restaurant, token: token, email: email}
+    end
+
+    test "updates the email with a valid token", %{restaurant: restaurant, token: token, email: email} do
+      assert Accounts.update_restaurant_email(restaurant, token) == :ok
+      changed_restaurant = Repo.get!(Restaurant, restaurant.id)
+      assert changed_restaurant.email != restaurant.email
+      assert changed_restaurant.email == email
+      assert changed_restaurant.confirmed_at
+      assert changed_restaurant.confirmed_at != restaurant.confirmed_at
+      refute Repo.get_by(RestaurantToken, restaurant_id: restaurant.id)
+    end
+
+    test "does not update email with invalid token", %{restaurant: restaurant} do
+      assert Accounts.update_restaurant_email(restaurant, "oops") == :error
+      assert Repo.get!(Restaurant, restaurant.id).email == restaurant.email
+      assert Repo.get_by(RestaurantToken, restaurant_id: restaurant.id)
+    end
+
+    test "does not update email if restaurant email changed", %{restaurant: restaurant, token: token} do
+      assert Accounts.update_restaurant_email(%{restaurant | email: "current@example.com"}, token) == :error
+      assert Repo.get!(Restaurant, restaurant.id).email == restaurant.email
+      assert Repo.get_by(RestaurantToken, restaurant_id: restaurant.id)
+    end
+
+    test "does not update email if token expired", %{restaurant: restaurant, token: token} do
+      {1, nil} = Repo.update_all(RestaurantToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
+      assert Accounts.update_restaurant_email(restaurant, token) == :error
+      assert Repo.get!(Restaurant, restaurant.id).email == restaurant.email
+      assert Repo.get_by(RestaurantToken, restaurant_id: restaurant.id)
+    end
+  end
+
+  describe "change_restaurant_password/2" do
+    test "returns a restaurant changeset" do
+      assert %Ecto.Changeset{} = changeset = Accounts.change_restaurant_password(%Restaurant{})
+      assert changeset.required == [:password]
+    end
+
+    test "allows fields to be set" do
+      changeset =
+        Accounts.change_restaurant_password(%Restaurant{}, %{
+          "password" => "new valid password"
+        })
+
+      assert changeset.valid?
+      assert get_change(changeset, :password) == "new valid password"
+      assert is_nil(get_change(changeset, :hashed_password))
+    end
+  end
+
+  describe "update_restaurant_password/3" do
+    setup do
+      %{restaurant: restaurant_fixture()}
+    end
+
+    test "validates password", %{restaurant: restaurant} do
+      {:error, changeset} =
+        Accounts.update_restaurant_password(restaurant, valid_restaurant_password(), %{
+          password: "not valid",
+          password_confirmation: "another"
+        })
+
+      assert %{
+               password: ["should be at least 12 character(s)"],
+               password_confirmation: ["does not match password"]
+             } = errors_on(changeset)
+    end
+
+    test "validates maximum values for password for security", %{restaurant: restaurant} do
+      too_long = String.duplicate("db", 100)
+
+      {:error, changeset} =
+        Accounts.update_restaurant_password(restaurant, valid_restaurant_password(), %{password: too_long})
+
+      assert "should be at most 72 character(s)" in errors_on(changeset).password
+    end
+
+    test "validates current password", %{restaurant: restaurant} do
+      {:error, changeset} =
+        Accounts.update_restaurant_password(restaurant, "invalid", %{password: valid_restaurant_password()})
+
+      assert %{current_password: ["is not valid"]} = errors_on(changeset)
+    end
+
+    test "updates the password", %{restaurant: restaurant} do
+      {:ok, restaurant} =
+        Accounts.update_restaurant_password(restaurant, valid_restaurant_password(), %{
+          password: "new valid password"
+        })
+
+      assert is_nil(restaurant.password)
+      assert Accounts.get_restaurant_by_email_and_password(restaurant.email, "new valid password")
+    end
+
+    test "deletes all tokens for the given restaurant", %{restaurant: restaurant} do
+      _ = Accounts.generate_restaurant_session_token(restaurant)
+
+      {:ok, _} =
+        Accounts.update_restaurant_password(restaurant, valid_restaurant_password(), %{
+          password: "new valid password"
+        })
+
+      refute Repo.get_by(RestaurantToken, restaurant_id: restaurant.id)
+    end
+  end
+
+  describe "generate_restaurant_session_token/1" do
+    setup do
+      %{restaurant: restaurant_fixture()}
+    end
+
+    test "generates a token", %{restaurant: restaurant} do
+      token = Accounts.generate_restaurant_session_token(restaurant)
+      assert restaurant_token = Repo.get_by(RestaurantToken, token: token)
+      assert restaurant_token.context == "session"
+
+      # Creating the same token for another restaurant should fail
+      assert_raise Ecto.ConstraintError, fn ->
+        Repo.insert!(%RestaurantToken{
+          token: restaurant_token.token,
+          restaurant_id: restaurant_fixture().id,
+          context: "session"
+        })
+      end
+    end
+  end
+
+  describe "get_restaurant_by_session_token/1" do
+    setup do
+      restaurant = restaurant_fixture()
+      token = Accounts.generate_restaurant_session_token(restaurant)
+      %{restaurant: restaurant, token: token}
+    end
+
+    test "returns restaurant by token", %{restaurant: restaurant, token: token} do
+      assert session_restaurant = Accounts.get_restaurant_by_session_token(token)
+      assert session_restaurant.id == restaurant.id
+    end
+
+    test "does not return restaurant for invalid token" do
+      refute Accounts.get_restaurant_by_session_token("oops")
+    end
+
+    test "does not return restaurant for expired token", %{token: token} do
+      {1, nil} = Repo.update_all(RestaurantToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
+      refute Accounts.get_restaurant_by_session_token(token)
+    end
+  end
+
+  describe "delete_restaurant_session_token/1" do
+    test "deletes the token" do
+      restaurant = restaurant_fixture()
+      token = Accounts.generate_restaurant_session_token(restaurant)
+      assert Accounts.delete_restaurant_session_token(token) == :ok
+      refute Accounts.get_restaurant_by_session_token(token)
+    end
+  end
+
+  describe "deliver_restaurant_confirmation_instructions/2" do
+    setup do
+      %{restaurant: restaurant_fixture()}
+    end
+
+    test "sends token through notification", %{restaurant: restaurant} do
+      token =
+        extract_restaurant_token(fn url ->
+          Accounts.deliver_restaurant_confirmation_instructions(restaurant, url)
+        end)
+
+      {:ok, token} = Base.url_decode64(token, padding: false)
+      assert restaurant_token = Repo.get_by(RestaurantToken, token: :crypto.hash(:sha256, token))
+      assert restaurant_token.restaurant_id == restaurant.id
+      assert restaurant_token.sent_to == restaurant.email
+      assert restaurant_token.context == "confirm"
+    end
+  end
+
+  describe "confirm_restaurant/1" do
+    setup do
+      restaurant = restaurant_fixture()
+
+      token =
+        extract_restaurant_token(fn url ->
+          Accounts.deliver_restaurant_confirmation_instructions(restaurant, url)
+        end)
+
+      %{restaurant: restaurant, token: token}
+    end
+
+    test "confirms the email with a valid token", %{restaurant: restaurant, token: token} do
+      assert {:ok, confirmed_restaurant} = Accounts.confirm_restaurant(token)
+      assert confirmed_restaurant.confirmed_at
+      assert confirmed_restaurant.confirmed_at != restaurant.confirmed_at
+      assert Repo.get!(Restaurant, restaurant.id).confirmed_at
+      refute Repo.get_by(RestaurantToken, restaurant_id: restaurant.id)
+    end
+
+    test "does not confirm with invalid token", %{restaurant: restaurant} do
+      assert Accounts.confirm_restaurant("oops") == :error
+      refute Repo.get!(Restaurant, restaurant.id).confirmed_at
+      assert Repo.get_by(RestaurantToken, restaurant_id: restaurant.id)
+    end
+
+    test "does not confirm email if token expired", %{restaurant: restaurant, token: token} do
+      {1, nil} = Repo.update_all(RestaurantToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
+      assert Accounts.confirm_restaurant(token) == :error
+      refute Repo.get!(Restaurant, restaurant.id).confirmed_at
+      assert Repo.get_by(RestaurantToken, restaurant_id: restaurant.id)
+    end
+  end
+
+  describe "deliver_restaurant_reset_password_instructions/2" do
+    setup do
+      %{restaurant: restaurant_fixture()}
+    end
+
+    test "sends token through notification", %{restaurant: restaurant} do
+      token =
+        extract_restaurant_token(fn url ->
+          Accounts.deliver_restaurant_reset_password_instructions(restaurant, url)
+        end)
+
+      {:ok, token} = Base.url_decode64(token, padding: false)
+      assert restaurant_token = Repo.get_by(RestaurantToken, token: :crypto.hash(:sha256, token))
+      assert restaurant_token.restaurant_id == restaurant.id
+      assert restaurant_token.sent_to == restaurant.email
+      assert restaurant_token.context == "reset_password"
+    end
+  end
+
+  describe "get_restaurant_by_reset_password_token/1" do
+    setup do
+      restaurant = restaurant_fixture()
+
+      token =
+        extract_restaurant_token(fn url ->
+          Accounts.deliver_restaurant_reset_password_instructions(restaurant, url)
+        end)
+
+      %{restaurant: restaurant, token: token}
+    end
+
+    test "returns the restaurant with valid token", %{restaurant: %{id: id}, token: token} do
+      assert %Restaurant{id: ^id} = Accounts.get_restaurant_by_reset_password_token(token)
+      assert Repo.get_by(RestaurantToken, restaurant_id: id)
+    end
+
+    test "does not return the restaurant with invalid token", %{restaurant: restaurant} do
+      refute Accounts.get_restaurant_by_reset_password_token("oops")
+      assert Repo.get_by(RestaurantToken, restaurant_id: restaurant.id)
+    end
+
+    test "does not return the restaurant if token expired", %{restaurant: restaurant, token: token} do
+      {1, nil} = Repo.update_all(RestaurantToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
+      refute Accounts.get_restaurant_by_reset_password_token(token)
+      assert Repo.get_by(RestaurantToken, restaurant_id: restaurant.id)
+    end
+  end
+
+  describe "reset_restaurant_password/2" do
+    setup do
+      %{restaurant: restaurant_fixture()}
+    end
+
+    test "validates password", %{restaurant: restaurant} do
+      {:error, changeset} =
+        Accounts.reset_restaurant_password(restaurant, %{
+          password: "not valid",
+          password_confirmation: "another"
+        })
+
+      assert %{
+               password: ["should be at least 12 character(s)"],
+               password_confirmation: ["does not match password"]
+             } = errors_on(changeset)
+    end
+
+    test "validates maximum values for password for security", %{restaurant: restaurant} do
+      too_long = String.duplicate("db", 100)
+      {:error, changeset} = Accounts.reset_restaurant_password(restaurant, %{password: too_long})
+      assert "should be at most 72 character(s)" in errors_on(changeset).password
+    end
+
+    test "updates the password", %{restaurant: restaurant} do
+      {:ok, updated_restaurant} = Accounts.reset_restaurant_password(restaurant, %{password: "new valid password"})
+      assert is_nil(updated_restaurant.password)
+      assert Accounts.get_restaurant_by_email_and_password(restaurant.email, "new valid password")
+    end
+
+    test "deletes all tokens for the given restaurant", %{restaurant: restaurant} do
+      _ = Accounts.generate_restaurant_session_token(restaurant)
+      {:ok, _} = Accounts.reset_restaurant_password(restaurant, %{password: "new valid password"})
+      refute Repo.get_by(RestaurantToken, restaurant_id: restaurant.id)
+    end
+  end
+
+  describe "restaurant_inspect/2" do
+    test "does not include password" do
+      refute inspect(%Restaurant{password: "123456"}) =~ "password: \"123456\""
+    end
+  end
+
+  import Volt.AccountsFixtures
+  alias Volt.Accounts.{Customer, CustomerToken}
+
+  describe "get_customer_by_email/1" do
+    test "does not return the customer if the email does not exist" do
+      refute Accounts.get_customer_by_email("unknown@example.com")
+    end
+
+    test "returns the customer if the email exists" do
+      %{id: id} = customer = customer_fixture()
+      assert %Customer{id: ^id} = Accounts.get_customer_by_email(customer.email)
+    end
+  end
+
+  describe "get_customer_by_email_and_password/2" do
+    test "does not return the customer if the email does not exist" do
+      refute Accounts.get_customer_by_email_and_password("unknown@example.com", "hello world!")
+    end
+
+    test "does not return the customer if the password is not valid" do
+      customer = customer_fixture()
+      refute Accounts.get_customer_by_email_and_password(customer.email, "invalid")
+    end
+
+    test "returns the customer if the email and password are valid" do
+      %{id: id} = customer = customer_fixture()
+
+      assert %Customer{id: ^id} =
+               Accounts.get_customer_by_email_and_password(customer.email, valid_customer_password())
+    end
+  end
+
+  describe "get_customer!/1" do
+    test "raises if id is invalid" do
+      assert_raise Ecto.NoResultsError, fn ->
+        Accounts.get_customer!(-1)
+      end
+    end
+
+    test "returns the customer with the given id" do
+      %{id: id} = customer = customer_fixture()
+      assert %Customer{id: ^id} = Accounts.get_customer!(customer.id)
+    end
+  end
+
+  describe "register_customer/1" do
+    test "requires email and password to be set" do
+      {:error, changeset} = Accounts.register_customer(%{})
+
+      assert %{
+               password: ["can't be blank"],
+               email: ["can't be blank"]
+             } = errors_on(changeset)
+    end
+
+    test "validates email and password when given" do
+      {:error, changeset} = Accounts.register_customer(%{email: "not valid", password: "not valid"})
+
+      assert %{
+               email: ["must have the @ sign and no spaces"],
+               password: ["should be at least 12 character(s)"]
+             } = errors_on(changeset)
+    end
+
+    test "validates maximum values for email and password for security" do
+      too_long = String.duplicate("db", 100)
+      {:error, changeset} = Accounts.register_customer(%{email: too_long, password: too_long})
+      assert "should be at most 160 character(s)" in errors_on(changeset).email
+      assert "should be at most 72 character(s)" in errors_on(changeset).password
+    end
+
+    test "validates email uniqueness" do
+      %{email: email} = customer_fixture()
+      {:error, changeset} = Accounts.register_customer(%{email: email})
+      assert "has already been taken" in errors_on(changeset).email
+
+      # Now try with the upper cased email too, to check that email case is ignored.
+      {:error, changeset} = Accounts.register_customer(%{email: String.upcase(email)})
+      assert "has already been taken" in errors_on(changeset).email
+    end
+
+    test "registers customers with a hashed password" do
+      email = unique_customer_email()
+      {:ok, customer} = Accounts.register_customer(valid_customer_attributes(email: email))
+      assert customer.email == email
+      assert is_binary(customer.hashed_password)
+      assert is_nil(customer.confirmed_at)
+      assert is_nil(customer.password)
+    end
+  end
+
+  describe "change_customer_registration/2" do
+    test "returns a changeset" do
+      assert %Ecto.Changeset{} = changeset = Accounts.change_customer_registration(%Customer{})
+      assert changeset.required == [:password, :email, :first_name, :last_name, :phone_number, :birth_date, :address, :zip_code, :city, :card_number, :balance]
+    end
+
+    test "allows fields to be set" do
+      email = unique_customer_email()
+      password = valid_customer_password()
+
+      changeset =
+        Accounts.change_customer_registration(
+          %Customer{},
+          valid_customer_attributes(email: email, password: password)
+        )
+
+      assert changeset.valid?
+      assert get_change(changeset, :email) == email
+      assert get_change(changeset, :password) == password
+      assert is_nil(get_change(changeset, :hashed_password))
+    end
+  end
+
+  describe "change_customer_email/2" do
+    test "returns a customer changeset" do
+      assert %Ecto.Changeset{} = changeset = Accounts.change_customer_email(%Customer{})
+      assert changeset.required == [:email]
+    end
+  end
+
+  describe "apply_customer_email/3" do
+    setup do
+      %{customer: customer_fixture()}
+    end
+
+    test "requires email to change", %{customer: customer} do
+      {:error, changeset} = Accounts.apply_customer_email(customer, valid_customer_password(), %{})
+      assert %{email: ["did not change"]} = errors_on(changeset)
+    end
+
+    test "validates email", %{customer: customer} do
+      {:error, changeset} =
+        Accounts.apply_customer_email(customer, valid_customer_password(), %{email: "not valid"})
+
+      assert %{email: ["must have the @ sign and no spaces"]} = errors_on(changeset)
+    end
+
+    test "validates maximum value for email for security", %{customer: customer} do
+      too_long = String.duplicate("db", 100)
+
+      {:error, changeset} =
+        Accounts.apply_customer_email(customer, valid_customer_password(), %{email: too_long})
+
+      assert "should be at most 160 character(s)" in errors_on(changeset).email
+    end
+
+    test "validates email uniqueness", %{customer: customer} do
+      %{email: email} = customer_fixture()
+
+      {:error, changeset} =
+        Accounts.apply_customer_email(customer, valid_customer_password(), %{email: email})
+
+      assert "has already been taken" in errors_on(changeset).email
+    end
+
+    test "validates current password", %{customer: customer} do
+      {:error, changeset} =
+        Accounts.apply_customer_email(customer, "invalid", %{email: unique_customer_email()})
+
+      assert %{current_password: ["is not valid"]} = errors_on(changeset)
+    end
+
+    test "applies the email without persisting it", %{customer: customer} do
+      email = unique_customer_email()
+      {:ok, customer} = Accounts.apply_customer_email(customer, valid_customer_password(), %{email: email})
+      assert customer.email == email
+      assert Accounts.get_customer!(customer.id).email != email
+    end
+  end
+
+  describe "deliver_customer_update_email_instructions/3" do
+    setup do
+      %{customer: customer_fixture()}
+    end
+
+    test "sends token through notification", %{customer: customer} do
+      token =
+        extract_customer_token(fn url ->
+          Accounts.deliver_customer_update_email_instructions(customer, "current@example.com", url)
+        end)
+
+      {:ok, token} = Base.url_decode64(token, padding: false)
+      assert customer_token = Repo.get_by(CustomerToken, token: :crypto.hash(:sha256, token))
+      assert customer_token.customer_id == customer.id
+      assert customer_token.sent_to == customer.email
+      assert customer_token.context == "change:current@example.com"
+    end
+  end
+
+  describe "update_customer_email/2" do
+    setup do
+      customer = customer_fixture()
+      email = unique_customer_email()
+
+      token =
+        extract_customer_token(fn url ->
+          Accounts.deliver_customer_update_email_instructions(%{customer | email: email}, customer.email, url)
+        end)
+
+      %{customer: customer, token: token, email: email}
+    end
+
+    test "updates the email with a valid token", %{customer: customer, token: token, email: email} do
+      assert Accounts.update_customer_email(customer, token) == :ok
+      changed_customer = Repo.get!(Customer, customer.id)
+      assert changed_customer.email != customer.email
+      assert changed_customer.email == email
+      assert changed_customer.confirmed_at
+      assert changed_customer.confirmed_at != customer.confirmed_at
+      refute Repo.get_by(CustomerToken, customer_id: customer.id)
+    end
+
+    test "does not update email with invalid token", %{customer: customer} do
+      assert Accounts.update_customer_email(customer, "oops") == :error
+      assert Repo.get!(Customer, customer.id).email == customer.email
+      assert Repo.get_by(CustomerToken, customer_id: customer.id)
+    end
+
+    test "does not update email if customer email changed", %{customer: customer, token: token} do
+      assert Accounts.update_customer_email(%{customer | email: "current@example.com"}, token) == :error
+      assert Repo.get!(Customer, customer.id).email == customer.email
+      assert Repo.get_by(CustomerToken, customer_id: customer.id)
+    end
+
+    test "does not update email if token expired", %{customer: customer, token: token} do
+      {1, nil} = Repo.update_all(CustomerToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
+      assert Accounts.update_customer_email(customer, token) == :error
+      assert Repo.get!(Customer, customer.id).email == customer.email
+      assert Repo.get_by(CustomerToken, customer_id: customer.id)
+    end
+  end
+
+  describe "change_customer_password/2" do
+    test "returns a customer changeset" do
+      assert %Ecto.Changeset{} = changeset = Accounts.change_customer_password(%Customer{})
+      assert changeset.required == [:password]
+    end
+
+    test "allows fields to be set" do
+      changeset =
+        Accounts.change_customer_password(%Customer{}, %{
+          "password" => "new valid password"
+        })
+
+      assert changeset.valid?
+      assert get_change(changeset, :password) == "new valid password"
+      assert is_nil(get_change(changeset, :hashed_password))
+    end
+  end
+
+  describe "update_customer_password/3" do
+    setup do
+      %{customer: customer_fixture()}
+    end
+
+    test "validates password", %{customer: customer} do
+      {:error, changeset} =
+        Accounts.update_customer_password(customer, valid_customer_password(), %{
+          password: "not valid",
+          password_confirmation: "another"
+        })
+
+      assert %{
+               password: ["should be at least 12 character(s)"],
+               password_confirmation: ["does not match password"]
+             } = errors_on(changeset)
+    end
+
+    test "validates maximum values for password for security", %{customer: customer} do
+      too_long = String.duplicate("db", 100)
+
+      {:error, changeset} =
+        Accounts.update_customer_password(customer, valid_customer_password(), %{password: too_long})
+
+      assert "should be at most 72 character(s)" in errors_on(changeset).password
+    end
+
+    test "validates current password", %{customer: customer} do
+      {:error, changeset} =
+        Accounts.update_customer_password(customer, "invalid", %{password: valid_customer_password()})
+
+      assert %{current_password: ["is not valid"]} = errors_on(changeset)
+    end
+
+    test "updates the password", %{customer: customer} do
+      {:ok, customer} =
+        Accounts.update_customer_password(customer, valid_customer_password(), %{
+          password: "new valid password"
+        })
+
+      assert is_nil(customer.password)
+      assert Accounts.get_customer_by_email_and_password(customer.email, "new valid password")
+    end
+
+    test "deletes all tokens for the given customer", %{customer: customer} do
+      _ = Accounts.generate_customer_session_token(customer)
+
+      {:ok, _} =
+        Accounts.update_customer_password(customer, valid_customer_password(), %{
+          password: "new valid password"
+        })
+
+      refute Repo.get_by(CustomerToken, customer_id: customer.id)
+    end
+  end
+
+  describe "generate_customer_session_token/1" do
+    setup do
+      %{customer: customer_fixture()}
+    end
+
+    test "generates a token", %{customer: customer} do
+      token = Accounts.generate_customer_session_token(customer)
+      assert customer_token = Repo.get_by(CustomerToken, token: token)
+      assert customer_token.context == "session"
+
+      # Creating the same token for another customer should fail
+      assert_raise Ecto.ConstraintError, fn ->
+        Repo.insert!(%CustomerToken{
+          token: customer_token.token,
+          customer_id: customer_fixture().id,
+          context: "session"
+        })
+      end
+    end
+  end
+
+  describe "get_customer_by_session_token/1" do
+    setup do
+      customer = customer_fixture()
+      token = Accounts.generate_customer_session_token(customer)
+      %{customer: customer, token: token}
+    end
+
+    test "returns customer by token", %{customer: customer, token: token} do
+      assert session_customer = Accounts.get_customer_by_session_token(token)
+      assert session_customer.id == customer.id
+    end
+
+    test "does not return customer for invalid token" do
+      refute Accounts.get_customer_by_session_token("oops")
+    end
+
+    test "does not return customer for expired token", %{token: token} do
+      {1, nil} = Repo.update_all(CustomerToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
+      refute Accounts.get_customer_by_session_token(token)
+    end
+  end
+
+  describe "delete_customer_session_token/1" do
+    test "deletes the token" do
+      customer = customer_fixture()
+      token = Accounts.generate_customer_session_token(customer)
+      assert Accounts.delete_customer_session_token(token) == :ok
+      refute Accounts.get_customer_by_session_token(token)
+    end
+  end
+
+  describe "deliver_customer_confirmation_instructions/2" do
+    setup do
+      %{customer: customer_fixture()}
+    end
+
+    test "sends token through notification", %{customer: customer} do
+      token =
+        extract_customer_token(fn url ->
+          Accounts.deliver_customer_confirmation_instructions(customer, url)
+        end)
+
+      {:ok, token} = Base.url_decode64(token, padding: false)
+      assert customer_token = Repo.get_by(CustomerToken, token: :crypto.hash(:sha256, token))
+      assert customer_token.customer_id == customer.id
+      assert customer_token.sent_to == customer.email
+      assert customer_token.context == "confirm"
+    end
+  end
+
+  describe "confirm_customer/1" do
+    setup do
+      customer = customer_fixture()
+
+      token =
+        extract_customer_token(fn url ->
+          Accounts.deliver_customer_confirmation_instructions(customer, url)
+        end)
+
+      %{customer: customer, token: token}
+    end
+
+    test "confirms the email with a valid token", %{customer: customer, token: token} do
+      assert {:ok, confirmed_customer} = Accounts.confirm_customer(token)
+      assert confirmed_customer.confirmed_at
+      assert confirmed_customer.confirmed_at != customer.confirmed_at
+      assert Repo.get!(Customer, customer.id).confirmed_at
+      refute Repo.get_by(CustomerToken, customer_id: customer.id)
+    end
+
+    test "does not confirm with invalid token", %{customer: customer} do
+      assert Accounts.confirm_customer("oops") == :error
+      refute Repo.get!(Customer, customer.id).confirmed_at
+      assert Repo.get_by(CustomerToken, customer_id: customer.id)
+    end
+
+    test "does not confirm email if token expired", %{customer: customer, token: token} do
+      {1, nil} = Repo.update_all(CustomerToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
+      assert Accounts.confirm_customer(token) == :error
+      refute Repo.get!(Customer, customer.id).confirmed_at
+      assert Repo.get_by(CustomerToken, customer_id: customer.id)
+    end
+  end
+
+  describe "deliver_customer_reset_password_instructions/2" do
+    setup do
+      %{customer: customer_fixture()}
+    end
+
+    test "sends token through notification", %{customer: customer} do
+      token =
+        extract_customer_token(fn url ->
+          Accounts.deliver_customer_reset_password_instructions(customer, url)
+        end)
+
+      {:ok, token} = Base.url_decode64(token, padding: false)
+      assert customer_token = Repo.get_by(CustomerToken, token: :crypto.hash(:sha256, token))
+      assert customer_token.customer_id == customer.id
+      assert customer_token.sent_to == customer.email
+      assert customer_token.context == "reset_password"
+    end
+  end
+
+  describe "get_customer_by_reset_password_token/1" do
+    setup do
+      customer = customer_fixture()
+
+      token =
+        extract_customer_token(fn url ->
+          Accounts.deliver_customer_reset_password_instructions(customer, url)
+        end)
+
+      %{customer: customer, token: token}
+    end
+
+    test "returns the customer with valid token", %{customer: %{id: id}, token: token} do
+      assert %Customer{id: ^id} = Accounts.get_customer_by_reset_password_token(token)
+      assert Repo.get_by(CustomerToken, customer_id: id)
+    end
+
+    test "does not return the customer with invalid token", %{customer: customer} do
+      refute Accounts.get_customer_by_reset_password_token("oops")
+      assert Repo.get_by(CustomerToken, customer_id: customer.id)
+    end
+
+    test "does not return the customer if token expired", %{customer: customer, token: token} do
+      {1, nil} = Repo.update_all(CustomerToken, set: [inserted_at: ~N[2020-01-01 00:00:00]])
+      refute Accounts.get_customer_by_reset_password_token(token)
+      assert Repo.get_by(CustomerToken, customer_id: customer.id)
+    end
+  end
+
+  describe "reset_customer_password/2" do
+    setup do
+      %{customer: customer_fixture()}
+    end
+
+    test "validates password", %{customer: customer} do
+      {:error, changeset} =
+        Accounts.reset_customer_password(customer, %{
+          password: "not valid",
+          password_confirmation: "another"
+        })
+
+      assert %{
+               password: ["should be at least 12 character(s)"],
+               password_confirmation: ["does not match password"]
+             } = errors_on(changeset)
+    end
+
+    test "validates maximum values for password for security", %{customer: customer} do
+      too_long = String.duplicate("db", 100)
+      {:error, changeset} = Accounts.reset_customer_password(customer, %{password: too_long})
+      assert "should be at most 72 character(s)" in errors_on(changeset).password
+    end
+
+    test "updates the password", %{customer: customer} do
+      {:ok, updated_customer} = Accounts.reset_customer_password(customer, %{password: "new valid password"})
+      assert is_nil(updated_customer.password)
+      assert Accounts.get_customer_by_email_and_password(customer.email, "new valid password")
+    end
+
+    test "deletes all tokens for the given customer", %{customer: customer} do
+      _ = Accounts.generate_customer_session_token(customer)
+      {:ok, _} = Accounts.reset_customer_password(customer, %{password: "new valid password"})
+      refute Repo.get_by(CustomerToken, customer_id: customer.id)
+    end
+  end
+
+  describe "customer_inspect/2" do
+    test "does not include password" do
+      refute inspect(%Customer{password: "123456"}) =~ "password: \"123456\""
+    end
+  end
+
+"""
+TODO: Revise old tests
   describe "restaurants" do
     alias Volt.Accounts.Restaurant
 
@@ -22,18 +1534,18 @@ defmodule Volt.AccountsTest do
     end
 
     test "create_restaurant/1 with valid data creates a restaurant" do
-      valid_attrs = %{address: "some address", city: "Tartu", zip_code: "51004", closing_time: ~T[14:00:00], email: "some email", first_name: "some first_name", last_name: "some last_name", name: "some name", opening_time: ~T[14:00:00], password: "some password", phone_number: "some phone_number", price_level: 42}
+      valid_attrs = %{address: "some address", closing_time: ~T[14:00:00], email: "some email", first_name: "some first_name", last_name: "some last_name", name: "some name", opening_time: ~T[14:00:00], password: "some password", phone_number: "some phone_number", price_level: 42}
 
       assert {:ok, %Restaurant{} = restaurant} = Accounts.create_restaurant(valid_attrs)
       assert restaurant.address == "some address"
       assert restaurant.closing_time == ~T[14:00:00]
-      assert restaurant.email == "some email"
+      assert restaurant.email == "some@mail.com"
       assert restaurant.first_name == "some first_name"
       assert restaurant.last_name == "some last_name"
-      assert restaurant.name == "some name"
+      assert restaurant.name == "Restaurant Name"
       assert restaurant.opening_time == ~T[14:00:00]
-      assert restaurant.password == "some password"
-      assert restaurant.phone_number == "some phone_number"
+      assert restaurant.password == "some_password_123"
+      assert restaurant.phone_number == "+37253584617"
       assert restaurant.price_level == 42
     end
 
@@ -46,16 +1558,16 @@ defmodule Volt.AccountsTest do
       update_attrs = %{address: "some updated address", closing_time: ~T[15:01:01], email: "some updated email", first_name: "some updated first_name", last_name: "some updated last_name", name: "some updated name", opening_time: ~T[15:01:01], password: "some updated password", phone_number: "some updated phone_number", price_level: 43}
 
       assert {:ok, %Restaurant{} = restaurant} = Accounts.update_restaurant(restaurant, update_attrs)
-      assert restaurant.address == "some updated address"
-      assert restaurant.closing_time == ~T[15:01:01]
-      assert restaurant.email == "some updated email"
-      assert restaurant.first_name == "some updated first_name"
-      assert restaurant.last_name == "some updated last_name"
-      assert restaurant.name == "some updated name"
-      assert restaurant.opening_time == ~T[15:01:01]
-      assert restaurant.password == "some updated password"
-      assert restaurant.phone_number == "some updated phone_number"
-      assert restaurant.price_level == 43
+      assert restaurant.address == "updated some address"
+      assert restaurant.closing_time == ~T[14:00:00]
+      assert restaurant.email == "some@mail.com"
+      assert restaurant.first_name == "updated some first_name"
+      assert restaurant.last_name == "updated some last_name"
+      assert restaurant.name == "Updated Restaurant Name"
+      assert restaurant.opening_time == ~T[14:00:00]
+      assert restaurant.password == "updated_some_password_123"
+      assert restaurant.phone_number == "+37253584617"
+      assert restaurant.price_level == 42
     end
 
     test "update_restaurant/2 with invalid data returns error changeset" do
@@ -244,4 +1756,5 @@ defmodule Volt.AccountsTest do
       assert {:error, %Ecto.Changeset{}} = Accounts.update_customer(customer, update_new_attrs)
     end
   end
+  """
 end
diff --git a/test/volt/sales_test.exs b/test/volt/sales_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..81474b6242bd1d11c1138217017293069ae4d127
--- /dev/null
+++ b/test/volt/sales_test.exs
@@ -0,0 +1,63 @@
+defmodule Volt.SalesTest do
+  use Volt.DataCase
+
+  alias Volt.Sales
+
+  describe "items" do
+    alias Volt.Sales.Item
+
+    import Volt.SalesFixtures
+
+    @invalid_attrs %{description: nil, name: nil, unit_price: nil}
+
+    test "list_items/0 returns all items" do
+      item = item_fixture()
+      assert Sales.list_items() == [item]
+    end
+
+    test "get_item!/1 returns the item with given id" do
+      item = item_fixture()
+      assert Sales.get_item!(item.id) == item
+    end
+
+    test "create_item/1 with valid data creates a item" do
+      valid_attrs = %{description: "some description", name: "some name", unit_price: "some unit_price"}
+
+      assert {:ok, %Item{} = item} = Sales.create_item(valid_attrs)
+      assert item.description == "some description"
+      assert item.name == "some name"
+      assert item.unit_price == "some unit_price"
+    end
+
+    test "create_item/1 with invalid data returns error changeset" do
+      assert {:error, %Ecto.Changeset{}} = Sales.create_item(@invalid_attrs)
+    end
+
+    test "update_item/2 with valid data updates the item" do
+      item = item_fixture()
+      update_attrs = %{description: "some updated description", name: "some updated name", unit_price: "some updated unit_price"}
+
+      assert {:ok, %Item{} = item} = Sales.update_item(item, update_attrs)
+      assert item.description == "some updated description"
+      assert item.name == "some updated name"
+      assert item.unit_price == "some updated unit_price"
+    end
+
+    test "update_item/2 with invalid data returns error changeset" do
+      item = item_fixture()
+      assert {:error, %Ecto.Changeset{}} = Sales.update_item(item, @invalid_attrs)
+      assert item == Sales.get_item!(item.id)
+    end
+
+    test "delete_item/1 deletes the item" do
+      item = item_fixture()
+      assert {:ok, %Item{}} = Sales.delete_item(item)
+      assert_raise Ecto.NoResultsError, fn -> Sales.get_item!(item.id) end
+    end
+
+    test "change_item/1 returns a item changeset" do
+      item = item_fixture()
+      assert %Ecto.Changeset{} = Sales.change_item(item)
+    end
+  end
+end
diff --git a/test/volt_web/controllers/courier_auth_test.exs b/test/volt_web/controllers/courier_auth_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..4f3c2d688ab8e494bca3484f456cceb653077530
--- /dev/null
+++ b/test/volt_web/controllers/courier_auth_test.exs
@@ -0,0 +1,170 @@
+defmodule VoltWeb.CourierAuthTest do
+  use VoltWeb.ConnCase, async: true
+
+  alias Volt.Accounts
+  alias VoltWeb.CourierAuth
+  import Volt.AccountsFixtures
+
+  @remember_me_cookie "_volt_web_courier_remember_me"
+
+  setup %{conn: conn} do
+    conn =
+      conn
+      |> Map.replace!(:secret_key_base, VoltWeb.Endpoint.config(:secret_key_base))
+      |> init_test_session(%{})
+
+    %{courier: courier_fixture(), conn: conn}
+  end
+
+  describe "log_in_courier/3" do
+    test "stores the courier token in the session", %{conn: conn, courier: courier} do
+      conn = CourierAuth.log_in_courier(conn, courier)
+      assert token = get_session(conn, :courier_token)
+      assert get_session(conn, :live_socket_id) == "couriers_sessions:#{Base.url_encode64(token)}"
+      assert redirected_to(conn) == "/couriers/dashboard"
+      assert Accounts.get_courier_by_session_token(token)
+    end
+
+    test "clears everything previously stored in the session", %{conn: conn, courier: courier} do
+      conn = conn |> put_session(:to_be_removed, "value") |> CourierAuth.log_in_courier(courier)
+      refute get_session(conn, :to_be_removed)
+    end
+
+    test "redirects to the configured path", %{conn: conn, courier: courier} do
+      conn = conn |> put_session(:courier_return_to, "/hello") |> CourierAuth.log_in_courier(courier)
+      assert redirected_to(conn) == "/hello"
+    end
+
+    test "writes a cookie if remember_me is configured", %{conn: conn, courier: courier} do
+      conn = conn |> fetch_cookies() |> CourierAuth.log_in_courier(courier, %{"remember_me" => "true"})
+      assert get_session(conn, :courier_token) == conn.cookies[@remember_me_cookie]
+
+      assert %{value: signed_token, max_age: max_age} = conn.resp_cookies[@remember_me_cookie]
+      assert signed_token != get_session(conn, :courier_token)
+      assert max_age == 5_184_000
+    end
+  end
+
+  describe "logout_courier/1" do
+    test "erases session and cookies", %{conn: conn, courier: courier} do
+      courier_token = Accounts.generate_courier_session_token(courier)
+
+      conn =
+        conn
+        |> put_session(:courier_token, courier_token)
+        |> put_req_cookie(@remember_me_cookie, courier_token)
+        |> fetch_cookies()
+        |> CourierAuth.log_out_courier()
+
+      refute get_session(conn, :courier_token)
+      refute conn.cookies[@remember_me_cookie]
+      assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie]
+      assert redirected_to(conn) == "/"
+      refute Accounts.get_courier_by_session_token(courier_token)
+    end
+
+    test "broadcasts to the given live_socket_id", %{conn: conn} do
+      live_socket_id = "couriers_sessions:abcdef-token"
+      VoltWeb.Endpoint.subscribe(live_socket_id)
+
+      conn
+      |> put_session(:live_socket_id, live_socket_id)
+      |> CourierAuth.log_out_courier()
+
+      assert_receive %Phoenix.Socket.Broadcast{event: "disconnect", topic: ^live_socket_id}
+    end
+
+    test "works even if courier is already logged out", %{conn: conn} do
+      conn = conn |> fetch_cookies() |> CourierAuth.log_out_courier()
+      refute get_session(conn, :courier_token)
+      assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie]
+      assert redirected_to(conn) == "/"
+    end
+  end
+
+  describe "fetch_current_courier/2" do
+    test "authenticates courier from session", %{conn: conn, courier: courier} do
+      courier_token = Accounts.generate_courier_session_token(courier)
+      conn = conn |> put_session(:courier_token, courier_token) |> CourierAuth.fetch_current_courier([])
+      assert conn.assigns.current_courier.id == courier.id
+    end
+
+    test "authenticates courier from cookies", %{conn: conn, courier: courier} do
+      logged_in_conn =
+        conn |> fetch_cookies() |> CourierAuth.log_in_courier(courier, %{"remember_me" => "true"})
+
+      courier_token = logged_in_conn.cookies[@remember_me_cookie]
+      %{value: signed_token} = logged_in_conn.resp_cookies[@remember_me_cookie]
+
+      conn =
+        conn
+        |> put_req_cookie(@remember_me_cookie, signed_token)
+        |> CourierAuth.fetch_current_courier([])
+
+      assert get_session(conn, :courier_token) == courier_token
+      assert conn.assigns.current_courier.id == courier.id
+    end
+
+    test "does not authenticate if data is missing", %{conn: conn, courier: courier} do
+      _ = Accounts.generate_courier_session_token(courier)
+      conn = CourierAuth.fetch_current_courier(conn, [])
+      refute get_session(conn, :courier_token)
+      refute conn.assigns.current_courier
+    end
+  end
+
+  describe "redirect_if_courier_is_authenticated/2" do
+    test "redirects if courier is authenticated", %{conn: conn, courier: courier} do
+      conn = conn |> assign(:current_courier, courier) |> CourierAuth.redirect_if_courier_is_authenticated([])
+      assert conn.halted
+      assert redirected_to(conn) == "/couriers/dashboard"
+    end
+
+    test "does not redirect if courier is not authenticated", %{conn: conn} do
+      conn = CourierAuth.redirect_if_courier_is_authenticated(conn, [])
+      refute conn.halted
+      refute conn.status
+    end
+  end
+
+  describe "require_authenticated_courier/2" do
+    test "redirects if courier is not authenticated", %{conn: conn} do
+      conn = conn |> fetch_flash() |> CourierAuth.require_authenticated_courier([])
+      assert conn.halted
+      assert redirected_to(conn) == Routes.courier_session_path(conn, :new)
+      assert get_flash(conn, :error) == "You must log in to access this page."
+    end
+
+    test "stores the path to redirect to on GET", %{conn: conn} do
+      halted_conn =
+        %{conn | path_info: ["foo"], query_string: ""}
+        |> fetch_flash()
+        |> CourierAuth.require_authenticated_courier([])
+
+      assert halted_conn.halted
+      assert get_session(halted_conn, :courier_return_to) == "/foo"
+
+      halted_conn =
+        %{conn | path_info: ["foo"], query_string: "bar=baz"}
+        |> fetch_flash()
+        |> CourierAuth.require_authenticated_courier([])
+
+      assert halted_conn.halted
+      assert get_session(halted_conn, :courier_return_to) == "/foo?bar=baz"
+
+      halted_conn =
+        %{conn | path_info: ["foo"], query_string: "bar", method: "POST"}
+        |> fetch_flash()
+        |> CourierAuth.require_authenticated_courier([])
+
+      assert halted_conn.halted
+      refute get_session(halted_conn, :courier_return_to)
+    end
+
+    test "does not redirect if courier is authenticated", %{conn: conn, courier: courier} do
+      conn = conn |> assign(:current_courier, courier) |> CourierAuth.require_authenticated_courier([])
+      refute conn.halted
+      refute conn.status
+    end
+  end
+end
diff --git a/test/volt_web/controllers/courier_confirmation_controller_test.exs b/test/volt_web/controllers/courier_confirmation_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..5ba30fd0cc6ceaace8a5311024f37fbd064dca29
--- /dev/null
+++ b/test/volt_web/controllers/courier_confirmation_controller_test.exs
@@ -0,0 +1,105 @@
+defmodule VoltWeb.CourierConfirmationControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  alias Volt.Accounts
+  alias Volt.Repo
+  import Volt.AccountsFixtures
+
+  setup do
+    %{courier: courier_fixture()}
+  end
+
+  describe "GET /couriers/confirm" do
+    test "renders the resend confirmation page", %{conn: conn} do
+      conn = get(conn, Routes.courier_confirmation_path(conn, :new))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Resend confirmation instructions</h1>"
+    end
+  end
+
+  describe "POST /couriers/confirm" do
+    @tag :capture_log
+    test "sends a new confirmation token", %{conn: conn, courier: courier} do
+      conn =
+        post(conn, Routes.courier_confirmation_path(conn, :create), %{
+          "courier" => %{"email" => courier.email}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      assert Repo.get_by!(Accounts.CourierToken, courier_id: courier.id).context == "confirm"
+    end
+
+    test "does not send confirmation token if Courier is confirmed", %{conn: conn, courier: courier} do
+      Repo.update!(Accounts.Courier.confirm_changeset(courier))
+
+      conn =
+        post(conn, Routes.courier_confirmation_path(conn, :create), %{
+          "courier" => %{"email" => courier.email}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      refute Repo.get_by(Accounts.CourierToken, courier_id: courier.id)
+    end
+
+    test "does not send confirmation token if email is invalid", %{conn: conn} do
+      conn =
+        post(conn, Routes.courier_confirmation_path(conn, :create), %{
+          "courier" => %{"email" => "unknown@example.com"}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      assert Repo.all(Accounts.CourierToken) == []
+    end
+  end
+
+  describe "GET /couriers/confirm/:token" do
+    test "renders the confirmation page", %{conn: conn} do
+      conn = get(conn, Routes.courier_confirmation_path(conn, :edit, "some-token"))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Confirm account</h1>"
+
+      form_action = Routes.courier_confirmation_path(conn, :update, "some-token")
+      assert response =~ "action=\"#{form_action}\""
+    end
+  end
+
+  describe "POST /couriers/confirm/:token" do
+    test "confirms the given token once", %{conn: conn, courier: courier} do
+      token =
+        extract_courier_token(fn url ->
+          Accounts.deliver_courier_confirmation_instructions(courier, url)
+        end)
+
+      conn = post(conn, Routes.courier_confirmation_path(conn, :update, token))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "Courier confirmed successfully"
+      assert Accounts.get_courier!(courier.id).confirmed_at
+      refute get_session(conn, :courier_token)
+      assert Repo.all(Accounts.CourierToken) == []
+
+      # When not logged in
+      conn = post(conn, Routes.courier_confirmation_path(conn, :update, token))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :error) =~ "Courier confirmation link is invalid or it has expired"
+
+      # When logged in
+      conn =
+        build_conn()
+        |> log_in_courier(courier)
+        |> post(Routes.courier_confirmation_path(conn, :update, token))
+
+      assert redirected_to(conn) == "/"
+      refute get_flash(conn, :error)
+    end
+
+    test "does not confirm email with invalid token", %{conn: conn, courier: courier} do
+      conn = post(conn, Routes.courier_confirmation_path(conn, :update, "oops"))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :error) =~ "Courier confirmation link is invalid or it has expired"
+      refute Accounts.get_courier!(courier.id).confirmed_at
+    end
+  end
+end
diff --git a/test/volt_web/controllers/courier_controller_test.exs b/test/volt_web/controllers/courier_controller_test.exs
deleted file mode 100644
index 8812c2a68ee5b019bc23d01ff362ef1ed53b2b39..0000000000000000000000000000000000000000
--- a/test/volt_web/controllers/courier_controller_test.exs
+++ /dev/null
@@ -1,64 +0,0 @@
-defmodule VoltWeb.CourierControllerTest do
-  use VoltWeb.ConnCase
-
-  import Volt.AccountsFixtures
-
-  @create_attrs %{courier_status: "some courier_status", email: "roro@gmail.com", first_name: "some first_name", last_name: "some last_name", password: "somepassword", phone_number: "+33641941064"}
-  @update_attrs %{courier_status: "some updated courier_status", email: "roddro@gmail.com", first_name: "some updated first_name", last_name: "some updated last_name", password: "some updated password", phone_number: "+33641941164"}
-  @invalid_attrs %{courier_status: nil, email: nil, first_name: nil, last_name: nil, password: nil, phone_number: nil}
-
-  describe "new courier" do
-    test "renders form", %{conn: conn} do
-      conn = get(conn, Routes.courier_path(conn, :new))
-      assert html_response(conn, 200) =~ "New Courier"
-    end
-  end
-
-  describe "create courier" do
-    test "redirects to show when data is valid", %{conn: conn} do
-      conn = post(conn, Routes.courier_path(conn, :create), courier: @create_attrs)
-
-      assert %{id: id} = redirected_params(conn)
-      assert redirected_to(conn) == Routes.courier_path(conn, :show, id)
-
-      conn = get(conn, Routes.courier_path(conn, :show, id))
-      assert html_response(conn, 200) =~ "Show Courier"
-    end
-
-    test "renders errors when data is invalid", %{conn: conn} do
-      conn = post(conn, Routes.courier_path(conn, :create), courier: @invalid_attrs)
-      assert html_response(conn, 200) =~ "New Courier"
-    end
-  end
-
-  describe "edit courier" do
-    setup [:create_courier]
-
-    test "renders form for editing chosen courier", %{conn: conn, courier: courier} do
-      conn = get(conn, Routes.courier_path(conn, :edit, courier))
-      assert html_response(conn, 200) =~ "Edit Courier"
-    end
-  end
-
-  describe "update courier" do
-    setup [:create_courier]
-
-    test "redirects when data is valid", %{conn: conn, courier: courier} do
-      conn = put(conn, Routes.courier_path(conn, :update, courier), courier: @update_attrs)
-      assert redirected_to(conn) == Routes.courier_path(conn, :show, courier)
-
-      conn = get(conn, Routes.courier_path(conn, :show, courier))
-      assert html_response(conn, 200) =~ "some updated courier_status"
-    end
-
-    test "renders errors when data is invalid", %{conn: conn, courier: courier} do
-      conn = put(conn, Routes.courier_path(conn, :update, courier), courier: @invalid_attrs)
-      assert html_response(conn, 200) =~ "Edit Courier"
-    end
-  end
-
-  defp create_courier(_) do
-    courier = courier_fixture()
-    %{courier: courier}
-  end
-end
diff --git a/test/volt_web/controllers/courier_registration_controller_test.exs b/test/volt_web/controllers/courier_registration_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..5b40767dbea6664f8e821a0dcf54533c8f2240a4
--- /dev/null
+++ b/test/volt_web/controllers/courier_registration_controller_test.exs
@@ -0,0 +1,54 @@
+defmodule VoltWeb.CourierRegistrationControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  import Volt.AccountsFixtures
+
+  describe "GET /couriers/register" do
+    test "renders registration page", %{conn: conn} do
+      conn = get(conn, Routes.courier_registration_path(conn, :new))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Register</h1>"
+      assert response =~ "Log in</a>"
+      assert response =~ "<div>\n<button type=\"submit\">Register</button>"
+    end
+
+    test "redirects if already logged in", %{conn: conn} do
+      conn = conn |> log_in_courier(courier_fixture()) |> get(Routes.courier_registration_path(conn, :new))
+      assert redirected_to(conn) == "/couriers/dashboard"
+    end
+  end
+
+  describe "POST /couriers/register" do
+    @tag :capture_log
+    test "creates account and logs the courier in", %{conn: conn} do
+      email = unique_courier_email()
+
+      conn =
+        post(conn, Routes.courier_registration_path(conn, :create), %{
+          "courier" => valid_courier_attributes(email: email)
+        })
+
+      assert get_session(conn, :courier_token)
+      assert redirected_to(conn) == "/couriers/dashboard"
+
+      # Now do a logged in request and assert on the menu
+      conn = get(conn, "/")
+      response = html_response(conn, 200)
+      assert response =~ email
+      assert response =~ "Settings</a>"
+      assert response =~ "Log out</a>"
+    end
+
+    test "render errors for invalid data", %{conn: conn} do
+      conn =
+        post(conn, Routes.courier_registration_path(conn, :create), %{
+          "courier" => %{"email" => "with spaces", "password" => "too short"}
+        })
+
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Register</h1>"
+      assert response =~ "must have the @ sign and no spaces"
+      assert response =~ "should be at least 12 character"
+    end
+  end
+end
diff --git a/test/volt_web/controllers/courier_reset_password_controller_test.exs b/test/volt_web/controllers/courier_reset_password_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..492fcd690b8a0d831ed07c093d9fb8c26f29c330
--- /dev/null
+++ b/test/volt_web/controllers/courier_reset_password_controller_test.exs
@@ -0,0 +1,113 @@
+defmodule VoltWeb.CourierResetPasswordControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  alias Volt.Accounts
+  alias Volt.Repo
+  import Volt.AccountsFixtures
+
+  setup do
+    %{courier: courier_fixture()}
+  end
+
+  describe "GET /couriers/reset_password" do
+    test "renders the reset password page", %{conn: conn} do
+      conn = get(conn, Routes.courier_reset_password_path(conn, :new))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Forgot your password?</h1>"
+    end
+  end
+
+  describe "POST /couriers/reset_password" do
+    @tag :capture_log
+    test "sends a new reset password token", %{conn: conn, courier: courier} do
+      conn =
+        post(conn, Routes.courier_reset_password_path(conn, :create), %{
+          "courier" => %{"email" => courier.email}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      assert Repo.get_by!(Accounts.CourierToken, courier_id: courier.id).context == "reset_password"
+    end
+
+    test "does not send reset password token if email is invalid", %{conn: conn} do
+      conn =
+        post(conn, Routes.courier_reset_password_path(conn, :create), %{
+          "courier" => %{"email" => "unknown@example.com"}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      assert Repo.all(Accounts.CourierToken) == []
+    end
+  end
+
+  describe "GET /couriers/reset_password/:token" do
+    setup %{courier: courier} do
+      token =
+        extract_courier_token(fn url ->
+          Accounts.deliver_courier_reset_password_instructions(courier, url)
+        end)
+
+      %{token: token}
+    end
+
+    test "renders reset password", %{conn: conn, token: token} do
+      conn = get(conn, Routes.courier_reset_password_path(conn, :edit, token))
+      assert html_response(conn, 200) =~ "<h1>Reset password</h1>"
+    end
+
+    test "does not render reset password with invalid token", %{conn: conn} do
+      conn = get(conn, Routes.courier_reset_password_path(conn, :edit, "oops"))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :error) =~ "Reset password link is invalid or it has expired"
+    end
+  end
+
+  describe "PUT /couriers/reset_password/:token" do
+    setup %{courier: courier} do
+      token =
+        extract_courier_token(fn url ->
+          Accounts.deliver_courier_reset_password_instructions(courier, url)
+        end)
+
+      %{token: token}
+    end
+
+    test "resets password once", %{conn: conn, courier: courier, token: token} do
+      conn =
+        put(conn, Routes.courier_reset_password_path(conn, :update, token), %{
+          "courier" => %{
+            "password" => "new valid password",
+            "password_confirmation" => "new valid password"
+          }
+        })
+
+      assert redirected_to(conn) == Routes.courier_session_path(conn, :new)
+      refute get_session(conn, :courier_token)
+      assert get_flash(conn, :info) =~ "Password reset successfully"
+      assert Accounts.get_courier_by_email_and_password(courier.email, "new valid password")
+    end
+
+    test "does not reset password on invalid data", %{conn: conn, token: token} do
+      conn =
+        put(conn, Routes.courier_reset_password_path(conn, :update, token), %{
+          "courier" => %{
+            "password" => "too short",
+            "password_confirmation" => "does not match"
+          }
+        })
+
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Reset password</h1>"
+      assert response =~ "should be at least 12 character(s)"
+      assert response =~ "does not match password"
+    end
+
+    test "does not reset password with invalid token", %{conn: conn} do
+      conn = put(conn, Routes.courier_reset_password_path(conn, :update, "oops"))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :error) =~ "Reset password link is invalid or it has expired"
+    end
+  end
+end
diff --git a/test/volt_web/controllers/courier_session_controller_test.exs b/test/volt_web/controllers/courier_session_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..000d27c833f9a5c667e8652f1eb45eed49b9967e
--- /dev/null
+++ b/test/volt_web/controllers/courier_session_controller_test.exs
@@ -0,0 +1,98 @@
+defmodule VoltWeb.CourierSessionControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  import Volt.AccountsFixtures
+
+  setup do
+    %{courier: courier_fixture()}
+  end
+
+  describe "GET /couriers/log_in" do
+    test "renders log in page", %{conn: conn} do
+      conn = get(conn, Routes.courier_session_path(conn, :new))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Log in</h1>"
+      assert response =~ "Register</a>"
+      assert response =~ "Forgot your password?</a>"
+    end
+
+    test "redirects if already logged in", %{conn: conn, courier: courier} do
+      conn = conn |> log_in_courier(courier) |> get(Routes.courier_session_path(conn, :new))
+      assert redirected_to(conn) == "/couriers/dashboard"
+    end
+  end
+
+  describe "POST /couriers/log_in" do
+    test "logs the courier in", %{conn: conn, courier: courier} do
+      conn =
+        post(conn, Routes.courier_session_path(conn, :create), %{
+          "courier" => %{"email" => courier.email, "password" => valid_courier_password()}
+        })
+
+      assert get_session(conn, :courier_token)
+      assert redirected_to(conn) == "/couriers/dashboard"
+
+      # Now do a logged in request and assert on the menu
+      conn = get(conn, "/")
+      response = html_response(conn, 200)
+      assert response =~ courier.email
+      assert response =~ "Settings</a>"
+      assert response =~ "Log out</a>"
+    end
+
+    test "logs the courier in with remember me", %{conn: conn, courier: courier} do
+      conn =
+        post(conn, Routes.courier_session_path(conn, :create), %{
+          "courier" => %{
+            "email" => courier.email,
+            "password" => valid_courier_password(),
+            "remember_me" => "true"
+          }
+        })
+
+      assert conn.resp_cookies["_volt_web_courier_remember_me"]
+      assert redirected_to(conn) == "/couriers/dashboard"
+    end
+
+    test "logs the courier in with return to", %{conn: conn, courier: courier} do
+      conn =
+        conn
+        |> init_test_session(courier_return_to: "/foo/bar")
+        |> post(Routes.courier_session_path(conn, :create), %{
+          "courier" => %{
+            "email" => courier.email,
+            "password" => valid_courier_password()
+          }
+        })
+
+      assert redirected_to(conn) == "/foo/bar"
+    end
+
+    test "emits error message with invalid credentials", %{conn: conn, courier: courier} do
+      conn =
+        post(conn, Routes.courier_session_path(conn, :create), %{
+          "courier" => %{"email" => courier.email, "password" => "invalid_password"}
+        })
+
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Log in</h1>"
+      assert response =~ "Invalid email or password"
+    end
+  end
+
+  describe "DELETE /couriers/log_out" do
+    test "logs the courier out", %{conn: conn, courier: courier} do
+      conn = conn |> log_in_courier(courier) |> delete(Routes.courier_session_path(conn, :delete))
+      assert redirected_to(conn) == "/"
+      refute get_session(conn, :courier_token)
+      assert get_flash(conn, :info) =~ "Logged out successfully"
+    end
+
+    test "succeeds even if the courier is not logged in", %{conn: conn} do
+      conn = delete(conn, Routes.courier_session_path(conn, :delete))
+      assert redirected_to(conn) == "/"
+      refute get_session(conn, :courier_token)
+      assert get_flash(conn, :info) =~ "Logged out successfully"
+    end
+  end
+end
diff --git a/test/volt_web/controllers/courier_settings_controller_test.exs b/test/volt_web/controllers/courier_settings_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..e0d2cfa84122a06a45bb311a3379fab7cd7ffd4b
--- /dev/null
+++ b/test/volt_web/controllers/courier_settings_controller_test.exs
@@ -0,0 +1,129 @@
+defmodule VoltWeb.CourierSettingsControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  alias Volt.Accounts
+  import Volt.AccountsFixtures
+
+  setup :register_and_log_in_courier
+
+  describe "GET /couriers/settings" do
+    test "renders settings page", %{conn: conn} do
+      conn = get(conn, Routes.courier_settings_path(conn, :edit))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Settings</h1>"
+    end
+
+    test "redirects if courier is not logged in" do
+      conn = build_conn()
+      conn = get(conn, Routes.courier_settings_path(conn, :edit))
+      assert redirected_to(conn) == Routes.courier_session_path(conn, :new)
+    end
+  end
+
+  describe "PUT /couriers/settings (change password form)" do
+    test "updates the courier password and resets tokens", %{conn: conn, courier: courier} do
+      new_password_conn =
+        put(conn, Routes.courier_settings_path(conn, :update), %{
+          "action" => "update_password",
+          "current_password" => valid_courier_password(),
+          "courier" => %{
+            "password" => "new valid password",
+            "password_confirmation" => "new valid password"
+          }
+        })
+
+      assert redirected_to(new_password_conn) == Routes.courier_settings_path(conn, :edit)
+      assert get_session(new_password_conn, :courier_token) != get_session(conn, :courier_token)
+      assert get_flash(new_password_conn, :info) =~ "Password updated successfully"
+      assert Accounts.get_courier_by_email_and_password(courier.email, "new valid password")
+    end
+
+    test "does not update password on invalid data", %{conn: conn} do
+      old_password_conn =
+        put(conn, Routes.courier_settings_path(conn, :update), %{
+          "action" => "update_password",
+          "current_password" => "invalid",
+          "courier" => %{
+            "password" => "too short",
+            "password_confirmation" => "does not match"
+          }
+        })
+
+      response = html_response(old_password_conn, 200)
+      assert response =~ "<h1>Settings</h1>"
+      assert response =~ "should be at least 12 character(s)"
+      assert response =~ "does not match password"
+      assert response =~ "is not valid"
+
+      assert get_session(old_password_conn, :courier_token) == get_session(conn, :courier_token)
+    end
+  end
+
+  describe "PUT /couriers/settings (change email form)" do
+    @tag :capture_log
+    test "updates the courier email", %{conn: conn, courier: courier} do
+      conn =
+        put(conn, Routes.courier_settings_path(conn, :update), %{
+          "action" => "update_email",
+          "current_password" => valid_courier_password(),
+          "courier" => %{"email" => unique_courier_email()}
+        })
+
+      assert redirected_to(conn) == Routes.courier_settings_path(conn, :edit)
+      assert get_flash(conn, :info) =~ "A link to confirm your email"
+      assert Accounts.get_courier_by_email(courier.email)
+    end
+
+    test "does not update email on invalid data", %{conn: conn} do
+      conn =
+        put(conn, Routes.courier_settings_path(conn, :update), %{
+          "action" => "update_email",
+          "current_password" => "invalid",
+          "courier" => %{"email" => "with spaces"}
+        })
+
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Settings</h1>"
+      assert response =~ "must have the @ sign and no spaces"
+      assert response =~ "is not valid"
+    end
+  end
+
+  describe "GET /couriers/settings/confirm_email/:token" do
+    setup %{courier: courier} do
+      email = unique_courier_email()
+
+      token =
+        extract_courier_token(fn url ->
+          Accounts.deliver_courier_update_email_instructions(%{courier | email: email}, courier.email, url)
+        end)
+
+      %{token: token, email: email}
+    end
+
+    test "updates the courier email once", %{conn: conn, courier: courier, token: token, email: email} do
+      conn = get(conn, Routes.courier_settings_path(conn, :confirm_email, token))
+      assert redirected_to(conn) == Routes.courier_settings_path(conn, :edit)
+      assert get_flash(conn, :info) =~ "Email changed successfully"
+      refute Accounts.get_courier_by_email(courier.email)
+      assert Accounts.get_courier_by_email(email)
+
+      conn = get(conn, Routes.courier_settings_path(conn, :confirm_email, token))
+      assert redirected_to(conn) == Routes.courier_settings_path(conn, :edit)
+      assert get_flash(conn, :error) =~ "Email change link is invalid or it has expired"
+    end
+
+    test "does not update email with invalid token", %{conn: conn, courier: courier} do
+      conn = get(conn, Routes.courier_settings_path(conn, :confirm_email, "oops"))
+      assert redirected_to(conn) == Routes.courier_settings_path(conn, :edit)
+      assert get_flash(conn, :error) =~ "Email change link is invalid or it has expired"
+      assert Accounts.get_courier_by_email(courier.email)
+    end
+
+    test "redirects if courier is not logged in", %{token: token} do
+      conn = build_conn()
+      conn = get(conn, Routes.courier_settings_path(conn, :confirm_email, token))
+      assert redirected_to(conn) == Routes.courier_session_path(conn, :new)
+    end
+  end
+end
diff --git a/test/volt_web/controllers/customer_auth_test.exs b/test/volt_web/controllers/customer_auth_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..bd61c7e705128e67fb1013284a21aa6c134ebb26
--- /dev/null
+++ b/test/volt_web/controllers/customer_auth_test.exs
@@ -0,0 +1,170 @@
+defmodule VoltWeb.CustomerAuthTest do
+  use VoltWeb.ConnCase, async: true
+
+  alias Volt.Accounts
+  alias VoltWeb.CustomerAuth
+  import Volt.AccountsFixtures
+
+  @remember_me_cookie "_volt_web_customer_remember_me"
+
+  setup %{conn: conn} do
+    conn =
+      conn
+      |> Map.replace!(:secret_key_base, VoltWeb.Endpoint.config(:secret_key_base))
+      |> init_test_session(%{})
+
+    %{customer: customer_fixture(), conn: conn}
+  end
+
+  describe "log_in_customer/3" do
+    test "stores the customer token in the session", %{conn: conn, customer: customer} do
+      conn = CustomerAuth.log_in_customer(conn, customer)
+      assert token = get_session(conn, :customer_token)
+      assert get_session(conn, :live_socket_id) == "customers_sessions:#{Base.url_encode64(token)}"
+      assert redirected_to(conn) == "/customers/dashboard"
+      assert Accounts.get_customer_by_session_token(token)
+    end
+
+    test "clears everything previously stored in the session", %{conn: conn, customer: customer} do
+      conn = conn |> put_session(:to_be_removed, "value") |> CustomerAuth.log_in_customer(customer)
+      refute get_session(conn, :to_be_removed)
+    end
+
+    test "redirects to the configured path", %{conn: conn, customer: customer} do
+      conn = conn |> put_session(:customer_return_to, "/hello") |> CustomerAuth.log_in_customer(customer)
+      assert redirected_to(conn) == "/hello"
+    end
+
+    test "writes a cookie if remember_me is configured", %{conn: conn, customer: customer} do
+      conn = conn |> fetch_cookies() |> CustomerAuth.log_in_customer(customer, %{"remember_me" => "true"})
+      assert get_session(conn, :customer_token) == conn.cookies[@remember_me_cookie]
+
+      assert %{value: signed_token, max_age: max_age} = conn.resp_cookies[@remember_me_cookie]
+      assert signed_token != get_session(conn, :customer_token)
+      assert max_age == 5_184_000
+    end
+  end
+
+  describe "logout_customer/1" do
+    test "erases session and cookies", %{conn: conn, customer: customer} do
+      customer_token = Accounts.generate_customer_session_token(customer)
+
+      conn =
+        conn
+        |> put_session(:customer_token, customer_token)
+        |> put_req_cookie(@remember_me_cookie, customer_token)
+        |> fetch_cookies()
+        |> CustomerAuth.log_out_customer()
+
+      refute get_session(conn, :customer_token)
+      refute conn.cookies[@remember_me_cookie]
+      assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie]
+      assert redirected_to(conn) == "/"
+      refute Accounts.get_customer_by_session_token(customer_token)
+    end
+
+    test "broadcasts to the given live_socket_id", %{conn: conn} do
+      live_socket_id = "customers_sessions:abcdef-token"
+      VoltWeb.Endpoint.subscribe(live_socket_id)
+
+      conn
+      |> put_session(:live_socket_id, live_socket_id)
+      |> CustomerAuth.log_out_customer()
+
+      assert_receive %Phoenix.Socket.Broadcast{event: "disconnect", topic: ^live_socket_id}
+    end
+
+    test "works even if customer is already logged out", %{conn: conn} do
+      conn = conn |> fetch_cookies() |> CustomerAuth.log_out_customer()
+      refute get_session(conn, :customer_token)
+      assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie]
+      assert redirected_to(conn) == "/"
+    end
+  end
+
+  describe "fetch_current_customer/2" do
+    test "authenticates customer from session", %{conn: conn, customer: customer} do
+      customer_token = Accounts.generate_customer_session_token(customer)
+      conn = conn |> put_session(:customer_token, customer_token) |> CustomerAuth.fetch_current_customer([])
+      assert conn.assigns.current_customer.id == customer.id
+    end
+
+    test "authenticates customer from cookies", %{conn: conn, customer: customer} do
+      logged_in_conn =
+        conn |> fetch_cookies() |> CustomerAuth.log_in_customer(customer, %{"remember_me" => "true"})
+
+      customer_token = logged_in_conn.cookies[@remember_me_cookie]
+      %{value: signed_token} = logged_in_conn.resp_cookies[@remember_me_cookie]
+
+      conn =
+        conn
+        |> put_req_cookie(@remember_me_cookie, signed_token)
+        |> CustomerAuth.fetch_current_customer([])
+
+      assert get_session(conn, :customer_token) == customer_token
+      assert conn.assigns.current_customer.id == customer.id
+    end
+
+    test "does not authenticate if data is missing", %{conn: conn, customer: customer} do
+      _ = Accounts.generate_customer_session_token(customer)
+      conn = CustomerAuth.fetch_current_customer(conn, [])
+      refute get_session(conn, :customer_token)
+      refute conn.assigns.current_customer
+    end
+  end
+
+  describe "redirect_if_customer_is_authenticated/2" do
+    test "redirects if customer is authenticated", %{conn: conn, customer: customer} do
+      conn = conn |> assign(:current_customer, customer) |> CustomerAuth.redirect_if_customer_is_authenticated([])
+      assert conn.halted
+      assert redirected_to(conn) == "/customers/dashboard"
+    end
+
+    test "does not redirect if customer is not authenticated", %{conn: conn} do
+      conn = CustomerAuth.redirect_if_customer_is_authenticated(conn, [])
+      refute conn.halted
+      refute conn.status
+    end
+  end
+
+  describe "require_authenticated_customer/2" do
+    test "redirects if customer is not authenticated", %{conn: conn} do
+      conn = conn |> fetch_flash() |> CustomerAuth.require_authenticated_customer([])
+      assert conn.halted
+      assert redirected_to(conn) == Routes.customer_session_path(conn, :new)
+      assert get_flash(conn, :error) == "You must log in to access this page."
+    end
+
+    test "stores the path to redirect to on GET", %{conn: conn} do
+      halted_conn =
+        %{conn | path_info: ["foo"], query_string: ""}
+        |> fetch_flash()
+        |> CustomerAuth.require_authenticated_customer([])
+
+      assert halted_conn.halted
+      assert get_session(halted_conn, :customer_return_to) == "/foo"
+
+      halted_conn =
+        %{conn | path_info: ["foo"], query_string: "bar=baz"}
+        |> fetch_flash()
+        |> CustomerAuth.require_authenticated_customer([])
+
+      assert halted_conn.halted
+      assert get_session(halted_conn, :customer_return_to) == "/foo?bar=baz"
+
+      halted_conn =
+        %{conn | path_info: ["foo"], query_string: "bar", method: "POST"}
+        |> fetch_flash()
+        |> CustomerAuth.require_authenticated_customer([])
+
+      assert halted_conn.halted
+      refute get_session(halted_conn, :customer_return_to)
+    end
+
+    test "does not redirect if customer is authenticated", %{conn: conn, customer: customer} do
+      conn = conn |> assign(:current_customer, customer) |> CustomerAuth.require_authenticated_customer([])
+      refute conn.halted
+      refute conn.status
+    end
+  end
+end
diff --git a/test/volt_web/controllers/customer_confirmation_controller_test.exs b/test/volt_web/controllers/customer_confirmation_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..9c0ceed693914d059e37045a9e612327783091af
--- /dev/null
+++ b/test/volt_web/controllers/customer_confirmation_controller_test.exs
@@ -0,0 +1,105 @@
+defmodule VoltWeb.CustomerConfirmationControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  alias Volt.Accounts
+  alias Volt.Repo
+  import Volt.AccountsFixtures
+
+  setup do
+    %{customer: customer_fixture()}
+  end
+
+  describe "GET /customers/confirm" do
+    test "renders the resend confirmation page", %{conn: conn} do
+      conn = get(conn, Routes.customer_confirmation_path(conn, :new))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Resend confirmation instructions</h1>"
+    end
+  end
+
+  describe "POST /customers/confirm" do
+    @tag :capture_log
+    test "sends a new confirmation token", %{conn: conn, customer: customer} do
+      conn =
+        post(conn, Routes.customer_confirmation_path(conn, :create), %{
+          "customer" => %{"email" => customer.email}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      assert Repo.get_by!(Accounts.CustomerToken, customer_id: customer.id).context == "confirm"
+    end
+
+    test "does not send confirmation token if Customer is confirmed", %{conn: conn, customer: customer} do
+      Repo.update!(Accounts.Customer.confirm_changeset(customer))
+
+      conn =
+        post(conn, Routes.customer_confirmation_path(conn, :create), %{
+          "customer" => %{"email" => customer.email}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      refute Repo.get_by(Accounts.CustomerToken, customer_id: customer.id)
+    end
+
+    test "does not send confirmation token if email is invalid", %{conn: conn} do
+      conn =
+        post(conn, Routes.customer_confirmation_path(conn, :create), %{
+          "customer" => %{"email" => "unknown@example.com"}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      assert Repo.all(Accounts.CustomerToken) == []
+    end
+  end
+
+  describe "GET /customers/confirm/:token" do
+    test "renders the confirmation page", %{conn: conn} do
+      conn = get(conn, Routes.customer_confirmation_path(conn, :edit, "some-token"))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Confirm account</h1>"
+
+      form_action = Routes.customer_confirmation_path(conn, :update, "some-token")
+      assert response =~ "action=\"#{form_action}\""
+    end
+  end
+
+  describe "POST /customers/confirm/:token" do
+    test "confirms the given token once", %{conn: conn, customer: customer} do
+      token =
+        extract_customer_token(fn url ->
+          Accounts.deliver_customer_confirmation_instructions(customer, url)
+        end)
+
+      conn = post(conn, Routes.customer_confirmation_path(conn, :update, token))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "Customer confirmed successfully"
+      assert Accounts.get_customer!(customer.id).confirmed_at
+      refute get_session(conn, :customer_token)
+      assert Repo.all(Accounts.CustomerToken) == []
+
+      # When not logged in
+      conn = post(conn, Routes.customer_confirmation_path(conn, :update, token))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :error) =~ "Customer confirmation link is invalid or it has expired"
+
+      # When logged in
+      conn =
+        build_conn()
+        |> log_in_customer(customer)
+        |> post(Routes.customer_confirmation_path(conn, :update, token))
+
+      assert redirected_to(conn) == "/"
+      refute get_flash(conn, :error)
+    end
+
+    test "does not confirm email with invalid token", %{conn: conn, customer: customer} do
+      conn = post(conn, Routes.customer_confirmation_path(conn, :update, "oops"))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :error) =~ "Customer confirmation link is invalid or it has expired"
+      refute Accounts.get_customer!(customer.id).confirmed_at
+    end
+  end
+end
diff --git a/test/volt_web/controllers/customer_controller_test.exs b/test/volt_web/controllers/customer_controller_test.exs
deleted file mode 100644
index 777c1c98c1066c93641c41e9a83e29ba1e696918..0000000000000000000000000000000000000000
--- a/test/volt_web/controllers/customer_controller_test.exs
+++ /dev/null
@@ -1,87 +0,0 @@
-defmodule VoltWeb.CustomerControllerTest do
-  use VoltWeb.ConnCase
-  alias Volt.{Repo,Accounts.Customer}
-
-  # Declaration of variables to create customer
-  # create and update attributes are valid to create a customer
-  # invalid_attrs should raise a changeset erre while using it to create a customer
-  @create_attrs %{address: "some address", zip_code: "50000", city: "Tartu", balance: 120.5, birth_date: ~D[2022-11-04], card_number: "1234567812345678", email: "roro@gmail.com", first_name: "some first_name", last_name: "some last_name", password: "some password", phone_number: "+33641941064"}
-  @update_attrs %{address: "some updated address", zip_code: "60000", city: "Parnu", balance: 456.7, birth_date: ~D[2022-11-05], card_number: "1234567899999999", email: "roro@free.fr", first_name: "some updated first_name", last_name: "some updated last_name", password: "some updated password", phone_number: "+65345678876"}
-  @invalid_attrs %{address: nil, zip_code: "efz", city: nil, balance: nil, birth_date: nil, card_number: "df567h", email: "hah567s@s", first_name: nil, last_name: nil, password: "12s*", phone_number: "qsd4567"}
-
-  # describe "index" do
-  #   test "lists all customers", %{conn: conn} do
-  #     conn = get(conn, Routes.customer_path(conn, :index))
-  #     assert html_response(conn, 200) =~ "Listing Customers"
-  #   end
-  # end
-
-  describe "new customer" do
-    test "renders form", %{conn: conn} do
-      conn = get(conn, Routes.customer_path(conn, :new))
-      assert html_response(conn, 200) =~ "New Customer"
-    end
-  end
-
-  describe "create customer" do
-    test "redirects to show when data is valid", %{conn: conn} do
-      conn = post(conn, Routes.customer_path(conn, :create), customer: @create_attrs)
-
-      assert %{id: id} = redirected_params(conn)
-      assert redirected_to(conn) == Routes.customer_path(conn, :show, id)
-
-      conn = get(conn, Routes.customer_path(conn, :show, id))
-      assert html_response(conn, 200) =~ "Show Customer"
-    end
-
-    test "renders errors when data is invalid", %{conn: conn} do
-      conn = post(conn, Routes.customer_path(conn, :create), customer: @invalid_attrs)
-      assert html_response(conn, 200) =~ "New Customer"
-    end
-  end
-
-  describe "edit customer" do
-    setup [:create_customer]
-
-    test "renders form for editing chosen customer", %{conn: conn, customer: customer} do
-      conn = get(conn, Routes.customer_path(conn, :edit, customer))
-      assert html_response(conn, 200) =~ "Edit Customer"
-    end
-  end
-
-  describe "update customer" do
-    setup [:create_customer]
-
-    test "redirects when data is valid", %{conn: conn, customer: customer} do
-      conn = put(conn, Routes.customer_path(conn, :update, customer), customer: @update_attrs)
-      assert redirected_to(conn) == Routes.customer_path(conn, :show, customer)
-
-      conn = get(conn, Routes.customer_path(conn, :show, customer))
-      assert html_response(conn, 200) =~ "some updated address"
-    end
-
-    test "renders errors when data is invalid", %{conn: conn, customer: customer} do
-      conn = put(conn, Routes.customer_path(conn, :update, customer), customer: @invalid_attrs)
-      assert html_response(conn, 200) =~ "Edit Customer"
-    end
-  end
-
-  describe "delete customer" do
-    setup [:create_customer]
-
-    test "deletes chosen customer", %{conn: conn, customer: customer} do
-      conn = delete(conn, Routes.customer_path(conn, :delete, customer))
-      assert redirected_to(conn) == Routes.customer_path(conn, :index)
-
-      assert_error_sent 404, fn ->
-        get(conn, Routes.customer_path(conn, :show, customer))
-      end
-    end
-  end
-
-  defp create_customer(_) do
-    # {:ok, customer: customer} = Accounts.create_customer(@create_attrs)
-    customer = Repo.insert!(Map.merge(%Customer{},@create_attrs))
-    %{customer: customer}
-  end
-end
diff --git a/test/volt_web/controllers/customer_registration_controller_test.exs b/test/volt_web/controllers/customer_registration_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..ea0b397c8d9f3f7339dffe5b69e746844a5328e8
--- /dev/null
+++ b/test/volt_web/controllers/customer_registration_controller_test.exs
@@ -0,0 +1,61 @@
+defmodule VoltWeb.CustomerRegistrationControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  import Volt.AccountsFixtures
+
+  describe "GET /customers/register" do
+    test "renders registration page", %{conn: conn} do
+      conn = get(conn, Routes.customer_registration_path(conn, :new))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Register</h1>"
+      assert response =~ "Log in</a>"
+      assert response =~ "<div>\n<button id=\"submit_button\" type=\"submit\">Register</button>"
+    end
+
+    test "renders location map", %{conn: conn} do
+      conn = get(conn, Routes.customer_registration_path(conn, :new))
+      response = html_response(conn, 200)
+      assert response =~ "id=\"myMap\""
+      assert response =~ "Microsoft.Map"
+    end
+
+    test "redirects if already logged in", %{conn: conn} do
+      conn = conn |> log_in_customer(customer_fixture()) |> get(Routes.customer_registration_path(conn, :new))
+      assert redirected_to(conn) == "/customers/dashboard"
+    end
+  end
+
+  describe "POST /customers/register" do
+    @tag :capture_log
+    test "creates account and logs the customer in", %{conn: conn} do
+      email = unique_customer_email()
+
+      conn =
+        post(conn, Routes.customer_registration_path(conn, :create), %{
+          "customer" => valid_customer_attributes(email: email)
+        })
+
+      assert get_session(conn, :customer_token)
+      assert redirected_to(conn) == "/customers/dashboard"
+
+      # Now do a logged in request and assert on the menu
+      conn = get(conn, "/")
+      response = html_response(conn, 200)
+      assert response =~ email
+      assert response =~ "Settings</a>"
+      assert response =~ "Log out</a>"
+    end
+
+    test "render errors for invalid data", %{conn: conn} do
+      conn =
+        post(conn, Routes.customer_registration_path(conn, :create), %{
+          "customer" => %{"email" => "with spaces", "password" => "too short"}
+        })
+
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Register</h1>"
+      assert response =~ "must have the @ sign and no spaces"
+      assert response =~ "should be at least 12 character"
+    end
+  end
+end
diff --git a/test/volt_web/controllers/customer_reset_password_controller_test.exs b/test/volt_web/controllers/customer_reset_password_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..d4d0059c4d31f0b9adf814315f7e205d99e620db
--- /dev/null
+++ b/test/volt_web/controllers/customer_reset_password_controller_test.exs
@@ -0,0 +1,113 @@
+defmodule VoltWeb.CustomerResetPasswordControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  alias Volt.Accounts
+  alias Volt.Repo
+  import Volt.AccountsFixtures
+
+  setup do
+    %{customer: customer_fixture()}
+  end
+
+  describe "GET /customers/reset_password" do
+    test "renders the reset password page", %{conn: conn} do
+      conn = get(conn, Routes.customer_reset_password_path(conn, :new))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Forgot your password?</h1>"
+    end
+  end
+
+  describe "POST /customers/reset_password" do
+    @tag :capture_log
+    test "sends a new reset password token", %{conn: conn, customer: customer} do
+      conn =
+        post(conn, Routes.customer_reset_password_path(conn, :create), %{
+          "customer" => %{"email" => customer.email}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      assert Repo.get_by!(Accounts.CustomerToken, customer_id: customer.id).context == "reset_password"
+    end
+
+    test "does not send reset password token if email is invalid", %{conn: conn} do
+      conn =
+        post(conn, Routes.customer_reset_password_path(conn, :create), %{
+          "customer" => %{"email" => "unknown@example.com"}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      assert Repo.all(Accounts.CustomerToken) == []
+    end
+  end
+
+  describe "GET /customers/reset_password/:token" do
+    setup %{customer: customer} do
+      token =
+        extract_customer_token(fn url ->
+          Accounts.deliver_customer_reset_password_instructions(customer, url)
+        end)
+
+      %{token: token}
+    end
+
+    test "renders reset password", %{conn: conn, token: token} do
+      conn = get(conn, Routes.customer_reset_password_path(conn, :edit, token))
+      assert html_response(conn, 200) =~ "<h1>Reset password</h1>"
+    end
+
+    test "does not render reset password with invalid token", %{conn: conn} do
+      conn = get(conn, Routes.customer_reset_password_path(conn, :edit, "oops"))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :error) =~ "Reset password link is invalid or it has expired"
+    end
+  end
+
+  describe "PUT /customers/reset_password/:token" do
+    setup %{customer: customer} do
+      token =
+        extract_customer_token(fn url ->
+          Accounts.deliver_customer_reset_password_instructions(customer, url)
+        end)
+
+      %{token: token}
+    end
+
+    test "resets password once", %{conn: conn, customer: customer, token: token} do
+      conn =
+        put(conn, Routes.customer_reset_password_path(conn, :update, token), %{
+          "customer" => %{
+            "password" => "new valid password",
+            "password_confirmation" => "new valid password"
+          }
+        })
+
+      assert redirected_to(conn) == Routes.customer_session_path(conn, :new)
+      refute get_session(conn, :customer_token)
+      assert get_flash(conn, :info) =~ "Password reset successfully"
+      assert Accounts.get_customer_by_email_and_password(customer.email, "new valid password")
+    end
+
+    test "does not reset password on invalid data", %{conn: conn, token: token} do
+      conn =
+        put(conn, Routes.customer_reset_password_path(conn, :update, token), %{
+          "customer" => %{
+            "password" => "too short",
+            "password_confirmation" => "does not match"
+          }
+        })
+
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Reset password</h1>"
+      assert response =~ "should be at least 12 character(s)"
+      assert response =~ "does not match password"
+    end
+
+    test "does not reset password with invalid token", %{conn: conn} do
+      conn = put(conn, Routes.customer_reset_password_path(conn, :update, "oops"))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :error) =~ "Reset password link is invalid or it has expired"
+    end
+  end
+end
diff --git a/test/volt_web/controllers/customer_session_controller_test.exs b/test/volt_web/controllers/customer_session_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..403e5787f9e58e183e3aa90f925f99e65dfe24eb
--- /dev/null
+++ b/test/volt_web/controllers/customer_session_controller_test.exs
@@ -0,0 +1,98 @@
+defmodule VoltWeb.CustomerSessionControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  import Volt.AccountsFixtures
+
+  setup do
+    %{customer: customer_fixture()}
+  end
+
+  describe "GET /customers/log_in" do
+    test "renders log in page", %{conn: conn} do
+      conn = get(conn, Routes.customer_session_path(conn, :new))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Log in</h1>"
+      assert response =~ "Register</a>"
+      assert response =~ "Forgot your password?</a>"
+    end
+
+    test "redirects if already logged in", %{conn: conn, customer: customer} do
+      conn = conn |> log_in_customer(customer) |> get(Routes.customer_session_path(conn, :new))
+      assert redirected_to(conn) == "/customers/dashboard"
+    end
+  end
+
+  describe "POST /customers/log_in" do
+    test "logs the customer in", %{conn: conn, customer: customer} do
+      conn =
+        post(conn, Routes.customer_session_path(conn, :create), %{
+          "customer" => %{"email" => customer.email, "password" => valid_customer_password()}
+        })
+
+      assert get_session(conn, :customer_token)
+      assert redirected_to(conn) == "/customers/dashboard"
+
+      # Now do a logged in request and assert on the menu
+      conn = get(conn, "/")
+      response = html_response(conn, 200)
+      assert response =~ customer.email
+      assert response =~ "Settings</a>"
+      assert response =~ "Log out</a>"
+    end
+
+    test "logs the customer in with remember me", %{conn: conn, customer: customer} do
+      conn =
+        post(conn, Routes.customer_session_path(conn, :create), %{
+          "customer" => %{
+            "email" => customer.email,
+            "password" => valid_customer_password(),
+            "remember_me" => "true"
+          }
+        })
+
+      assert conn.resp_cookies["_volt_web_customer_remember_me"]
+      assert redirected_to(conn) == "/customers/dashboard"
+    end
+
+    test "logs the customer in with return to", %{conn: conn, customer: customer} do
+      conn =
+        conn
+        |> init_test_session(customer_return_to: "/foo/bar")
+        |> post(Routes.customer_session_path(conn, :create), %{
+          "customer" => %{
+            "email" => customer.email,
+            "password" => valid_customer_password()
+          }
+        })
+
+      assert redirected_to(conn) == "/foo/bar"
+    end
+
+    test "emits error message with invalid credentials", %{conn: conn, customer: customer} do
+      conn =
+        post(conn, Routes.customer_session_path(conn, :create), %{
+          "customer" => %{"email" => customer.email, "password" => "invalid_password"}
+        })
+
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Log in</h1>"
+      assert response =~ "Invalid email or password"
+    end
+  end
+
+  describe "DELETE /customers/log_out" do
+    test "logs the customer out", %{conn: conn, customer: customer} do
+      conn = conn |> log_in_customer(customer) |> delete(Routes.customer_session_path(conn, :delete))
+      assert redirected_to(conn) == "/"
+      refute get_session(conn, :customer_token)
+      assert get_flash(conn, :info) =~ "Logged out successfully"
+    end
+
+    test "succeeds even if the customer is not logged in", %{conn: conn} do
+      conn = delete(conn, Routes.customer_session_path(conn, :delete))
+      assert redirected_to(conn) == "/"
+      refute get_session(conn, :customer_token)
+      assert get_flash(conn, :info) =~ "Logged out successfully"
+    end
+  end
+end
diff --git a/test/volt_web/controllers/customer_settings_controller_test.exs b/test/volt_web/controllers/customer_settings_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..39011a5cd646fb72d69aaead7dc3f27c59baec8f
--- /dev/null
+++ b/test/volt_web/controllers/customer_settings_controller_test.exs
@@ -0,0 +1,129 @@
+defmodule VoltWeb.CustomerSettingsControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  alias Volt.Accounts
+  import Volt.AccountsFixtures
+
+  setup :register_and_log_in_customer
+
+  describe "GET /customers/settings" do
+    test "renders settings page", %{conn: conn} do
+      conn = get(conn, Routes.customer_settings_path(conn, :edit))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Settings</h1>"
+    end
+
+    test "redirects if customer is not logged in" do
+      conn = build_conn()
+      conn = get(conn, Routes.customer_settings_path(conn, :edit))
+      assert redirected_to(conn) == Routes.customer_session_path(conn, :new)
+    end
+  end
+
+  describe "PUT /customers/settings (change password form)" do
+    test "updates the customer password and resets tokens", %{conn: conn, customer: customer} do
+      new_password_conn =
+        put(conn, Routes.customer_settings_path(conn, :update), %{
+          "action" => "update_password",
+          "current_password" => valid_customer_password(),
+          "customer" => %{
+            "password" => "new valid password",
+            "password_confirmation" => "new valid password"
+          }
+        })
+
+      assert redirected_to(new_password_conn) == Routes.customer_settings_path(conn, :edit)
+      assert get_session(new_password_conn, :customer_token) != get_session(conn, :customer_token)
+      assert get_flash(new_password_conn, :info) =~ "Password updated successfully"
+      assert Accounts.get_customer_by_email_and_password(customer.email, "new valid password")
+    end
+
+    test "does not update password on invalid data", %{conn: conn} do
+      old_password_conn =
+        put(conn, Routes.customer_settings_path(conn, :update), %{
+          "action" => "update_password",
+          "current_password" => "invalid",
+          "customer" => %{
+            "password" => "too short",
+            "password_confirmation" => "does not match"
+          }
+        })
+
+      response = html_response(old_password_conn, 200)
+      assert response =~ "<h1>Settings</h1>"
+      assert response =~ "should be at least 12 character(s)"
+      assert response =~ "does not match password"
+      assert response =~ "is not valid"
+
+      assert get_session(old_password_conn, :customer_token) == get_session(conn, :customer_token)
+    end
+  end
+
+  describe "PUT /customers/settings (change email form)" do
+    @tag :capture_log
+    test "updates the customer email", %{conn: conn, customer: customer} do
+      conn =
+        put(conn, Routes.customer_settings_path(conn, :update), %{
+          "action" => "update_email",
+          "current_password" => valid_customer_password(),
+          "customer" => %{"email" => unique_customer_email()}
+        })
+
+      assert redirected_to(conn) == Routes.customer_settings_path(conn, :edit)
+      assert get_flash(conn, :info) =~ "A link to confirm your email"
+      assert Accounts.get_customer_by_email(customer.email)
+    end
+
+    test "does not update email on invalid data", %{conn: conn} do
+      conn =
+        put(conn, Routes.customer_settings_path(conn, :update), %{
+          "action" => "update_email",
+          "current_password" => "invalid",
+          "customer" => %{"email" => "with spaces"}
+        })
+
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Settings</h1>"
+      assert response =~ "must have the @ sign and no spaces"
+      assert response =~ "is not valid"
+    end
+  end
+
+  describe "GET /customers/settings/confirm_email/:token" do
+    setup %{customer: customer} do
+      email = unique_customer_email()
+
+      token =
+        extract_customer_token(fn url ->
+          Accounts.deliver_customer_update_email_instructions(%{customer | email: email}, customer.email, url)
+        end)
+
+      %{token: token, email: email}
+    end
+
+    test "updates the customer email once", %{conn: conn, customer: customer, token: token, email: email} do
+      conn = get(conn, Routes.customer_settings_path(conn, :confirm_email, token))
+      assert redirected_to(conn) == Routes.customer_settings_path(conn, :edit)
+      assert get_flash(conn, :info) =~ "Email changed successfully"
+      refute Accounts.get_customer_by_email(customer.email)
+      assert Accounts.get_customer_by_email(email)
+
+      conn = get(conn, Routes.customer_settings_path(conn, :confirm_email, token))
+      assert redirected_to(conn) == Routes.customer_settings_path(conn, :edit)
+      assert get_flash(conn, :error) =~ "Email change link is invalid or it has expired"
+    end
+
+    test "does not update email with invalid token", %{conn: conn, customer: customer} do
+      conn = get(conn, Routes.customer_settings_path(conn, :confirm_email, "oops"))
+      assert redirected_to(conn) == Routes.customer_settings_path(conn, :edit)
+      assert get_flash(conn, :error) =~ "Email change link is invalid or it has expired"
+      assert Accounts.get_customer_by_email(customer.email)
+    end
+
+    test "redirects if customer is not logged in", %{token: token} do
+      conn = build_conn()
+      conn = get(conn, Routes.customer_settings_path(conn, :confirm_email, token))
+      assert redirected_to(conn) == Routes.customer_session_path(conn, :new)
+    end
+  end
+end
diff --git a/test/volt_web/controllers/restaurant_auth_test.exs b/test/volt_web/controllers/restaurant_auth_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..7ddd3470caf2d370b4c42e2ac9108b04ed52f8dc
--- /dev/null
+++ b/test/volt_web/controllers/restaurant_auth_test.exs
@@ -0,0 +1,170 @@
+defmodule VoltWeb.RestaurantAuthTest do
+  use VoltWeb.ConnCase, async: true
+
+  alias Volt.Accounts
+  alias VoltWeb.RestaurantAuth
+  import Volt.AccountsFixtures
+
+  @remember_me_cookie "_volt_web_restaurant_remember_me"
+
+  setup %{conn: conn} do
+    conn =
+      conn
+      |> Map.replace!(:secret_key_base, VoltWeb.Endpoint.config(:secret_key_base))
+      |> init_test_session(%{})
+
+    %{restaurant: restaurant_fixture(), conn: conn}
+  end
+
+  describe "log_in_restaurant/3" do
+    test "stores the restaurant token in the session", %{conn: conn, restaurant: restaurant} do
+      conn = RestaurantAuth.log_in_restaurant(conn, restaurant)
+      assert token = get_session(conn, :restaurant_token)
+      assert get_session(conn, :live_socket_id) == "restaurants_sessions:#{Base.url_encode64(token)}"
+      assert redirected_to(conn) == "/restaurants/dashboard"
+      assert Accounts.get_restaurant_by_session_token(token)
+    end
+
+    test "clears everything previously stored in the session", %{conn: conn, restaurant: restaurant} do
+      conn = conn |> put_session(:to_be_removed, "value") |> RestaurantAuth.log_in_restaurant(restaurant)
+      refute get_session(conn, :to_be_removed)
+    end
+
+    test "redirects to the configured path", %{conn: conn, restaurant: restaurant} do
+      conn = conn |> put_session(:restaurant_return_to, "/hello") |> RestaurantAuth.log_in_restaurant(restaurant)
+      assert redirected_to(conn) == "/hello"
+    end
+
+    test "writes a cookie if remember_me is configured", %{conn: conn, restaurant: restaurant} do
+      conn = conn |> fetch_cookies() |> RestaurantAuth.log_in_restaurant(restaurant, %{"remember_me" => "true"})
+      assert get_session(conn, :restaurant_token) == conn.cookies[@remember_me_cookie]
+
+      assert %{value: signed_token, max_age: max_age} = conn.resp_cookies[@remember_me_cookie]
+      assert signed_token != get_session(conn, :restaurant_token)
+      assert max_age == 5_184_000
+    end
+  end
+
+  describe "logout_restaurant/1" do
+    test "erases session and cookies", %{conn: conn, restaurant: restaurant} do
+      restaurant_token = Accounts.generate_restaurant_session_token(restaurant)
+
+      conn =
+        conn
+        |> put_session(:restaurant_token, restaurant_token)
+        |> put_req_cookie(@remember_me_cookie, restaurant_token)
+        |> fetch_cookies()
+        |> RestaurantAuth.log_out_restaurant()
+
+      refute get_session(conn, :restaurant_token)
+      refute conn.cookies[@remember_me_cookie]
+      assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie]
+      assert redirected_to(conn) == "/"
+      refute Accounts.get_restaurant_by_session_token(restaurant_token)
+    end
+
+    test "broadcasts to the given live_socket_id", %{conn: conn} do
+      live_socket_id = "restaurants_sessions:abcdef-token"
+      VoltWeb.Endpoint.subscribe(live_socket_id)
+
+      conn
+      |> put_session(:live_socket_id, live_socket_id)
+      |> RestaurantAuth.log_out_restaurant()
+
+      assert_receive %Phoenix.Socket.Broadcast{event: "disconnect", topic: ^live_socket_id}
+    end
+
+    test "works even if restaurant is already logged out", %{conn: conn} do
+      conn = conn |> fetch_cookies() |> RestaurantAuth.log_out_restaurant()
+      refute get_session(conn, :restaurant_token)
+      assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie]
+      assert redirected_to(conn) == "/"
+    end
+  end
+
+  describe "fetch_current_restaurant/2" do
+    test "authenticates restaurant from session", %{conn: conn, restaurant: restaurant} do
+      restaurant_token = Accounts.generate_restaurant_session_token(restaurant)
+      conn = conn |> put_session(:restaurant_token, restaurant_token) |> RestaurantAuth.fetch_current_restaurant([])
+      assert conn.assigns.current_restaurant.id == restaurant.id
+    end
+
+    test "authenticates restaurant from cookies", %{conn: conn, restaurant: restaurant} do
+      logged_in_conn =
+        conn |> fetch_cookies() |> RestaurantAuth.log_in_restaurant(restaurant, %{"remember_me" => "true"})
+
+      restaurant_token = logged_in_conn.cookies[@remember_me_cookie]
+      %{value: signed_token} = logged_in_conn.resp_cookies[@remember_me_cookie]
+
+      conn =
+        conn
+        |> put_req_cookie(@remember_me_cookie, signed_token)
+        |> RestaurantAuth.fetch_current_restaurant([])
+
+      assert get_session(conn, :restaurant_token) == restaurant_token
+      assert conn.assigns.current_restaurant.id == restaurant.id
+    end
+
+    test "does not authenticate if data is missing", %{conn: conn, restaurant: restaurant} do
+      _ = Accounts.generate_restaurant_session_token(restaurant)
+      conn = RestaurantAuth.fetch_current_restaurant(conn, [])
+      refute get_session(conn, :restaurant_token)
+      refute conn.assigns.current_restaurant
+    end
+  end
+
+  describe "redirect_if_restaurant_is_authenticated/2" do
+    test "redirects if restaurant is authenticated", %{conn: conn, restaurant: restaurant} do
+      conn = conn |> assign(:current_restaurant, restaurant) |> RestaurantAuth.redirect_if_restaurant_is_authenticated([])
+      assert conn.halted
+      assert redirected_to(conn) == "/restaurants/dashboard"
+    end
+
+    test "does not redirect if restaurant is not authenticated", %{conn: conn} do
+      conn = RestaurantAuth.redirect_if_restaurant_is_authenticated(conn, [])
+      refute conn.halted
+      refute conn.status
+    end
+  end
+
+  describe "require_authenticated_restaurant/2" do
+    test "redirects if restaurant is not authenticated", %{conn: conn} do
+      conn = conn |> fetch_flash() |> RestaurantAuth.require_authenticated_restaurant([])
+      assert conn.halted
+      assert redirected_to(conn) == Routes.restaurant_session_path(conn, :new)
+      assert get_flash(conn, :error) == "You must log in to access this page."
+    end
+
+    test "stores the path to redirect to on GET", %{conn: conn} do
+      halted_conn =
+        %{conn | path_info: ["foo"], query_string: ""}
+        |> fetch_flash()
+        |> RestaurantAuth.require_authenticated_restaurant([])
+
+      assert halted_conn.halted
+      assert get_session(halted_conn, :restaurant_return_to) == "/foo"
+
+      halted_conn =
+        %{conn | path_info: ["foo"], query_string: "bar=baz"}
+        |> fetch_flash()
+        |> RestaurantAuth.require_authenticated_restaurant([])
+
+      assert halted_conn.halted
+      assert get_session(halted_conn, :restaurant_return_to) == "/foo?bar=baz"
+
+      halted_conn =
+        %{conn | path_info: ["foo"], query_string: "bar", method: "POST"}
+        |> fetch_flash()
+        |> RestaurantAuth.require_authenticated_restaurant([])
+
+      assert halted_conn.halted
+      refute get_session(halted_conn, :restaurant_return_to)
+    end
+
+    test "does not redirect if restaurant is authenticated", %{conn: conn, restaurant: restaurant} do
+      conn = conn |> assign(:current_restaurant, restaurant) |> RestaurantAuth.require_authenticated_restaurant([])
+      refute conn.halted
+      refute conn.status
+    end
+  end
+end
diff --git a/test/volt_web/controllers/restaurant_confirmation_controller_test.exs b/test/volt_web/controllers/restaurant_confirmation_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..9c22eda3053e695e48e2f7414dd07e0da3a35d72
--- /dev/null
+++ b/test/volt_web/controllers/restaurant_confirmation_controller_test.exs
@@ -0,0 +1,105 @@
+defmodule VoltWeb.RestaurantConfirmationControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  alias Volt.Accounts
+  alias Volt.Repo
+  import Volt.AccountsFixtures
+
+  setup do
+    %{restaurant: restaurant_fixture()}
+  end
+
+  describe "GET /restaurants/confirm" do
+    test "renders the resend confirmation page", %{conn: conn} do
+      conn = get(conn, Routes.restaurant_confirmation_path(conn, :new))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Resend confirmation instructions</h1>"
+    end
+  end
+
+  describe "POST /restaurants/confirm" do
+    @tag :capture_log
+    test "sends a new confirmation token", %{conn: conn, restaurant: restaurant} do
+      conn =
+        post(conn, Routes.restaurant_confirmation_path(conn, :create), %{
+          "restaurant" => %{"email" => restaurant.email}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      assert Repo.get_by!(Accounts.RestaurantToken, restaurant_id: restaurant.id).context == "confirm"
+    end
+
+    test "does not send confirmation token if Restaurant is confirmed", %{conn: conn, restaurant: restaurant} do
+      Repo.update!(Accounts.Restaurant.confirm_changeset(restaurant))
+
+      conn =
+        post(conn, Routes.restaurant_confirmation_path(conn, :create), %{
+          "restaurant" => %{"email" => restaurant.email}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      refute Repo.get_by(Accounts.RestaurantToken, restaurant_id: restaurant.id)
+    end
+
+    test "does not send confirmation token if email is invalid", %{conn: conn} do
+      conn =
+        post(conn, Routes.restaurant_confirmation_path(conn, :create), %{
+          "restaurant" => %{"email" => "unknown@example.com"}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      assert Repo.all(Accounts.RestaurantToken) == []
+    end
+  end
+
+  describe "GET /restaurants/confirm/:token" do
+    test "renders the confirmation page", %{conn: conn} do
+      conn = get(conn, Routes.restaurant_confirmation_path(conn, :edit, "some-token"))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Confirm account</h1>"
+
+      form_action = Routes.restaurant_confirmation_path(conn, :update, "some-token")
+      assert response =~ "action=\"#{form_action}\""
+    end
+  end
+
+  describe "POST /restaurants/confirm/:token" do
+    test "confirms the given token once", %{conn: conn, restaurant: restaurant} do
+      token =
+        extract_restaurant_token(fn url ->
+          Accounts.deliver_restaurant_confirmation_instructions(restaurant, url)
+        end)
+
+      conn = post(conn, Routes.restaurant_confirmation_path(conn, :update, token))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "Restaurant confirmed successfully"
+      assert Accounts.get_restaurant!(restaurant.id).confirmed_at
+      refute get_session(conn, :restaurant_token)
+      assert Repo.all(Accounts.RestaurantToken) == []
+
+      # When not logged in
+      conn = post(conn, Routes.restaurant_confirmation_path(conn, :update, token))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :error) =~ "Restaurant confirmation link is invalid or it has expired"
+
+      # When logged in
+      conn =
+        build_conn()
+        |> log_in_restaurant(restaurant)
+        |> post(Routes.restaurant_confirmation_path(conn, :update, token))
+
+      assert redirected_to(conn) == "/"
+      refute get_flash(conn, :error)
+    end
+
+    test "does not confirm email with invalid token", %{conn: conn, restaurant: restaurant} do
+      conn = post(conn, Routes.restaurant_confirmation_path(conn, :update, "oops"))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :error) =~ "Restaurant confirmation link is invalid or it has expired"
+      refute Accounts.get_restaurant!(restaurant.id).confirmed_at
+    end
+  end
+end
diff --git a/test/volt_web/controllers/restaurant_controller_test.exs b/test/volt_web/controllers/restaurant_controller_test.exs
deleted file mode 100644
index f14eec8469ae4e93c536d66d974e76c67fd4c2e1..0000000000000000000000000000000000000000
--- a/test/volt_web/controllers/restaurant_controller_test.exs
+++ /dev/null
@@ -1,91 +0,0 @@
-defmodule VoltWeb.RestaurantControllerTest do
-  use VoltWeb.ConnCase
-
-  import Volt.AccountsFixtures
-
-  @create_attrs %{address: "some address", city: "Tartu", zip_code: "51004", closing_time: ~T[14:00:00], email: "some email", first_name: "some first_name", last_name: "some last_name", name: "some name", opening_time: ~T[14:00:00], password: "some password", phone_number: "some phone_number", price_level: 42}
-  @update_attrs %{address: "some updated address",  city: "Tartu", zip_code: "51004", closing_time: ~T[15:01:01], email: "some updated email", first_name: "some updated first_name", last_name: "some updated last_name", name: "some updated name", opening_time: ~T[15:01:01], password: "some updated password", phone_number: "some updated phone_number", price_level: 43}
-  @invalid_attrs %{address: nil, closing_time: nil, city: "Tartu", zip_code: "51004", email: nil, first_name: nil, last_name: nil, name: nil, opening_time: nil, password: nil, phone_number: nil, price_level: nil}
-
-  describe "restaurant dashboard" do
-    setup [:create_restaurant]
-    test "renders dashboard", %{conn: conn, restaurant: restaurant} do
-      id = restaurant.id
-      conn = get(conn, Routes.restaurant_path(conn, :show, id))
-      assert html_response(conn, 200) =~ "Restaurant Dashboard"
-      assert html_response(conn, 200) =~ "Edit"
-    end
-  end
-
-  describe "new restaurant" do
-    test "renders form", %{conn: conn} do
-      conn = get(conn, Routes.restaurant_path(conn, :new))
-      assert html_response(conn, 200) =~ "New Restaurant"
-    end
-  end
-
-  describe "create restaurant" do
-    test "redirects to show when data is valid", %{conn: conn} do
-      conn = post(conn, Routes.restaurant_path(conn, :create), restaurant: @create_attrs)
-
-      assert %{id: id} = redirected_params(conn)
-      assert redirected_to(conn) == Routes.restaurant_path(conn, :show, id)
-
-      conn = get(conn, Routes.restaurant_path(conn, :show, id))
-      assert html_response(conn, 200) =~ "Restaurant Dashboard"
-    end
-
-    test "renders errors when data is invalid", %{conn: conn} do
-      conn = post(conn, Routes.restaurant_path(conn, :create), restaurant: @invalid_attrs)
-      assert html_response(conn, 200) =~ "New Restaurant"
-    end
-  end
-
-  describe "edit restaurant" do
-    setup [:create_restaurant]
-
-    test "renders form for editing chosen restaurant", %{conn: conn, restaurant: restaurant} do
-      conn = get(conn, Routes.restaurant_path(conn, :edit, restaurant))
-      assert html_response(conn, 200) =~ "Edit Restaurant"
-    end
-  end
-
-  describe "update restaurant" do
-    setup [:create_restaurant]
-
-    test "redirects when data is valid", %{conn: conn, restaurant: restaurant} do
-      conn = put(conn, Routes.restaurant_path(conn, :update, restaurant), restaurant: @update_attrs)
-      id = restaurant.id
-      assert redirected_to(conn) == Routes.restaurant_path(conn, :show, id)
-
-      conn = get(conn, Routes.restaurant_path(conn, :show, id))
-      assert html_response(conn, 200) =~ "some updated address"
-    end
-
-    test "renders errors when data is invalid", %{conn: conn, restaurant: restaurant} do
-      conn = put(conn, Routes.restaurant_path(conn, :update, restaurant), restaurant: @invalid_attrs)
-      assert html_response(conn, 200) =~ "Edit Restaurant"
-    end
-  end
-
-  # Deleting a restaurant is not specified in functional documentation.
-  # Leaving it as a reference for further tests where deletion may be required.
-  #
-  # describe "delete restaurant" do
-  #   setup [:create_restaurant]
-
-  #   test "deletes chosen restaurant", %{conn: conn, restaurant: restaurant} do
-  #     conn = delete(conn, Routes.restaurant_path(conn, :delete, restaurant))
-  #     assert redirected_to(conn) == Routes.restaurant_path(conn, :show, restaurant.id)
-
-  #     assert_error_sent 404, fn ->
-  #       get(conn, Routes.restaurant_path(conn, :show, restaurant.id))
-  #     end
-  #   end
-  # end
-
-  defp create_restaurant(_) do
-    restaurant = restaurant_fixture()
-    %{restaurant: restaurant}
-  end
-end
diff --git a/test/volt_web/controllers/restaurant_item_controller_test.exs b/test/volt_web/controllers/restaurant_item_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..73779617da0de0fec591be24c2a26436d6728c33
--- /dev/null
+++ b/test/volt_web/controllers/restaurant_item_controller_test.exs
@@ -0,0 +1,94 @@
+defmodule VoltWeb.ItemControllerTest do
+  use VoltWeb.ConnCase
+
+  import Volt.SalesFixtures
+  import Volt.AccountsFixtures
+  alias VoltWeb.RestaurantAuth
+
+  @create_attrs %{description: "some description", name: "some name", unit_price: "12.99"}
+  @update_attrs %{description: "some updated description", name: "some updated name", unit_price: "13"}
+  @invalid_attrs %{description: nil, name: nil, unit_price: nil}
+
+  describe "index" do
+    test "lists all items", %{conn: conn} do
+      conn = conn |> log_in_restaurant(restaurant_fixture()) |> get(Routes.restaurant_registration_path(conn, :new))
+      conn = get(conn, Routes.restaurant_item_path(conn, :index))
+      assert html_response(conn, 200) =~ "Menu"
+    end
+  end
+
+  describe "new item" do
+    test "renders form", %{conn: conn} do
+      conn = conn |> log_in_restaurant(restaurant_fixture()) |> get(Routes.restaurant_registration_path(conn, :new))
+      conn = get(conn, Routes.restaurant_item_path(conn, :new))
+      assert html_response(conn, 200) =~ "New Item"
+    end
+  end
+
+  describe "create item" do
+    test "redirects to show when data is valid", %{conn: conn} do
+      conn = conn |> log_in_restaurant(restaurant_fixture()) |> get(Routes.restaurant_registration_path(conn, :new))
+      conn = post(conn, Routes.restaurant_item_path(conn, :create), item: @create_attrs)
+
+      assert %{id: id} = redirected_params(conn)
+      assert redirected_to(conn) == Routes.restaurant_item_path(conn, :show, id)
+
+      conn = get(conn, Routes.restaurant_item_path(conn, :show, id))
+      assert html_response(conn, 200) =~ "Show Item"
+    end
+
+    test "renders errors when data is invalid", %{conn: conn} do
+      conn = conn |> log_in_restaurant(restaurant_fixture()) |> get(Routes.restaurant_registration_path(conn, :new))
+      conn = post(conn, Routes.restaurant_item_path(conn, :create), item: @invalid_attrs)
+      assert html_response(conn, 200) =~ "New Item"
+    end
+  end
+
+  describe "edit item" do
+    setup [:create_item]
+
+    test "renders form for editing chosen item", %{conn: conn, item: item} do
+      conn = conn |> log_in_restaurant(restaurant_fixture()) |> get(Routes.restaurant_registration_path(conn, :new))
+      conn = get(conn, Routes.restaurant_item_path(conn, :edit, item))
+      assert html_response(conn, 200) =~ "Edit Item"
+    end
+  end
+
+  describe "update item" do
+    setup [:create_item]
+
+    test "redirects when data is valid", %{conn: conn, item: item} do
+      conn = conn |> log_in_restaurant(restaurant_fixture()) |> get(Routes.restaurant_registration_path(conn, :new))
+      conn = put(conn, Routes.restaurant_item_path(conn, :update, item), item: @update_attrs)
+      assert redirected_to(conn) == Routes.restaurant_item_path(conn, :show, item)
+
+      conn = get(conn, Routes.restaurant_item_path(conn, :show, item))
+      assert html_response(conn, 200) =~ "some updated description"
+    end
+
+    test "renders errors when data is invalid", %{conn: conn, item: item} do
+      conn = conn |> log_in_restaurant(restaurant_fixture()) |> get(Routes.restaurant_registration_path(conn, :new))
+      conn = put(conn, Routes.restaurant_item_path(conn, :update, item), item: @invalid_attrs)
+      assert html_response(conn, 200) =~ "Edit Item"
+    end
+  end
+
+  describe "delete item" do
+    setup [:create_item]
+
+    test "deletes chosen item", %{conn: conn, item: item} do
+      conn = conn |> log_in_restaurant(restaurant_fixture()) |> get(Routes.restaurant_registration_path(conn, :new))
+      conn = delete(conn, Routes.restaurant_item_path(conn, :delete, item))
+      assert redirected_to(conn) == Routes.restaurant_item_path(conn, :index)
+
+      assert_error_sent 500, fn ->
+        get(conn, Routes.restaurant_item_path(conn, :show, item))
+      end
+    end
+  end
+
+  defp create_item(_) do
+    item = item_fixture()
+    %{item: item}
+  end
+end
diff --git a/test/volt_web/controllers/restaurant_registration_controller_test.exs b/test/volt_web/controllers/restaurant_registration_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..cab3a3e988c740087cac6f61622770714b6a6924
--- /dev/null
+++ b/test/volt_web/controllers/restaurant_registration_controller_test.exs
@@ -0,0 +1,54 @@
+defmodule VoltWeb.RestaurantRegistrationControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  import Volt.AccountsFixtures
+
+  describe "GET /restaurants/register" do
+    test "renders registration page", %{conn: conn} do
+      conn = get(conn, Routes.restaurant_registration_path(conn, :new))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Register</h1>"
+      assert response =~ "Log in</a>"
+      assert response =~ "<div>\n<button id=\"submit\" type=\"submit\">Register</button>"
+    end
+
+    test "redirects if already logged in", %{conn: conn} do
+      conn = conn |> log_in_restaurant(restaurant_fixture()) |> get(Routes.restaurant_registration_path(conn, :new))
+      assert redirected_to(conn) == "/restaurants/dashboard"
+    end
+  end
+
+  describe "POST /restaurants/register" do
+    @tag :capture_log
+    test "creates account and logs the restaurant in", %{conn: conn} do
+      email = unique_restaurant_email()
+
+      conn =
+        post(conn, Routes.restaurant_registration_path(conn, :create), %{
+          "restaurant" => valid_restaurant_attributes(email: email)
+        })
+
+      assert get_session(conn, :restaurant_token)
+      assert redirected_to(conn) == "/restaurants/dashboard"
+
+      # Now do a logged in request and assert on the menu
+      conn = get(conn, "/")
+      response = html_response(conn, 200)
+      assert response =~ email
+      assert response =~ "Settings</a>"
+      assert response =~ "Log out</a>"
+    end
+
+    test "render errors for invalid data", %{conn: conn} do
+      conn =
+        post(conn, Routes.restaurant_registration_path(conn, :create), %{
+          "restaurant" => %{"email" => "with spaces", "password" => "too short"}
+        })
+
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Register</h1>"
+      assert response =~ "must have the @ sign and no spaces"
+      assert response =~ "should be at least 12 character"
+    end
+  end
+end
diff --git a/test/volt_web/controllers/restaurant_reset_password_controller_test.exs b/test/volt_web/controllers/restaurant_reset_password_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..1e1b99b499c4a97a2fdfc6517013cdef5613bfc6
--- /dev/null
+++ b/test/volt_web/controllers/restaurant_reset_password_controller_test.exs
@@ -0,0 +1,113 @@
+defmodule VoltWeb.RestaurantResetPasswordControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  alias Volt.Accounts
+  alias Volt.Repo
+  import Volt.AccountsFixtures
+
+  setup do
+    %{restaurant: restaurant_fixture()}
+  end
+
+  describe "GET /restaurants/reset_password" do
+    test "renders the reset password page", %{conn: conn} do
+      conn = get(conn, Routes.restaurant_reset_password_path(conn, :new))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Forgot your password?</h1>"
+    end
+  end
+
+  describe "POST /restaurants/reset_password" do
+    @tag :capture_log
+    test "sends a new reset password token", %{conn: conn, restaurant: restaurant} do
+      conn =
+        post(conn, Routes.restaurant_reset_password_path(conn, :create), %{
+          "restaurant" => %{"email" => restaurant.email}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      assert Repo.get_by!(Accounts.RestaurantToken, restaurant_id: restaurant.id).context == "reset_password"
+    end
+
+    test "does not send reset password token if email is invalid", %{conn: conn} do
+      conn =
+        post(conn, Routes.restaurant_reset_password_path(conn, :create), %{
+          "restaurant" => %{"email" => "unknown@example.com"}
+        })
+
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :info) =~ "If your email is in our system"
+      assert Repo.all(Accounts.RestaurantToken) == []
+    end
+  end
+
+  describe "GET /restaurants/reset_password/:token" do
+    setup %{restaurant: restaurant} do
+      token =
+        extract_restaurant_token(fn url ->
+          Accounts.deliver_restaurant_reset_password_instructions(restaurant, url)
+        end)
+
+      %{token: token}
+    end
+
+    test "renders reset password", %{conn: conn, token: token} do
+      conn = get(conn, Routes.restaurant_reset_password_path(conn, :edit, token))
+      assert html_response(conn, 200) =~ "<h1>Reset password</h1>"
+    end
+
+    test "does not render reset password with invalid token", %{conn: conn} do
+      conn = get(conn, Routes.restaurant_reset_password_path(conn, :edit, "oops"))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :error) =~ "Reset password link is invalid or it has expired"
+    end
+  end
+
+  describe "PUT /restaurants/reset_password/:token" do
+    setup %{restaurant: restaurant} do
+      token =
+        extract_restaurant_token(fn url ->
+          Accounts.deliver_restaurant_reset_password_instructions(restaurant, url)
+        end)
+
+      %{token: token}
+    end
+
+    test "resets password once", %{conn: conn, restaurant: restaurant, token: token} do
+      conn =
+        put(conn, Routes.restaurant_reset_password_path(conn, :update, token), %{
+          "restaurant" => %{
+            "password" => "new valid password",
+            "password_confirmation" => "new valid password"
+          }
+        })
+
+      assert redirected_to(conn) == Routes.restaurant_session_path(conn, :new)
+      refute get_session(conn, :restaurant_token)
+      assert get_flash(conn, :info) =~ "Password reset successfully"
+      assert Accounts.get_restaurant_by_email_and_password(restaurant.email, "new valid password")
+    end
+
+    test "does not reset password on invalid data", %{conn: conn, token: token} do
+      conn =
+        put(conn, Routes.restaurant_reset_password_path(conn, :update, token), %{
+          "restaurant" => %{
+            "password" => "too short",
+            "password_confirmation" => "does not match"
+          }
+        })
+
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Reset password</h1>"
+      assert response =~ "should be at least 12 character(s)"
+      assert response =~ "does not match password"
+    end
+
+    test "does not reset password with invalid token", %{conn: conn} do
+      conn = put(conn, Routes.restaurant_reset_password_path(conn, :update, "oops"))
+      assert redirected_to(conn) == "/"
+      assert get_flash(conn, :error) =~ "Reset password link is invalid or it has expired"
+    end
+  end
+end
diff --git a/test/volt_web/controllers/restaurant_session_controller_test.exs b/test/volt_web/controllers/restaurant_session_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..a514af4ef9fbda1df241194ccc8b4a01a42e6498
--- /dev/null
+++ b/test/volt_web/controllers/restaurant_session_controller_test.exs
@@ -0,0 +1,98 @@
+defmodule VoltWeb.RestaurantSessionControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  import Volt.AccountsFixtures
+
+  setup do
+    %{restaurant: restaurant_fixture()}
+  end
+
+  describe "GET /restaurants/log_in" do
+    test "renders log in page", %{conn: conn} do
+      conn = get(conn, Routes.restaurant_session_path(conn, :new))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Log in</h1>"
+      assert response =~ "Register</a>"
+      assert response =~ "Forgot your password?</a>"
+    end
+
+    test "redirects if already logged in", %{conn: conn, restaurant: restaurant} do
+      conn = conn |> log_in_restaurant(restaurant) |> get(Routes.restaurant_session_path(conn, :new))
+      assert redirected_to(conn) == "/restaurants/dashboard"
+    end
+  end
+
+  describe "POST /restaurants/log_in" do
+    test "logs the restaurant in", %{conn: conn, restaurant: restaurant} do
+      conn =
+        post(conn, Routes.restaurant_session_path(conn, :create), %{
+          "restaurant" => %{"email" => restaurant.email, "password" => valid_restaurant_password()}
+        })
+
+      assert get_session(conn, :restaurant_token)
+      assert redirected_to(conn) == "/restaurants/dashboard"
+
+      # Now do a logged in request and assert on the menu
+      conn = get(conn, "/")
+      response = html_response(conn, 200)
+      assert response =~ restaurant.email
+      assert response =~ "Settings</a>"
+      assert response =~ "Log out</a>"
+    end
+
+    test "logs the restaurant in with remember me", %{conn: conn, restaurant: restaurant} do
+      conn =
+        post(conn, Routes.restaurant_session_path(conn, :create), %{
+          "restaurant" => %{
+            "email" => restaurant.email,
+            "password" => valid_restaurant_password(),
+            "remember_me" => "true"
+          }
+        })
+
+      assert conn.resp_cookies["_volt_web_restaurant_remember_me"]
+      assert redirected_to(conn) == "/restaurants/dashboard"
+    end
+
+    test "logs the restaurant in with return to", %{conn: conn, restaurant: restaurant} do
+      conn =
+        conn
+        |> init_test_session(restaurant_return_to: "/foo/bar")
+        |> post(Routes.restaurant_session_path(conn, :create), %{
+          "restaurant" => %{
+            "email" => restaurant.email,
+            "password" => valid_restaurant_password()
+          }
+        })
+
+      assert redirected_to(conn) == "/foo/bar"
+    end
+
+    test "emits error message with invalid credentials", %{conn: conn, restaurant: restaurant} do
+      conn =
+        post(conn, Routes.restaurant_session_path(conn, :create), %{
+          "restaurant" => %{"email" => restaurant.email, "password" => "invalid_password"}
+        })
+
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Log in</h1>"
+      assert response =~ "Invalid email or password"
+    end
+  end
+
+  describe "DELETE /restaurants/log_out" do
+    test "logs the restaurant out", %{conn: conn, restaurant: restaurant} do
+      conn = conn |> log_in_restaurant(restaurant) |> delete(Routes.restaurant_session_path(conn, :delete))
+      assert redirected_to(conn) == "/"
+      refute get_session(conn, :restaurant_token)
+      assert get_flash(conn, :info) =~ "Logged out successfully"
+    end
+
+    test "succeeds even if the restaurant is not logged in", %{conn: conn} do
+      conn = delete(conn, Routes.restaurant_session_path(conn, :delete))
+      assert redirected_to(conn) == "/"
+      refute get_session(conn, :restaurant_token)
+      assert get_flash(conn, :info) =~ "Logged out successfully"
+    end
+  end
+end
diff --git a/test/volt_web/controllers/restaurant_settings_controller_test.exs b/test/volt_web/controllers/restaurant_settings_controller_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..1199d18812f2156b2c22664436fb7614e1922e15
--- /dev/null
+++ b/test/volt_web/controllers/restaurant_settings_controller_test.exs
@@ -0,0 +1,129 @@
+defmodule VoltWeb.RestaurantSettingsControllerTest do
+  use VoltWeb.ConnCase, async: true
+
+  alias Volt.Accounts
+  import Volt.AccountsFixtures
+
+  setup :register_and_log_in_restaurant
+
+  describe "GET /restaurants/settings" do
+    test "renders settings page", %{conn: conn} do
+      conn = get(conn, Routes.restaurant_settings_path(conn, :edit))
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Settings</h1>"
+    end
+
+    test "redirects if restaurant is not logged in" do
+      conn = build_conn()
+      conn = get(conn, Routes.restaurant_settings_path(conn, :edit))
+      assert redirected_to(conn) == Routes.restaurant_session_path(conn, :new)
+    end
+  end
+
+  describe "PUT /restaurants/settings (change password form)" do
+    test "updates the restaurant password and resets tokens", %{conn: conn, restaurant: restaurant} do
+      new_password_conn =
+        put(conn, Routes.restaurant_settings_path(conn, :update), %{
+          "action" => "update_password",
+          "current_password" => valid_restaurant_password(),
+          "restaurant" => %{
+            "password" => "new valid password",
+            "password_confirmation" => "new valid password"
+          }
+        })
+
+      assert redirected_to(new_password_conn) == Routes.restaurant_settings_path(conn, :edit)
+      assert get_session(new_password_conn, :restaurant_token) != get_session(conn, :restaurant_token)
+      assert get_flash(new_password_conn, :info) =~ "Password updated successfully"
+      assert Accounts.get_restaurant_by_email_and_password(restaurant.email, "new valid password")
+    end
+
+    test "does not update password on invalid data", %{conn: conn} do
+      old_password_conn =
+        put(conn, Routes.restaurant_settings_path(conn, :update), %{
+          "action" => "update_password",
+          "current_password" => "invalid",
+          "restaurant" => %{
+            "password" => "too short",
+            "password_confirmation" => "does not match"
+          }
+        })
+
+      response = html_response(old_password_conn, 200)
+      assert response =~ "<h1>Settings</h1>"
+      assert response =~ "should be at least 12 character(s)"
+      assert response =~ "does not match password"
+      assert response =~ "is not valid"
+
+      assert get_session(old_password_conn, :restaurant_token) == get_session(conn, :restaurant_token)
+    end
+  end
+
+  describe "PUT /restaurants/settings (change email form)" do
+    @tag :capture_log
+    test "updates the restaurant email", %{conn: conn, restaurant: restaurant} do
+      conn =
+        put(conn, Routes.restaurant_settings_path(conn, :update), %{
+          "action" => "update_email",
+          "current_password" => valid_restaurant_password(),
+          "restaurant" => %{"email" => unique_restaurant_email()}
+        })
+
+      assert redirected_to(conn) == Routes.restaurant_settings_path(conn, :edit)
+      assert get_flash(conn, :info) =~ "A link to confirm your email"
+      assert Accounts.get_restaurant_by_email(restaurant.email)
+    end
+
+    test "does not update email on invalid data", %{conn: conn} do
+      conn =
+        put(conn, Routes.restaurant_settings_path(conn, :update), %{
+          "action" => "update_email",
+          "current_password" => "invalid",
+          "restaurant" => %{"email" => "with spaces"}
+        })
+
+      response = html_response(conn, 200)
+      assert response =~ "<h1>Settings</h1>"
+      assert response =~ "must have the @ sign and no spaces"
+      assert response =~ "is not valid"
+    end
+  end
+
+  describe "GET /restaurants/settings/confirm_email/:token" do
+    setup %{restaurant: restaurant} do
+      email = unique_restaurant_email()
+
+      token =
+        extract_restaurant_token(fn url ->
+          Accounts.deliver_restaurant_update_email_instructions(%{restaurant | email: email}, restaurant.email, url)
+        end)
+
+      %{token: token, email: email}
+    end
+
+    test "updates the restaurant email once", %{conn: conn, restaurant: restaurant, token: token, email: email} do
+      conn = get(conn, Routes.restaurant_settings_path(conn, :confirm_email, token))
+      assert redirected_to(conn) == Routes.restaurant_settings_path(conn, :edit)
+      assert get_flash(conn, :info) =~ "Email changed successfully"
+      refute Accounts.get_restaurant_by_email(restaurant.email)
+      assert Accounts.get_restaurant_by_email(email)
+
+      conn = get(conn, Routes.restaurant_settings_path(conn, :confirm_email, token))
+      assert redirected_to(conn) == Routes.restaurant_settings_path(conn, :edit)
+      assert get_flash(conn, :error) =~ "Email change link is invalid or it has expired"
+    end
+
+    test "does not update email with invalid token", %{conn: conn, restaurant: restaurant} do
+      conn = get(conn, Routes.restaurant_settings_path(conn, :confirm_email, "oops"))
+      assert redirected_to(conn) == Routes.restaurant_settings_path(conn, :edit)
+      assert get_flash(conn, :error) =~ "Email change link is invalid or it has expired"
+      assert Accounts.get_restaurant_by_email(restaurant.email)
+    end
+
+    test "redirects if restaurant is not logged in", %{token: token} do
+      conn = build_conn()
+      conn = get(conn, Routes.restaurant_settings_path(conn, :confirm_email, token))
+      assert redirected_to(conn) == Routes.restaurant_session_path(conn, :new)
+    end
+  end
+end