Commit 9aed2b3a authored by Yoon, Daeki's avatar Yoon, Daeki 😅
Browse files

백엔드 인증 작업 로그인, 회원가입

parent fd387eae
{
"arrowParens": "always",
"bracketSpacing": true,
"configPath": "",
"documentSelectors": [],
"embeddedLanguageFormatting": "auto",
"enable": true,
"enableDebugLogs": false,
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "css",
"ignorePath": ".prettierignore",
"insertPragma": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"prettierPath": "",
"printWidth": 80,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requireConfig": false,
"requirePragma": false,
"resolveGlobalModules": false,
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"useEditorConfig": true,
"useTabs": false,
"vueIndentScriptAndStyle": false,
"withNodeModules": false
}
...@@ -113,3 +113,99 @@ project_directory ...@@ -113,3 +113,99 @@ project_directory
- `src/index.tsx`가 프로그램 실행 진입점입니다. - `src/index.tsx`가 프로그램 실행 진입점입니다.
- `src/SurveyRouter.tsx`는 라우트에 관련된 것을 다룹니다. - `src/SurveyRouter.tsx`는 라우트에 관련된 것을 다룹니다.
- `src/App.tsx`가 최상위 레이아웃 입니다. - `src/App.tsx`가 최상위 레이아웃 입니다.
## 코드 포맷터 설정
### VS Code 설정
VS Code에서 `Ctrl + Shift + p`를 눌러 `설정 검색` 창에 `editor: format on save`를 검색하여 항목을 선택 합니다. 이 기능은 파일을 저장할 때마다 파일 확장자에 따라서 자동으로 포맷을 맞추고 저장을 합니다.
### Prettier 설치
VS Code 확장에서 [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)를 설치합니다.
- `.prettierrc.json` 파일 설정(이미 설정되어 있습니다).
- 대부분의 파일에서 VS Code 맨 아래 상태 바에 Prettier라는 항목이 체크 표시되어 나타나야 합니다.
## 백엔드 데이터베이스
다음은 모두 백엔드 터미널에서 실행해야 합니다. 즉, 프로젝트 루트 디렉토리에서 실행해야 합니다.
### 몽고디비 설치
[몽고디비 윈도우즈 설치](https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-windows/#install-mongodb-community-edition)
- 윈도우즈 서비스로 실행하도록 설치합니다.
### 몽고디비 ORM(mongoose) 설치
```bash
npm install mongoose --save
```
## 백엔드 유효성 검사
```bash
npm install validator
npm i -D @types/validator
```
`validator`는 문자열을 검사하는 모듈입니다.
사용방법
```js
import isLength from "validator/lib/isLength";
import isEmail from "validator/lib/isEmail";
export const signup = (req, res) => {
// 생략
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("유효하지 않은 이메일입니다");
}
// 생략
};
```
## 백엔드 비밀번호 암호화
`bcryptjs` 모듈은 암호화합니다.
```bash
npm i bcryptjs
npm i -D @types/bcryptjs
```
사용법
```js
import bcrypt from "bcryptjs";
const hash = await bcrypt.hash(password, 10);
const newUser = await userDb.createUser({
email,
password: hash,
});
```
## 백엔드 쿠기 파서
쿠키 파서를 이용해서 쿠키를 해석합니다.
```bash
npm i cookie-parser
npm i -D @types/cookie-parser
```
사용법
```js
// src/app.ts
import cookieParser from "cookie-parser";
app.use(cookieParser());
```
...@@ -23,12 +23,21 @@ ...@@ -23,12 +23,21 @@
"author": "Daeki Yoon", "author": "Daeki Yoon",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.2",
"@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/jsonwebtoken": "^8.5.8",
"@types/validator": "^13.7.3",
"nodemon": "^2.0.16", "nodemon": "^2.0.16",
"ts-node": "^10.8.1", "ts-node": "^10.8.1",
"typescript": "^4.7.4" "typescript": "^4.7.4"
}, },
"dependencies": { "dependencies": {
"express": "^4.18.1" "bcryptjs": "^2.4.3",
"cookie-parser": "^1.4.6",
"express": "^4.18.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^6.4.1",
"validator": "^13.7.0"
} }
} }
import express from "express"; import cookieParser from "cookie-parser";
import express, { Request, Response, NextFunction } from "express";
import router from "./routes";
const app = express(); const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.use("/api", router);
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
console.log("익스프레스 에러: ", err);
res.status(err.statusCode || 500).send(err.message || "서버 에러");
});
app.use(function (req, res, next) {
res.status(404).send("잘못된 경로를 요청했습니다");
});
export default app; export default app;
export const mongoUri = "mongodb://localhost/survey";
export const jwtCofig = {
secret: "HelloSecretString",
expires: "7d",
};
export const cookieConfig = {
name: "survey",
maxAge: 60 * 60 * 24 * 7 * 1000,
};
export const envConfig = {
mode: "development",
};
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<T> 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, {
expiresIn: jwtCofig.expires,
});
// 4) 토큰을 쿠키에 저장
res.cookie(cookieConfig.name, token, {
maxAge: cookieConfig.maxAge,
path: "/",
httpOnly: envConfig.mode === "production",
secure: envConfig.mode === "production",
});
// 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<string | JwtPayload>;
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();
} 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) 비밀번호 암호화
const hash = await bcrypt.hash(password, 10);
// 4) 새로운 사용자 만들기
const newUser = await userDb.createUser({
email,
password: hash,
});
// 5) 사용자 반환
res.json(newUser);
});
export * as userCtrl from "./user.controller";
export * as authCtrl from "./auth.controller";
import { userDb } from "../db";
import { asyncWrap } from "../helpers/asyncWrap";
export const getUsers = asyncWrap(async (req, res) => {
const users = await userDb.getUsers();
return res.json(users);
});
export const createUser = asyncWrap(async (req, res) => {
const user = req.body;
console.log("user body", user);
const newUser = await userDb.createUser(user);
return res.json(user);
});
export * as userDb from "./user.db";
import { IUser, User } from "../models";
export const createUser = async (user: IUser) => {
const newUser = await User.create(user);
return newUser;
};
export const findUserByEmail = async (
email: string,
includePassword: boolean = false
) => {
let user;
if (includePassword) {
user = await User.findOne({ email }).select("+password");
} else {
user = await User.findOne({ email });
}
return user;
};
export const getUsers = async () => {
const users = await User.find({});
return users;
};
export const isUser = async (email: string) => {
const user = await User.findOne({ email });
if (user) {
return true;
} else {
return false;
}
};
import { Request, Response, NextFunction } from "express";
type AsyncRequestHandler = (
req: Request,
res: Response,
next: NextFunction
) => Promise<any>;
export const asyncWrap = (asyncFn: AsyncRequestHandler) => {
return async (req: Request, res: Response, next: NextFunction) => {
try {
return await asyncFn(req, res, next);
} catch (error) {
return next(error);
}
};
};
export { asyncWrap } from "./asyncWrap";
import { connect } from "mongoose";
import app from "./app"; import app from "./app";
import { mongoUri } from "./config";
app.listen(3000, () => { connect(mongoUri)
console.log(`server is running on port ${3000}`); .then((mgs) => {
}); console.log(`Mongoose is connected with version: ${mgs.version}`);
app.listen(3000, () => {
console.log(`server is running on port ${3000}`);
});
})
.catch((error) => {
console.log(`에러: ${error}`);
});
export { default as User, IUser } from "./user.model";
import { model, Schema } from "mongoose";
interface IRole {
name: string;
priority: number;
}
const schema = new Schema<IRole>({
name: { type: String },
priority: { type: Number },
});
export default model<IRole>("Role", schema);
import { model, Schema, Types } from "mongoose";
export interface IUser {
email: string;
name?: string;
password: string;
role?: Types.ObjectId;
}
const validateEmail = (email: string) => {
const re = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
return re.test(email);
};
const schema = new Schema<IUser>({
email: {
type: String,
rquired: true,
unique: true,
validate: [validateEmail, "이메일을 입력해주세요"],
},
name: { type: String },
password: { type: String, required: true, select: false },
role: { type: Schema.Types.ObjectId, ref: "Role" },
});
export default model<IUser>("User", schema);
import express from "express";
import { authCtrl } from "../controllers";
const router = express.Router();
router.route("/signup").post(authCtrl.signup);
router.route("/login").post(authCtrl.login);
router.route("/logout").get(authCtrl.logout);
export default router;
import express from "express";
import userRouter from "./user.route";
import authRouter from "./auth.route";
const router = express.Router();
router.use("/users", userRouter);
router.use("/auth", authRouter);
export default router;
import express from "express";
import { userCtrl, authCtrl } from "../controllers";
const router = express.Router();
router
.route("/")
.get(authCtrl.requireLogin, userCtrl.getUsers)
.post(authCtrl.requireLogin, userCtrl.createUser);
export default router;
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment