인프런 커뮤니티 질문&답변

Hongsun1님의 프로필 이미지
Hongsun1

작성한 질문수

[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지

10.6 재질문…!

해결된 질문

작성

·

508

·

수정됨

0

exports.apiLimiter = async (req, res, next) => {
  let user;
  if (res.locals.decoded) {
    user = await User.findOne({ where: { id: res.locals.decoded.id } });
  }
  rateLimit({
    widowMs: 60 * 1000,
    max: user?.type === "premium" ? 10 : 1,
    handler(req, res) {
      res.status(this.statusCode).json({
        code: this.statusCode,
        message: "1분에  열 번만 요청 할 수 있습니다...",
      });
    },
  })(req, res, next);
};

 

Api 프리미엄고객만 1분에 열번만요청할수있게 미들웨어 확장패턴으로

만들었습니다 그런데 localhost:4000/myposts 접속해서 10 번이상새로고침해도 api가 제한이 안되고 제가작성한 게시글 목록만 뜹니다

답변 1

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

이 부분 apiLimiter가 호출될 때마다 rateLimit 미들웨어가 새로 생성되서 그렇습니다. 다음과 같이 수정해야할 것 같네요.

const limiter = rateLimit({
  widowMs: 60 * 1000,
  max: (req, res) => {
    if (req.user?.type === 'premium') { return 10 }
    return 1;
  },
  handler(req, res) {
    res.status(this.statusCode).json({
      code: this.statusCode,
      message: `1분에 ${req.user?.type === 'premium' ? '열' : '한'} 번만 요청 할 수 있습니다...`,
    });
  },
});

exports.apiLimiter = async (req, res, next) => {
  let user;
  if (res.locals.decoded) {
    user = await User.findOne({ where: { id: res.locals.decoded.id } });
  }
  req.user = user;
  limiter(req, res, next);
};

 

Hongsun1님의 프로필 이미지
Hongsun1
질문자

선생님 감사합니다^^

Hongsun1님의 프로필 이미지
Hongsun1
질문자

선생님 프리미엄 클라이언트 시크릿키발급하고 실행했는데 1분에 10번이아니고 1번만 요청할수있다고 뜹니다 혹시다른부분에서 문제가 생겨서 그런걸까요? ㅠㅜ

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

handler 안에서 console.log(req.user) 해보세요

Hongsun1님의 프로필 이미지
Hongsun1
질문자

Executing (default): SELECT `id`, `host`, `type`, `clientSecret`, `createdAt`, `updatedAt`, `deletedAt`, `UserId` FROM `domains` AS `Domain` WHERE (`Domain`.`deletedAt` IS NULL AND `Domain`.`host` = 'localhost:4000') LIMIT 1;
Executing (default): SELECT `Domain`.`id`, `Domain`.`host`, `Domain`.`type`, `Domain`.`clientSecret`, `Domain`.`createdAt`, `Domain`.`updatedAt`, `Domain`.`deletedAt`, `Domain`.`UserId`, `User`.`id` AS `User.id`, `User`.`email` AS `User.email`, `User`.`nick` AS `User.nick`, `User`.`password` AS `User.password`, `User`.`provider` AS `User.provider`, `User`.`snsId` AS `User.snsId`, `User`.`createdAt` AS `User.createdAt`, `User`.`updatedAt` AS `User.updatedAt`, `User`.`deletedAt` AS `User.deletedAt` FROM `domains` AS `Domain` LEFT OUTER JOIN `Users` AS `User` ON `Domain`.`UserId` = `User`.`id` AND (`User`.`deletedAt` IS NULL) WHERE (`Domain`.`deletedAt` IS NULL AND `Domain`.`clientSecret` = '3b7b5e45-c56f-480f-9f3b-6d59afd800b6') LIMIT 1;
POST /v2/token 200 165.745 ms - 253
Executing (default): SELECT `id`, `host`, `type`, `clientSecret`, `createdAt`, `updatedAt`, `deletedAt`, `UserId` FROM `domains` AS `Domain` WHERE (`Domain`.`deletedAt` IS NULL AND `Domain`.`host` = 'localhost:4000') LIMIT 1;
Executing (default): SELECT `id`, `email`, `nick`, `password`, `provider`, `snsId`, `createdAt`, `updatedAt`, `deletedAt` FROM `Users` AS `User` WHERE (`User`.`deletedAt` IS NULL AND `User`.`id` = 2);

노드버드 api 콘솔에 select domain 맨끝에 보시면 clientSecret` = '3b7b5e45-c56f-480f-9f3b-6d59afd800b6' 프리미엄 클라이언트 키 발급한거 나오고

GET /myposts 304 378.750 ms - -

노드캣 콘솔에는 이렇게 나오는데

handler 안에서 console.log(req.user)했는데도 똑같습니다 답변감사합니다!!!

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

console.log(req.user) 는 단순히 콘솔에 찍는거고 아무것도 안 바꾸니 당연히 똑같죠.

req.user값이 안 나오나요?

Hongsun1님의 프로필 이미지
Hongsun1
질문자

localhost:4000/myposts 접속해서 새로고침하면 nodebird-api콘솔에 Req.user 값 나옵니다

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

console.log(req.rateLimit) 도 찍어서 요청 보낼 때마다 limit을 위한 숫자 올라가는지 확인해봐야할 것 같습니다.

Hongsun1님의 프로필 이미지
Hongsun1
질문자

console.log(req.rateLimit) 도 찍었는데 limit숫자가 1로 그대로입니다

Hongsun1님의 프로필 이미지
Hongsun1
질문자

const limiter = rateLimit({
  widowMs: 60 * 1000,
  max: async(req, res) => {
    if (await req.user?.type === 'premium') { return 10 }
    return 1;
  },
  handler(req, res) {
console.log(req.user)
console.log(req.rateLimit) 
res.status(this.statusCode).json({
      code: this.statusCode,
      message: `1분에 ${req.user?.type === 'premium' ? '열' : '한'} 번만 요청 할 수 있습니다...`,
    });
  },
});

exports.apiLimiter = async (req, res, next) => {
  let user;
  if (res.locals.decoded) {
    user = await User.findOne({ where: { id: res.locals.decoded.id } });
  }
  req.user = user;
  limiter(req, res, next);
};

급행 속도 제한 - npm (npmjs.com)

여기 공식문서에 혹시나해서 max부분에 async await 해봤는데 똑같습니다

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

router.get('/posts/my', apiLimiter, verifyToken, getMyPosts);

이거 verifyToken이 더 뒤에 있어서 apiLimit에 req.user가 없네요.

Hongsun1님의 프로필 이미지
Hongsun1
질문자

감사합니다 제가 밖에있어서 이따 실행하고 말씀드릴게요 ^^

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

const limiter = rateLimit({
  widowMs: 60 * 1000,
  max: (req, res) => {
    if (req.user?.Domains[0]?.type === 'premium') { return 10 }
    return 1;
  },
  handler(req, res) {
    res.status(this.statusCode).json({
      code: this.statusCode,
      message: `1분에 ${req.user?.Domains[0]?.type === 'premium' ? '열' : '한'} 번만 요청 할 수 있습니다...`,
    });
  },
});

exports.apiLimiter = async (req, res, next) => {
  let user;
  if (res.locals.decoded) {
    user = await User.findOne({ where: { id: res.locals.decoded.id }, include: { model: Domain } });
  }
  req.user = user;
  limiter(req, res, next);
};
Hongsun1님의 프로필 이미지
Hongsun1
질문자

선생님 코드 감사합니다 아쉽게도 많이 배워야할것같아서 ㅠㅜ 혹시 제가 빠트린 코드가 있을까요 ? 아니면 다른부분 확인할 사항이 있을까요? 작성한 코드 올려드립니다

nodebird-api -> middlewares-> index.js

const jwt = require("jsonwebtoken"); //토큰을 검사하는 미들웨어
const rateLimit = require("express-rate-limit");
const User = require("../models/user");
const { Domain } = require("../models/");
const cors = require("cors");

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}`); //localhost:8001? error=메시지
  }
};

//토근검사
exports.verifyToken = (req, res, next) => {
  try {
    res.locals.decoded = jwt.verify(
      req.headers.authorization,
      process.env.JWT_SECRET
    );
    return next();
  } catch (error) {
    if (error.name === "TokenExpiredError") {
      return res.status(419).json({
        code: 419,
        message: "토큰이 만료되었습니다.",
      });
    }
    return res.status(401).json({
      code: 401,
      message: "유효하지 않은 토큰입니다.",
    });
  }
};

const limiter = rateLimit({
  widowMs: 60 * 1000,
  max: (req, res) => {
    if (req.user?.Domains[0]?.type === "premium") {
      return 10;
    }
    return 1;
  },
  handler(req, res) {
    res.status(this.statusCode).json({
      code: this.statusCode,
      message: `1분에 ${
        req.user?.Domains[0]?.type === "premium" ? "열" : "한"
      } 번만 요청 할 수 있습니다...`,
    });
  },
});

exports.apiLimiter = async (req, res, next) => {
  let user;
  if (res.locals.decoded) {
    user = await User.findOne({
      where: { id: res.locals.decoded.id },
      include: { model: Domain },
    });
  }
  req.user = user;
  limiter(req, res, next);
};

exports.deprecated = (req, res) => {
  res.status(410).json({
    code: 410,
    message: "새로운 버전이 나왔습니다. 새로운 버전을 사용하세요",
  });
};

exports.corsWhenDomainMatches = async (req, res, next) => {
  const domain = await Domain.findOne({
    where: { host: new URL(req.get("origin")).host },
  });
  if (domain) {
    cors({
      origin: true,
      Credential: true,
    })(req, res, next); //미들웨어 확장패턴
  } else {
    next();
  }
};

 

nodebird-api -> routes -> v2.js

const express = require("express");
const {
  verifyToken,
  apiLimiter,
  corsWhenDomainMatches,
} = require("../middlewares");
const {
  createToken,
  tokenTest,
  getMyPosts,
  getPostsByHashtag,
} = require("../controllers/v2");
const cors = require("cors");

const router = express.Router();

router.use(corsWhenDomainMatches);

router.use(
  cors({
    origin: true, 
    credentials: true, //쿠키요청
  })
);

router.post("/token", apiLimiter, createToken);


router.get("/test", verifyToken, apiLimiter, tokenTest);


router.get("/posts/my", verifyToken, apiLimiter, getMyPosts);

// GET /v2/posts/hashtag/:title
router.get("/posts/hashtag/:title", verifyToken, apiLimiter, getPostsByHashtag);

module.exports = router;

 

Hongsun1님의 프로필 이미지
Hongsun1

작성한 질문수

질문하기