--- # 9.2帳密登陸管理+存檔案 --- # 9.2帳密登陸管理+存檔案 ## 將密碼加密後=>儲存/供登陸比對 ## 註冊 routes ```javascript= import express from 'express' import { register } from '../controller/user.js' const router = express.Router() router.post('/', register) export default router ``` controller ```javascript= import users from '../models/users.js' import bcrypt from 'bcrypt' export const register = async (req, res) => { try { // 略過驗證密碼長度/帳號重複等 req.body.password = bcrypt.hashSync(req.body.password, 10) await users.create(req.body) res.status(200).json({ success: true, message: '' }) } ``` --- ## 登陸 routes ```javascript= // 之前內容略過 import { login } from '../controller/user.js' import * as auth from '../middleware/auth.js' router.post('/login', auth.login, login) ``` passport ```javascript= // 之前內容略過 import passport from 'passport' import bcrypt from 'bcrypt' import passportLocal from 'passport-local' import passportJWT from 'passport-jwt' import users from '../models/users.js' const LocalStrategy = passportLocal.Strategy // bcrypt驗證帳秘(供middleware匯入用) passport.use('login', new LocalStrategy({ usernameField: 'username', passwordField: 'password' }, async (username, password, done) => { try { const user = await users.findOne({ username }) if (!user) { return done(null, false, { message: '無該帳號' }) } if (!bcrypt.compareSync(password, user.password)) { return done(null, false, { message: '密碼錯誤' }) } return done(null, user) } catch (err) { return done(err, false) } } )) ``` middleware ```javascript= // 無使用到的略過 import passport from 'passport' import '../passport/passport.js' // passport 驗證過後,才req.user = user 並下一步,不然報錯 export const login = (req, res, next) => { passport.authenticate('login', { session: false }, (err, user, info) => { if (err) { res.status(400).json({ success: false, message: err }) return } if (!user) { res.status(400).json({ success: false, message: info.message }) return } req.user = user next() })(req, res, next) } ``` controller .env 新增 JWT_SECRET=隨便打一段密鑰 ```javascript= // 無使用到的略過 import bcrypt from 'bcrypt' import jwt from 'jsonwebtoken' export const login = async (req, res) => { // 把id變成不易讀token (內容,加密鑰,過期時間) const token = jwt.sign({ _id: req.user._id.toString() }, process.env.JWT_SECRET, { expiresIn: '1h' }) req.user.tokens.push(token) await req.user.save() res.status(200).json({ success: true, message: '登陸成功', token }) } ``` --- ## 讀取 routes ```javascript= // 之前內容略過 import { getData } from '../controller/user.js' import * as auth from '../middleware/auth.js' router.get('/me', auth.jwt, getData) ``` passport ```javascript= // 之前內容略過 import passport from 'passport' import passportJWT from 'passport-jwt' import users from '../models/users.js' const JWTStrategy = passportJWT.Strategy const ExtractJWT = passportJWT.ExtractJwt // passport 使用JWTStrategy // 讀取Bearer的token+ 使用JWT_SECRET解碼 +紀錄Req供使用 // passport.use('jwt', new JWTStrategy({ jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_SECRET, passReqToCallback: true }, async (req, payload, done) => { // 拆解出token 跟使用者檢查是否相符/未過期 const token = req.headers.authorization.split(' ')[1] try { const user = await users.findOne({ _id: payload._id, tokens: token }) if (user) { return done(null, { user, token }) } return done(null, false, { message: '請重新登錄' }) } catch (err) { return done(err, false) } } )) ``` middleware ```javascript= // 無使用到的略過 import passport from 'passport' import jsonwebtoken from 'jsonwebtoken' import '../passport/passport.js' export const jwt = (req, res, next) => { passport.authenticate('jwt', { session: false }, (err, data, info) => { if (err) { res.status(400).json({ success: false, message: err }) return } if (!data) { if (info instanceof jsonwebtoken.JsonWebTokenError) { res.status(400).json({ success: false, message: 'JWT錯誤' }) } else { res.status(400).json({ success: false, message: info.message }) } return } req.user = data.user req.token = data.token next() })(req, res, next) } ``` controller ```javascript= // 無使用到的略過 export const getData = async (req, res) => { res.status(200).json({ success: true, message: '', result: { _id: req.user._id, username: req.user.username, avatar: req.user.avatar } }) } ``` --- ## 登出 routes ```javascript= // 之前內容略過 import { logout } from '../controller/user.js' router.delete('/logout', auth.jwt, logout) ``` controller ```javascript= // 無使用到的略過 export const logout = async (req, res) => { try { req.user.tokens = req.user.tokens.filter(tk => tk !== req.token) await req.user.save() res.status(200).json({ success: true, message: '登出成功' }) } catch (err) { res.status(500).json({ success: false, message: '伺服器錯誤' }) } } ``` --- # 新增頭貼 ## 辦帳號 雲端存檔案平台=>註冊 記得要改成下方的選單 不然要重辦帳號 ![](https://i.imgur.com/qNm0Zpl.png) npm i bcrypt(加密.?) passport(驗證登錄套件 ex:discord line 等) passport-jwt passport-local ![](https://i.imgur.com/Kc0em1L.png) ![](https://i.imgur.com/h2JlrCz.png) ![](https://i.imgur.com/viRse0e.png) --- ## 上傳檔案 npm i multer(處理上傳檔案) cloudinary() multer-storage-cloudinary(處理後再傳到雲端) routes ```javascript= // 之前內容略過 import { editAvatar } from '../controller/user.js' import upload from '../middleware/upload.js' router.patch('/avatar', auth.jwt, upload, editAvatar) ``` middleware => upload.js .env 新增 CLOUDINARY_NAME= CLOUDINARY_KEY= CLOUDINARY_SECRET= ```javascript= // 無使用到的略過 import multer from 'multer' import { v2 as cloudinary } from 'cloudinary' import { CloudinaryStorage } from 'multer-storage-cloudinary' // 帳密供登陸cloudinary cloudinary.config({ cloud_name: process.env.CLOUDINARY_NAME, api_key: process.env.CLOUDINARY_KEY, api_secret: process.env.CLOUDINARY_SECRET }) // 定義 上傳方式/克制限制/基本限制 const upload = multer({ storage: new CloudinaryStorage({ cloudinary }), fileFilter (req, file, callback) { if (!file.mimetype.includes('image')) { callback(new multer.MulterError('LIMIT_FORMAT'), false) } else { callback(null, true) } }, limits: { fileSize: 1024 * 1024 } }) // 如果有err => 是multer的就是上船相關(自定義文字)/伺服器=>正常上傳下一步 export default async (req, res, next) => { upload.single('image')(req, res, err => { if (err instanceof multer.MulterError) { let message = '上傳錯誤' if (err.code === 'LIMIT_FORMAT') { message = '檔案格式錯誤' } else if (err.code === 'LIMIT_FILE_SIZE') { message = '檔案太大' } res.status(400).json({ success: false, message }) } else if (err) { res.status(500).json({ success: false, message: '伺服器錯誤' }) } else { next() } }) } ``` controller ```javascript= // 無使用到的略過 export const editAvatar = async (req, res) => { try { // 把上船的檔案路徑,加到該用戶內 req.user.avatar = req.file.path await req.user.save() res.status(200).json({ success: true, message: '上傳成功' }) } catch (err) { res.status(500).json({ success: false, message: '伺服器錯誤' }) } } ``` 讀FORM-DATA 靠下方 MULTER ![](https://i.imgur.com/seUFu4F.png) ![](https://i.imgur.com/iKwhtyo.png)