Initial commit

This commit is contained in:
2025-10-05 17:37:51 +09:00
parent 5cbe9a2524
commit 3a7fabb830
219 changed files with 81295 additions and 0 deletions

116
backend/forms/auth_forms.py Normal file
View File

@@ -0,0 +1,116 @@
# backend/forms/auth_forms.py (refactor)
from __future__ import annotations
import re
import unicodedata
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import (
DataRequired, Length, Email, EqualTo, ValidationError, Regexp
)
from backend.models.user import User
# ─────────────────────────────────────────────────────────────
# 공통 필터/유틸
def strip_filter(x: str | None) -> str | None:
return x.strip() if isinstance(x, str) else x
def lower_strip(x: str | None) -> str | None:
return x.strip().lower() if isinstance(x, str) else x
def nfc_korean(x: str | None) -> str | None:
if not isinstance(x, str):
return x
# 한글 이름 등 유니코드 정규화 (NFC)
return unicodedata.normalize("NFC", x.strip())
# 비밀번호 정책: 8~64자, 대문자/소문자/숫자/특수문자 각 1개 이상
password_policy_validators = [
Length(min=8, max=64, message="비밀번호는 8~64자여야 합니다."),
Regexp(r".*[A-Z].*", message="비밀번호에 대문자가 1자 이상 포함되어야 합니다."),
Regexp(r".*[a-z].*", message="비밀번호에 소문자가 1자 이상 포함되어야 합니다."),
Regexp(r".*\d.*", message="비밀번호에 숫자가 1자 이상 포함되어야 합니다."),
Regexp(r".*[^A-Za-z0-9].*", message="비밀번호에 특수문자가 1자 이상 포함되어야 합니다."),
]
# ─────────────────────────────────────────────────────────────
class RegistrationForm(FlaskForm):
username = StringField(
"이름",
filters=[nfc_korean],
validators=[
DataRequired(message="이름을 입력해주세요."),
Length(min=2, max=20, message="이름은 2~20자 사이여야 합니다."),
],
render_kw={
"placeholder": "이름 (한글만 허용)",
"autocomplete": "name",
"autocapitalize": "off",
"autocorrect": "off",
"spellcheck": "false",
},
)
email = StringField(
"이메일",
filters=[lower_strip],
validators=[
DataRequired(message="이메일을 입력해주세요."),
Email(message="유효한 이메일을 입력하세요."),
],
render_kw={
"placeholder": "예: user@example.com",
"autocomplete": "email",
"inputmode": "email",
},
)
password = PasswordField(
"비밀번호",
validators=[DataRequired(message="비밀번호를 입력해주세요."), *password_policy_validators],
render_kw={"placeholder": "비밀번호", "autocomplete": "new-password"},
)
confirm_password = PasswordField(
"비밀번호 확인",
validators=[
DataRequired(message="비밀번호 확인을 입력해주세요."),
EqualTo("password", message="비밀번호가 일치하지 않습니다."),
],
render_kw={"placeholder": "비밀번호 다시 입력", "autocomplete": "new-password"},
)
submit = SubmitField("회원가입")
def validate_username(self, field):
# 한글만 허용(2~20자) 기존 로직 유지
if not re.fullmatch(r"[가-힣]{2,20}", field.data or ""):
raise ValidationError("이름은 한글로만 2~20자 입력 가능합니다.")
# 중복 체크
user = User.query.filter_by(username=field.data).first()
if user:
raise ValidationError("이미 사용 중인 이름입니다.")
def validate_email(self, field):
# 이메일은 소문자 비교(필터로 이미 소문자화)
user = User.query.filter_by(email=field.data).first()
if user:
raise ValidationError("이미 등록된 이메일입니다.")
class LoginForm(FlaskForm):
email = StringField(
"이메일",
filters=[lower_strip],
validators=[DataRequired(message="이메일을 입력해주세요."), Email(message="유효한 이메일을 입력하세요.")],
render_kw={"placeholder": "이메일 주소", "autocomplete": "username", "inputmode": "email"},
)
password = PasswordField(
"비밀번호",
validators=[DataRequired(message="비밀번호를 입력해주세요.")],
render_kw={"placeholder": "비밀번호", "autocomplete": "current-password"},
)
remember = BooleanField("로그인 유지")
submit = SubmitField("로그인")