이전 내용
[파이썬] FastAPI - 메모 앱 프로젝트 13: 아이디 찾기
이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 12: 파일 분할하기이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 11: 회원 탈퇴이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 10: 환영 이메일 발송이전
puppy-foot-it.tistory.com
비밀번호 찾기 대략적인 과정
비밀번호의 경우, 아이디와 등록한 이메일을 검증받아 일치하는 경우 등록된 이메일로 임시 비밀번호를 보내 로그인하고 비밀번호를 수정하도록 구현해 본다.
우선 구현해볼 것은 회원가입 시 등록한 아이디(username)와 이메일 주소를 입력하면, 해당 정보가 등록된 정보와 일치하는지 확인한 후 일치하면 등록된 이메일로 임시 비밀번호를 발송하는 기능을 구현해 보려 한다.
앞서 진행한 아이디 찾기와 거의 비슷한데, 대략적인 과정은
- email_service.py: 임시비밀번호 생성하여 발송하는 로직 구현 (Faker 라이브러리 사용)
- email_class.py: 클래스 추가 (2개) - 이메일 전송 클래스 / 아이디 & 이메일 검색 클래스
- users_controller.py: 라우터 생성 (아이디 / 비밀번호 검색 시 이메일 발송)
- find_id.html: find_acount.html로 파일명을 수정하고 비밀번호 찾기를 위한 프런트엔드 페이지 구현
임시비밀번호 생성 및 전송 구현
임시비밀번호 생성 로직은 Faker 라이브러리를 이용해 구현할 것이므로, 먼저 Faker 라이브러리를 설치한다.
conda install faker
◆ email_service.py
email_service.py 에 임시 비밀번호를 생성하여 DB를 임시비밀번호로 업데이트하고, 이메일을 전송하는 함수를 정의한다.
from faker import Faker
# 임시비밀번호 생성용
fake = Faker()
....
# 임시 비밀번호 생성 함수
def generate_temp_pw() -> str:
return fake.password()
# 임시 비밀번호 이메일 전송
def send_temp_pw_email(email: str, username: str, temp_password: str):
email_service = EmailServiceSendTempPW() # EmailServiceSendTempPW 클래스 인스턴스 생성
try:
email_service.send_email(receiver_email=email, username=username, temp_password=temp_password)
logger.info(f"사용자 {username}의 임시 비밀번호를 {email}로 전송했습니다.")
except Exception as e:
logger.error(f"이메일 전송 실패: {e}") # 에러 로깅
# 임시 비밀번호 업데이트 함수
def update_user_password(db: Session, user: User, new_password: str) -> bool:
# 비밀번호 해시 설정
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
hashed_password = pwd_context.hash(new_password) # 비밀번호 해싱
user.password = hashed_password # 비밀번호 업데이트
try:
db.commit() # 변경사항 저장
logger.info(f"사용자 ID {user.username}의 비밀번호가 업데이트 되었습니다.")
return True
except Exception as e:
db.rollback()
logger.error(f"비밀번호 업데이트 실패: {e}")
return False
◆ email_class.py
email_class.py 로 넘어와서 'EmailServiceSendTempPW' 클래스와 'UsernameEmailRequest' 클래스를 작성하고, 기존 클래스들의 예외 처리를 보완해 준다.
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from dotenv import load_dotenv
import os
from pydantic import BaseModel
import logging
load_dotenv()
# 로거 설정
logger = logging.getLogger(__name__)
class EmailServiceWelcome:
def send_email(
self,
receiver_email: str,
):
sender_email = os.getenv('EMAIL_ADDRESS')
password = os.getenv('EMAIL_PASSWORD')
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = "회원 가입을 환영합니다."
body = "메모 앱 서비스를 이용해 주셔서 감사합니다."
message.attach(MIMEText(body, "plain"))
try:
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(sender_email, password)
server.send_message(message)
logger.info(f"회원가입 이메일이 {receiver_email}로 성공적으로 전송되었습니다.")
except Exception as e:
logger.error(f"이메일 전송 실패: {e}")
class EmailServiceBye:
def send_email(
self,
receiver_email: str,
):
sender_email = os.getenv('EMAIL_ADDRESS')
password = os.getenv('EMAIL_PASSWORD')
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = "탈퇴 완료 이메일."
body = "그동안 메모 앱 서비스를 이용해 주셔서 감사합니다."
message.attach(MIMEText(body, "plain"))
try:
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(sender_email, password)
server.send_message(message)
logger.info(f"탈퇴 안내 이메일이 {receiver_email}로 성공적으로 전송되었습니다.")
except Exception as e:
logger.error(f"이메일 전송 실패: {e}")
class EmailServiceFindId:
def send_email(
self,
receiver_email: str,
username: str,
):
sender_email = os.getenv('EMAIL_ADDRESS')
password = os.getenv('EMAIL_PASSWORD')
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = "아이디 찾기 이메일."
body = f"사용자님의 아이디는 {username} 입니다."
message.attach(MIMEText(body, "plain"))
try:
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(sender_email, password)
server.send_message(message)
logger.info(f"아이디가 {receiver_email}로 성공적으로 전송되었습니다.")
except Exception as e:
logger.error(f"이메일 전송 실패: {e}")
class EmailRequest(BaseModel):
email: str
class EmailServiceSendTempPW:
def send_email(
self,
receiver_email: str,
username: str,
temp_password: str,
):
sender_email = os.getenv('EMAIL_ADDRESS')
password = os.getenv('EMAIL_PASSWORD')
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = "임시 비밀번호 이메일."
body = f"사용자님의 임시비밀번호는 {temp_password} 입니다."
message.attach(MIMEText(body, "plain"))
try:
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(sender_email, password)
server.send_message(message)
logger.info(f"임시 비밀번호가 {receiver_email}로 성공적으로 전송되었습니다.")
except Exception as e:
logger.error(f"이메일 전송 실패: {e}")
class UsernameEmailRequest(BaseModel):
username: str
email: str
◆ email_service.py
그리고 email_service.py에 앞서 만든 EmailServiceSendTempPW 클래스를 import
from service.email_class import EmailServiceWelcome, EmailServiceBye, EmailServiceFindId, EmailServiceSendTempPW
◆ users_controller.py
from service.email_service import send_bye_email, send_welcome_email, send_username_email, generate_temp_pw, send_temp_pw_email
from service.email_class import EmailRequest, UsernameEmailRequest
...
# 임시 비밀번호 이메일 발송
@router.post("/reset-password")
async def reset_password(req: UsernameEmailRequest, db: Session = Depends(get_db)):
username = req.username
email = req.email
logger.info(f"비밀번호 재설정 요청: 아이디 {username}, 이메일 {email}")
user = db.query(User).filter(User.username == username, User.email == email).first()
if user is None:
logger.warning(f"비밀번호 재설정 실패: 일치하는 정보가 없습니다. 아이디: {username}, 이메일:{email}")
raise HTTPException(status_code=404, detail="일치하는 정보가 없습니다.")
# 임시비밀번호 생성 및 DB 업데이트
temp_password = generate_temp_pw() # 임시 비밀번호 생성
# 비밀번호 DB 업데이트
if not update_user_password(db, user, temp_password):
logger.error(f"사용자 ID {user.username}에 대한 비밀번호 업데이트에 실패하였습니다.")
raise HTTPException(status_code=500, detail="비밀번호 업데이트에 실패하였습니다.")
# 임시비밀번호 이메일 발송
try:
send_temp_pw_email(email=email, username=username, temp_password=temp_password)
logger.info(f"임시 비밀번호를 {email}로 성공적으로 전송했습니다.")
except Exception as e:
logger.error(f"이메일 전송 실패: {e}")
raise HTTPException(status_code=500, detail="이메일 전송에 실패했습니다. 다시 시도해 주세요")
return {"success": True, "message": "임시비밀번호가 이메일로 발송되었습니다."}
◆ find_id.html > find_account.html
먼저 파일명을 아래와 같이 변경한다.
그리고 아이디 찾기와 비밀번호 찾기가 세로 한 줄로 정렬될 수 있게끔 CSS와 HTML을 수정해주고, 자바스크립트에 비밀번호 찾기와 관련된 동적 기능을 구현한다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>아이디/비밀번호 찾기</title>
<style>
body {
font-family: 'Noto Sans KR', sans-serif;
background-color: #f8f9fa;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
}
.wrapper {
display: flex;
flex-direction: column; /* 세로 정렬 */
align-items: center;
gap: 2rem; /* 위아래 간격 */
}
.container {
max-width: 400px;
padding: 2rem;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 100%;
}
h1 {
font-size: 1.5rem;
color: #007bff;
margin-bottom: 2rem;
}
.form-group {
margin-bottom: 1rem;
width: 100%;
}
.form-group label {
margin-bottom: .5rem;
color: #888;
text-align: left;
display: block;
}
.form-group input {
padding: 0.75rem;
border: 1px solid #ced4da;
border-radius: 5px;
width: 100%;
box-sizing: border-box;
}
.form-group input:focus {
border-color: #80bdff;
box-shadow: 0 0 0 2px rgba(0,123,255,.25);
}
.buttons button {
width: 100%;
padding: 0.75rem;
border: none;
border-radius: 5px;
background-color: #007bff;
color: white;
margin-top: 0.5rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
box-sizing: border-box;
}
.buttons button:hover {
background-color: #0056b3;
}
#message {
margin-top: 1rem;
color: #28a745;
font-weight: 500;
}
@media (max-width: 768px) {
.container {
width: 90%;
padding: 1.5rem;
}
h1 {
font-size: 1.25rem;
}
}
</style>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="wrapper">
<div class="container">
<h1>아이디 찾기</h1>
<form id="find-id-form" onsubmit="return submitFormID(event)">
<div class="form-group">
<label for="find-id-email">가입한 이메일 주소</label>
<input type="email" id="find-id-email" name="email" required>
</div>
<div class="buttons">
<button type="submit">아이디 전송</button>
</div>
</form>
<div id="find-id-message"></div>
</div>
<div class="container">
<h1>비밀번호 찾기</h1>
<form id="find-pw-form" onsubmit="return submitFormPW(event)">
<div class="form-group">
<label for="find-pw-username">사용자 이름</label>
<input type="text" id="find-pw-username" name="username" required>
</div>
<div class="form-group">
<label for="find-pw-email">이메일 주소</label>
<input type="email" id="find-pw-email" name="email" required>
</div>
<div class="buttons">
<button type="submit">임시비밀번호 전송</button>
</div>
</form>
<div id="find-pw-message"></div>
</div>
</div>
<script>
function submitFormID(event) {
event.preventDefault(); // 기본 제출 이벤트 방지
const email = document.getElementById('find-id-email').value;
const messageDiv = document.getElementById('find-id-message');
fetch('/send-username', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email: email })
})
.then(response => {
if (!response.ok) {
return response.json().then(err => {
throw new Error(err.detail || '이메일 전송에 실패했습니다.');
});
}
return response.json();
})
.then(data => {
messageDiv.innerText = data.message;
messageDiv.style.color = '#28a745'; // 성공 메시지 색상
})
.catch(error => {
messageDiv.innerText = error.message;
messageDiv.style.color = '#dc3545'; // 에러 메시지 색상
});
}
function submitFormPW(event) {
event.preventDefault(); // 기본 제출 이벤트 방지
const username = document.getElementById('find-pw-username').value;
const email = document.getElementById('find-pw-email').value;
const messageDiv = document.getElementById('find-pw-message');
fetch('/reset-password', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username: username, email: email })
})
.then(response => {
if (!response.ok) {
return response.json().then(err => {
throw new Error(err.detail || '이메일 전송에 실패했습니다.');
});
}
return response.json();
})
.then(data => {
messageDiv.innerText = data.message;
messageDiv.style.color = '#28a745'; // 성공 메시지 색상
})
.catch(error => {
messageDiv.innerText = error.message;
messageDiv.style.color = '#dc3545'; // 에러 메시지 색상
});
}
</script>
</body>
</html>
디자인은 아래와 같다.
◆ home.html
a href= 부분을 find_id 에서 find_account로 변경해 준다.
<p>
<a href="find_account" style="text-decoration: none; color:blue;">
아이디/비밀번호 찾기 </a>
</p>
◆ main.py
기존의 엔드포인트 였던 find_id 를 find_account로 수정해 준다.
# 아이디 비밀번호 찾기 페이지 엔드포인트 추가
@app.get("/find_account", response_class=HTMLResponse)
async def read_find_id(request: Request):
logger.info("아이디/비밀번호 찾기 페이지 요청 수신")
return templates.TemplateResponse('find_account.html', {"request": request})
임시비밀번호 테스트하기
main.py를 실행하여 서버를 실행한 뒤, ID와 이메일 주소를 입력하여 임시 비밀번호를 받는 기능이 잘 구현되는지 테스트해 본다.
임시 비밀번호가 이메일로 잘 도착했다.
과연 새로운 비밀번호로 로그인이 되는지, 시도해보니 새로운 임시 비밀번호로 로그인이 안 된다.
임시 비밀번호 생성 및 업데이트가 로그에서는 완료되었다고 하는데, DB를 조회해보면 계속 똑같아서 그냥 직접 SQL 쿼리를 줘서 바꾸기로 하였다.
따라서, 아래의 부분을
# 임시 비밀번호 업데이트 함수
def update_user_password(db: Session, user: User, new_password: str) -> bool:
# 비밀번호 해시 설정
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
hashed_password = pwd_context.hash(new_password) # 비밀번호 해싱
user.password = hashed_password # 비밀번호 업데이트
try:
db.commit() # 변경사항 저장
logger.info(f"사용자 ID {user.username}의 비밀번호가 업데이트 되었습니다.")
return True
except Exception as e:
db.rollback()
logger.error(f"비밀번호 업데이트 실패: {e}")
return False
이렇게 바꾸고 코드를 다시 실행한 뒤
from sqlalchemy import text
# 임시 비밀번호 업데이트 함수
def update_user_password(db: Session, user: User, new_password: str):
# 비밀번호 해시 설정
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
user.hashed_password = pwd_context.hash(new_password) # 비밀번호 해싱
try:
# SQL 문을 text() 함수로 감싸줌.
db.execute(
text("UPDATE users SET hashed_password = :hashed_password WHERE username = :username"),
{"hashed_password": user.hashed_password, "username": user.username}
)
db.commit() # 변경 사항 저장
logger.info(f"사용자 ID {user.username}의 비밀번호가 업데이트 되었습니다.")
except Exception as e:
db.rollback()
logger.error(f"비밀번호 업데이트 실패: {e}")
raise
임시비밀번호로 로그인 해보니, 드디어 성공했다. (몇 시간을 붙잡고 있었다.)
◆ find_account.html
그리고 find_account.html에 뒤로가기 링크를 추가하여 홈페이지 URL로 리디렉션 하게 바꿔준다. (임시 비밀번호 전송 밑)
<div class="buttons">
<button type="submit">임시비밀번호 전송</button>
</div>
</form>
<div id="find-pw-message"></div>
</div>
<p>
<a href="/" style="text-decoration: none; color:blue;">
뒤로가기 </a>
</p>
비밀번호 변경하기
임시 비밀번호가 생성되면서 DB의 비밀번호가 임시비밀번호로 변경되기 때문에 사용자 입장에서는 기억하기가 쉽지 않다. 따라서 이를 변경해주는 기능을 구현해 본다.
먼저, templates 디렉터리 내에 'change_pw.html' 파일을 생성한다. 내부 코드 내용은 이따가 작성한다.
◆ main.py
그리고 main.py에 엔드포인트를 추가한다.
# 비밀번호 변경하기 페이지 엔드포인트 추가
@app.get("/change_pw", response_class=HTMLResponse)
async def read_change_pw(request: Request):
logger.info("비밀번호 변경 페이지 요청 수신")
return templates.TemplateResponse('change_pw.html', {"request": request})
다음으로, home.html에 change_pw 사이트로 이동할 수 있는 링크를 추가한다. (현재는 기능 구현이 주목적이므로 프론트 엔드 부분은 추후 수정하기로 한다.)
<p>
<a href="change_pw" style="text-decoration: none; color:blue;">
비밀번호 변경 </a>
</p>
비밀번호 변경을 누르면 http://127.0.0.1:8000/change_pw 로 잘 넘어간다.
◆ schemas.py
# 비밀번호 변경 시 데이터 검증
class UserUpdate(BaseModel):
username: str
current_password: str # 해시 전 패스워드
new_password: str
new_password_confirm: str
◆ users_controller.py
users_controller.py에 비밀번호 변경 라우터를 생성하여 추가해 준다.
그리고 회원가입 때와 동일한 비밀번호 규칙을 사용하기 위해, 회원 가입 함수 내에 있던 비밀번호 규칙 확인 변수를 바깥으로 꺼내 전역 변수로 만든다.
from passlib.context import CryptContext
from schemas import UserCreate, UserLogin, UserUpdate # 스키마 import
# 비밀번호 규칙 확인
password_regex = re.compile(r"""
(?=.*[a-z]) # 적어도 하나의 소문자
(?=.*[A-Z]) # 적어도 하나의 대문자
(?=.*\d) # 적어도 하나의 숫자
(?=.*[@$!%*?&\-_+=<>]) # 적어도 하나의 특수문자
.{10,} # 길이는 10자 이상
""", re.VERBOSE)
# 비밀번호 변경
@router.put("/change_pw")
async def update_password(req: UserUpdate, db: Session = Depends(get_db)):
username = req.username
current_password = req.current_password # 현재 비밀번호
new_password = req.new_password # 새로운 비밀번호
new_password_confirm = req.new_password_confirm # 새로운 비밀번호 확인
logger.info(f"비밀번호 변경 요청: 아이디 {username}")
user = db.query(User).filter(User.username == username).first()
if user is None:
logger.warning(f"비밀번호 변경 실패: 사용자 ID {username}가 존재하지 않습니다.")
raise HTTPException(status_code=404, detail="사용자가 존재하지 않습니다.")
# 현재 비밀번호 확인
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
if not pwd_context.verify(current_password, user.hashed_password):
logger.warning(f"비밀번호 변경 실패: 현재 비밀번호가 일치하지 않습니다.")
raise HTTPException(status_code=403, detail="현재 비밀번호가 일치하지 않습니다.")
# 새로운 비밀번호와 확인 비밀번호 일치 여부 확인
if new_password != new_password_confirm:
logger.warning(f"비밀번호 변경 실패: 새로운 비밀번호와 비밀번호 확인이 일치하지 않습니다.")
raise HTTPException(status_code=400, detail="새로운 비밀번호와 비밀번호 확인이 일치하지 않습니다.")
# 비밀번호 규칙 확인
if not password_regex.match(new_password):
logger.warning("비밀번호 변경 실패: 비밀번호 규칙 위반.")
raise HTTPException(status_code=400, detail="비밀번호는 최소 10자 이상이며, 대문자, 소문자, 숫자 및 특수문자가 포함되어야 합니다.")
# 비밀번호 업데이트 처리
update_user_password(db, user, new_password) # 기존 함수를 호출하여 비밀번호 업데이트
return {"success": True, "message": "비밀번호가 성공적으로 변경되었습니다."}
◆ change_pw.html
이제 비밀번호 변경 프론트엔드 역할을 할 change_pw.html 파일의 코드를 작성해 준다.
해당 페이지의 주요 기능은
- 사용자 이름(ID), 현재 비밀번호, 새로운 비밀번호, 새로운 비밀번호 확인 입력 받음
- 새로운 비밀번호가 현재 비밀번호와 같을 경우 에러 발생
- 새로운 비밀번호와 새로운 비밀번호 확인이 불일치할 경우 에러 발생
- 새로운 비밀번호가 비밀번호 규칙을 지키지 않을 경우 에러 발생
- 각 에러 메시지는 팝업으로 안내
- 비밀번호 변경이 성공하면 입력 칸이 reset (초기화) 됨.
[change_pw.html 전체코드]
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>아이디/비밀번호 찾기</title>
<style>
body {
font-family: 'Noto Sans KR', sans-serif;
background-color: #f8f9fa;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
}
.container {
max-width: 400px;
padding: 2rem;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 100%;
}
h1 {
font-size: 1.5rem;
color: #007bff;
margin-bottom: 2rem;
}
.form-group {
margin-bottom: 1rem;
width: 100%;
}
.form-group label {
margin-bottom: .5rem;
color: #888;
text-align: left;
display: block;
}
.form-group input {
padding: 0.75rem;
border: 1px solid #ced4da;
border-radius: 5px;
width: 100%;
box-sizing: border-box;
}
.form-group input:focus {
border-color: #80bdff;
box-shadow: 0 0 0 2px rgba(0,123,255,.25);
}
.buttons button {
width: 100%;
padding: 0.75rem;
border: none;
border-radius: 5px;
background-color: #007bff;
color: white;
margin-top: 0.5rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
box-sizing: border-box;
}
.buttons button:hover {
background-color: #0056b3;
}
#message {
margin-top: 1rem;
color: #28a745;
font-weight: 500;
}
@media (max-width: 768px) {
.container {
width: 90%;
padding: 1.5rem;
}
h1 {
font-size: 1.25rem;
}
}
</style>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<h1>비밀번호 변경하기</h1>
<form id="changePWForm" onsubmit="submitChangePWForm(event)">
<div class="form-group">
<label for="username">사용자 이름:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="current-pw">현재 비밀번호:</label>
<input type="password" id="current-pw" name="current_password" required>
</div>
<div class="form-group">
<label for="new-pw">새로운 비밀번호:</label>
<input type="password" id="new-pw" name="new_password" required>
</div>
<div class="form-group">
<label for="new-pw-confirm">비밀번호 확인:</label>
<input type="password" id="new-pw-confirm" name="confirm_password" required>
</div>
<div class="buttons">
<button type="submit">변경하기</button>
</div>
<div id="find-id-message" style="margin-top:10px;"></div> <!-- 메시지 표시용 -->
<p>
<a href="/" style="text-decoration: none; color:blue;">
뒤로가기 </a>
</p>
</form>
<script>
function submitChangePWForm(event) {
event.preventDefault(); // 기본 제출 이벤트 방지
const username = document.getElementById('username').value;
const currentpw = document.getElementById('current-pw').value;
const newpw = document.getElementById('new-pw').value;
const confirmpw = document.getElementById('new-pw-confirm').value;
const messageDiv = document.getElementById('find-id-message');
if (currentpw == newpw) {
alert( "새 비밀번호는 현재 비밀번호와 달라야 합니다.");
return ;
}
if (newpw != confirmpw) {
alert( "새 비밀번호가 서로 일치하지 않습니다.");
return ;
}
fetch('/change_pw', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: username,
current_password: currentpw,
new_password: newpw,
new_password_confirm: confirmpw
})
})
.then(response => response.json().then(body => ({
status: response.status, body: body
})))
.then(result => {
if (result.status == 200) {
alert(result.body.message); // 성공 팝업
document.getElementById('changePWForm').reset(); // 비밀번호 변경 모든 칸 초기화
//window.location.href = '/'; // 메인 페이지로 리다이렉트
} else {
throw new Error(result.body.detail || '비밀번호 변경에 실패하였습니다.');
}
})
.catch(error => {
console.error('Error', error);
alert(error.message) // 실패 팝업
});
}
</script>
해당 코드는 users_controller.py의 @router.put("/change_pw")
async def update_password(req: UserUpdate, db: Session = Depends(get_db)): 부분과 연결된다.
- 사용자 이름(ID), 현재 비밀번호, 새로운 비밀번호, 새로운 비밀번호 확인 입력 받음
▶ function submitChangePWForm(event) 함수
- 새로운 비밀번호가 현재 비밀번호와 같을 경우 에러 발생
▶ 팝업 메시지: if (currentpw == newpw) { 부분
백엔드 구현: users_controller.py의 update_password 함수에서 if current_password == new_password: 부분
- 새로운 비밀번호와 새로운 비밀번호 확인이 불일치할 경우 에러 발생
▶ 팝업 메시지: if (newpw != confirmpw) { 부분
백엔드 구현: users_controller.py의 update_password 함수에서 if new_password != new_password_confirm: 부분
- 새로운 비밀번호가 비밀번호 규칙을 지키지 않을 경우 에러 발생
▶ 백엔드 구현: users_controller.py의 update_password 함수에서 if not password_regex.match(new_password): 부분
- 각 에러 메시지는 팝업으로 안내
.catch(error => {
console.error('Error', error);
alert(error.message) // 실패 팝업
});
비밀번호 변경이 성공하면 입력 칸이 reset (초기화) 됨.
.then(result => {
if (result.status == 200) {
alert(result.body.message); // 성공 팝업
document.getElementById('changePWForm').reset(); // 비밀번호 변경 모든 칸 초기화
비밀번호 변경 테스트
main.py를 실행하여 서버를 실행한 뒤, 비밀번호 변경 페이지로 이동하여 실제로 변경되는지 테스트해본다.
현재 비밀번호 일치 여부(불일치 시 에러)
새 비밀번호 현재 비밀번호 일치 여부 (일치 시 에러)
새 비밀번호 규칙 준수 여부 (규칙 준수하지 않을 시 에러)
비밀번호 변경 성공 (모든 조건 만족 시)
다음 내용
[파이썬] FastAPI - 메모 앱 프로젝트 15: 이메일 기능 보완
이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 14: 비밀번호 찾기(수정)이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 13: 아이디 찾기이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 12: 파일 분할하기
puppy-foot-it.tistory.com
'[파이썬 Projects] > <파이썬 웹개발>' 카테고리의 다른 글
[파이썬] FastAPI - 메모 앱 프로젝트 16: 메인 페이지 나누기 (1) | 2025.05.14 |
---|---|
[파이썬] FastAPI - 메모 앱 프로젝트 15: 이메일 기능 보완 (0) | 2025.05.14 |
[파이썬] FastAPI - 메모 앱 프로젝트 13: 아이디 찾기 (0) | 2025.05.13 |
[파이썬] FastAPI - 메모 앱 프로젝트 12: 파일 분할하기 (0) | 2025.05.13 |
[파이썬]FastAPI - 다른 컴퓨터에서 프로젝트(VSCODE, 깃허브) (0) | 2025.05.12 |