// router/auth.js
import express from "express";
import { body } from 'express-validator';
import * as authController from '../controller/auth.js';
import { validate } from "../middleware/validator.js";
import { isAuth } from '../middleware/auth.js';
const router = express.Router();
const validateLogin = [
body('username').trim().notEmpty().withMessage('username을 입력하세요'),
body('password').trim().isLength({min:4}).withMessage('password는 최소 4자 이상 입력하세요'), validate
];
const validateSignup = [
... validateLogin,
body('name').trim().notEmpty().withMessage('name을 입력하세요'),
body('email').isEmail().withMessage('이메일 형식 확인하세요'), validate,
body('url').isURL().withMessage('URL 형식 확인하세요'), validate
];
router.post('/signup', validateSignup, authController.signup);
router.post('/login', validateLogin, authController.login);
router.get('/me', isAuth, authController.me);
export default router;
// router/tweets.js
import express from "express";
import * as tweetController from '../controller/tweet.js';
import { body } from 'express-validator';
import { validate } from "../middleware/validator.js";
import { isAuth } from "../middleware/auth.js";
const router = express.Router();
/*
Post,Put에 text에 대해 빈문자열을 없애고, 최소 3자 이상 입력해야 데이터를 저장하도록 API에 적용
*/
const validateTweet = [
body('text').trim().isLength({min: 3}).withMessage('최소 3자 이상 입력'), validate
]
// 해당 아이디에 대한 트윗 가져오기
// GET
// http://localhost:8080/tweets?username=:username
router.get('/', isAuth, tweetController.getTweets);
// 글번호에 대한 트윗 가져오기
// GET
// http://localhost:8080/tweets/:id
router.get('/:id', isAuth, tweetController.getTweet);
// 트윗하기
// POST
// http://localhost:8080/tweets
// name, username, text
// json 형태로 입력 후 추가된 데이터까지 모두 json으로 출력
router.post('/', validateTweet, isAuth, tweetController.createTweet);
// 트윗 수정하기
// PUT
// http://localhost:8080/tweets/:id
// id, username, text
// json 형태로 입력 후 변경된 데이터까지 모두 json으로 출력
router.put('/:id', validateTweet, isAuth, tweetController.updateTweet);
// 트윗 삭제하기
// DELETE
// http://localhost:8080/tweets/:id
router.delete('/:id', isAuth, tweetController.deleteTweet);
export default router;
// middleware/auth.js
import jwt from 'jsonwebtoken';
import * as authRepository from '../data/auth.js';
const AUTH_ERROR = {message: "인증에러"};
export const isAuth = async(req, res, next) => {
const authHeader = req.get('Authorization');
console.log(authHeader);
if(!(authHeader && authHeader.startsWith('Bearer'))) {
console.log('에러1');
return res.status(401).json(AUTH_ERROR);
}
const token = authHeader.split(' ')[1];
jwt.verify(
token, 'abcd1234%^&*',async(error, decoded) => {
if(error) {
console.log('에러2');
return res.status(401).json(AUTH_ERROR);
}
const user = await authRepository.findById(decoded.id);
if(!user){
console.log('에러3');
return res.status(401).json(AUTH_ERROR);
}
req.userId = user.id;
next();
}
);
}
//data/auth.js
let users = [
{
id: '1',
// userid: "apple",
username: "apple",
password: "$2b$10$TvIhNWYtvCNzOA0ZkmNg6.YU3EG49msPLzxphLYahMT63DVWRWZTe",
name: "김사과",
email: "apple@apple.com",
url: "https://www.logoyogo.com/web/wp-content/uploads/edd/2021/02/logoyogo-1-45.jpg"
},
{
id: '2',
// userid: "banana",
username: "banana",
password: "$2b$10$TvIhNWYtvCNzOA0ZkmNg6.YU3EG49msPLzxphLYahMT63DVWRWZTe",
name: "반하나",
email: "banana@banana.com",
url: "https://img.freepik.com/premium-vector/banana-cute-kawaii-style-fruit-character-vector-illustration_787461-1772.jpg"
}
];
// 아이디 중복검사
export async function findByUsername(username){
return users.find((user) => user.username === username);
}
// id 중복검사
export async function findById(id){
return users.find((user) => user.id === id);
}
export async function createUser(user){
const created = {id: '10', ...user }
users.push(created);
return created.id;
}
export async function login(username){
return users.find((users) => users.username === username);
}
// controller/auth.js
import * as authRepository from '../data/auth.js';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
const secretKey = "abcd1234%^&*";
const jwtExpiresInDays = '2d';
const bcryptSaltRounds = 10;
// async function makeToken(id){
// const token = jswonwebtoken.sign({
// id: id,
// isAdmin: false
// }, secret, {expiresIn: '1h'})
// return token;
// }
function createJwtToken(id){
return jwt.sign({id}, secretKey, {expiresIn: jwtExpiresInDays});
}
// 회원가입
export async function signup(req, res, next){
const { username, password, name, email, url } = req.body;
const found = await authRepository.findByUsername(username);
if(found){
return res.status(409).json({message:`${username}이 이미 있습니다`});
}
const hashed =bcrypt.hash(password, bcryptSaltRounds);
const userId = await authRepository.createUser({username, hashed, name, email, url});
const token = createJwtToken(userId);
res.status(201).json({token, username});
}
//로그인
export async function login(req, res, next){
const { username, password } = req.body;
// const user = await authRepository.login(username);\
const user = await authRepository.findByUsername(username);
if(!user){
return res.status(401).json({message: `아이디를 찾을 수 없음`});
}
const isValidpassword = await bcrypt.compareSync(password, user.password);
if(!isValidpassword){
return res.status(401).json({message: `비밀번호가 틀렸음`});
}
const token =createJwtToken(user.id);
res.status(200).json({token, username});
}
//토큰이 있는지 여부, 로그인 흔적 확인
// export async function verify(req, res, next){
// const token = req.header['Token'];
// if(token){
// res.status(200).json(token);
// }
// }
export async function me(req, res, next){
const user = await authRepository.findById(req.userId);
if(!user){
return res.status(404).json({message: `일치하는 사용자가 없음`});
}
res.status(200).json({token: req.token, username: user.username});
}
// postman
🤍
ChatGPT에게 물어본 오늘의 공부-
✔ router.get('/', isAuth, tweetController.getTweets);
Express.js에서는 라우팅을 관리하기 위해 express.Router()를 사용합니다. express.Router()를 사용하면 라우팅을 모듈화하여 코드를 구조화할 수 있습니다. 위의 코드에서 router.get('/', isAuth, tweetController.getTweets); 부분은 express.Router()를 사용하여 정의된 라우터의 하나의 경로('/')에 대한 GET 요청을 처리하는 것입니다.
여기서 각 요소를 설명하겠습니다:
- express.Router():
- express.Router()는 Express의 라우터 객체를 생성하는 함수입니다. 이 객체는 HTTP 요청 메서드(GET, POST, PUT, DELETE 등)와 해당 경로에 대한 핸들러를 정의할 수 있는 메서드를 제공합니다.
- router.get('/'):
- router.get() 메서드는 GET 요청을 처리하기 위해 사용됩니다. 첫 번째 매개변수는 요청 경로를 나타내며, 두 번째 매개변수는 해당 경로에 대한 요청을 처리하는 핸들러 함수입니다.
- 위의 코드에서는 '/' 경로에 대한 GET 요청을 처리하기 위해 router.get() 메서드를 사용했습니다.
- isAuth:
- isAuth는 미들웨어 함수로, 해당 경로에 대한 요청을 인증(authentication)하는 역할을 합니다. 이 미들웨어가 요청을 처리하기 전에 실행되어 요청이 인증되었는지 확인하고, 인증되지 않은 경우 요청을 거부할 수 있습니다.
- 미들웨어는 router.get() 메서드의 두 번째 매개변수로 전달됩니다.
- tweetController.getTweets:
- tweetController.getTweets는 요청을 처리하는 컨트롤러 함수입니다. 해당 경로에 대한 요청이 들어오면 이 컨트롤러 함수가 실행되어 요청을 처리하고 응답을 반환합니다.
- 컨트롤러 함수는 미들웨어와 마찬가지로 router.get() 메서드의 두 번째 매개변수로 전달됩니다.
따라서 위의 코드는 '/' 경로에 대한 GET 요청을 처리하기 위해 미들웨어(isAuth)를 거쳐 tweetController.getTweets 컨트롤러 함수를 실행하는 Express의 라우터를 정의하는 것입니다.
✔ /:id를 쓰는 이유?
Express.js에서 URL의 동적인 부분을 나타내기 위해 콜론(:)으로 시작하는 매개변수를 사용합니다. 이를 통해 경로의 특정 부분을 변수로 사용할 수 있습니다. 위의 코드에서 /:id는 경로의 일부를 나타내며, 이 부분에는 글의 고유한 식별자(ID)가 위치합니다.
이렇게 URL의 일부를 변수로 지정함으로써 동적인 경로를 처리할 수 있습니다. 예를 들어, '/tweets/1', '/tweets/2', '/tweets/3'과 같이 각각 다른 ID를 가지는 글에 대한 요청을 처리할 수 있습니다. Express는 이러한 동적인 매개변수를 추출하여 해당하는 요청을 처리하는 컨트롤러 함수에 전달합니다.
따라서 /:id를 사용하여 글번호를 나타내는 것은 URL의 동적인 부분을 나타내고, 해당하는 글에 대한 요청을 처리하기 위한 수단으로 활용됩니다.
✔ Authorization 헤더
Authorization 헤더는 HTTP 요청에 인증 정보를 포함하는 헤더입니다. 주로 클라이언트가 서버에 인증을 요청할 때 사용됩니다. 일반적으로 JWT(JSON Web Token)와 함께 사용되며, JWT를 포함한 인증 토큰을 Authorization 헤더에 담아서 서버에 전달합니다.
위의 코드에서 req.get('Authorization')를 통해 Authorization 헤더의 값을 가져오고 있습니다. 이 값을 통해 클라이언트가 서버에 전송한 인증 토큰을 확인하고, 해당 토큰을 통해 사용자를 인증할 수 있습니다.
만약 Authorization 헤더가 없거나, Bearer 스키마로 시작하지 않는다면, 클라이언트는 인증되지 않은 상태로 간주됩니다. 따라서 서버는 인증 에러를 반환하거나 적절한 조치를 취할 수 있습니다.
✔ authHeader.split(' ')
위 코드는 Authorization 헤더에서 JWT 토큰을 추출하기 위해 사용됩니다.
- authHeader.split(' '): Authorization 헤더를 공백을 기준으로 분할합니다. 이를 통해 Bearer와 토큰 값이 분리됩니다.
- [1]: 분할된 결과에서 두 번째 요소를 선택합니다. 여기서 두 번째 요소는 Bearer 뒤에 오는 JWT 토큰입니다.
따라서 authHeader.split(' ')[1]은 Authorization 헤더에서 JWT 토큰을 추출하는 과정을 나타냅니다. 이를 통해 클라이언트가 서버에 전송한 JWT 토큰을 가져올 수 있습니다.
✔ HTTP 상태 코드 409
HTTP 상태 코드 409는 "충돌"을 나타내며, 클라이언트의 요청이 서버의 현재 상태와 충돌하고 있음을 나타냅니다. 일반적으로 이러한 상황은 클라이언트가 리소스를 업데이트하려고 할 때 발생할 수 있습니다. 예를 들어, 클라이언트가 리소스를 업데이트하려는 동안 다른 클라이언트가 이미 해당 리소스를 변경한 경우 충돌이 발생할 수 있습니다.
409 충돌 상태 코드는 보통 다음과 같은 상황에서 발생할 수 있습니다:
- 동시 업데이트 충돌: 두 개 이상의 클라이언트가 동시에 같은 리소스를 수정하는 경우 발생할 수 있습니다. 이러한 경우 서버는 충돌을 방지하기 위해 클라이언트의 요청을 거부하고 409 상태 코드를 반환할 수 있습니다.
- 예상하지 못한 상태 변경: 클라이언트가 리소스를 수정하기 전에 해당 리소스의 상태가 변경된 경우에도 충돌이 발생할 수 있습니다. 예를 들어, 클라이언트가 리소스의 업데이트를 시작하기 전에 다른 클라이언트가 이미 해당 리소스를 삭제한 경우가 이에 해당합니다.
이러한 경우에는 클라이언트가 충돌을 해결하고 다시 요청을 보내야 합니다. 일반적으로 클라이언트는 현재 서버 상태를 다시 확인하고, 충돌이 발생한 이유를 파악하여 적절한 조치를 취할 수 있습니다.
🤍