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
加入 session
controller
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
...