import bcrypt from "bcryptjs"; import { NextFunction, Request, Response } from "express"; import jwt, { JwtPayload } from "jsonwebtoken"; import isLength from "validator/lib/isLength"; import isEmail from "validator/lib/isEmail"; import { asyncWrap } from "../helpers"; import { userDb } from "../db"; import { jwtCofig, envConfig, cookieConfig } from "../config"; export interface TypedRequestAuth extends Request { auth: T; } export const login = asyncWrap(async (req, res) => { const { email, password } = req.body; console.log(`email: ${email}, password: ${password}`); // 1) 사용자 존재 확인 const user = await userDb.findUserByEmail(email, true); console.log("user =", user); if (!user) { return res.status(422).send(`${email} 사용자가 존재하지 않습니다`); } // 2) 비밀번호 확인 const passwordMatch = await bcrypt.compare(password, user.password); if (!passwordMatch) { return res.status(401).send("잘못된 비밀번호를 입력하셨습니다"); } // 3) 비밀번호가 맞으면 토큰 생성 const token = jwt.sign({ userId: user.id }, jwtCofig.secret, { //userId를 토큰에다 넣는 중. expiresIn: jwtCofig.expires, }); res.cookie(cookieConfig.name, token, { //token은 쿠키에 무엇을 실렸는가 이다. 항상 갖고 있다가 홈페이지 들어가면 서버로 접속 maxAge: cookieConfig.maxAge, // 이 기간 내에서만 유효 path: "/", //어떠한 경로에 관해서만 쓴다. 지금은 전부에 쓴다. httpOnly: envConfig.mode === "production", //false면 브라우저에서 쿠키를 조작, true면 조작할 수 없다. secure: envConfig.mode === "production", //true 면 https를 통해서만 쿠키 전달, false면 }); // 5) 사용자 반환 res.json({ isLoggedIn: true, email: user.email, }); }); export const logout = (req: Request, res: Response) => { res.clearCookie(cookieConfig.name); res.send("Logout Successful"); }; export const requireLogin = asyncWrap(async (reqExp, res, next) => { const req = reqExp as TypedRequestAuth; try { // 1) 쿠키 토큰 존재 여부 확인 const token = req.cookies[cookieConfig.name]; //클라이언트 쪽에서 보낸 토큰을 받는중 if (!token) { throw new Error("토큰이 존재하지 않습니다"); } // 2) 쿠키 유효성 검사 const decodedUser = jwt.verify(token, jwtCofig.secret); // 아까보낸 토근을 디코딩중. // 3) 요청 객체에 토큰 사용자 객체 추가 req.auth = decodedUser; next(); // 에러가 안나오면 next 사용, 나오면 catch쪽으로. } catch (error) { res.clearCookie(cookieConfig.name); console.log("error in requreLogin===\n", error); return res .status(401) .json({ message: "로그인이 필요합니다", redirectUrl: "/login" }); } }); export const signup = asyncWrap(async (req, res) => { const { name, email, password } = req.body; // 1) name, email, password 유효성 검사 if (!isLength(name ?? "", { min: 2, max: 10 })) { return res.status(422).send("이름은 2-10자로 입력해주세요"); } else if (!isLength(password ?? "", { min: 6 })) { return res.status(422).send("비밀번호는 6자 이상으로 입력해주세요"); } else if (!isEmail(email ?? "")) { return res.status(422).send("유효하지 않은 이메일입니다"); } // 2) 사용자 중복 확인 const userExist = await userDb.isUser(email); if (userExist) { return res.status(422).send(`${email} 사용자가 이미 존재합니다`); } // 3) 비밀번호 암호화는 useDb.createUser에서 처리 const hash = await bcrypt.hash(password, 10); // 4) 새로운 사용자 만들기 const newUser = await userDb.createUser({ email, password, }); // 5) 사용자 반환(내부적으로 몽구스가 toJSON() 호출) res.json(newUser); });