diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index fa0456f..b953c58 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -1,6 +1,6 @@ import axios from "axios"; import { getTTLFromToken } from "../Auths/token.js"; -import { checkDuplicatedEmail, checkDuplicatedUsername, signUpUser, loginUser, updateRefreshToken, SendVerifyEmailCode, checkEmailCode, getAccountInfo, resetPassword, logoutUser, withdrawUser, verifySocialAccount, socialLoginUser, socialLoginCertification } from "../services/auth.service.js"; +import { checkDuplicatedEmail, checkDuplicatedUsername, signUpUser, loginUser, updateRefreshToken, SendVerifyEmailCode, checkEmailCode, getAccountInfo, resetPassword, logoutUser, withdrawUser, verifySocialAccount, socialLoginUser, socialLoginCertification, changePassword } from "../services/auth.service.js"; import { createSocialUserDTO } from "../dtos/auth.dto.js"; export const handleSignUp = async (req, res, next) => { @@ -180,6 +180,18 @@ export const handleResetPassword = async (req, res, next) => { try{ const result = await resetPassword({userId, oldPassword, newPassword}); + res.status(200).success(result); + } catch(err) { + next(err); + } +} + +export const handleChangePassword = async (req, res, next) => { + const userId = req.user.id; + const {password} = req.body; + try{ + const result = await changePassword({userId, password}); + res.status(200).success(result); } catch(err) { next(err); diff --git a/src/index.js b/src/index.js index 75d8af4..dcdb717 100644 --- a/src/index.js +++ b/src/index.js @@ -11,7 +11,7 @@ import { specs } from "./configs/swagger.config.js"; import { jwtStrategy } from "./Auths/strategies/jwt.strategy.js"; import { handleGetFriendsList, handlePostFriendsRequest, handleGetIncomingFriendRequests, handleGetOutgoingFriendRequests, handleAcceptFriendRequest, handleRejectFriendRequest, handleDeleteFriendRequest } from "./controllers/friend.controller.js"; import { handleSendMyLetter, handleSendOtherLetter, handleGetLetterDetail, handleRemoveLetterLike, handleAddLetterLike, handleGetPublicLetterFromOther, handleGetPublicLetterFromFriend, handleGetUserLetterStats, handleGetLetterAssets, handleGetLetterByAiKeyword } from "./controllers/letter.controller.js"; -import { handleCheckDuplicatedEmail, handleLogin, handleRefreshToken, handleSignUp, handleSendVerifyEmailCode, handleCheckEmailCode, handleGetAccountInfo, handleResetPassword, handleLogout, handleWithdrawUser, handleCheckDuplicatedUsername, handleSocialLogin, handleSocialLoginCertification, handleSocialLoginCallback } from "./controllers/auth.controller.js"; +import { handleCheckDuplicatedEmail, handleLogin, handleRefreshToken, handleSignUp, handleSendVerifyEmailCode, handleCheckEmailCode, handleGetAccountInfo, handleResetPassword, handleLogout, handleWithdrawUser, handleCheckDuplicatedUsername, handleSocialLogin, handleSocialLoginCertification, handleSocialLoginCallback, handleChangePassword } from "./controllers/auth.controller.js"; import { handlePostMatchingSession, handlePatchMatchingSessionStatusDiscarded, handlePatchMatchingSessionStatusFriends, handlePostSessionReview } from "./controllers/session.controller.js"; import { handleCreateUserAgreements, handlePatchOnboardingStep1, handleGetAllInterests, handleGetMyInterests, handleUpdateMyOnboardingInterests, handleGetMyNotificationSettings, handleUpdateMyNotificationSettings, handleGetMyProfile, handlePatchMyProfile, handlePostMyProfileImage, handlePutMyPushSubscription, handleGetMyConsents, handlePatchMyConsents, handleUpdateActivity, } from "./controllers/user.controller.js"; import { handleGetAnonymousThreads, handleGetAnonymousThreadLetters, handleGetSelfMailbox, handleGetLetterFromFriend, } from "./controllers/mailbox.controller.js"; @@ -20,7 +20,7 @@ import { handleGetCommunityGuidelines, handleGetTerms, handleGetPrivacy, } from import { handleGetWeeklyReport } from "./controllers/weeklyReport.controller.js"; import { handleGetTodayQuestion } from "./controllers/question.controller.js"; import { validate } from "./middlewares/validate.middleware.js"; -import { emailSchema, loginSchema, passwordSchema, SignUpSchema, usernameSchema, verificationConfirmCodeSchema, verificationSendCodeSchema } from "./schemas/auth.schema.js"; +import { changePasswordSchema, emailSchema, loginSchema, resetPasswordSchema, SignUpSchema, usernameSchema, verificationConfirmCodeSchema, verificationSendCodeSchema } from "./schemas/auth.schema.js"; import { handleInsertInquiryAsUser, handleInsertInquiryAsAdmin, handleGetInquiry, handleGetInquiryDetail } from "./controllers/inquiry.controller.js"; import { isLogin } from "./middlewares/auth.middleware.js"; import { isRestricted } from "./middlewares/restriction.middleware.js"; @@ -160,7 +160,8 @@ app.get("/auth/refresh", handleRefreshToken); app.post("/auth/:type/verification-codes", validate(verificationSendCodeSchema), handleSendVerifyEmailCode); // 이메일 인증번호 전송 app.post("/auth/:type/verification-codes/confirm", validate(verificationConfirmCodeSchema), handleCheckEmailCode); // 이메일 인증번호 확인 app.post("/auth/find-id", validate(emailSchema), handleGetAccountInfo); // 아이디 찾기 -app.patch("/auth/reset-password", isLogin, validate(passwordSchema), handleResetPassword); // 비밀번호 찾기 +app.patch("/auth/reset-password", isLogin, validate(resetPasswordSchema), handleResetPassword); // 비밀번호 초기화 +app.patch("/auth/change-password", isLogin, validate(changePasswordSchema), handleChangePassword); // 비밀번호 변경 app.post("/auth/logout", isLogin, handleLogout); // 로그아웃 app.delete("/users", isLogin, handleWithdrawUser); // 탈퇴 app.post("/users/me/agreements", isLogin, validate(createUserAgreementsSchema), handleCreateUserAgreements) // 이용약관 동의 diff --git a/src/schemas/auth.schema.js b/src/schemas/auth.schema.js index 8461f12..76e4181 100644 --- a/src/schemas/auth.schema.js +++ b/src/schemas/auth.schema.js @@ -2,7 +2,7 @@ import { z } from "zod"; const emailPart = z.email("이메일 형식이 올바르지 않습니다."); const usernamePart = z.string("아이디는 필수입니다.").min(6, "아이디는 최소 6자 이상입니다.").max(16, "아이디는 최대 6자 이상입니다."); -const passwordPart = z.string("비밀번호는 필수입니다.").regex(/^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]{8,16}$/, "비밀번호는 최소 8자-16자 제한입니다. (영문, 숫자 포함해 최대 16자리)"); +const passwordPart = z.string("비밀번호는 필수입니다.").regex(/^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9!@#$%^&*]{8,16}$/, "비밀번호는 최소 8자-16자 제한입니다. (영문, 숫자 포함해 최대 16자리)"); const phoneNumberPart = z.string("전화번호는 필수입니다.").regex(/^01(?:0|1|[6-9])-(?:\d{3}|\d{4})-\d{4}$/, "전화번호 형식이 올바르지 않습니다."); export const SignUpSchema = z.object({ @@ -39,7 +39,13 @@ export const usernameSchema = z.object({ }) }) -export const passwordSchema = z.object({ +export const changePasswordSchema = z.object({ + body: z.object({ + password: passwordPart + }) +}) + +export const resetPasswordSchema = z.object({ body: z.object({ oldPassword: passwordPart, newPassword: passwordPart diff --git a/src/services/auth.service.js b/src/services/auth.service.js index 4782c01..1be245c 100644 --- a/src/services/auth.service.js +++ b/src/services/auth.service.js @@ -300,5 +300,12 @@ export const resetPassword = async ({userId, oldPassword, newPassword}) => { const newPasswordHash = await bcrypt.hash(newPassword, 10); await updatePassword({userId, newPassword: newPasswordHash}); + return { message: "비밀번호 재설정이 완료되었습니다." }; +} + +export const changePassword = async ({userId, password}) => { + const newPasswordHash = await bcrypt.hash(password, 10); + await updatePassword({userId, newPassword: newPasswordHash}); + return { message: "비밀번호 재설정이 완료되었습니다." }; } \ No newline at end of file diff --git a/src/swagger/auth.swagger.js b/src/swagger/auth.swagger.js index 0794977..bf573cd 100644 --- a/src/swagger/auth.swagger.js +++ b/src/swagger/auth.swagger.js @@ -691,7 +691,7 @@ * @swagger * /auth/reset-password: * patch: - * summary: 비밀번호 재설정 + * summary: 비밀번호 초기화 * description: "/auth/reset-password/confirm 에서 발급받은 임시 AccessToken이 필요합니다." * tags: [로그인] * security: @@ -810,6 +810,111 @@ * example: "기존 비밀번호를 찾을 수 없습니다." */ +/** + * @swagger + * /auth/change-password: + * patch: + * summary: 비밀번호 변경 + * description: "비밀번호 변경 API입니다." + * tags: [로그인] + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - password + * properties: + * password: + * type: string + * example: "Password123!" + * responses: + * 200: + * description: 비밀번호 변경 성공 + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - properties: + * success: + * type: object + * properties: + * message: + * type: string + * example: "비밀번호 재설정이 완료되었습니다." + * 401: + * description: | + * 인증 실패: + * - `AUTH_TOKEN_EXPIRED`: 토큰이 만료되었습니다. + * - `AUTH_INVALID_TOKEN`: 액세스 토큰이 아니거나 유효하지 않습니다. + * - `AUTH_NOT_ACCESS_TOKEN`: 액세스 토큰이 아닙니다. + * - `AUTH_EXPIRED_TOKEN`: 이미 로그아웃된 토큰입니다. + * - `AUTH_UNAUTHORIZED`: 액세스 토큰이 유효하지 않습니다. + * - `AUTH_NOT_FOUND`: 인증 토큰이 없습니다. + * content: + * application/json: + * schema: + * oneOf: + * - allOf: + * - $ref: '#/components/schemas/ErrorResponse' + * - properties: + * error: + * properties: + * errorCode: + * example: "AUTH_TOKEN_EXPIRED" + * reason: + * example: "토큰이 만료되었습니다." + * - allOf: + * - $ref: '#/components/schemas/ErrorResponse' + * - properties: + * error: + * properties: + * errorCode: + * example: "AUTH_INVALID_TOKEN" + * reason: + * example: "액세스 토큰이 아니거나 유효하지 않습니다." + * - allOf: + * - $ref: '#/components/schemas/ErrorResponse' + * - properties: + * error: + * properties: + * errorCode: + * example: "AUTH_NOT_ACCESS_TOKEN" + * reason: + * example: "액세스 토큰이 아닙니다." + * - allOf: + * - $ref: '#/components/schemas/ErrorResponse' + * - properties: + * error: + * properties: + * errorCode: + * example: "AUTH_EXPIRED_TOKEN" + * reason: + * example: "이미 로그아웃된 토큰입니다." + * - allOf: + * - $ref: '#/components/schemas/ErrorResponse' + * - properties: + * error: + * properties: + * errorCode: + * example: "AUTH_UNAUTHORIZED" + * reason: + * example: "액세스 토큰이 유효하지 않습니다." + * - allOf: + * - $ref: '#/components/schemas/ErrorResponse' + * - properties: + * error: + * properties: + * errorCode: + * example: "AUTH_NOT_FOUND" + * reason: + * example: "인증 토큰이 없습니다." + */ + /** * @swagger * /auth/logout: