iT邦幫忙

4

[12 Project 學 Node.js] Project 3: User Login System

Description

  • 使用者驗證登入功能
  • 參考Passportjs網站,提供許多驗證方法說明
  • Register Page / Login Page

這章節新使用到的Module

  • mongodb: 提供 mongodb 連線接口的 module
  • mongoose: 目前最多人用的mongodb物件模組化工具之一,讓操作mongodb更方便
  • connect-flash: message的暫存器,暫存器裡的message使用一次後即被清空,適合用來作網站的提示資訊
  • express-messages: 協助connect-flash,提供flash通知的呈現
  • express-validator: express 表單驗證功能,可加入自定義的驗證
  • express-session: 用來設定 session,可用來實作 login/logout 功能  
  • passport: 身份認證,為每種認證提供一種策略,使用時只要載入需要的策略即可
  • passport-local: 用來處理本機模組的身份驗證策略
  • passport-http: 用來處理HTTP的身份驗證策略
  • multer: 用來upload image

Install MongoDB
接下來會使用 MongoDB 儲存 Register info.
註:
通常node.js會搭配MongoDB,因為MongoDB是使用javascript為腳本開發的,資料使用JSON格式儲存,這代表 MongoDB & Node.js 之間不需要額外的資料轉換就可以做資料交換。

下載MongoDB後安裝
MongoDB官網

安裝完畢後,到安裝路徑下準備mongodb資料和log儲存路徑
bin 資料夾內包含 Mongodb 的啟動相關程式
在bin同一層建立 data 資料夾、log資料夾
data內建立db資料夾
http://ithelp.ithome.com.tw/upload/images/20170223/20104222DX2WMRMAku.png

系統管理員權限打開cmd
輸入以下指令啟動MongoDB Service
(如果出現NET 2185存取被拒,表示沒有用系統管理員權限啟動服務)

# 設定MongoDB服務
mongod --directoryperdb --dbpath D:\MongoDB\Server\3.4\data\db --logpath D:\MongoDB\Server\3.4\log\mongodb.log --logappend --rest --install

# 啟動服務
net start MongoDB

Command Options:

  • directoryperdb: 為每個DB的資料儲存在分開的資料夾,資料夾路徑由dbpath指定
  • dbpath: db資料儲存位置
  • logpath: 將db log從console log移到特定檔案位置
  • logappend: mongoDB預設在寫新log時會移除掉舊log檔案,使用此option可用append的方式加上新log
  • rest: 啟動簡單REST API,即啟用HTTP介面
  • install: 將mongoDB安裝為Windows Service,安裝完關閉視窗

詳細說明參考官網文件

也可以把這串指令存成 bat 檔,以後啟動時只要執行這個bat就好了,bat如下:

@echo off
set work_dir=D:\Setups\MongoDB\Server\3.4\

d:
cd %work_dir%bin

rem mongod -dbpath "d:\Setups\Mongodata"
mongod --directoryperdb --dbpath %work_dir%data\db --logpath %work_dir%log\mongodb.log --logappend --rest --install

net start MongoDB

順利啟動的話,可以在工作管理員的服務tab看到mongodb service
http://ithelp.ithome.com.tw/upload/images/20170223/20104222ERnXgss01o.png

開啟cmd,切換到mongo的bin目錄下,輸入以下指令進入mongo shell
mongo

註:
建議把mongo路徑加到環境變數,以後可以直接在cmd下指令,不需要切換目錄,會方便很多

測試指令確認mongo db正常

# 查看目前使用的db name
db
# 使用/建立 customers db
use customers
# 查看DB list (這時看不到customers db,因為此db還沒有任何資料)
show db
# create customers collection in customers db
db.createCollection("customers");
# 查看DB list (此時customers db有customers collection,可以看到db了)
show dbs

建立資料庫,作為user register & login使用

# 使用/建立 nodeauth db
use nodeauth
# create users collection
db.createCollection("users");
# 檢查collection list,確認users collection存在
show collections

以下指令測試 insert/find/update/remove資料

# insert 資料到users collection
db.users.insert({name: "Grace Yu", email: "grace@example.com", username: "grace", password: "1234"});
db.users.insert({name: "Yuki", email: "yuki@example.com", username: "yuki", password: "1234"});

# find 所有資料 (query) from users collection
db.users.find()
# 加上pretty(),用整齊展開的JSON format呈現find結果
db.users.find().pretty();

# update 資料 by 特定條件
db.users.update({username:"yuki"},{$set:{email:"yuki@gmail.com}}};

# remove 資料 by 特定條件
db.users.remove({username:"grace"});

App & Middleware Setup

DB 設定好之後,開新project folder,開始撰寫 node.js 程式,首先要加入會用到的 middleware

首先在新project folder內使用npm安裝 express和 express-generator

npm install -g express
npm install -g express-generator

自動create express目錄
express

目錄如下
http://ithelp.ithome.com.tw/upload/images/20170223/20104222BIlCbGp70k.png

  • bin: 啟動server的檔案 (www)
  • routes: 放置子page的js檔案
  • uploads: 放置upload檔案
  • views: 放置子page的jade檔案
  • public: 放置網頁用到的css\javascript\jquery等檔案

和之前手動撰寫的 express website 不同
此時的 app.js 不包含啟動 server的code,而是用來設定 middleware
最後再 export 成 function,讓此 project 的其他 js 檔案可以使用 app.js 中的 middleware

打開package.json,可以看到已經定義好一些dependencies
加入幾個此project需要用到的module

{
  "name": "3-nodeauth",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.16.0",
    "cookie-parser": "~1.4.3",
    "debug": "~2.6.0",
    "express": "~4.14.1",
    "jade": "~1.11.0",
    "morgan": "~1.7.0",
    "serve-favicon": "~2.3.2",
    "mongodb": "*",
    "mongoose": "*",
    "connect-flash": "*",
    "express-messages": "*",
    "express-validator": "*",
    "express-session": "*",
    "passport": "*",
    "passport-local": "*",
    "passport-http": "*",
    "multer": "*"
  }
}

使用 npm 安裝新增的模組
npm install

也可以用以下方式安裝,加上 --save options才會把安裝的module加到package.json
npm install mongodb mongoose connect-flash express-messages express-validator express-session passport passport-local passport-http multer --save

修改 app.js,加入新模組,並使用mongoonse建立DB連線

var express = require('express');
......
var bodyParser = require('body-parser');

//add new module
var session = require('express-session');
var passport = require('passport');
var expressValidator = require('express-validator');
var LocalStrategy = require('passport-local').Strategy;
var multer = require('multer');  
var upload = multer({dest: './uploads'}); # setup multer upload destination
var flash = require('connect-flash');
var mongo = require('mongodb');
var mongoose = require('mongoose');

//create db connection using mongoose
var db = mongoose.connection;

var routes = require('./routes/index');
var users = require('./routes/users');
...

加入 Sessions \ Passport \ validator \ Messages Middleware

...
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

// Handle Sessions
app.use(session({
    secret:'secret',
    saveUninitialized: true,
    resave: true
}));

// Passport
app.use(passport.initialize());
app.use(passport.session());

// validator
app.use(expressValidator({
  errorFormatter: function(param, msg, value) {
      var namespace = param.split('.')
      , root    = namespace.shift()
      , formParam = root;

    while(namespace.length) {
      formParam += '[' + namespace.shift() + ']';
    }
    return {
      param : formParam,
      msg   : msg,
      value : value
    };
  }
}));

// messages (express-messages / connect-flash)
app.use(require('connect-flash')());
app.use(function (req, res, next) {
  res.locals.messages = require('express-messages')(req, res);
  next();
});


app.use('/', routes);
app.use('/users', users);
...

setup 好之後,測試 server 是否可正常啟動
npm start

看到這頁面出現,而且 console 沒有 error 就算是有正常啟動了
http://ithelp.ithome.com.tw/upload/images/20170223/20104222vQfR4B4nmK.png

註:
這裡要用 npm start,不能像前面的project一樣用 node app.js
因為啟動server的code是存在bin\www檔案中,而非app.js

打開 www 檔案,會看到最上方有 import app function (從app.js export而來)
http.createServer(app) function,就是用來啟動 server的指令,且套用 app 定義的 middleware
http://ithelp.ithome.com.tw/upload/images/20170223/20104222GmMhCVoOoD.png

查看 package.json,可以看到 npm start 指令實際上會被轉換成 node ./bin/www
透過scripts 的設定,也可以設定快速指令來執行不同的 js
http://ithelp.ithome.com.tw/upload/images/20170223/20104222a1Q7kBAosM.png

比如說撰寫了 www_test 檔案用來測試website功能,則修改package.json為

{
  ...
  "scripts": {
    "start": "node ./bin/www",
    "test": "node ./bin/www_test"
  },
  "dependencies": {
   ...

之後輸入 npm run test 就可以執行 www_test

Views & Layout
使用 Jade 作出web介面

首先將bootstrap.css放到public\stylesheets資料夾
並清空style.css的內容

修改layout.jade,從前一個project的layout.jade copy過來修改

doctype html
html
  head
    title Welcome //修改網頁Title
    //加入bootstrap
    link(href='/stylesheets/bootstrap.css', rel='stylesheet')
    //加入css
    link(href='/stylesheets/style.css', rel='stylesheet')
  body
    //修改navbar樣式
    nav.navbar.navbar-default(role='navigation')
      .container
        .navbar-header
          //修改button樣式
          button.navbar-toggle(type='button', data-toggle='collapse', data-target='.navbar-collapse') 
            span.sr-only Toggle navigation
            span.icon-bar
            span.icon-bar
            span.icon-bar
          //修改project name
          a.navbar-brand(href='#') NodeAuth
        //修改navbar樣式
        .navbar-collapse.collapse
          ul.nav.navbar-nav
            //如果目前位置為此頁面,樣式設為active(選取中)
            li(class=(title == 'Members' ? 'active' : ''))
              //修改連結文字為Member
              a(href='/') Members
            li(class=(title == 'Register' ? 'active' : ''))
              //修改連結文字為Register
              a(href='/users/register') Register
            li(class=(title == 'Login' ? 'active' : ''))
              //修改連結文字為Login
              a(href='/users/login') Login
          //navbar右側加入logout區塊
          ul.nav.navbar-nav.navbar-right
           li
              //加入logout連結
              a(href='/users/logout') Logout
    .container
     block content
    script(src='https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js')
    script(src='javascripts/bootstrap.js')

修改index.jade,此頁面會作為user登入後的畫面,有點類似會員中心 (member area)

extends layout

block content
  h1 Member Area
  p Welcome to the members area

修改index.js,修改title為 Members

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Members' });
});

module.exports = router;

從layout看到有三個page,index(member) \ register \ login
接下來加入 routing,讓點選連結時可以正常切換到指定 page

修改 users.js

var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});

//加入register routing
router.get('/register', function(req, res, next) {
  res.render('register', {title: 'Register'});
});

//加入login routing
router.get('/login', function(req, res, next) {
  res.render('login', {title: 'Login'});
});

module.exports = router;

同 index.jade,register\login 也需要有自己的 jade view,因此 create register.jade 和 login.jade
首先從 register.jade 開始

extends layout

block content
 h2.page-header Register
 p Please register using the form below

//表單內容
 form(method='post', action='/users/register', enctype='multipart/form-data')
   //multipart/form-data: 表單中要upload file時使用
  .form-group
   label Name
   input.form-control(name='name', type='text', placeholder='Enter name')
  .form-group
   label Email   
   input.form-control(name='email', type='email', placeholder='Enter email')
  .form-group
   label Username
   input.form-control(name='username', type='text', placeholder='Enter username')
  .form-group   
   label Password
   input.form-control(name='password', type='password', placeholder='Enter password')
  .form-group   
   label Confirm Password
   input.form-control(name='password2', type='password', placeholder='Confirm password')
  .form-group     
   label Profile Image
   input.form-control(name='profileimage', type='file')
  .form-group
   input.btn.btn-primary(type='submit', name='submit', value='Register')

重啟 server,打開 http://localhost:3000/users/register,檢查 register 頁面的表單是否正常
http://ithelp.ithome.com.tw/upload/images/20170223/20104222ukZRHOswDx.png

沒問題的話,copy register.jade的內容到 login.jade,login頁面相對簡單,只需要留下username\password\submit button
因為不用upload file,所以 form option也不需要加入multipart/form-data

修改login.jade

extends layout

block content
 h2.page-header Login
 p Please login below

 form(method='post', action='/users/login')
   label Username
   input.form-control(name='username', type='text', placeholder='Enter username')
  .form-group   
   label Password
   input.form-control(name='password', type='password', placeholder='Enter password')
  .form-group   
   input.btn.btn-primary(type='submit', name='submit', value='Login')

重啟server,確認 login 頁面是否正常,也順便確認 navbar的連結是不是都有通
http://ithelp.ithome.com.tw/upload/images/20170223/2010422249tyvT5Jhk.png

到此,View & Layout 設定完成

註冊表單 & 驗證

前面完成了register的GET
當使用者的瀏覽器發送HTTP GET request時,可以show出register頁面

當使用者填寫完資料需要submit時,就是發送HTTP POST request
因此現在要先加入 POST request

這邊會使用到Multer,通常在parse res.body時,會使用 body parser,不過 body parser 無法處理上傳檔案,因此改用multer取代
首先require multer

var multer = require('multer');
var upload = multer({dest: './uploads'});

修改user.js,在router.get後面加入 route.post
Parsing request.body 的參數後,檢查表單填寫內容是否為正確格式,並且檢查是否有上傳檔案,上傳的檔案會存在uploads路徑下
如果檢查欄位發現有錯誤訊息,會回傳errors message

...
router.get('/login', function(req, res, next) {
  res.render('login', {title: 'Login'});
});

//POST request to register
router.post('/register', upload.single('profileimage'), function(req, res, next) {
    //using multer
    var name = req.body.name;
    var email = req.body.email;
    var username = req.body.username;
    var password = req.body.password;
    var password2 = req.body.password2;

    //Form Validator
    req.checkBody('name', 'Name field is required').notEmpty();
    req.checkBody('email', 'Email field is required').notEmpty();
    req.checkBody('email', 'Email is not valid').isEmail();
    req.checkBody('username', 'Username field is required').notEmpty();
    req.checkBody('password', 'Password field is required').notEmpty();
    req.checkBody('password2', 'Passwords do not match').equals(req.body.password);

    //console.log(req.file); //show uploaded image info.
    if(req.file){
        console.log('Uploading File...');
        var profileimage = req.file.filename;
    } else {
        console.log('No File Uploaded...');
        var profileimage = 'noimage.jpg'; //use default image
    }
});

module.exports = router;

如果檢查有回傳任何error message,需要在網頁上顯示
因此修改 register.jade,show出error message

...
block content
 h2.page-header Register
 p Please register using the form below
 //Show error message
 if errors
  each error, i in errors
   div.alert.alert-danger #{error.msg}

 form(method='post', action='/users/register', enctype='multipart/form-data')
 ...

測試看看,應填欄位為空就會出現error
http://ithelp.ithome.com.tw/upload/images/20170223/201042229MW333dxFg.png

Models & 使用者註冊

建立 Models 資料夾,透過mongoose 儲存 user 資料

在project root path下建立models資料夾

在models下新增user.js,撰寫User資料 schema,並export 新增user function

//using mongoose to connect mongodb
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/nodeauth');
var db = mongoose.connection;

//User Schema
var UserSchema = mongoose.Schema({
    username: {
        type: String,
        index: true
    },
    password: {
        type: String
    },
    email: {
        type: String
    },
    name: {
        type: String
    },
    profileimage: {
        type: String
    }
});

//export User schema
var User = module.exports = mongoose.model('User', UserSchema);

//export createUser function
module.exports.createUser = function(newUser, callback){
    newUser.save(callback); //mongoose function to insert to DB
};

設定完 Models,接下來要在 register POST request 把資料存進DB

修改 routes\users.js,import models

var express = require('express');
var router = express.Router();
var multer = require('multer');
var upload = multer({dest: './uploads'});

//import Data Model
var User = require('../models/user');

......

修改 routes\users.js 的 Check errors 區塊
當沒有檢查到error時,呼叫createUser function insert user 資料,並切換到member area page

    ......
    //Check Errors
    var errors = req.validationErrors();
  
    if(errors){
        res.render('register', {
            errors: errors
        });
    } else {
        var newUser = new User({
            name: name,
            email: email,
            username: username,
            password: password,
            profileimage: profileimage
        });

        User.createUser(newUser, function(err, user){
            //track for error
            if(err) throw err;
            console.log(user);
        });

        res.location('/');
        res.redirect('/');
    }

登入成功時,通常會出現"登入成功"的message,這邊會使用connect-flash show message
繼續修改routes\user.js

        ...
        User.createUser(newUser, function(err, user){
            //track for error
            if(err) throw err;
            console.log(user);
        });

        //Show success message with flash
        req.flash('success', 'You are now registered and can login');

        res.location('/');
        res.redirect('/');
        ...

修改user.jade,在block content上方顯示message

     ......
          ul.nav.navbar-nav.navbar-right
           li
              a(href='/users/logout') Logout
    .container
     != messages()
     block content

    script(src='https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js')
    script(src='javascripts/bootstrap.js')

還需要替message設定css樣式,修改 stylesheets\style.css,success & error 只有顏色屬性不同

ul.success li {
    padding: 15px;
    margin-bottom: 20px;
    border: 1px solid transparent;
    border-radius: 4px;
    color: #3c763d;
    background-color: #dff0d8;
    border-color: #d6e9c6;
    list-style: none;
}

ul.error li {
    padding: 15px;
    margin-bottom: 20px;
    border: 1px solid transparent;
    border-radius: 4px;
    color: #a94442;
    background-color: #f2dede;
    border-color: #ebccd1;
    list-style: none;
}

重啟 Server,在 Register 輸入資料後 submit,應該會切換到 Member Area,並出現 success message

至此,基本的註冊功能就完成了
http://ithelp.ithome.com.tw/upload/images/20170223/20104222cQCaYFqHfU.png

使用 BCrypt.js 進行密碼雜湊計算

首先安裝 bcryptjs
npm install bcryptjs --save

在 model\user.js & app.js 兩個檔案中 require bcrypt module
var bcrypt = require('bcryptjs');

copy bcryptjs 的 Usage-Async example
在model\user.js的 createUser function 中貼上,將bcrypt.hash()中的字串改成我們要儲存的 newUser.password
加密後的password會透過bcrypt.hash()的callback回傳,將這個值assign給newUser.password
再用 save() 儲存到 mongodb

...
var User = module.exports = mongoose.model('User', UserSchema);

module.exports.createUser = function(newUser, callback){
    bcrypt.genSalt(10, function(err, salt) {
        bcrypt.hash(newUser.password, salt, function(err, hash) {
            // Store hash in your password DB.
            newUser.password = hash;
            newUser.save(callback);
        });
    });
};

重啟 server,註冊一筆資料,可以從 console 或是 mongodb 看到剛剛註冊的密碼已經加密了
http://ithelp.ithome.com.tw/upload/images/20170223/20104222NfZ6mDTFJk.png

使用 Passport 進行登入驗證

這邊會使用到 Passport Module 的 Authenticate 功能

修改routes\users.js,import passport 和 passport-local strategy (從app.js copy)

var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;

加入login的HTTP POST request
//POST request to login
router.post('/login',
  passport.authenticate('local', {failureRedirect:'/users/login', failureFlash: 'Invalid username or password'}),
  function(req, res) {
      req.flash('success', 'You are now logged in');
      res.redirect('/');
  });

加入 passport 驗證 (從passport document - authenticate copy),修改成下面這樣
其中 getUserByUsername 和 comparePassword 分別用來檢查username和password是否正確,稍後會在model\user.js定義

passport.use(new LocalStrategy(function(username, password, done){
    //compare username
    User.getUserByUsername(username, function(err, user){
        if(err) throw err;
        if(!user){
            return done(null, false, {message: 'Unknown User'});
        }
        //compare password
        User.comparePassword(password, user.password, function(err, isMatch){
            if(err) throw err;
            if(isMatch){
                return done(null, user);
            } else {
                return done(null, false, {message: 'Invalid Password'});
            }
        });

    });
}));

繼續加入 session (一樣從passport document copy,keyword: serializeUser),將 findById() 改成 getUserById(),稍後也會定義

passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.getUserById(id, function(err, user) {
    done(err, user);
  });
});

註:
passport serializeUser 和 deserializeUser
會先對 user 做 serialize,確認 user 驗證通過後,passport 會儲存找到的 user.id
接下來需要用到此 User Object時,就會將 user.id 拋給 deserializeUser 去 query User Object

細節可以再參考以下說明
Passport Document - Configure -> Sessions
StackOverFlow

修改 login.jade,加上 error message check

extends layout

block content
 h2.page-header Login
 p Please login below
 if errors
  each error, i in errors
   div.alert.alert-danger #{error.msg}
 form(method='post', action='/users/login')
  .form-group   
   label Username
   input.form-control(name='username', type='text', placeholder='Enter username')
  .form-group   
   label Password
   input.form-control(name='password', type='password', placeholder='Enter password')
  input.btn.btn-primary(type='submit', name='submit', value='Login')

修改 models\user.js,新增 getUserById, getUserByUsername, comparePassword 等方法,並export
getUserByUsername 會使用 username 去 mongo query,並回傳query到的 User object
comparePassword 用來比對輸入的password和 db query 到的 User.password 是否一致

......
var User = module.exports = mongoose.model('User', UserSchema);

module.exports.getUserById = function(id, callback){
    User.findById(id, callback);
}

module.exports.getUserByUsername = function(username, callback){
    var query = {username: username};
    User.findOne(query, callback);
}

module.exports.comparePassword = function(candidatePassword, hash, callback){
    // Load hash from your password DB.
    bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
        callback(null, isMatch);
    });   
}

module.exports.createUser = function(newUser, callback){
     ......

重啟 Server,在 login 輸入錯誤的密碼,會出現 error message
http://ithelp.ithome.com.tw/upload/images/20170223/20104222chWuYMObjn.png
輸入正確密碼,則會 redirect 到 Member Area,並出現登入成功的訊息

登出 & 存取控制

前面已經實作完註冊、登入、驗證功能
目前無法登出,也可以不登入就直接存取 Member Area,現在來把這塊完成

修改 routes\users.js 加入 logout routing

router.get('/logout', function(req, res){
    req.logout();
![http://ithelp.ithome.com.tw/upload/images/20170223/20104222sL4ERODVfp.png](http://ithelp.ithome.com.tw/upload/images/20170223/20104222sL4ERODVfp.png)    req.flash('success', 'You are now logged out');
    res.redirect('/users/login');
});

修改 index.js,加入members 頁面的存取控制 function ensureAuthenticated
在進入 member area 時會檢查是否已登入,如果沒登入,則切換到 login 頁面

... 

function ensureAuthenticated(req, res, next){
    if(req.isAuthenticated()){
        return next();
    }
    res.redirect('/users/login');
}

module.exports = router;

修改 routing.get,將 ensureAuthenticated function 加入 function 參數

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', ensureAuthenticated, function(req, res, next) {
  res.render('index', { title: 'Members' });
});

...

重啟 server,確認點選 members 會自動切換到 login
在 login 成功登入後,才可以切到 member area
http://ithelp.ithome.com.tw/upload/images/20170223/2010422216crf4Nacp.png

目前 login 後,還看得到 register 和 login 選單,而 logout 後,還看得到 logout 選單
因此要新增一個 global 變數來儲存登入狀態,讓選單可以正確顯示

修改 app.js,app.get('*') 代表所有頁面的 get

......
app.use(function (req, res, next) {
  res.locals.messages = require('express-messages')(req, res);
  next();
});

//set global variable for login/logout status
app.get('*', function(req, res, next){
  res.locals.user = req.user || null;
  next();
});

app.use('/', routes);
app.use('/users', users);
......

修改 layout.jade,在 li 各連結前加入 if 判斷
如果現在沒有 user global 變數,代表沒有登入,則顯示 register 和 login 選單
相反地,如果現在有 user global 變數,代表有登入,則顯示 member 和 logout 選單

     ......
          ul.nav.navbar-nav
           if user
            li(class=(title == 'Members' ? 'active' : ''))
              a(href='/') Members
            if !user
             li(class=(title == 'Register' ? 'active' : ''))
              a(href='/users/register') Register
             li(class=(title == 'Login' ? 'active' : ''))
              a(href='/users/login') Login
          ul.nav.navbar-nav.navbar-right
           if user
            li
              a(href='/users/logout') Logout
    .container
    ......

重啟 server,測試 login 前後,選單是否正常顯示
http://ithelp.ithome.com.tw/upload/images/20170223/20104222VF2KqOwLOb.pnghttp://ithelp.ithome.com.tw/upload/images/20170223/20104222GDJt4OONGQ.png

到此,一個粗略的註冊、登入、登出驗證系統就結束囉

目前想到有一些還可以加強的地方,有時間再來實作看看

  • 加上檢查 username 不能重覆的機制
  • Flash Message show 出 username 或是 Name

尚未有邦友留言

立即登入留言