From b8e3d676813cee90b1c44a70c21fd460a7b117ac Mon Sep 17 00:00:00 2001 From: sillonjeu Date: Fri, 11 Jul 2025 15:45:59 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[feat]=20middleware(auth.js),=20modles(Auth?= =?UTF-8?q?.js),=20routes(auth.js),=20utils(auth.js)=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20#1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 42 ++++++++++++++++++++++++++----- middleware/auth.js | 32 +++++++++++++++++++++++ models/Auth.js | 63 ++++++++++++++++++++++++++++++++++++++++++++++ routes/auth.js | 50 ++++++++++++++++++++++++++++++++++++ utils/auth.js | 20 +++++++++++++++ 5 files changed, 201 insertions(+), 6 deletions(-) create mode 100644 middleware/auth.js create mode 100644 models/Auth.js create mode 100644 routes/auth.js create mode 100644 utils/auth.js diff --git a/app.js b/app.js index 9255653..875b24e 100644 --- a/app.js +++ b/app.js @@ -3,21 +3,51 @@ var express = require("express"); var path = require("path"); var cookieParser = require("cookie-parser"); var logger = require("morgan"); - +var cors = require("cors"); +/* --------------------------------------- */ var indexRouter = require("./routes/index"); var usersRouter = require("./routes/users"); - +/* --------------------------------------- */ +const mongoose = require("mongoose"); +const dotenv = require("dotenv"); +const jwt = require("jsonwebtoken"); +const SECRET_KEY = "MyJWT"; +/* --------------------------------------- */ +dotenv.config(); +pw = process.env.PW; +// TODO DB_URL 몽고디비 파고 넣기 +const DB_URL = ``; +mongoose + .connect(DB_URL, { + retryWrites: true, + w: "majority", + appName: "express-mongodb", + }) + .then(() => { + console.log("Connected Successful"); + }) + .catch((err) => { + console.log(err); + }); +/* --------------------------------------- */ var app = express(); - +app.use( + cors({ + origin: ["http://localhost:3000"], // TODO: 클라이언트 주소 배포하면 추가해주기 + methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], + credentials: true, + }) +); +/* --------------------------------------- */ app.use(logger("dev")); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, "public"))); - +/* --------------------------------------- */ app.use("/", indexRouter); -app.use("/users", usersRouter); - +app.use("/auth", authRouter); +/* --------------------------------------- */ // catch 404 and forward to error handler app.use(function (req, res, next) { next(createError(404)); diff --git a/middleware/auth.js b/middleware/auth.js new file mode 100644 index 0000000..0a82512 --- /dev/null +++ b/middleware/auth.js @@ -0,0 +1,32 @@ +const { verifyToken } = require("../utils/auth"); + +function authenticate(req, res, next) { + try { + let token; + if (req.headers.authorization) { + token = req.headers.authorization.split(" ")[1]; + } else if (req.cookies.authToken) { + token = req.cookies.authToken; + } + if (token) { + console.log("서버가 받은 토큰 (앞 30자):", token.slice(0, 30) + "..."); + } else { + console.log("서버가 받은 토큰: 없음"); + } + + const userPayload = verifyToken(token); + + if (!userPayload) { + throw new Error("유효하지 않거나 존재하지 않는 토큰입니다."); + } + + req.user = userPayload; + next(); + } catch (error) { + const authError = new Error("Authorization Failed: 인증에 실패했습니다."); + authError.status = 401; + next(authError); + } +} + +module.exports = { authenticate }; diff --git a/models/Auth.js b/models/Auth.js new file mode 100644 index 0000000..b5934cd --- /dev/null +++ b/models/Auth.js @@ -0,0 +1,63 @@ +const mongoose = require("mongoose"); +const { isEmail } = require("validator"); +const bcrypt = require("bcrypt"); + +const authSchema = new mongoose.Schema({ + email: { + type: String, + required: [true, "이메일을 입력해 주세요."], + unique: true, + lowercase: true, + validator: [isEmail, "올바른 이메일 형식이 아닙니다."], + }, + password: { + type: String, + required: [true, "비밀번호를 입력해 주세요."], + }, + nickname: { + type: String, + required: [true, "닉네임을 입력해 주세요."], + }, +}); + +authSchema.statics.login = async function (email, password) { + const auth = await this.findOne({ email }); + if (auth) { + const signup = await bcrypt.compare(password, auth.password); + if (signup) { + return auth.visibleUser; + } + throw Error("비밀번호가 맞지 않습니다."); + } + throw Error("이메일이 맞지 않습니다."); +}; + +const visibleUser = authSchema.virtual("visibleUser"); +visibleUser.get(function (value, virtual, doc) { + return { + _id: doc._id, + email: doc.email, + }; +}); + +authSchema.statics.signUp = async function (email, password, nickname) { + const salt = await bcrypt.genSalt(); + + try { + const hashedPassword = await bcrypt.hash(password, salt); + const auth = await this.create({ + email, + password: hashedPassword, + nickname, + }); + return { + _id: auth._id, + nickname: auth._id, + }; + } catch (err) { + throw err; + } +}; + +const Auth = mongoose.model("Auth", authSchema, "Auth"); +module.exports = Auth; diff --git a/routes/auth.js b/routes/auth.js new file mode 100644 index 0000000..076491b --- /dev/null +++ b/routes/auth.js @@ -0,0 +1,50 @@ +var express = require("express"); +const Auth = require("../models/Auth"); +const { createToken, verifyToken } = require("../utils/auth"); +var router = express.Router(); + +router.post("/signup", async (req, res, next) => { + try { + const { email, password, nickname } = req.body; + console.log(req.body); + const user = await Auth.signUp(email, password, nickname); + res.status(201).json(user); + } catch (err) { + console.error(err); + res.status(400); + next(err); + } +}); + +router.post("/login", async (req, res, next) => { + try { + const { email, password } = req.body; + const user = await Auth.login(email, password); + const tokenMaxAge = 60 * 60 * 24 * 3; + const token = createToken(user, tokenMaxAge); + user.token = token; + + // TODO: user 콘솔 한 번 찍어보기 + console.log(user); + + res.status(201).json(user); + } catch (err) { + console.error(err); + res.status(400); + next(err); + } +}); + +router.all("/logout", (req, res) => { + // 쿠키에 authToken이 있으면 지우기 + res.clearCookie("authToken", { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + path: "/", + }); + // 클라이언트에 응답 + res.status(200).json({ message: "로그아웃되었습니다." }); +}); + +module.exports = router; diff --git a/utils/auth.js b/utils/auth.js new file mode 100644 index 0000000..33bf5e6 --- /dev/null +++ b/utils/auth.js @@ -0,0 +1,20 @@ +const jwt = require("jsonwebtoken"); + +function createToken(visibleUser, maxAge = 60 * 60 * 24 * 3) { + return jwt.sign(visibleUser, process.env.JWT_SECRET || "MyJWT", { + expiresIn: maxAge, + }); +} + +function verifyToken(_token) { + if (!_token) { + return null; + } + const verifiedToken = jwt.verify(_token, process.env.JWT_SECRET || "MyJWT"); + return verifiedToken; +} + +module.exports = { + createToken, + verifyToken, +}; From 9d46d8b3949988c3f73d9c3347ab097415dca337 Mon Sep 17 00:00:00 2001 From: sillonjeu Date: Fri, 11 Jul 2025 16:15:13 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[feat]=20=EB=AA=BD=EA=B3=A0=EB=94=94?= =?UTF-8?q?=EB=B9=84=20=EA=B5=AC=EC=B6=95,=20DB=5FURL=EC=97=90=20.env=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=A1=9C=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=A0=80=EC=9E=A5=20#1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 3 +- package-lock.json | 149 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 151 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 875b24e..85a8be5 100644 --- a/app.js +++ b/app.js @@ -15,8 +15,7 @@ const SECRET_KEY = "MyJWT"; /* --------------------------------------- */ dotenv.config(); pw = process.env.PW; -// TODO DB_URL 몽고디비 파고 넣기 -const DB_URL = ``; +const DB_URL = `mongodb+srv://pius0316:${pw}@upanddown.n3ptkyf.mongodb.net/?retryWrites=true&w=majority&appName=UpAndDown`; mongoose .connect(DB_URL, { retryWrites: true, diff --git a/package-lock.json b/package-lock.json index 830f843..41148fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,15 +7,41 @@ "": { "name": "upanddown-server", "version": "0.0.0", + "license": "ISC", "dependencies": { "cookie-parser": "~1.4.4", "debug": "~2.6.9", "ejs": "~2.6.1", "express": "~4.16.1", "http-errors": "~1.6.3", + "mongodb": "^6.17.0", "morgan": "~1.9.1" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", + "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -68,6 +94,15 @@ "node": ">= 0.8" } }, + "node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -323,6 +358,12 @@ "node": ">= 0.6" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -368,6 +409,62 @@ "node": ">= 0.6" } }, + "node_modules/mongodb": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", + "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, "node_modules/morgan": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", @@ -448,6 +545,15 @@ "node": ">= 0.10" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -538,6 +644,15 @@ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "license": "ISC" }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -547,6 +662,18 @@ "node": ">= 0.6" } }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -586,6 +713,28 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } } } } diff --git a/package.json b/package.json index 8149758..b3874ba 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "ejs": "~2.6.1", "express": "~4.16.1", "http-errors": "~1.6.3", + "mongodb": "^6.17.0", "morgan": "~1.9.1" } } From 1c7705221c9163fb2fded894f3d81bc3756332ac Mon Sep 17 00:00:00 2001 From: sillonjeu Date: Mon, 14 Jul 2025 12:54:30 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[feat]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EC=97=90=EB=9F=AC=20ret?= =?UTF-8?q?urn=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20#1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 11 +- bin/www | 86 ++++++++++++++ models/Auth.js | 59 +++++++--- package-lock.json | 290 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 8 +- routes/auth.js | 23 ++-- 6 files changed, 449 insertions(+), 28 deletions(-) create mode 100644 bin/www diff --git a/app.js b/app.js index 85a8be5..7a1bd27 100644 --- a/app.js +++ b/app.js @@ -7,6 +7,7 @@ var cors = require("cors"); /* --------------------------------------- */ var indexRouter = require("./routes/index"); var usersRouter = require("./routes/users"); +var authRouter = require("./routes/auth"); /* --------------------------------------- */ const mongoose = require("mongoose"); const dotenv = require("dotenv"); @@ -32,7 +33,7 @@ mongoose var app = express(); app.use( cors({ - origin: ["http://localhost:3000"], // TODO: 클라이언트 주소 배포하면 추가해주기 + origin: ["http://localhost"], // TODO: 클라이언트 주소 배포하면 추가해주기 methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], credentials: true, }) @@ -47,7 +48,13 @@ app.use(express.static(path.join(__dirname, "public"))); app.use("/", indexRouter); app.use("/auth", authRouter); /* --------------------------------------- */ -// catch 404 and forward to error handler +const port = process.env.PORT || 3001; + +// 서버 시작 +app.listen(port, () => { + console.log(`▶️ Server is listening on http://localhost:${port}`); +}); + app.use(function (req, res, next) { next(createError(404)); }); diff --git a/bin/www b/bin/www new file mode 100644 index 0000000..b14576f --- /dev/null +++ b/bin/www @@ -0,0 +1,86 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require("../app"); +var debug = require("debug")("neukkim-server:server"); +var http = require("http"); + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || "3001"); +app.set("port", port); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on("error", onError); +server.on("listening", onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== "listen") { + throw error; + } + + var bind = typeof port === "string" ? "Pipe " + port : "Port " + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case "EACCES": + console.error(bind + " requires elevated privileges"); + process.exit(1); + break; + case "EADDRINUSE": + console.error(bind + " is already in use"); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === "string" ? "pipe " + addr : "port " + addr.port; + debug("Listening on " + bind); +} diff --git a/models/Auth.js b/models/Auth.js index b5934cd..9d21e79 100644 --- a/models/Auth.js +++ b/models/Auth.js @@ -8,7 +8,7 @@ const authSchema = new mongoose.Schema({ required: [true, "이메일을 입력해 주세요."], unique: true, lowercase: true, - validator: [isEmail, "올바른 이메일 형식이 아닙니다."], + validate: [isEmail, "올바른 이메일 형식이 아닙니다."], }, password: { type: String, @@ -20,31 +20,43 @@ const authSchema = new mongoose.Schema({ }, }); +// 로그인 authSchema.statics.login = async function (email, password) { const auth = await this.findOne({ email }); - if (auth) { - const signup = await bcrypt.compare(password, auth.password); - if (signup) { - return auth.visibleUser; - } - throw Error("비밀번호가 맞지 않습니다."); + if (!auth) { + // 이메일이 없을 때 + const error = new Error("이메일을 다시 확인해 주세요."); + error.field = "email"; + throw error; + } + + const isMatch = await bcrypt.compare(password, auth.password); + if (!isMatch) { + // 비밀번호가 틀렸을 때 + const error = new Error("비밀번호를 다시 확인해 주세요."); + error.field = "password"; + throw error; } - throw Error("이메일이 맞지 않습니다."); + + // 성공 시 + return auth.visibleUser; }; -const visibleUser = authSchema.virtual("visibleUser"); -visibleUser.get(function (value, virtual, doc) { +// 노출할 필드만 반환하는 가상 프로퍼티 +authSchema.virtual("visibleUser").get(function () { return { - _id: doc._id, - email: doc.email, + _id: this._id, + email: this.email, + nickname: this.nickname, }; }); +// 회원가입 authSchema.statics.signUp = async function (email, password, nickname) { const salt = await bcrypt.genSalt(); + const hashedPassword = await bcrypt.hash(password, salt); try { - const hashedPassword = await bcrypt.hash(password, salt); const auth = await this.create({ email, password: hashedPassword, @@ -52,9 +64,28 @@ authSchema.statics.signUp = async function (email, password, nickname) { }); return { _id: auth._id, - nickname: auth._id, + nickname: auth.nickname, }; } catch (err) { + // 중복된 이메일 처리 + if (err.code === 11000 && err.keyPattern && err.keyPattern.email) { + const error = new Error( + "이미 사용 중인 이메일입니다. 다른 이메일을 입력해 주세요." + ); + error.field = "email"; + throw error; + } + + // mongoose validation error 처리 (예: 이메일 형식이 잘못되었거나, 필수 값 누락 등) + if (err.name === "ValidationError") { + const firstErrorField = Object.keys(err.errors)[0]; + const errorMessage = err.errors[firstErrorField].message; + const error = new Error(errorMessage); + error.field = firstErrorField; + throw error; + } + + // 그 외 에러는 그대로 던짐 throw err; } }; diff --git a/package-lock.json b/package-lock.json index 41148fb..7d4770d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,19 @@ "version": "0.0.0", "license": "ISC", "dependencies": { + "bcrypt": "^6.0.0", "cookie-parser": "~1.4.4", + "cors": "^2.8.5", "debug": "~2.6.9", + "dotenv": "^17.2.0", "ejs": "~2.6.1", "express": "~4.16.1", "http-errors": "~1.6.3", + "jsonwebtoken": "^9.0.2", "mongodb": "^6.17.0", - "morgan": "~1.9.1" + "mongoose": "^8.16.3", + "morgan": "~1.9.1", + "validator": "^13.15.15" } }, "node_modules/@mongodb-js/saslprep": { @@ -73,6 +79,20 @@ "node": ">= 0.8" } }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/body-parser": { "version": "1.18.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", @@ -103,6 +123,12 @@ "node": ">=16.20.1" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -158,6 +184,19 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -182,6 +221,27 @@ "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", "license": "MIT" }, + "node_modules/dotenv": { + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz", + "integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -349,6 +409,106 @@ "node": ">= 0.10" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -465,6 +625,34 @@ "whatwg-url": "^14.1.0 || ^13.0.0" } }, + "node_modules/mongoose": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.3.tgz", + "integrity": "sha512-p2JOsRQG7j0vXhLpsWw5Slm2VnDeJK8sRyqSyegk5jQujuP9BTOZ1Di9VX/0lYfBhZ2DpAExi51QTd4pIqSgig==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.4", + "kareem": "2.6.3", + "mongodb": "~6.17.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/morgan": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", @@ -481,6 +669,50 @@ "node": ">= 0.8.0" } }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/mquery/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -496,6 +728,35 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.4.0.tgz", + "integrity": "sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -599,6 +860,18 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", @@ -644,6 +917,12 @@ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "license": "ISC" }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" + }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -705,6 +984,15 @@ "node": ">= 0.4.0" } }, + "node_modules/validator": { + "version": "13.15.15", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", + "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index b3874ba..5382dd0 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,18 @@ }, "homepage": "https://github.com/InserToken/UpAndDown-Server#readme", "dependencies": { + "bcrypt": "^6.0.0", "cookie-parser": "~1.4.4", + "cors": "^2.8.5", "debug": "~2.6.9", + "dotenv": "^17.2.0", "ejs": "~2.6.1", "express": "~4.16.1", "http-errors": "~1.6.3", + "jsonwebtoken": "^9.0.2", "mongodb": "^6.17.0", - "morgan": "~1.9.1" + "mongoose": "^8.16.3", + "morgan": "~1.9.1", + "validator": "^13.15.15" } } diff --git a/routes/auth.js b/routes/auth.js index 076491b..b7975c6 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -1,9 +1,9 @@ var express = require("express"); const Auth = require("../models/Auth"); -const { createToken, verifyToken } = require("../utils/auth"); +const { createToken } = require("../utils/auth"); var router = express.Router(); -router.post("/signup", async (req, res, next) => { +router.post("/signup", async (req, res) => { try { const { email, password, nickname } = req.body; console.log(req.body); @@ -11,12 +11,14 @@ router.post("/signup", async (req, res, next) => { res.status(201).json(user); } catch (err) { console.error(err); - res.status(400); - next(err); + res.status(400).json({ + field: err.field || null, + message: err.message || "회원가입 중 오류가 발생했습니다.", + }); } }); -router.post("/login", async (req, res, next) => { +router.post("/login", async (req, res) => { try { const { email, password } = req.body; const user = await Auth.login(email, password); @@ -24,14 +26,15 @@ router.post("/login", async (req, res, next) => { const token = createToken(user, tokenMaxAge); user.token = token; - // TODO: user 콘솔 한 번 찍어보기 - console.log(user); + console.log(user); // 로그인 성공 시 콘솔 확인 - res.status(201).json(user); + res.status(200).json(user); } catch (err) { console.error(err); - res.status(400); - next(err); + res.status(400).json({ + field: err.field || null, + message: err.message || "로그인 중 오류가 발생했습니다.", + }); } }); From ce13ffb19a89ff90d8b1020b19a2f4a27fa80f9e Mon Sep 17 00:00:00 2001 From: sillonjeu Date: Mon, 14 Jul 2025 13:00:46 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[merge]=20develop=20<->=20feat/1-auth/eundo?= =?UTF-8?q?ng=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/app.js b/app.js index 22d7209..2ae05e1 100644 --- a/app.js +++ b/app.js @@ -8,7 +8,6 @@ var cors = require("cors"); /* --------------------------------------- */ var indexRouter = require("./routes/index"); var usersRouter = require("./routes/users"); -<<<<<<< HEAD var authRouter = require("./routes/auth"); /* --------------------------------------- */ const mongoose = require("mongoose"); @@ -35,29 +34,12 @@ mongoose var app = express(); app.use( cors({ - origin: ["http://localhost"], // TODO: 클라이언트 주소 배포하면 추가해주기 + origin: ["http://localhost:3000"], // TODO: 클라이언트 주소 배포하면 추가해주기 methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], credentials: true, }) ); /* --------------------------------------- */ -======= -var balanceRouter = require("./routes/real"); - -var app = express(); -const cors = require("cors"); - -/* CORS */ -const allowedOrigins = ["http://localhost:3000"]; - -app.use( - cors({ - origin: allowedOrigins, - credentials: true, - }) -); - ->>>>>>> develop app.use(logger("dev")); app.use(express.json()); app.use(express.urlencoded({ extended: false })); @@ -65,8 +47,10 @@ app.use(cookieParser()); app.use(express.static(path.join(__dirname, "public"))); /* --------------------------------------- */ app.use("/", indexRouter); -<<<<<<< HEAD app.use("/auth", authRouter); +app.use("/users", usersRouter); +var balanceRouter = require("./routes/real"); +app.use("/api/real", balanceRouter); /* --------------------------------------- */ const port = process.env.PORT || 3001; @@ -74,10 +58,6 @@ const port = process.env.PORT || 3001; app.listen(port, () => { console.log(`▶️ Server is listening on http://localhost:${port}`); }); -======= -app.use("/users", usersRouter); -app.use("/api/real", balanceRouter); ->>>>>>> develop app.use(function (req, res, next) { next(createError(404)); @@ -94,10 +74,4 @@ app.use(function (err, req, res, next) { res.render("error"); }); -const PORT = process.env.PORT || 3001; - -app.listen(PORT, () => { - console.log(`🚀 서버가 실행 중: http://localhost:${PORT}`); -}); - module.exports = app;