authenticate, redux (1)_back

김종민·2022년 12월 13일
0

mern-Chat

목록 보기
3/5

들어가기
1. authenticate 관련해서, backend부터 시작해봅니다.


1. server.js

const express = require('express')
const app = express()
const dotenv = require('dotenv')
///express, dotenv를 import한다.

const databaseConnect = require('./config/db')
const authRouter = require('./routes/authRoute')
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')
///dataBase, bodyParser, cookieParser를
///불러준다.

dotenv.config({
  path: 'backend/config/.env',
})
///server에서 .env파일을 import하는 문법.
///file path를 저정해 준다.

app.use(bodyParser.json())
app.use(cookieParser())
///bodyParser를 넣어서 front에서 오는 data를 받을 수 있게함.

app.use('/api/messenger', authRouter)
///authRouter 사용하긋다.

const PORT = process.env.PORT || 5000
///port저정해 주기

app.get('/', (req, res) => {
  res.send('This is backend')
})
///home path접근시 controller

databaseConnect()
///위에서 부른 './db'를 실행한다.

app.listen(PORT, () => {
  console.log(`Server is running at ${PORT}`)
})
///서버 시작한다.

2. routes/authRoute.js

const { userRegister, userLogin } = require('../controller/authController')

const router = require('express').Router()

router.post('/user-register', userRegister)
router.post('/user-login', userLogin)

module.exports = router
///router를 불러주는 구 문법을 이해한다.
/// userRegister, userLogin 2개 controller를 만든다.
/// 두 개의 path를 만들어 준다

3. config/.env

PORT = 5000
DB_URL=mongodb://localhost:27017/messenger
SECRET_KEY: ASDFEW@234fSDF@
TOKEN_EXP: 7d
COOKIE_EXP=7

///.env 셋팅을 확인한다.

4. config/db.js

DB만드는 방법, DB connect를 확인한다.

const mongoose = require('mongoose')

const databaseConnect = () => {
  mongoose
    .connect(process.env.DB_URL, {
      useNewUrlParser: true, 
      useUnifiedTopology: true,
      ///위에 두개는 반사적으로 넣는다.
    })
    .then(() => {
      console.log('Mongo DB connected')
    })
    .catch((error) => {
      console.log(error)
    })
    ///.connect에 성공하면, .then, 실패하면, .catch.
}

module.exports = databaseConnect

5. models/authModel.js

const { model, Schema } = require('mongoose')

const registerSchema = new Schema(
  {
    userName: { type: String, required: true },
    email: { type: String, required: true },
    password: { type: String, required: true },
    image: { type: String, required: true },
  },
  { timestamps: true }
)

module.exports = model('user', registerSchema)

///authModel을 만드는 방법을 확인한다.

6. controller/authController.js

const formidable = require('formidable')
///json과 file을 동시에 server에서 받을 경우 사용
///client에서 formData로 보냈을때, 통으로 받음.
const validator = require('validator')
///front에서느 넘어오는 data의 유효성을 확인

const registerModel = require('../models/authModel')
///authModel을 불러줌.

const fs = require('fs')
///file을 다루는 라이브러리

const bcrypt = require('bcrypt')
const jwt = require('jsonwebtoken')
const { options } = require('../routes/authRoute')
///bcrypt, jsonwebtoken을 import 해 준다.

///user Register부분.
module.exports.userRegister = (req, res) => {
  const form = formidable()
  form.parse(req, async (err, fields, files) => {
    const { userName, email, password, confirmPassword } = fields
    const { image } = files
    const error = []
    ///form부분을 잘 봐준다.
    ///formidalble()로 받으면, json, file을 동시에
    ///server에서 받을 수 있다
    ///json은 fields로 받고 파일은 files로 서버에서 받는다.
    ///여기서 error를 담을 error=[]를 만들어 놓는다.

	///아래부터 각각의 data가 전송되지 않았다면,
    ///error=[]에 message를 담아준다.
    if (!userName) {
      error.push('Please provide your name')
    }
    if (!email) {
      error.push('Please provide your email')
    }
    if (email && !validator.isEmail(email)) {
      error.push('Please provide your valid email')
    }
    if (!password) {
      error.push('Please provide your password')
    }
    if (!confirmPassword) {
      error.push('Please provide your confirmPassword')
    }
    if (password && confirmPassword && confirmPassword !== password) {
      error.push('password is different confirmPassword')
    }
    if (password && password.length < 6) {
      error.push('Please provide password much than 6 chat\r')
    }
    if (Object.keys(files).length === 0) {
      error.push('Please provide user image')
    }
    ///files을 check할 떄, Object.keys(files)로
    ///files를 확인하는 부분은 잘 확인해 둔다.
    
    if (error.length > 0) {
      res.status(400).json({
        error: {
          errorMessage: error,
        },
      })
      ///error에 error가 push된다면,
      ///error:{errorMessage}에 위에서 만든 error들을
      ///담아준다.
    } else {  ///error.length = 0 일 경우~
      const getImageName = files.image.originalFilename
      const randNumber = Math.floor(Math.random() * 9999)
      const newImageName = randNumber + getImageName
      files.image.originalFilename = newImageName
      ///client에서 보내온 fileName을 get해서
      ///rnadNumber를 만들어서 두 개를 결합해서
      ///newImageName을 만든 다음, 이름을 교체해 준다.

      const newPath =
        __dirname +
        `../../../frontend/public/image/${files.image.originalFilename}`
        ///사진이 저장될 path를 만들고 입력될 fileName을 
        ///${}로 만들어 준다.
        
      console.log(newImageName)
      try {
        const checkUser = await registerModel.findOne({
          email: email,
        })
        ///register controller이기 떄문에, email 중복체크함
        
        if (checkUser) {
          res.status(404).json({
            error: {
              errorMessage: ['Your email alreadt exist.'],
            },
          })
          ///같은 email이 있으면, error날려줌.
        } else {
          fs.copyFile(files.image.filepath, newPath, async (error) => {
      ///fs.copyFile을 이용해서, clinet에서 select한
      ///filePath, 위에서만든 사진이 저장될 newPath를
      ///argument로 넣고, callback함수 만듬.
      ///error가 없을 경우 아래부분 실햄.
          
            if (!error) {
              const userCreate = await registerModel.create({
                userName,
                email,
                password: await bcrypt.hash(password, 10),
                image: files.image.originalFilename,
              })
              ///DB의 registerModel을 불러서, DB에
              ///user data를 저장한다.
              ///password는 hash화 해서 넣어준다.
              
              const token = jwt.sign(
                {
                  id: userCreate._id,
                  email: userCreate.email,
                  userName: userCreate.userName,
                  image: userCreate.image,
                  registerTime: userCreate.createdAt,
                },
                ///jsonwebtoken을 이용해서 token을 만드는데,
                ///token에 userInfo를 저장해 놓는다.
                
                process.env.SECRET_KEY,
                { expiresIn: process.env.TOKEN_EXP }
              )
              ///SECRET_KEY와 expiresIn을 설정한다.
              
              const options = {
                expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
              }
              ///options로 현재시간 부터 7일 후의 시간을
              ///만든다.
              res.status(201).cookie('authToken', token, options).json({
                successMessage: 'Your Register Successful',
                token,
              })
              ///201을 날려주고, cookie에 'authToken'으로
              ///token을 저장시키고,
              ///client에 token을 return해 준다.
              ///cookie까지 저장시키는 이유는 추후 알아보자
            }
          })
        }
      } catch (error) {
        res.status(500).json({
          error: {
            errorMessage: ['Internal Server error'],
          },
        })
        ///error발생시 error=[]에 담아서 errorMessage날려줌.
      }
    }
  })
}

///User Login 부분.
module.exports.userLogin = async (req, res) => {
  const error = []
  ///error를 담을 공간을 만들어 줌.
  const { email, password } = req.body
  ///client부분에서 email, password받아줌.
  console.log(email, password)

  if (!email) {
    error.push('Please provide your email')
  }
  if (email && !validator.isEmail(email)) {
    error.push('Please provide your valid email')
  }
  if (!password) {
    error.push('Please Password provide')
  }
  if (error.length > 0) {
    res.status(400).json({
      error: {
        errorMessage: error,
      },
    })
    ///error발생시, error만들고, error에 담아줌.
    
  } else {
    try {
      const checkUser = await registerModel.findOne({
        email: email,
      })
      ///client에서 받은 email이 DB에 있는지 check함.

      if (checkUser) {
        const matchPassword = await bcrypt.compare(password, checkUser.password)
        ///matchPasswordf로 client에서 보내온 password와
        ///DB의 hash화된 password를 compare해서
        ///boolean으로 return해 줌.

		///true return일때,
        if (matchPassword) {
        
        ///register와 같이 token을 만들고 return해 준다.
          const token = jwt.sign(
            {
              id: checkUser._id,
              email: checkUser.email,
              userName: checkUser.userName,
              image: checkUser.image,
              registerTime: checkUser.createdAt,
            },
            process.env.SECRET_KEY,
            {
              expiresIn: process.env.TOKEN_EXP,
            }
          )
          console.log(token)
          const options = {
            expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
          }
          res.status(201).cookie('authToken', token, options).json({
            successMessage: 'Your login Successful',
            token,
          })
        } else {
          res.status(400).json({
            error: {
              errorMessage: ['Your password not valid'],
            },
          })
        }
      } else {
        res.status(400).json({
          error: {
            errorMessage: ['Your email nor found'],
          },
        })
      }
    } catch (err) {
      res.status(400).json({
        error: {
          errorMessage: ['Internal server error'],
        },
      })
    }
  }
}

///여기까지가 register, login 부분.
formidable부분이 신선하긴 한데,
formidable로 받은 file을 나중에 S3나 cloudflare에
넣을려면, 어떻게 해야 하는지 고민이 필요할듯.
///걍 multer로 받아야 하나 싶기도 함.

profile
코딩하는초딩쌤

1개의 댓글

comment-user-thumbnail
2023년 5월 10일

이것들은 제가 배우고 있는 복잡한 코드 조각입니다. Vampire Survivors 공유해주셔서 감사합니다

답글 달기