app.js
import express from "express";
import helmet from "helmet";
import morgan from "morgan";
import cors from "cors";
import tweetController from "./tweet/tweet.controller.js";
import authController from "./auth/auth.controller.js";
const app = express();
app.use(express.json());
app.use(morgan("dev"));
app.use(helmet());
app.use(cors());
app.use("/tweet", tweetController);
app.use("/auth", authController);
app.use((req, res, next) => {
res.sendStatus(404);
});
app.use((err, req, res, next) => {
res.sendStatus(500);
});
app.listen(8000);
auth.js
import jwt from "jsonwebtoken";
import * as userRepository from "../user/user.repository.js";
export const auth = async (req, res, next) => {
const authHeader = req.get("Authorization");
if (authHeader && authHeader.startsWith("Bearer ")) {
const token = authHeader.split(" ")[1];
jwt.verify(token, "secret", async (err, data) => {
if (err) {
res.status(401).json({ message: "인증 에러" });
}
else {
const user = await userRepository.findById(data.id);
if (user) {
req.token = token;
req.userId = user.id;
next();
}
else {
res.status(401).json({ message: "인증 에러" });
}
}
});
}
else {
res.status(401).json({ message: "인증 에러" });
}
};
auth.controller.js
import express from "express";
import { body } from "express-validator";
import { auth } from "../middleware/auth.js";
import { validate } from "../middleware/validator.js";
import * as authService from "./auth.service.js";
const authController = express.Router();
const loginValidation = [
body("username").trim().notEmpty().withMessage("username을 입력해주세요."),
body("password")
.trim()
.isLength({ min: 5 })
.withMessage("비밀번호는 최소 5글자 이상 입력해주세요."),
validate,
];
const signUpValidation = [
...loginValidation,
body("name").trim().notEmpty().withMessage("이름이 비어있습니다."),
body("email")
.isEmail()
.normalizeEmail()
.withMessage("이메일 형식에 맞지 않습니다"),
validate,
];
authController.post("/signup", signUpValidation, authService.signup);
authController.post("/login", loginValidation, authService.login);
authController.get("/me", auth, authService.me);
export default authController;
auth.service.js
import * as userRepository from "../user/user.repository.js";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
const jwtSecretKey = "secret";
const jwtExpiresInDays = "2d";
const bcryptSaltRounds = 12;
const createToken = (id) => {
return jwt.sign({ id }, jwtSecretKey, { expiresIn: jwtExpiresInDays });
};
export const signup = async (req, res) => {
const { username, password, name, email } = req.body;
const user = await userRepository.findByUsername(username);
if (user) {
return res.status(409).json({ message: `이미 가입된 유저입니다.` });
}
const hashed = await bcrypt.hash(password, bcryptSaltRounds);
const createdUser = await userRepository.createUser({
username,
password: hashed,
name,
email,
});
const token = createToken(createdUser.id);
res.status(201).json({ token, username });
};
export const login = async (req, res) => {
const { username, password } = req.body;
const user = await userRepository.findByUsername(username);
if (!user) {
return res
.status(401)
.json({ message: "아이디 혹은 비밀번호가 틀렸습니다." });
}
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res
.status(401)
.json({ message: "아이디 혹은 비밀번호가 틀렸습니다." });
}
const token = createToken(user.id);
res.status(200).json({ token, username });
};
export const me = async (req, res) => {
const user = await userRepository.findById(req.userId);
if (user) {
return res
.status(200)
.json({ token: req.token, username: user.username });
}
res.status(404).json({ message: "유저가 존재하지 않습니다." });
};
import express from "express";
import * as tweetService from "./tweet.service.js";
import { body } from "express-validator";
import { validate } from "../middleware/validator.js";
import { auth } from "../middleware/auth.js";
const tweetController = express.Router();
const textValidator = [
body("text")
.trim()
.isLength({ min: 5 })
.withMessage("text를 최소 5 글자 이상 입력해주세요."),
validate,
];
tweetController.get("/", auth, tweetService.getTweets);
tweetController.get("/:id", auth, tweetService.getTweet);
tweetController.post("/", auth, textValidator, tweetService.createTweet);
tweetController.put("/:id", auth, textValidator, tweetService.updateTweet);
tweetController.delete("/:id", auth, tweetService.deleteTweet);
export default tweetController;
1. 토큰 없이 API 요청 (401: Unauthorized)
2. 토큰을 담아서 API 요청 (200)
3. 다른 유저의 토큰을 담아서 API 요청 (403: Forbidden)