4 - 建立使用者

新增完使用者但我們還不能登入,現在要做「登入」功能 我們用「Plug」做登入功能,我們會把它放在 routing flow 裡面,
可以把它想成一個閘門、接頭、電流開關

新增 auth.ex

#web/controllers/auth.ex
defmodule ShoppingSite.Auth do
  import Plug.Conn

  def init(opts) do
    Keyword.fetch!(opts, :repo)
  end

  def call(conn, repo) do
    user_id = get_session(conn, :user_id)
    user = user_id && repo.get(ShoppingSite.User, user_id)
    assign(conn, :current_user, user)
  end
end

加進 routing flow

#web/router.ex
  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
   +plug ShoppingSite.Auth, repo: ShoppingSite.Repo
  end

有了上面的 plug, 「電流」裡面有 :current_user 了
接下來的 「電流開關」都可以檢查是否有 :current_user
像是

:index, :show 要先登入



先寫個輔助 function

#web/controllers/user_controller.ex
  defp authenticate(conn) do
    if conn.assigns.current_user do
      conn
    else
      conn
      |> put_flash(:error, "You must log in to access that page.")
      |> redirect(to: page_path(conn, :index))
      |> halt
    end
  end

再把 authenticate 也弄成 「Plug」 這樣就可以到處 import直接使用 不過要符合 Plug的格式 我們把 authenticate 弄成 plug function (2 parammeters with first is conn)

defp authenticate(conn, _params)
 ...

之後就可以用

plug :authenticate when action in [:index, :show]

這樣的方法去檢查是否登入


再來做 login功能

#web/controllers/auth.ex
  def login(conn, user) do
    conn
    |> assign(:current_user, user)
    |> put_session(:user_id, user.id)
    |> configure_session(renew: true)
  end


還需要 session,讓網站有登入登出的介面
加入 route

#web/route.ex
  scope "/", ShoppingSite do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
    resources "/users", UserController, only: [:new, :create]
    resources "/sessions", SessionController, only: [:new, :create, :delete]
  end


加入 sessioncontroller

defmodule ShoppingSite.SessionController do
  use ShoppingSite.Web,  :controller

  def new(conn, _params) do
    render conn, "new.html"
  end

  def create(conn, %{"session" => %{"username" => user, "password" => pass}}) do
    case ShoppingSite.Auth.login_by_username_and_pass(conn, user, pass, repo: Repo) do
      {:ok, conn} ->
        conn
        |> put_flash(:info, "Welcone back!")
        |> redirect(to: page_path(conn, :index))
      {:error, _reason, conn} ->
        conn
        |> put_flash(:error, "Invalid username/password")
        |> render("new.html")
    end
  end
end
#web/controllers/auth.ex
  import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0]
  ...
  def login_by_username_and_pass(conn, username, given_pass, opts) do
    repo = Keyword.fetch!(opts, :repo)
    user = repo.get_by(ShoppingSite.User, username: username)

    cond do
      user && checkpw(given_pass, user.encrypted_password) ->
        {:ok, login(conn, user)}
      user ->
        {:error, :unauthorized, conn}
      true ->
        dummy_checkpw
        {:error, :not_found, conn}
    end
  end

建立 session_view.ex, templates

#web/views/session_view.ex
defmodule Rumbl.SessionView do
  use Rumbl.Web,  :view
end
#web/templates/session/new.html.eex
<h1>Login</h1>

<!-- fetch session from conn -->
<%= form_for @conn, session_path(@conn, :create), [as: :session] , fn f -> %>
  <div class="form-group">
    <%= text_input f, :username, placeholder: "Username", class: "form-control" %>
  </div>
  <div class="form-group">
    <%= password_input f, :password, placeholder: "Password", class: "form-control" %>
  </div>
  <%= submit "Log In", class: "btn btn-primary" %>
<% end %>

改一下網站 layout 顯示登入的使用者

#web/templates/layout/app.html.eex
 <div class="header">
      <ol class="breadcrumb text-right">
      <%= if @current_user do %>
          <li><%= @current_user.username %></li>
          <li><%= link "Log out", to: session_path(@conn, :delete, @current_user),
                      method: "delete" %></li>
      <% else %>
          <li><%= link "Register" , to: user_path(@conn, :new) %></li>
          <li><%= link "Log in" , to:  session_path(@conn, :new) %></li>
      <% end %>
  </ol>
  <span class="logo"></span>
</div>

畫面會像這樣

建立使用者後就登入

#web/controllers/user_controller.ex
def create(conn, %{"user" => user_params}) do
    changeset = User.registration_changeset(%User{}, user_params)

    case Repo.insert(changeset) do
      {:ok, user} ->
        conn
       +|> ShoppingSite.Auth.login(user) #param 1 is already conn
        |> put_flash(:info, "#{user.username} created")
        |> redirect(to: page_path(conn, :index))

      {:error, changeset} ->
        render conn, "new.html", changeset: changeset
    end
  end

輸入 localhost:4000/users/new 並新增 user 新增完會回到首頁並有訊息顯示使用者已經成功新增

加入 log out功能

#web/controllers/auth.ex
  def logout(conn) do
    configure_session(conn, drop: true)
  end

在 session controller加入 delete

#web/controllers/session_controller.ex
  def delete(conn, _params) do
    conn
    |> ShoppingSite.Auth.logout
    |> redirect(to: page_path(conn, :index))
  end

最後,產品的所有頁面一定要登入才能看到 為了要 import,要先將 authenticate 改為 def 定義

#web/controllers/admin/product_controller.ex
alias ShoppingSite.Product

import ShoppingSite.UserController, only: [authenticate: 2]
plug :authenticate
...

results matching ""

    No results matching ""