다음 내용은 인프런에서 공부한 내용을 복습하는 차원에서 기록한 것입니다.
출처 : https://www.inflearn.com/course/%EB%85%B8%EB%93%9C-js-%EA%B5%90%EA%B3%BC%EC%84%9C
const Sequelize = require('sequelize');
class Domain extends Sequelize.Model {
static initiate(sequelize) {
Domain.init({
host: {
type: Sequelize.STRING(80),
allowNull: false,
},
type: {
type: Sequelize.ENUM('free', 'premium'),
allowNull: false,
},
clientSecret: {
type: Sequelize.UUID,
allowNull: false,
}
}, {
sequelize,
timestamps: true,
paranoid: true,
modelName: "Domain",
tableName: "domains",
})
}
static associate(db) {
db.Domain.belongsTo(db.User);
}
}
module.exports = Domain;
const express = require('express');
const { isLoggedIn } = require('../middlewares');
const { renderLogin, createDomain } = require('../controllers');
const router = express.Router();
router.get('/', renderLogin);
router.post('/domain', isLoggedIn, createDomain);
module.exports = router;
const { User, Domain } = require('../models');
const { v4: uuidv4 } = require('uuid');
exports.renderLogin = async (req, res, next) => {
try {
const user = await User.findOne({ where: { id: req.user?.id || null }, include: { model: Domain } });
res.render('login', {
user,
domains: user?.Domains,
})
} catch (error) {
console.error(error);
next(error);
}
};
exports.createDomain = async (req, res, next) => {
try {
await Domain.create({
UserId: req.user.id,
host: req.body.host,
type: req.body.type,
clientSecret: uuidv4()
})
res.redirect('/');
} catch (error) {
console.error(error);
next(error);
}
};

const { User, Post, Domain } = require('../models');
const jwt = require('jsonwebtoken');
...
exports.verfiyToken = (req, res, next) => {
try {
res.locals.decoded = jwt.verify(req.headers.authorization, process.env.JWT_SECRET);
return next();
} catch (error) {
if (error.name === 'TokenExpireError') {
return res.status(419).json({
code: 419,
message: '토큰이 만료되었습니다.',
})
}
return res.status(401).json({
code: 401,
message: '유효하지 않은 토큰입니다.',
})
}
}
const { User, Post, Domain } = require('../models');
const jwt = require('jsonwebtoken');
exports.createToken = async (req, res) => {
const { clientSecret } = req.body;
try {
const domain = await Domain.findOne({
where: { clientSecret },
include: [{
model: User,
attributes: ['id', 'nick'],
}]
});
if (!domain) {
return res.status(401).json({
code: 401,
message: '등록되지 않은 도메인입니다. 먼저 도메인을 등록하세요.',
});
}
const token = jwt.sign({
id: domain.User.id,
nick: domain.User.nick,
}, process.env.JWT_SECRET, {
expiresIn: '1m',
issuer: 'nodebird',
})
return res.json({
code: 200,
message: '토큰 발급되었습니다.',
token,
})
} catch (error) {
console.error(error);
return res.status(500).json({
code: 500,
message: '서버 에러',
})
}
};
const axios = require('axios');
exports.test = async (req, res, next) => {
try {
if (!req.session.jwt) {
const tokenResult = await axios.post('http://localhost:8002/v1/token', {
clientSecret: process.env.CLIENT_SECRET,
});
if (tokenResult.data?.code === 200) {
req.session.jwt = tokenResult.data.token;
} else {
return res.status(tokenResult.data?.code).json(tokenResult.data);
}
}
const result = await axios.get('http://localhost:8002/v1/test', {
headers: { authorization: req.session.jwt }
});
return res.json(result.data);
} catch (error) {
console.error(error);
if (error.response?.status === 419) {
return res.json(error.response.data);
}
return next(error);
}
};
const { User, Post, Domain } = require('../models');
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
...
// 요청에 회수제한을 둠
// 무료랑 프리미엄을 나누기 위해 미들웨어 확장패턴 사용
exports.apiLimiter = async (req, res, next) => {
let user;
if (res.locals.decoded) {
user = await User.findOne({ where: { id: res.locals.decoded.id } });
}
rateLimit({
windowMs: 60 * 1000,
max: user?.type === 'premium' ? 1000 : 10,
handler(req, res) {
res.status(this.statusCode).json({
code: this.statusCode,
message: '1분에 한 번만 요청할 수 있습니다.',
});
}
}) (req, res, next);
}
// 옛 버전 사용하지 말라고 에러 안내
exports.deprecated = (req, res) => {
res.status(410).json({
code: 410,
message: '새로운 버전이 나왔습니다. 새로운 버전을 사용하세요.',
});
}
<!DOCTYPE html>
<html>
<head>
<title>프론트 API 요청</title>
</head>
<body>
<div id="result"></div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
axios.post('http://localhost:8002/v3/token', {
clientSecret: '{{key}}',
})
.then((res) => {
document.querySelector('#result').textContent = JSON.stringify(res.data);
})
.catch((err) => {
console.error(err);
});
</script>
</body>
</html>

const express = require('express');
const { verfiyToken, apiLimiter, corsWhenDomainMatches } = require('../middlewares');
const { createToken, tokenTest, getMyPosts, getPostsByHashtag } = require('../controllers/v2');
const cors = require('cors');
const router = express.Router();
// 모든 라우터에 적용
router.use(corsWhenDomainMatches);
// /v2/token
router.post('/token', apiLimiter, createToken); //req.body.clientSecret 을 이용해서 토큰 생성
router.get('/test', verfiyToken, apiLimiter, tokenTest);
router.get('/posts/my', verfiyToken, apiLimiter, getMyPosts);
router.get('/posts/hashtag/:title', verfiyToken, apiLimiter, getPostsByHashtag);
module.exports = router;
const { User, Post, Domain } = require('../models');
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
const cors = require('cors');
...
// 미들웨어 확장 패턴 사용
// cors의 origin부분에는 'http://localhost:4000'도 사용 가능 (사람마다 다름)
// 하지만 사람마다 다르기 때문에 클라이언트에서 요청할 때 보내는 req의 origin을 사용
exports.corsWhenDomainMatches = async (req, res, next) => {
const domain = await Domain.findOne({
where: { host: new URL(req.get('origin'))?.host } // localhost:4000
});
if (domain) {
cors({
origin: req.get('origin'), // http://localhost:4000
credentials: true, // 쿠키요청 허용 (origin에 * 사용 불가)
}) (req, res, next);
} else {
next();
}
}
router.use(cors());
router.use((req, res, next) => {
cors()(req, res, next);
});
