260706_2_로그인 인증 검토

This commit is contained in:
2026-07-07 20:40:10 +09:00
parent f8633bb1fe
commit 5da0260be3
29 changed files with 720 additions and 336 deletions
+11 -1
View File
@@ -90,7 +90,9 @@ async def verify_session(request: Request) -> dict[str, Any]:
now = datetime.utcnow()
expired = now > row[3] or (now - row[4]).total_seconds() > SESSION_IDLE_TIMEOUT_SECONDS
if expired or row[10] != "ACTIVE":
# NO_COMPANY(이메일 인증 완료·회사 미연결)도 로그인 유지 대상. 회사 연결 강제는
# require_company 의존성에서 별도 처리한다.
if expired or row[10] not in ("ACTIVE", "NO_COMPANY"):
await cursor.execute("DELETE FROM sessions WHERE id = %s", (session_id,))
await connection.commit()
raise HTTPException(status_code=401, detail="세션이 만료되었습니다.")
@@ -135,3 +137,11 @@ async def require_system_admin(
if session["role"] != "SYSTEM_ADMIN":
raise HTTPException(status_code=403, detail="시스템 관리자 권한이 필요합니다.")
return session
async def require_company(
session: dict[str, Any] = Depends(verify_session),
) -> dict[str, Any]:
if session["company_id"] is None:
raise HTTPException(status_code=403, detail="회사 연결이 필요합니다.")
return session
+52 -36
View File
@@ -34,46 +34,17 @@ async def create_registration(data: dict[str, Any], password_hash: str) -> int:
await connection.begin()
await cursor.execute(
"""INSERT INTO users
(email, password_hash, name, role, is_master, status)
VALUES (%s, %s, %s, %s, %s, 'PENDING_EMAIL')""",
(email, password_hash, name, position, phone, role, is_master, status)
VALUES (%s, %s, %s, %s, %s, 'MEMBER', FALSE, 'PENDING_EMAIL')""",
(
data["email"],
password_hash,
data["name"],
data["account_type"],
data["account_type"] == "MASTER",
data.get("position"),
data.get("phone"),
),
)
user_id = cursor.lastrowid
if data["account_type"] == "MASTER":
await cursor.execute(
"""INSERT INTO companies
(name, business_registration_number, business_address, created_by,
company_code, phone_number, representative_name, master_user_id)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""",
(
data["company_name"],
data.get("business_number"),
data.get("address"),
user_id,
_company_code(),
data.get("phone_number"),
data.get("representative_name"),
user_id,
),
)
company_id = cursor.lastrowid
await cursor.execute(
"UPDATE users SET company_id = %s WHERE id = %s", (company_id, user_id)
)
else:
await cursor.execute(
"INSERT INTO join_requests (user_id, company_id) VALUES (%s, %s)",
(user_id, data["company_id"]),
)
await cursor.execute(
"UPDATE users SET company_id = %s WHERE id = %s", (data["company_id"], user_id)
)
await cursor.execute(
"""INSERT INTO user_consents
(user_id, terms_version, terms_agreed, privacy_agreed, marketing_agreed)
@@ -93,6 +64,52 @@ async def create_registration(data: dict[str, Any], password_hash: str) -> int:
raise
async def refresh_pending_registration(
user_id: int, data: dict[str, Any], password_hash: str
) -> None:
"""미인증(PENDING_EMAIL) 계정의 정보를 최신 가입 요청으로 갱신한다.
같은 이메일로 재가입을 시도하면 기존 미인증 계정을 덮어써서
새 비밀번호·이름·약관 동의로 갱신하고 새 OTP를 받을 수 있게 한다.
"""
pool = get_db_pool()
async with pool.acquire() as connection, connection.cursor() as cursor:
try:
await connection.begin()
await cursor.execute(
"""UPDATE users
SET password_hash = %s, name = %s, position = %s, phone = %s
WHERE id = %s AND status = 'PENDING_EMAIL'""",
(
password_hash,
data["name"],
data.get("position"),
data.get("phone"),
user_id,
),
)
await cursor.execute(
"""INSERT INTO user_consents
(user_id, terms_version, terms_agreed, privacy_agreed, marketing_agreed)
VALUES (%s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
terms_agreed = VALUES(terms_agreed),
privacy_agreed = VALUES(privacy_agreed),
marketing_agreed = VALUES(marketing_agreed)""",
(
user_id,
data["terms_version"],
data["terms_agreed"],
data["privacy_agreed"],
data["marketing_agreed"],
),
)
await connection.commit()
except Exception:
await connection.rollback()
raise
async def replace_otp(user_id: int, purpose: str, otp_hash: str) -> None:
expires_at = datetime.utcnow() + timedelta(minutes=OTP_VALID_MINUTES)
pool = get_db_pool()
@@ -132,13 +149,12 @@ async def consume_otp(otp_id: int) -> None:
async def complete_registration(user_id: int, is_master: bool) -> None:
status = "ACTIVE" if is_master else "PENDING"
pool = get_db_pool()
async with pool.acquire() as connection, connection.cursor() as cursor:
await cursor.execute(
"""UPDATE users SET status = %s, last_email_verified_at = CURRENT_TIMESTAMP
"""UPDATE users SET status = 'NO_COMPANY', last_email_verified_at = CURRENT_TIMESTAMP
WHERE id = %s""",
(status, user_id),
(user_id,),
)
await connection.commit()