From 93130623e88d05af7d4eb2f5020eef59502485f4 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:07:21 +0900 Subject: [PATCH 01/15] =?UTF-8?q?feat(utils):=20rate=20limit=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - R, CUD에 해당하는 rate limit 모듈 추가 --- backend/src/v1/utils/rateLimiter.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 backend/src/v1/utils/rateLimiter.ts diff --git a/backend/src/v1/utils/rateLimiter.ts b/backend/src/v1/utils/rateLimiter.ts new file mode 100644 index 00000000..101e0f93 --- /dev/null +++ b/backend/src/v1/utils/rateLimiter.ts @@ -0,0 +1,21 @@ +import rateLimit, { RateLimitRequestHandler } from "express-rate-limit"; + +const message: string = '너무 많은 요청을 보냈습니다. 잠시 후 다시 시도해주세요.'; + +/** + * GET method에 대해 rate limit을 적용한다. 초당 100회의 요청을 허용한다. + */ +export const getRateLimiter: RateLimitRequestHandler = rateLimit({ + windowMs: 1000, + max: 100, + message, +}); + +/** + * GET method를 제외한 모든 HTTP method에 대해 rate limit을 적용한다. 초당 10회의 요청을 허용한다. + */ +export const cudRateLimiter: RateLimitRequestHandler = rateLimit({ + windowMs: 1000, + max: 10, + message, +}); From d80f15b115de2c8b12983835179910e397d38f32 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:18:31 +0900 Subject: [PATCH 02/15] =?UTF-8?q?refactor(cursus):=20rate=20limit=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20import=ED=95=B4=EC=84=9C=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/v1/routes/cursus.routes.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/backend/src/v1/routes/cursus.routes.ts b/backend/src/v1/routes/cursus.routes.ts index b0d0c737..f437a781 100644 --- a/backend/src/v1/routes/cursus.routes.ts +++ b/backend/src/v1/routes/cursus.routes.ts @@ -1,18 +1,12 @@ import { Router } from 'express'; -import rateLimit from 'express-rate-limit'; import { recommendBook, getProjects } from '~/v1/cursus/cursus.controller'; import { roleSet } from '~/v1/auth/auth.type'; import authValidate from '~/v1/auth/auth.validate'; +import { getRateLimiter } from "~/v1/utils/rateLimiter.ts"; export const path = '/cursus'; export const router = Router(); -const limiter = rateLimit({ - windowMs: 60 * 1000, // 1분 - max: 100, // 1분에 100번 - message: '너무 많은 요청을 보냈습니다. 잠시 후 다시 시도해주세요.', -}); - router /** * @openapi @@ -94,7 +88,7 @@ router * description: error decription * example: { errorCode: 500 } */ - .get('/recommend/books', limiter, authValidate(roleSet.all), recommendBook); + .get('/recommend/books', getRateLimiter, authValidate(roleSet.all), recommendBook); router /** From af9fc8a25af3c97a58a5250bff9553b08c7f552b Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:20:42 +0900 Subject: [PATCH 03/15] =?UTF-8?q?feat:=20getRateLimiter=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/v1/routes/auth.routes.ts | 3 +++ backend/src/v1/routes/users.routes.ts | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/src/v1/routes/auth.routes.ts b/backend/src/v1/routes/auth.routes.ts index 6fe39443..accf4f1a 100644 --- a/backend/src/v1/routes/auth.routes.ts +++ b/backend/src/v1/routes/auth.routes.ts @@ -14,6 +14,7 @@ import { login, logout, } from '~/v1/auth/auth.controller'; +import { getRateLimiter } from "~/v1/utils/rateLimiter.ts"; export const path = '/auth'; export const router = Router(); @@ -78,6 +79,7 @@ router.get('/oauth', getOAuth); */ router.get( '/token', + getRateLimiter, passport.authenticate('42', { session: false, failureRedirect: `${oauthUrlOption.clientURL}/login?errorCode=${errorCode.ACCESS_DENIED}`, @@ -326,6 +328,7 @@ router.get('/getIntraAuthentication', getIntraAuthentication); */ router.get( '/intraAuthentication', + getRateLimiter, passport.authenticate('42Auth', { session: false, failureRedirect: `${oauthUrlOption.clientURL}/mypage?errorCode=${errorCode.ACCESS_DENIED}`, diff --git a/backend/src/v1/routes/users.routes.ts b/backend/src/v1/routes/users.routes.ts index 409d215a..63cd5580 100644 --- a/backend/src/v1/routes/users.routes.ts +++ b/backend/src/v1/routes/users.routes.ts @@ -2,6 +2,7 @@ import { Router } from 'express'; import { roleSet } from '~/v1/auth/auth.type'; import authValidate from '~/v1/auth/auth.validate'; import { create, getVersion, myupdate, search, update, mydata } from '~/v1/users/users.controller'; +import { getRateLimiter } from "~/v1/utils/rateLimiter.ts"; export const path = '/users'; @@ -406,11 +407,11 @@ export const router = Router(); * example: [] */ router - .get('/search', authValidate(roleSet.librarian), search) + .get('/search', getRateLimiter, authValidate(roleSet.librarian), search) .post('/create', create) .patch('/update/:id', authValidate(roleSet.librarian), update) .patch('/myupdate', authValidate(roleSet.all), myupdate) - .get('/me', authValidate(roleSet.all), mydata) + .get('/me', getRateLimiter, authValidate(roleSet.all), mydata) .get('/EasterEgg', getVersion); // .delete('/delete/:id', authValidate(roleSet.librarian), deleteUser); From 089c02b713a1afbee6d556a13b53522ffe504c48 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:45:47 +0900 Subject: [PATCH 04/15] =?UTF-8?q?feat(books):=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=ED=95=9C=20ISBN=EC=9D=B8=EC=A7=80=20=EA=B2=80=EC=82=AC?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/v1/books/books.service.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/backend/src/v1/books/books.service.ts b/backend/src/v1/books/books.service.ts index 1438b150..d8345804 100644 --- a/backend/src/v1/books/books.service.ts +++ b/backend/src/v1/books/books.service.ts @@ -27,9 +27,23 @@ import { categoryWithBookCount } from '../DTO/common.interface'; import * as searchKeywordsService from '../search-keywords/searchKeywords.service'; import BookInfoSearchKeywordRepository from '../search-keywords/booksInfoSearchKeywords.repository'; +/** + * 유효한 ISBN인지 정규식을 통해 검사한다. + * @param isbn 검사할 ISBN + * @return 유효한 ISBN이면 true, 아니면 false + */ +const isValidISBN = (isbn: string) => { + const isbnPattern = /^(?:ISBN(?:-1[03])?:? )?(?=[-0-9 ]{17}$|[-0-9X ]{13}$|[0-9X]{10}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?(?:[0-9]+[- ]?){2}[0-9X]$/; + return isbnPattern.test(isbn); +} + const getInfoInNationalLibrary = async (isbn: string) => { let book; let searchResult; + + if (!isValidISBN(isbn)) { + throw new Error(errorCode.ISBN_SEARCH_FAILED); + } await axios .get( `https://www.nl.go.kr/seoji/SearchApi.do?cert_key=${nationalIsbnApiKey}&result_style=json&page_no=1&page_size=10&isbn=${isbn}`, @@ -62,6 +76,9 @@ const getInfoInNationalLibrary = async (isbn: string) => { const getAuthorInNaver = async (isbn: string) => { let author; + if (!isValidISBN(isbn)) { + return author; + } await axios .get( ` From 84ee53ebb52233ec76d2665c2ba38340667b8635 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:51:05 +0900 Subject: [PATCH 05/15] =?UTF-8?q?feat(tags):=20tags=20router=EC=97=90=20ra?= =?UTF-8?q?te=20limit=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/v1/routes/tags.routes.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/backend/src/v1/routes/tags.routes.ts b/backend/src/v1/routes/tags.routes.ts index 4c3b2650..ae8abe84 100644 --- a/backend/src/v1/routes/tags.routes.ts +++ b/backend/src/v1/routes/tags.routes.ts @@ -14,6 +14,7 @@ import { } from '~/v1/tags/tags.controller'; import authValidate from '~/v1/auth/auth.validate'; import { roleSet } from '~/v1/auth/auth.type'; +import { cudRateLimiter, getRateLimiter } from "~/v1/utils/rateLimiter.ts"; export const path = '/tags'; export const router = Router(); @@ -95,7 +96,7 @@ router * type: number * example: 500 */ - .patch('/super', authValidate(roleSet.librarian), updateSuperTags); + .patch('/super', cudRateLimiter, authValidate(roleSet.librarian), updateSuperTags); router /** @@ -164,7 +165,7 @@ router * type: number * example: 500 */ - .patch('/sub', authValidate(roleSet.librarian), updateSubTags); + .patch('/sub', cudRateLimiter, authValidate(roleSet.librarian), updateSubTags); router /** @@ -260,7 +261,7 @@ router * type: number * example: 500 */ - .patch('/:bookInfoId/merge', authValidate(roleSet.librarian), mergeTags); + .patch('/:bookInfoId/merge', cudRateLimiter, authValidate(roleSet.librarian), mergeTags); router /** @@ -326,7 +327,7 @@ router * value : * errorCode: 109 */ - .post('/default', authValidate(roleSet.all), createDefaultTags); + .post('/default', cudRateLimiter, authValidate(roleSet.all), createDefaultTags); router /** @@ -392,7 +393,7 @@ router * value : * errorCode: 109 */ - .post('/super', authValidate(roleSet.librarian), createSuperTags); + .post('/super', cudRateLimiter, authValidate(roleSet.librarian), createSuperTags); router /** @@ -452,7 +453,7 @@ router * value: * errorCode: 903 */ - .delete('/sub/:tagId', authValidate(roleSet.all), deleteSubTags); + .delete('/sub/:tagId', cudRateLimiter, authValidate(roleSet.all), deleteSubTags); router /** @@ -512,7 +513,7 @@ router * value: * errorCode: 903 */ - .delete('/super/:tagId', authValidate(roleSet.librarian), deleteSuperTags); + .delete('/super/:tagId', cudRateLimiter, authValidate(roleSet.librarian), deleteSuperTags); router /** @@ -755,7 +756,7 @@ router * '500': * description: db 에러 */ - .get('/:superTagId/sub', authValidate(roleSet.all), searchSubTags); + .get('/:superTagId/sub', getRateLimiter, authValidate(roleSet.all), searchSubTags); router /** @@ -812,7 +813,7 @@ router * '500': * description: db 에러 */ - .get('/manage/:superTagId/sub', authValidate(roleSet.librarian), searchSubTags); + .get('/manage/:superTagId/sub', getRateLimiter, authValidate(roleSet.librarian), searchSubTags); router /** From 5cb5a90996d500385f8b9a2d922d914982b37d22 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:54:40 +0900 Subject: [PATCH 06/15] =?UTF-8?q?feat(routes):=20router=EC=97=90=EC=84=9C?= =?UTF-8?q?=20authValidate=EB=A5=BC=20=EB=AF=B8=EB=93=A4=EC=9B=A8=EC=96=B4?= =?UTF-8?q?=EB=A1=9C=20=EC=93=B0=EB=8A=94=20=EA=B3=B3=EC=97=90=20rate=20li?= =?UTF-8?q?miter=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/v1/routes/books.routes.ts | 13 +++++++------ backend/src/v1/routes/histories.routes.ts | 3 ++- backend/src/v1/routes/lendings.routes.ts | 9 +++++---- backend/src/v1/routes/reservations.routes.ts | 11 ++++++----- backend/src/v1/routes/reviews.routes.ts | 13 +++++++------ backend/src/v1/routes/users.routes.ts | 6 +++--- 6 files changed, 30 insertions(+), 25 deletions(-) diff --git a/backend/src/v1/routes/books.routes.ts b/backend/src/v1/routes/books.routes.ts index ff70baaf..6f512c84 100644 --- a/backend/src/v1/routes/books.routes.ts +++ b/backend/src/v1/routes/books.routes.ts @@ -17,6 +17,7 @@ import { import authValidate from '~/v1/auth/auth.validate'; import authValidateDefaultNullUser from '~/v1/auth/auth.validateDefaultNullUser'; import { roleSet } from '~/v1/auth/auth.type'; +import { cudRateLimiter, getRateLimiter } from "~/v1/utils/rateLimiter.ts"; export const path = '/books'; export const router = Router(); @@ -714,7 +715,7 @@ router * type: json * example : { errorCode: 311 } */ - .post('/create', authValidate(roleSet.librarian), createBook); + .post('/create', cudRateLimiter, authValidate(roleSet.librarian), createBook); router /** @@ -795,7 +796,7 @@ router * type: json * example: { errorCode : 310 } */ - .get('/create', authValidate(roleSet.librarian), createBookInfo); + .get('/create', getRateLimiter, authValidate(roleSet.librarian), createBookInfo); router /** @@ -917,7 +918,7 @@ router * description: 좋아요할 bookInfo의 id * example : { userId: 123, bookInfoId: 456 } */ - .post('/info/:bookInfoId/like', authValidate(roleSet.service), createLike); + .post('/info/:bookInfoId/like', cudRateLimiter, authValidate(roleSet.service), createLike); router /** @@ -958,7 +959,7 @@ router * type: json * example : { errorCode: 603} */ - .delete('/info/:bookInfoId/like', authValidate(roleSet.service), deleteLike); + .delete('/info/:bookInfoId/like', cudRateLimiter, authValidate(roleSet.service), deleteLike); router /** @@ -1100,5 +1101,5 @@ router * type: json * example : { errorCode: 311 } */ - .patch('/update', authValidate(roleSet.librarian), updateBookInfo) - .patch('/donator', authValidate(roleSet.librarian), updateBookDonator); + .patch('/update', cudRateLimiter, authValidate(roleSet.librarian), updateBookInfo) + .patch('/donator', cudRateLimiter, authValidate(roleSet.librarian), updateBookDonator); diff --git a/backend/src/v1/routes/histories.routes.ts b/backend/src/v1/routes/histories.routes.ts index 8a58ba7c..d65409f9 100644 --- a/backend/src/v1/routes/histories.routes.ts +++ b/backend/src/v1/routes/histories.routes.ts @@ -2,6 +2,7 @@ import { Router } from 'express'; import { histories } from '~/v1/histories/histories.controller'; import authValidate from '~/v1/auth/auth.validate'; import { roleSet } from '~/v1/auth/auth.type'; +import { getRateLimiter } from "~/v1/utils/rateLimiter.ts"; export const path = '/histories'; export const router = Router(); @@ -153,4 +154,4 @@ router * type: integer * example: 700 */ - .get('/', authValidate(roleSet.all), histories); + .get('/', getRateLimiter, authValidate(roleSet.all), histories); diff --git a/backend/src/v1/routes/lendings.routes.ts b/backend/src/v1/routes/lendings.routes.ts index 1e15965b..660cf6b8 100644 --- a/backend/src/v1/routes/lendings.routes.ts +++ b/backend/src/v1/routes/lendings.routes.ts @@ -2,6 +2,7 @@ import { Router } from 'express'; import { create, search, lendingId, returnBook } from '~/v1/lendings/lendings.controller'; import authValidate from '~/v1/auth/auth.validate'; import { roleSet } from '~/v1/auth/auth.type'; +import { cudRateLimiter, getRateLimiter } from "~/v1/utils/rateLimiter.ts"; export const path = '/lendings'; export const router = Router(); @@ -61,7 +62,7 @@ router * '500': * description: db 에러 * */ - .post('/', authValidate(roleSet.librarian), create) + .post('/', cudRateLimiter, authValidate(roleSet.librarian), create) /** * @openapi @@ -190,7 +191,7 @@ router * '500': * description: db 에러 */ - .get('/search', authValidate(roleSet.librarian), search) + .get('/search', cudRateLimiter, authValidate(roleSet.librarian), search) /** * @openapi @@ -260,7 +261,7 @@ router * '500': * description: db 에러 */ - .get('/:id', authValidate(roleSet.librarian), lendingId) + .get('/:id', getRateLimiter, authValidate(roleSet.librarian), lendingId) /** * @openapi @@ -316,4 +317,4 @@ router * type: integer * */ - .patch('/return', authValidate(roleSet.librarian), returnBook); + .patch('/return', cudRateLimiter, authValidate(roleSet.librarian), returnBook); diff --git a/backend/src/v1/routes/reservations.routes.ts b/backend/src/v1/routes/reservations.routes.ts index e26d259a..e1026245 100644 --- a/backend/src/v1/routes/reservations.routes.ts +++ b/backend/src/v1/routes/reservations.routes.ts @@ -8,6 +8,7 @@ import { } from '~/v1/reservations/reservations.controller'; import authValidate from '~/v1/auth/auth.validate'; import { roleSet } from '~/v1/auth/auth.type'; +import { cudRateLimiter, getRateLimiter } from "~/v1/utils/rateLimiter.ts"; export const path = '/reservations'; export const router = Router(); @@ -274,8 +275,8 @@ export const router = Router(); * */ router - .post('/', authValidate(roleSet.service), create) - .get('/search', authValidate(roleSet.librarian), search) - .patch('/cancel/:reservationId', authValidate(roleSet.service), cancel) - .get('/count', authValidate(roleSet.all), count) - .get('/', authValidate(roleSet.service), userReservations); + .post('/', cudRateLimiter, authValidate(roleSet.service), create) + .get('/search', getRateLimiter, authValidate(roleSet.librarian), search) + .patch('/cancel/:reservationId', cudRateLimiter, authValidate(roleSet.service), cancel) + .get('/count', getRateLimiter, authValidate(roleSet.all), count) + .get('/', getRateLimiter, authValidate(roleSet.service), userReservations); diff --git a/backend/src/v1/routes/reviews.routes.ts b/backend/src/v1/routes/reviews.routes.ts index 1c06f306..24263df9 100644 --- a/backend/src/v1/routes/reviews.routes.ts +++ b/backend/src/v1/routes/reviews.routes.ts @@ -9,6 +9,7 @@ import { import authValidate from '~/v1/auth/auth.validate'; import { roleSet } from '~/v1/auth/auth.type'; import wrapAsyncController from '~/v1/middlewares/wrapAsyncController'; +import { cudRateLimiter, getRateLimiter } from "~/v1/utils/rateLimiter.ts"; export const path = '/reviews'; export const router = Router(); @@ -71,7 +72,7 @@ router * value : * errorCode: 109 */ - .post('/', authValidate(roleSet.all), wrapAsyncController(createReviews)); + .post('/', cudRateLimiter, authValidate(roleSet.all), wrapAsyncController(createReviews)); router /** @@ -272,7 +273,7 @@ router * value : * errorCode: 109 */ - .get('/', authValidate(roleSet.librarian), wrapAsyncController(getReviews)); + .get('/', getRateLimiter, authValidate(roleSet.librarian), wrapAsyncController(getReviews)); router /** @@ -463,7 +464,7 @@ router * value : * errorCode: 109 */ - .get('/my-reviews', authValidate(roleSet.all), wrapAsyncController(getReviews)); + .get('/my-reviews', getRateLimiter, authValidate(roleSet.all), wrapAsyncController(getReviews)); router /** @@ -540,7 +541,7 @@ router * value: * errorCode: 804 */ - .put('/:reviewsId', authValidate(roleSet.all), wrapAsyncController(updateReviews)); + .put('/:reviewsId', cudRateLimiter, authValidate(roleSet.all), wrapAsyncController(updateReviews)); router /** @@ -561,7 +562,7 @@ router * '200': * description: 리뷰가 DB에 정상적으로 fetch됨. */ - .patch('/:reviewsId', authValidate(roleSet.librarian), wrapAsyncController(patchReviews)); + .patch('/:reviewsId', cudRateLimiter, authValidate(roleSet.librarian), wrapAsyncController(patchReviews)); router /** @@ -621,4 +622,4 @@ router * value: * errorCode: 804 */ - .delete('/:reviewsId', authValidate(roleSet.all), wrapAsyncController(deleteReviews)); + .delete('/:reviewsId', cudRateLimiter, authValidate(roleSet.all), wrapAsyncController(deleteReviews)); diff --git a/backend/src/v1/routes/users.routes.ts b/backend/src/v1/routes/users.routes.ts index 63cd5580..efe9eb9f 100644 --- a/backend/src/v1/routes/users.routes.ts +++ b/backend/src/v1/routes/users.routes.ts @@ -2,7 +2,7 @@ import { Router } from 'express'; import { roleSet } from '~/v1/auth/auth.type'; import authValidate from '~/v1/auth/auth.validate'; import { create, getVersion, myupdate, search, update, mydata } from '~/v1/users/users.controller'; -import { getRateLimiter } from "~/v1/utils/rateLimiter.ts"; +import { cudRateLimiter, getRateLimiter } from "~/v1/utils/rateLimiter.ts"; export const path = '/users'; @@ -409,8 +409,8 @@ export const router = Router(); router .get('/search', getRateLimiter, authValidate(roleSet.librarian), search) .post('/create', create) - .patch('/update/:id', authValidate(roleSet.librarian), update) - .patch('/myupdate', authValidate(roleSet.all), myupdate) + .patch('/update/:id', cudRateLimiter, authValidate(roleSet.librarian), update) + .patch('/myupdate', cudRateLimiter, authValidate(roleSet.all), myupdate) .get('/me', getRateLimiter, authValidate(roleSet.all), mydata) .get('/EasterEgg', getVersion); From 2de0795f375cd7ea4c0a9335484d2b930fc2f68d Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:57:17 +0900 Subject: [PATCH 07/15] =?UTF-8?q?feat(auth):=20/get/me=EC=97=90=20rate=20l?= =?UTF-8?q?imiter=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/v1/routes/auth.routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/v1/routes/auth.routes.ts b/backend/src/v1/routes/auth.routes.ts index accf4f1a..fa2cb5ec 100644 --- a/backend/src/v1/routes/auth.routes.ts +++ b/backend/src/v1/routes/auth.routes.ts @@ -162,7 +162,7 @@ router.get( * message: * type: string */ -router.get('/me', authValidate(roleSet.all), getMe); +router.get('/me', getRateLimiter, authValidate(roleSet.all), getMe); /** * @openapi From 802227d100469b74170441666f022d1795d0410e Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 16:06:02 +0900 Subject: [PATCH 08/15] =?UTF-8?q?feat(users):=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=ED=9B=84=20created=20=EB=AC=B8=EC=9E=A5?= =?UTF-8?q?=20=EC=B6=9C=EB=A0=A5=20=EC=8B=9C,=20db=EC=97=90=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EB=90=9C=20email=20=EA=B0=92=EC=9D=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/v1/users/users.controller.ts | 4 ++-- backend/src/v1/users/users.repository.ts | 4 +++- backend/src/v1/users/users.service.ts | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/src/v1/users/users.controller.ts b/backend/src/v1/users/users.controller.ts index b7aec194..64b79250 100644 --- a/backend/src/v1/users/users.controller.ts +++ b/backend/src/v1/users/users.controller.ts @@ -76,8 +76,8 @@ export const create = async (req: Request, res: Response, next: NextFunction) => if (!pwSchema.validate(String(password))) { return next(new ErrorResponse(errorCode.INVALIDATE_PASSWORD, status.BAD_REQUEST)); } - await usersService.createUser(String(email), await bcrypt.hash(String(password), 10)); - return res.status(status.OK).send(`${email} created!`); + const result = await usersService.createUser(String(email), await bcrypt.hash(String(password), 10)); + return res.status(status.OK).send(`${result.email} created!`); } catch (error: any) { const errorNumber = parseInt(error.message, 10); if (errorNumber >= 200 && errorNumber < 300) { diff --git a/backend/src/v1/users/users.repository.ts b/backend/src/v1/users/users.repository.ts index a210982d..0c187f2b 100644 --- a/backend/src/v1/users/users.repository.ts +++ b/backend/src/v1/users/users.repository.ts @@ -9,6 +9,7 @@ import { UserReservation, User, } from '~/entity/entities'; +import { InsertResult } from "kysely"; import * as models from '../DTO/users.model'; export default class UsersRepository extends Repository { @@ -111,11 +112,12 @@ export default class UsersRepository extends Repository { async insertUser(email: string, password: string) { const penaltyEndDate = new Date(0); penaltyEndDate.setDate(penaltyEndDate.getDate() - 1); - await this.insert({ + const result = await this.save({ email, password, penaltyEndDate: formatDate(penaltyEndDate), }); + return result; } async updateUser(id: number, values: {}): Promise { diff --git a/backend/src/v1/users/users.service.ts b/backend/src/v1/users/users.service.ts index ee4bdba6..1a2588a8 100644 --- a/backend/src/v1/users/users.service.ts +++ b/backend/src/v1/users/users.service.ts @@ -98,8 +98,8 @@ export default class UsersService { if (emailCount > 0) { throw new Error(errorCode.EMAIL_OVERLAP); } - await this.usersRepository.insertUser(email, password); - return null; + const result = await this.usersRepository.insertUser(email, password); + return result; } async updateUserEmail(id: number, email: string) { From 56205dc4fc45b51ac18d4f44ed53337cb083c6a5 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 16:57:19 +0900 Subject: [PATCH 09/15] =?UTF-8?q?build:=20csrf=20=EB=B0=A9=EC=A7=80?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20lusca=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package.json | 2 ++ pnpm-lock.yaml | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 530503bf..bf1c7a5b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -27,6 +27,7 @@ "@types/http-errors": "^2.0.1", "@types/jest": "^29.5.2", "@types/jsonwebtoken": "^9.0.2", + "@types/lusca": "^1.7.4", "@types/morgan": "^1.9.4", "@types/node-schedule": "^2.1.0", "@types/passport": "^1.0.12", @@ -68,6 +69,7 @@ "jsonwebtoken": "^8.5.1", "kysely": "^0.26.1", "kysely-paginate": "^0.2.0", + "lusca": "^1.7.0", "morgan": "^1.10.0", "mysql2": "^2.3.3", "node-schedule": "^2.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd243be6..bc860bf2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.2' settings: autoInstallPeers: true @@ -90,6 +90,9 @@ importers: kysely-paginate: specifier: ^0.2.0 version: 0.2.0(kysely@0.26.1) + lusca: + specifier: ^1.7.0 + version: 1.7.0 morgan: specifier: ^1.10.0 version: 1.10.0 @@ -166,6 +169,9 @@ importers: '@types/jsonwebtoken': specifier: ^9.0.2 version: 9.0.2 + '@types/lusca': + specifier: ^1.7.4 + version: 1.7.4 '@types/morgan': specifier: ^1.9.4 version: 1.9.4 @@ -1591,6 +1597,12 @@ packages: '@types/node': 18.16.1 dev: true + /@types/lusca@1.7.4: + resolution: {integrity: sha512-fdLI0mxG5RFy/OegqGc82v+/Bqbu9Qr1MemWv4JjhaBhtrSdVeM6LX9+J1WSMR8BJF7kb+MALDCuh4fsIOA6zg==} + dependencies: + '@types/express': 4.17.17 + dev: true + /@types/mime@1.3.2: resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} dev: true @@ -4956,6 +4968,13 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + /lusca@1.7.0: + resolution: {integrity: sha512-msnrplCfY7zaqlZBDEloCIKld+RUeMZVeWzSPaGUKeRXFlruNSdKg2XxCyR+zj6BqzcXhXlRnvcvx6rAGgsvMA==} + engines: {node: '>=0.8.x'} + dependencies: + tsscmp: 1.0.6 + dev: false + /luxon@1.28.1: resolution: {integrity: sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==} dev: false @@ -6748,6 +6767,11 @@ packages: /tslib@2.6.0: resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==} + /tsscmp@1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + dev: false + /tunnel@0.0.6: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} From 3c5bd566fdd2ae05bdc2b54f37fc0d58233e1a58 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 16:57:41 +0900 Subject: [PATCH 10/15] =?UTF-8?q?feat(app):=20csrf=20=EB=B0=A9=EC=A7=80=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/app.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/app.ts b/backend/src/app.ts index ed62e1b4..cd0aefbc 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -1,4 +1,5 @@ import cookieParser from 'cookie-parser'; +import csrf from 'lusca'; import cors from 'cors'; import express from 'express'; import passport from 'passport'; @@ -24,6 +25,7 @@ const app: express.Application = express(); app.use(morganMiddleware); app.use(cookieParser()); +app.use(csrf()); app.use(passport.initialize()); app.use(express.urlencoded({ extended: true })); app.use(express.json()); From 0563e987d23adb27e5e9747282eada992446b0a9 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 17:04:58 +0900 Subject: [PATCH 11/15] =?UTF-8?q?feat(app):=20csrf=20=EB=B0=A9=EC=A7=80=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/app.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index cd0aefbc..f0c6fe67 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -1,5 +1,4 @@ import cookieParser from 'cookie-parser'; -import csrf from 'lusca'; import cors from 'cors'; import express from 'express'; import passport from 'passport'; @@ -19,13 +18,25 @@ import { createExpressEndpoints } from '@ts-rest/express'; import router from '~/v1/routes'; import routerV2 from '~/v2/routes'; +import lusca from "lusca"; import { morganMiddleware } from './logger'; const app: express.Application = express(); app.use(morganMiddleware); app.use(cookieParser()); -app.use(csrf()); +app.use(lusca.csrf( + { + cookie: { + name: 'CSRF-TOKEN', + options: { + httpOnly: true, + sameSite: 'strict', + secure: true, + } + }, + }, +)); app.use(passport.initialize()); app.use(express.urlencoded({ extended: true })); app.use(express.json()); From 16a6a5e4ef920fdfbf2e6b73db569e2fb95129c9 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 17:15:42 +0900 Subject: [PATCH 12/15] =?UTF-8?q?build:=20express-session=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package.json | 2 ++ pnpm-lock.yaml | 47 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index bf1c7a5b..8852a2ae 100644 --- a/backend/package.json +++ b/backend/package.json @@ -24,6 +24,7 @@ "@types/cookie-parser": "^1.4.3", "@types/cors": "^2.8.13", "@types/express": "^4.17.17", + "@types/express-session": "^1.17.10", "@types/http-errors": "^2.0.1", "@types/jest": "^29.5.2", "@types/jsonwebtoken": "^9.0.2", @@ -62,6 +63,7 @@ "dotenv": "^16.0.0", "express": "^4.17.2", "express-rate-limit": "^6.9.0", + "express-session": "^1.17.3", "hangul-js": "^0.2.6", "http-errors": "^2.0.0", "http-status": "^1.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc860bf2..6a8cc7a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.2' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -69,6 +69,9 @@ importers: express-rate-limit: specifier: ^6.9.0 version: 6.9.0(express@4.17.2) + express-session: + specifier: ^1.17.3 + version: 1.17.3 hangul-js: specifier: ^0.2.6 version: 0.2.6 @@ -160,6 +163,9 @@ importers: '@types/express': specifier: ^4.17.17 version: 4.17.17 + '@types/express-session': + specifier: ^1.17.10 + version: 1.17.10 '@types/http-errors': specifier: ^2.0.1 version: 2.0.1 @@ -1536,6 +1542,12 @@ packages: '@types/send': 0.17.1 dev: true + /@types/express-session@1.17.10: + resolution: {integrity: sha512-U32bC/s0ejXijw5MAzyaV4tuZopCh/K7fPoUDyNbsRXHvPSeymygYD1RFL99YOLhF5PNOkzswvOTRaVHdL1zMw==} + dependencies: + '@types/express': 4.17.17 + dev: true + /@types/express@4.17.17: resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} dependencies: @@ -2667,6 +2679,11 @@ packages: engines: {node: '>= 0.6'} dev: false + /cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + dev: false + /cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} @@ -3340,6 +3357,22 @@ packages: express: 4.17.2 dev: false + /express-session@1.17.3: + resolution: {integrity: sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==} + engines: {node: '>= 0.8.0'} + dependencies: + cookie: 0.4.2 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + on-headers: 1.0.2 + parseurl: 1.3.3 + safe-buffer: 5.2.1 + uid-safe: 2.1.5 + transitivePeerDependencies: + - supports-color + dev: false + /express@4.17.2: resolution: {integrity: sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==} engines: {node: '>= 0.10.0'} @@ -5947,6 +5980,11 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true + /random-bytes@1.0.0: + resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} + engines: {node: '>= 0.8'} + dev: false + /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -7028,6 +7066,13 @@ packages: dev: true optional: true + /uid-safe@2.1.5: + resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} + engines: {node: '>= 0.8'} + dependencies: + random-bytes: 1.0.0 + dev: false + /uid2@0.0.4: resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==} dev: false From 9b001a830d1915611ac270c7051d1a44187f070f Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 17:16:20 +0900 Subject: [PATCH 13/15] =?UTF-8?q?feat(app):=20lusca=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=20=EC=9C=A0=EC=A7=80=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=84=B8?= =?UTF-8?q?=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/app.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/src/app.ts b/backend/src/app.ts index f0c6fe67..647f7f88 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -19,10 +19,17 @@ import { createExpressEndpoints } from '@ts-rest/express'; import router from '~/v1/routes'; import routerV2 from '~/v2/routes'; import lusca from "lusca"; +import session from 'express-session'; +import * as crypto from "crypto"; import { morganMiddleware } from './logger'; const app: express.Application = express(); +app.use(session({ + secret: crypto.randomBytes(42).toString('hex'), + resave: false, + saveUninitialized: true, +})); app.use(morganMiddleware); app.use(cookieParser()); app.use(lusca.csrf( From 86a403788f9858207f73a0397b9a209b0912b1d6 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 17:23:31 +0900 Subject: [PATCH 14/15] =?UTF-8?q?feat(app):=20cookie=EC=97=90=EB=8F=84=20s?= =?UTF-8?q?ecret=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/app.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index 647f7f88..dec235e4 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -24,14 +24,17 @@ import * as crypto from "crypto"; import { morganMiddleware } from './logger'; const app: express.Application = express(); +const secret = crypto.randomBytes(42).toString('hex'); app.use(session({ - secret: crypto.randomBytes(42).toString('hex'), + secret, resave: false, saveUninitialized: true, })); app.use(morganMiddleware); -app.use(cookieParser()); +app.use(cookieParser( + secret, +)); app.use(lusca.csrf( { cookie: { From ec75cbb52e9c6bcf1e78c04fcce3193e3082615d Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Sat, 27 Jan 2024 17:27:20 +0900 Subject: [PATCH 15/15] =?UTF-8?q?feat(app):=20session=EC=97=90=EC=84=9C=20?= =?UTF-8?q?cookie=20=EC=84=A4=EC=A0=95=20=EB=B0=8F=20lusca=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=B6=80=EA=B0=80=EC=A0=81=EC=9D=B8=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/app.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index dec235e4..e75d8908 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -30,23 +30,17 @@ app.use(session({ secret, resave: false, saveUninitialized: true, + cookie: { + httpOnly: true, + sameSite: 'strict', + secure: true, + } })); app.use(morganMiddleware); app.use(cookieParser( secret, )); -app.use(lusca.csrf( - { - cookie: { - name: 'CSRF-TOKEN', - options: { - httpOnly: true, - sameSite: 'strict', - secure: true, - } - }, - }, -)); +app.use(lusca.csrf()); app.use(passport.initialize()); app.use(express.urlencoded({ extended: true })); app.use(express.json());