PJCHENder
4/14/2017 - 2:11 AM

Learn to Use Passport(Passport 學習筆記)

Learn to Use Passport(Passport 學習筆記)

// ./models/user


const mongoose = require('mongoose')  //  ODM for Mongo


const UserSchema = new mongoose.Schema({
  email: {
    type: String,  
    required: true, 
    trim: true,     
    unique: true    
  },
  name: {
    type: String,
    required: true,
    trim: true
  },
  favoriteBook: {
    type: String,
    required: true,
    trim: true
  },
  password: {
    type: String,
    required: true
  }
})

var User = mongoose.model('User', UserSchema)


module.exports = User
const passport = require('passport')
const bcrypt = require('bcrypt')      //  hashing module
const LocalStrategy = require('passport-local')
const JwtStrategy = require('passport-jwt').Strategy
const ExtractJwt = require('passport-jwt').ExtractJwt
const User = require('../models/user')
const jwtConfig = require('../config/jwt')



/**
 * passport.use('驗證策略名稱', '想建立的策略類型')
 * passReqToCallback: 讓我們在後面的 callback 中可以使用 req 參數
 */
//  Passport Initialization
passport.serializeUser(function (user, done) {
  done(null, user._id)
})
passport.deserializeUser(function (id, done) {
  User.findById(id, function (err, user) {
    done(err, user)
  })
})


let jwtStrategy = new JwtStrategy({
  secreteOrKey: jwtConfig.secret,
  jwtFromRequest: ExtractJwt.fromExtractors([
    ExtractJwt.versionOneCompatibility({authScheme: 'Bearer'}),
    ExtractJwt.fromAuthHeader()
  ])
}, function (payload, done) {
  User.findById(payload.sub, function (err, user) {
    if (err) return done(err)
    if (!user) return done(null, false, {message: 'Wrong JWT Token'})
    if (payload.aud !== user.email) return done(null, false, {message: 'Wrong JWT Token'})

    const exp = payload.exp
    const nbf = payload.nbf
    const curr = ~~(new Date().getTime() / 1000)
    if (curr > exp || curr < nbf) {
      return done(null, false, 'Token Expired')
    }
    return done(null, user)
  })
})

let loginStrategy = new LocalStrategy({
  usernameField: 'email',
  passReqToCallback: true
}, function (req, email, password, done) {
  User.findOne({ email: email }, function (err, user) {
    if (err) {
      return done(err)
    }
    if (!user) {
      return done(null, false, 'Username is not exists')
    }
    let isValidPassword = function (user, password) {
      return bcrypt.compareSync(password, user.password)
    }
    if (!isValidPassword(user, password)) {
      return done(null, false, 'Invalid Password')
    }
    return done(null, user)
  })
})

let signupStrategy = new LocalStrategy({
  usernameField: 'email',
  passReqToCallback: true
}, function (req, email, password, done) {
  if (password !== req.body.confirmPassword) {
    return done(new Error('Confirmation is not match with password'))
  }

  // if (password.length < 8) {
  //   return done(new Error('Password must has 8 characters at least'))
  // }

  const findOrCreateUser = function () {
    User.findOne({ email: email }, function (err, user) {
      if (err) {
        return done(err)
      }
      if (user) {
        return done(null, false, 'Username Already Exists')
      } else {
        let newUser = new User()
        newUser.email = email
        newUser.password = bcrypt.hashSync(password, 10)
        newUser.name = req.body.name
        newUser.favoriteBook = req.body.favoriteBook
        newUser.save(function (err, user) {
          if (err) {
            throw err
          }
          return done(null, user)
        })
      }
    })
  }
  process.nextTick(findOrCreateUser)
})

passport.use('jwt', jwtStrategy)
passport.use('login', loginStrategy)
passport.use('signup', signupStrategy)

module.exports = passport
// ./routes/index

const express = require('express')
const router = express.Router()
const User = require('../models/user')
const mid = require('../middleware')    //  將自己寫的 middleware 載入
const passport = require('../middleware/passport')



// POST /login
router.post('/login', function (req, res, next) {
  passport.authenticate('login', function (err, user, info){
    if (err) return next(err)
    if (!user) {
      err = new Error('User not found')
      res.status(409)
      return next(err)
    }
    req.login(user, function (err) {
      if (err) return next(err)
      req.session.userId = user._id
      return res.redirect('/profile')
    })
  })(req, res, next)
})



// POST /register
router.post('/register', function (req, res, next) {
  passport.authenticate('signup', function (err, user, info) {
    if (err) {
      err = new Error('Singup Error')
      err.status = 409
      return next(err)
    }

    if (!user) {
      err = new Error(info)
      err.status = 409
      return next(err)
    }

    req.login(user, function (err) {
      if (err) return next(err)
      console.log('User account created successfully')
      req.session.userId = user._id
      return res.redirect('/profile')
    })
  })(req, res, next)
})
// 匯入需要的模組
const express = require('express')
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')
const path = require('path')
const logger = require('morgan')
const mongoose = require('mongoose')
const session = require('express-session')
const passport = require('./middleware/passport')
const MongoStore = require('connect-mongo')(session)    //  直接執行並將 session 存進去,logout 後會自動刪除該 document
const dbconfig = require('./db')

//  和 mongoDB 連線
mongoose.connect(dbconfig.connection)   // 等同於,mongoose.connect('mongodb://localhost:27017/bookworm')
const db = mongoose.connection
db.on('error', console.error.bind(console, 'connection error')) //  mongo error handler

//  載入 express
const app = express()

// 設定 view engine 和模版路徑
app.set('view engine', 'pug')
app.set('views', path.join('./views'))

// middleware
app.use(logger('dev'))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(cookieParser())
app.use(express.static(path.join('./public')))  // 讀取 ./public 中的靜態檔案

//  使用 session 來追蹤使用者
app.use(session({
  secret: 'I love NodeJS',    // secret: 必要欄位,用來註冊 session ID cookie 的字串。如此將增加安全性,避免他人在瀏覽器中偽造 cookie。
  resave: false,              // resave: 不論是否 request 的過程中有無變更都重新將 session 儲存在 session store。
  saveUninitialized: false,    // saveUninitialized: 將 uninitialized session(新的、未被變更過的) 儲存在 session store 中。
  store: new MongoStore({
    mongooseConnection: db
  })
}))

app.use(passport.initialize())
app.use(passport.session())

//  讓 userID 可以在 template 中被存取,名稱為 currentUser
app.use(function (req, res, next) {
  res.locals.currentUser = req.session.userId // res.locals 屬性在所有 view 中都可以存取到
  next()    //  執行下一個 middleware
})

// 載入路由檔
const index = require('./routes/index')
app.use('/', index)

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  var err = new Error('File Not Found')
  err.status = 404
  next(err)
})

// error handler
// define as the last app.use callback
app.use(function (err, req, res, next) {
  res.status(err.status || 500)
  res.render('error', {
    message: err.message,
    error: {}
  })
})

// listen on port 3000
app.listen(3000, function () {
  console.log('Express app listening on port 3000')
})