티스토리 뷰

728x90
SMALL

npx sequelize init

 

N:M 관계 이거는 유저대유저야 신기하지

  static associate(db) {
    db.User.hasMany(db.Post);
    db.User.belongsToMany(db.User, {
      foreignKey: 'followingId',
      as: 'Followers',
      through: 'Follow',
    });
    db.User.belongsToMany(db.User, {
      foreignKey: 'followerId',
      as: 'Followings',
      through: 'Follow',
    });
  }

 

hasMany에 옵션 안넣으면 반대편 post에서 UserId로 생성

유저 팔로워, 팔로잉 볼거야

through로 생성할 모델 이름을 정한다.

foreignKey 옵션에 followerId, followingId를 붙여서 두 사용자 아이디를 구분

as는 foreignKey와 반대되는 모델을 가리킨다.

as로 user.getFollowers, user.getFollowings 같은 관계 메서드 사용 가능. include도 마찬가지

자동으로 생성된 모델도 db.sequelize.models.Follow로 접근 가능

 

시퀄라이즈는 config.json을 읽어 데이터베이스를 생성해주는 기능이 있다.

npx sequelize db:create

시퀄라이즈는 테이블 생성 쿼리문에 IF NOT EXISTS를 넣어주므로 테이블이 없을 때 테이블을 자동으로 생성

 

passport

serializeUser는 로그인 시 실행되며, req.session(세션) 객체에 어떤 데이터를 저장할지 정하는 메서드

done 함수의 첫번째 인수는 에러 발생 시 사용, 두번째 인수는 저장하고 싶은 데이터

deserializeUser는 매 요청 시 실행된다.

즉, serializeUser는 사용자 정보 객체를 세션에 아이디로 저장하는 것이고, deserializeUser는 세션에 저장한 아이디를 통해 사용자 정보 객체를 불러오는 것이다. 이는 세션에 불필요한 데이터를 담아두지 않기 위한 과정

 

전체 과정

1. 라우터를 통해 로그인 요청이 들어옴

2. 라우터에서 passport.authenticate 메서드 호출

3. 로그인 전략 수행

4. 로그인 성공 시 사용자 정보 객체와 함께 req.login 호출

5. req.login 메서드가 passport.serializeUser 호출

6. req.session에 사용자 아이디만 저장

7. 로그인 완료

 

로그인 이후 과정

1. 요청이 들어옴

2. 라우터에 요청이 도달하기 전에 passport.session 미들웨어가 passport.deserializeUser 메서드 호출

3. req.session에 저장된 아이디로 데이터베이스에서 사용자 조회

4. 조회된 사용자 정보를 req.user에 저장

5. 라우터에서 req.user 객체 사용 가능

 

로그인 됬는지 확인하는 함수로 모듈 만들자

exports.isLoggedIn = (req, res, next) => {
  if (req.isAuthenticated()) {
    next();
  } else {
    res.status(403).send('로그인 필요');
  }
};

exports.isNotLoggedIn = (req, res, next) => {
  if (!req.isAuthenticated()) {
    next();
  } else {
    const message = encodeURIComponent('로그인한 상태입니다.');
    res.redirect(`/?error=${message}`);
  }
};

 

비밀번호는 bcrypt로 암호화, 첫번째 인수 비밀번호, 두번째 인수는 커질수록 비밀번호 알아내기 어려워. 근데 암호화 시간 오래 걸려. 12이상 추천 31까지 가능

    const hash = await bcrypt.hash(password, 12);

 

로그인 라우터

router.post('/login', isNotLoggedIn, (req, res, next) => {
  passport.authenticate('local', (authError, user, info) => {
    if (authError) {
      console.error(authError);
      return next(authError);
    }
    if (!user) {
      return res.redirect(`/?loginError=${info.message}`);
    }
    return req.login(user, (loginError) => {
      if (loginError) {
        console.error(loginError);
        return next(loginError);
      }
      return res.redirect('/');
    });
  })(req, res, next); // 미들웨어 내의 미들웨어에는 (req, res, next)를 붙입니다.
});

로그아웃 라우터

router.get('/logout', isLoggedIn, (req, res) => {
  req.logout();
  req.session.destroy();
  res.redirect('/');
});

 

로컬 로그인 전략

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

const User = require('../models/user');
module.exports = () => {
  passport.use(
    new LocalStrategy(
      {
        usernameField: 'email',
        passwordField: 'password',
      },
      async (email, password, done) => {
        try {
          const exUser = await User.findOne({ where: { email } });
          if (exUser) {
            const result = await bcrypt.compare(password, exUser.password);
            if (result) {
              done(null, exUser);
            } else {
              done(null, false, { message: '비밀번호가 일치하지 않습니다.' });
            }
          } else {
            done(null, false, { messsage: '가입되지 않은 회원입니다.' });
          }
        } catch (error) {
          console.error(error);
          done(error);
        }
      }
    )
  );
};

 

LocalStrategy 생성자의 첫번째 인수로 주어진 객체는 전략에 관한 설정을 하는 곳이다. usernameFile와 passwordField에는 일치하는 로그인 라우터의 req.body 속성명을 적으면 된다.

LocalStratregy 생성자의 두번째 인수인 async 함수

첫번째 인수에서 넣어준 email과 password는 각각 async 함수의 첫번째와 두번째 매개변수가 된다. 세번째 매개변수는

passport.authenticatre의 콜백 함수이다.

 

카카오 로그인

SNS 로그인의 특징은 회원가입 절차가 따로 없다는 것이다. 청므 로그인할 때는 회원가입 처리를 해야 하고, 두번째 로그인부터는 로그인 처리를 해야한다. 따라서 SNS 로그인 전략은 로컬 로그인 전략보다 다소 복잡하다.

const passport = require('passport');
const KakaoStrategy = require('passport-kakao').Strategy;

const User = require('../models/user');

module.exports = () => {
  passport.use(
    new KakaoStrategy(
      {
        clientID: process.env.KAKAO_ID,
        callbackURL: '/auth/kakao/callback',
      },
      async (accessToken, refreshToken, profile, done) => {
        console.log('kakao profile', profile);
        try {
          const exUser = await User.findOne({
            where: { snsId: profile.id, provider: 'kakao' },
          });
          if (exUser) {
            done(null, exUser);
          } else {
            const newUser = await User.create({
              email: profile._json && profile._json.kakao_account_email,
              nick: profile.displayName,
              snsId: profile.id,
              provider: 'kakao',
            });
            done(null, newUser);
          }
        } catch (error) {
          console.error(error);
          done(error);
        }
      }
    )
  );
};

clientId는 카카오에서 발급해주는 아이디이다. 노출되지 않아야 하므로 process.env.KAKAO_ID에서 가져옴

callbackURL은 카카오로부터 인증 결과를 받을 라우터 주소

기존에 카카오를 통해 회원가입을 했다면 done호출하고 전략 종료

없다면 회원가입 진행. 카카오에서는 인증 후 callbackURL에 적힌 주소로 accessToken, refreshToken, profile을 보낸다.

router.get('/kakao', passport.authenticate('kakao'));

router.get(
  '/kakao/callback',
  passport.authenticate('kakao', {
    failureRediret: '/',
  }),
  (req, res) => {
    res.redirect('/');
  }
);

/auth/kakao로 접근하면 카카오 로그인 과정이 시작. 처음에는 카카오 로그인 창으로 리다이렉트하고 그 창에서 로그인 후 성공 여부 결과를 GET /auth/kakao/callback으로 받는다. 이 라우터에서는 카카오 로그인 전략을 다시 수행한다.

로컬 로그인과 다른 점은 passport.authenticate 메서드에 콜백 함수를 제공하지 않는다는 점이다. 카카오 로그인은 로그인 성공 시 내부적으로 req.login을 호출한다.

 

이제 clientID를 발급받아야 한다.

developers.kakao.com에 접속. 로그인

내 에플리케이션 메뉴 - 애플리케이션 추가하기

추가해

.env 파일에 KAKAO_ID= REST API키

 

앱 설정 - 플랫폼 - Web 플랫폼 등록

도메인주소입력

 

제품설정

활성화 설정 상태 스위치 ON

Redirect URI 수정

 

제품설정 - 카카오 로그인 - 동의항목

로그인 동의항목을 작성한다. 원하는 정보가 있다면 선택

예제에서는 이메일이 필요

 

developers.kakao.com/docs/latest/ko/kakaologin/rest-api#logout

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

세부내용은 맨날바뀌니 카카오 디벨로퍼 홈페이지에서 찾아봐야돼

 

post

const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');

const { Post, Hashtag } = require('../models');
const { isLoggedIn } = require('./middlewares');

const router = express.Router();

try {
  fs.readdirSync('uploads');
} catch (error) {
  console.error('uploads 폴더가 없어 uploads 폴더를 생성합니다.');
  fs.mkdirSync('uploads');
}

const upload = multer({
  storage: multer.diskStorage({
    destination(req, file, cb) {
      cb(null, 'uploads/');
    },
    filename(req, file, cb) {
      const ext = path.extname(file.originalname);
      cb(null, path.basename(file.originalname, ext) + Date.now() + ext);
    },
  }),
  limits: { fileSize: 5 + 1024 * 1024 },
});

router.post('/img', isLoggedIn, upload.single('img'), (req, res) => {
  console.log(req.file);
  res.json({ url: `/img/${req.file.filename}` });
});

const upload2 = multer();
router.post('/', isLoggedIn, upload2.none(), async (req, res, next) => {
  try {
    const post = await Post.create({
      content: req.body.content,
      img: req.body.url,
      UserId: req.user.id,
    });
    const hashtags = req.body.content.match(/#[^\s#]+/g);
    if (hashtags) {
      const result = await Promise.all(
        hashtags.map((tag) => {
          return Hashtag.findOrCreate({
            where: { title: tag.slice(1).toLowerCase() },
          });
        })
      );
      await post.addHashtags(result.map((r) => r[0]));
    }
    res.redirect('/');
  } catch (error) {
    console.error(error);
    next(error);
  }
});

module.exports = router;

 

 

 

스스로 해보기

팔로잉 끊기(시퀄라이즈의 destroy 메서드와 라우터 활용)

프로필 정보 변경하기(시퀄라이즈의 update 메서드와 라우터 활용)

게시글 좋아요 누르기 및 좋아요 취소하기(사용자 - 게시글 모델 간 N:M 관계 정립 후 라우터 활용)

게시글 삭제하기(등록자와 현재 로그인한 사용자가 같을 때, 시퀄라이즈의 destroy 메서드와 라우터 활용)

매번 데이터베이스를 조회하지 않도록 deserializeUser 캐싱하기(객체 선언 후 객체에 사용자 정보 저장, 객체 안에 캐싱된 값이 있으면 조회)

 

 

 

 

 

728x90
LIST

' > nodejs 교과서' 카테고리의 다른 글

11. 노드 서비스 테스트하기  (0) 2021.03.02
10. API 서버 이해하기  (0) 2021.02.27
8. 몽고디비  (0) 2021.02.24
7. MYSQL  (0) 2021.02.22
6. 익스프레스 웹 서버 만들기  (0) 2021.02.21
댓글
공지사항