mariadb.js 로 코드와 db 연동
//mysql 모듈 소환
const mariadb = require("mysql2");
//DB와 연결 통로 생성
const connection = mariadb.createConnection({
hose: "localhost",
user: "root",
password: "rood",
database: "Bookshop",
dataStrings: true,
});
module.exports = connection;
users.js 회원가입 api
//회원가입
router.post("/join", (req, res) => {
const {email, password} = req.body;
let sql = 'INSERT INTO users (email, password) VALUES (?, ?)';
let values = [email, password];
conn.query(sql, values,(err,results)=>{
if(err){
console.log(err);
return res.status(400).end(); //Bad request
}
res.status(201).json(results);
})
res.json("회원가입");
});
npm http-status-codes
status 상태코드 숫자가 아닌, 변수에 할당해서 표현하기
모듈 가져올때 전달 메뉴가 많아서 json 형태로 많이 전달함
const { StatusCodes } = require("http-status-codes"); //status codes module
return res.status(StatusCodes.BAD_REQUEST).end(); //Bad request
res.status(StatusCodes.CREATED).json(results);
node.js 패키지 파일 구조
app.js : 프로젝트의 메인 라우터 역할
/routes > users.js, vooks.js : 하위 라우터 역할
# 단점 : 라우터가 로직까지 다 수행할때 > 프로젝트 규모가 커질수록 코드가 복잡하고, 가독성 떨어지고, 트러블슈팅 X > 유지보수하기 어렵다
*유지보수란? 10년이상 운영하면서, 요구사항 반영하고 에러를 해결하는 등...
#해결 : 코드를 간결하고 가독성이 높게 만들기
콜백함수를 컨트롤러로 분리하자!
👍 컨트롤러
프로젝트에서 매니저 역할을 하는 파일
직접 동작하지 않고, 다른 파일이 동작하도록 연결하는 역할
router를 통해서 사용자의 요청 req가 url을 따라서 찾아오면, 콜백함수(controller)가 맞이하고, 다른 파일에서 일을 시키고, 다시 controller가 사용자에게 res를 돌려준다.
`../controller/UserController.js`에 회원가입의 콜백함수를 가져오고, 모듈화해서 export 한다.
const conn = require("../mariadb"); //db module
const { StatusCodes } = require("http-status-codes"); //status codes module
const join = (req, res) => {
const { email, password } = req.body;
let sql = "INSERT INTO users (email, password) VALUES (?, ?)";
let values = [email, password];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.CREATED).json(results);
});
}
module.exports =join;
그리고 users.js 는 라우터 경로 역할만 하는것! 콜백함수는 모듈화된 join을 받아와서 사용한다.
const join = require("../controller/UserController");
router.use(express.json());
router.post("/join", join);
유효성 검사는 콜백함수 호출 전에 실행되야 하므로 users.js에서 선언한다.
로그인 api
const login = (req, res) => {
const { email, password } = req.body;
let sql = "SELECT * FROM users WHERE email=?";
conn.query(sql, email, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
const loginUser = results[0];
if (loginUser && loginUser.password === password) {
//토근 발행
const token = jwt.sign(
{
email: loginUser.email,
},
process.env.PRIVATE_KEY,
{
expiresIn: "5m",
issuer: "dah",
}
);
//토큰 쿠키에 담기
res.cookie("token", token, {
httpOnly: true,
});
console.log(token);
return res.status(StatusCodes.OK).json(results);
} else {
return res.status(StatusCodes.UNAUTHORIZED).end();
}
});
};
403 Forbidden 접근권한 없다 > 누군지 알고있을경우
401 UnAuthorized 비인증 상태이다 > 누군지 모를 경우
비밀번호 초기화 요청 + 초기화
const passwordResetRequest = (req, res) => {
const { email } = req.body;
let sql = "SELECT * FROM users WHERE email = ?";
conn.query(sql, email, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
//이메일로 유저가 있는지 찾아봄
const user = results[0];
if (user)
return res.status(StatusCodes.OK).json({
email: email,
});
else return results.status(StatusCodes.UNAUTHORIZED).end();
});
};
const passwordReset = (req, res) => {
const { email, password } = req.body;
let sql = "UPDATE users SET password = ? WHERE email = ?";
let values = [password, email];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
if (results.affectedRows == 0)
return res.status(StatusCodes.BAD_REQUEST).end();
else return res.status(StatusCodes.OK).json(results);
});
};
비밀번호 password 암호화
crypto 모듈
const crypto = require("crypto"); //crypto module : 암호화
const salt = crypto.randomBytes(64).toString("base64");
const hashPwd = crypto
.pbkdf2Sync(password, salt, 10000, 64, "sha512")
.toString("base64");
`salt` : hashPwd 생성할때 쓰는 변수. randomBytes 매개변수를 가지고 랜덤으로 만들어서, 문자열로 만든다.
`hashPwd` : `pbkdf2Sync` (해싱할 값, 랜덤 salt 값, 해시함수 반복횟수, 키 길이, 알고리즘)
단방향 알고리즘 > 복호화 불가능하다.
salt를 랜덤으로 하면, 계속 수시로 변경된다. > salt는 DB에 저장한다?!
로직
회원가입 시, 비밀번호를 암호화해서 암호화된 비밀번호와, salt 값을 같이 저장한다.
로그인 시, 이메일 & 비밀번호(날 것) > salt 값 꺼내서 비밀번호 암호화 해보고 > DB 비밀번호와 비교
회원가입 시,
let sql = "INSERT INTO users (email, password, salt) VALUES (?, ?, ?)";
//비밀번호 암호화
const salt = crypto.randomBytes(10).toString("base64");
const hashPwd = crypto
.pbkdf2Sync(password, salt, 10000, 10, "sha512")
.toString("base64");
let values = [email, hashPwd, salt]; //암호화된 pwd와 salt 값을 같이 DB에 저장
로그인 시,
//salt 값 꺼내서 날것으로 들어온 비밀번호를 암호화 해보고
const hashPwd = crypto
.pbkdf2Sync(password, loginUser.salt, 10000, 10, "sha512")
.toString("base64");
//DB 비밀번호랑 비교
if (loginUser && loginUser.password == hashPwd) {
//...
비밀번호 초기화 때도 암호화 시키기
select 과정 필요한가? 이전 passwordResetRequest 에서 전달한 password를 다시 받아서 쓰기 때문에 무관!
const passwordReset = (req, res) => {
const { email, password } = req.body;
let sql = "UPDATE users SET password = ?, salt = ? WHERE email = ?";
//비밀번호 암호화
const salt = crypto.randomBytes(10).toString("base64");
const hashPwd = crypto
.pbkdf2Sync(password, salt, 10000, 10, "sha512")
.toString("base64");
let values = [hashPwd, salt, email];
conn.query(sql, values, (err, results) => {
☑️ 배운 점
crypto 모듈. 비밀번호를 암호화하여 보관하는 알고리즘 개굿👍
http-status-codes 모듈로 `Statuscode.상태` 를 간편히 나타낼 수 있다는 것
controller로 코드 분리하는법 > 저번주 팀미팅에서 팀원분이 MVC모델 적용해서 코드 분리했다고 설명해주셨는데, 강사님께서 적용해주셨다. 그분처럼 한발 앞서나가서 생각하고 적용해보자~!