260706_1_계정, 로그인, 보안 초안 작성
This commit is contained in:
@@ -0,0 +1,471 @@
|
||||
# 개발 환경 설정 가이드 (Setup Guide)
|
||||
|
||||
## 🚀 개발 시작 전 필수 설정
|
||||
|
||||
이 가이드는 **로그인/보안 시스템 개발**을 시작하기 전에 필요한 모든 설정을 다룹니다.
|
||||
|
||||
---
|
||||
|
||||
## 1️⃣ Google Gmail SMTP 설정 (개발용)
|
||||
|
||||
### Step 1: Google 계정 2단계 인증 활성화
|
||||
|
||||
```
|
||||
1. Google 계정으로 로그인
|
||||
→ https://myaccount.google.com
|
||||
|
||||
2. 왼쪽 메뉴: "보안" (Security)
|
||||
|
||||
3. "2단계 인증" → 활성화
|
||||
(이미 활성화되어 있으면 스킵)
|
||||
```
|
||||
|
||||
### Step 2: 앱 비밀번호 생성
|
||||
|
||||
```
|
||||
1. Google 계정 관리 페이지
|
||||
→ https://myaccount.google.com/apppasswords
|
||||
|
||||
2. 앱 선택: "메일(Mail)"
|
||||
|
||||
3. 기기 선택: "Windows" (또는 개발 환경)
|
||||
|
||||
4. [생성] 버튼 클릭
|
||||
|
||||
5. 16자리 비밀번호 생성됨
|
||||
예: "abcd efgh ijkl mnop"
|
||||
|
||||
6. 이 비밀번호를 안전히 보관
|
||||
```
|
||||
|
||||
⚠️ **주의**: 이 비밀번호는 .env 파일에만 저장하고, Git에 절대 커밋하지 마세요!
|
||||
|
||||
### Step 3: .env 파일 작성
|
||||
|
||||
프로젝트 루트에 `config/.env` 파일 생성:
|
||||
|
||||
```bash
|
||||
# config/.env
|
||||
|
||||
# ===== Email Infrastructure (Phase 1) =====
|
||||
SMTP_PROVIDER=google
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USERNAME=umsangdon@gmail.com
|
||||
SMTP_PASSWORD=abcd efgh ijkl mnop # 위에서 생성한 앱 비밀번호
|
||||
SMTP_USE_TLS=true
|
||||
|
||||
# 발송 이메일 주소
|
||||
EMAIL_FROM_ADDRESS=umsangdon@gmail.com
|
||||
EMAIL_FROM_NAME=Aislo Support
|
||||
EMAIL_NOREPLY_ADDRESS=umsangdon+noreply@gmail.com
|
||||
|
||||
# 이메일 발송 설정
|
||||
EMAIL_RETRY_COUNT=3
|
||||
EMAIL_RETRY_DELAY_SECONDS=2
|
||||
EMAIL_OTP_VALID_MINUTES=5
|
||||
EMAIL_NOTIFICATION_ENABLED=true
|
||||
|
||||
# 관리자 이메일
|
||||
ADMIN_EMAIL=umsangdon@gmail.com
|
||||
|
||||
# ===== Database =====
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_USER=root
|
||||
DB_PASSWORD=your_password
|
||||
DB_NAME=aislo_db
|
||||
DB_CHARSET=utf8mb4
|
||||
DB_COLLATION=utf8mb4_unicode_ci
|
||||
```
|
||||
|
||||
### Step 4: .gitignore 확인
|
||||
|
||||
프로젝트 루트의 `.gitignore`에 다음 추가:
|
||||
|
||||
```bash
|
||||
# 환경 변수 (절대 Git에 커밋하지 않음)
|
||||
config/.env
|
||||
config/.env.local
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2️⃣ Python 환경 설정
|
||||
|
||||
### 필요한 라이브러리 설치
|
||||
|
||||
```bash
|
||||
# 프로젝트 루트에서 실행
|
||||
|
||||
# FastAPI + 비동기 ASGI
|
||||
pip install fastapi uvicorn
|
||||
|
||||
# 데이터베이스
|
||||
pip install aiomysql
|
||||
|
||||
# 유효성 검사
|
||||
pip install pydantic email-validator
|
||||
|
||||
# 비밀번호 암호화
|
||||
pip install bcrypt
|
||||
|
||||
# 환경 변수 관리
|
||||
pip install python-dotenv
|
||||
|
||||
# 이메일 발송 (기본 내장, 추가 설치 불필요)
|
||||
# smtplib, email (Python 표준 라이브러리)
|
||||
```
|
||||
|
||||
### requirements.txt 생성 (권장)
|
||||
|
||||
```bash
|
||||
pip freeze > requirements.txt
|
||||
```
|
||||
|
||||
그 다음, 팀원이 환경 구축할 때:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3️⃣ 데이터베이스 초기화
|
||||
|
||||
### MariaDB 서버 확인
|
||||
|
||||
```bash
|
||||
# MariaDB 실행 중 확인
|
||||
mysql -u root -p -e "SELECT VERSION();"
|
||||
```
|
||||
|
||||
### DB 및 테이블 생성
|
||||
|
||||
```bash
|
||||
# MySQL 클라이언트 실행
|
||||
mysql -u root -p
|
||||
|
||||
# 다음 SQL 실행:
|
||||
```
|
||||
|
||||
```sql
|
||||
-- 데이터베이스 생성
|
||||
CREATE DATABASE IF NOT EXISTS aislo_db
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE aislo_db;
|
||||
|
||||
-- 회사 테이블
|
||||
CREATE TABLE IF NOT EXISTS companies (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
company_name VARCHAR(255) NOT NULL,
|
||||
company_code VARCHAR(20) UNIQUE NOT NULL,
|
||||
business_number VARCHAR(20),
|
||||
phone_number VARCHAR(20),
|
||||
address VARCHAR(500),
|
||||
representative_name VARCHAR(100),
|
||||
master_user_id INT NOT NULL,
|
||||
status ENUM('ACTIVE', 'INACTIVE', 'SUSPENDED') DEFAULT 'ACTIVE',
|
||||
subscription_status ENUM('FREE', 'TRIAL', 'PAID', 'EXPIRED') DEFAULT 'FREE',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (master_user_id) REFERENCES users(id)
|
||||
);
|
||||
|
||||
-- 사용자 테이블
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
company_id INT,
|
||||
is_master BOOLEAN DEFAULT FALSE,
|
||||
status ENUM('PENDING', 'ACTIVE', 'INACTIVE', 'REJECTED') DEFAULT 'PENDING',
|
||||
last_email_verified_at TIMESTAMP,
|
||||
login_failures INT DEFAULT 0,
|
||||
last_failed_at TIMESTAMP,
|
||||
account_locked_until TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (company_id) REFERENCES companies(id),
|
||||
INDEX (email),
|
||||
INDEX (company_id)
|
||||
);
|
||||
|
||||
-- OTP 저장 (선택사항, users 테이블에 컬럼 추가 가능)
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS otp_hash VARCHAR(255);
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS otp_expires_at TIMESTAMP;
|
||||
|
||||
-- 가입 요청 테이블
|
||||
CREATE TABLE IF NOT EXISTS join_requests (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
company_id INT NOT NULL,
|
||||
status ENUM('PENDING', 'APPROVED', 'REJECTED') DEFAULT 'PENDING',
|
||||
requested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
reviewed_at TIMESTAMP,
|
||||
reviewed_by INT,
|
||||
review_comment TEXT,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
FOREIGN KEY (company_id) REFERENCES companies(id),
|
||||
FOREIGN KEY (reviewed_by) REFERENCES users(id),
|
||||
INDEX (status),
|
||||
INDEX (company_id)
|
||||
);
|
||||
|
||||
-- 로그인 이력 테이블
|
||||
CREATE TABLE IF NOT EXISTS login_logs (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT,
|
||||
email VARCHAR(255),
|
||||
status ENUM('SUCCESS', 'FAILURE') NOT NULL,
|
||||
failure_reason VARCHAR(100),
|
||||
user_agent TEXT,
|
||||
login_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
INDEX (user_id),
|
||||
INDEX (login_at)
|
||||
);
|
||||
|
||||
-- 활동 로그 테이블
|
||||
CREATE TABLE IF NOT EXISTS activity_logs (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
action_type ENUM('FILE_UPLOAD', 'FILE_DOWNLOAD', 'PROJECT_SAVE', 'DATA_PROCESS', 'PAGE_VIEW'),
|
||||
action_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
resource_id VARCHAR(255),
|
||||
status ENUM('SUCCESS', 'FAILURE') DEFAULT 'SUCCESS',
|
||||
details JSON,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
INDEX (user_id),
|
||||
INDEX (action_at)
|
||||
);
|
||||
|
||||
-- 시스템 관리자 로그
|
||||
CREATE TABLE IF NOT EXISTS system_admin_logs (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
admin_user_id INT NOT NULL,
|
||||
action_type ENUM('USER_DELETE', 'COMPANY_DELETE', 'QA_MODIFY', 'QA_DELETE', 'RESET_PASSWORD'),
|
||||
target_id INT,
|
||||
action_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
details JSON,
|
||||
FOREIGN KEY (admin_user_id) REFERENCES users(id),
|
||||
INDEX (admin_user_id),
|
||||
INDEX (action_at)
|
||||
);
|
||||
|
||||
-- Q&A 요청 테이블
|
||||
CREATE TABLE IF NOT EXISTS support_requests (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
company_id INT,
|
||||
user_email VARCHAR(255),
|
||||
subject VARCHAR(255) NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
status ENUM('NEW', 'REVIEWED', 'RESOLVED', 'DELETED') DEFAULT 'NEW',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
reviewed_at TIMESTAMP,
|
||||
reviewed_by INT,
|
||||
review_notes TEXT,
|
||||
FOREIGN KEY (company_id) REFERENCES companies(id),
|
||||
FOREIGN KEY (reviewed_by) REFERENCES users(id),
|
||||
INDEX (status),
|
||||
INDEX (created_at)
|
||||
);
|
||||
|
||||
-- 세션 테이블 (Secure HttpOnly 쿠키 저장소)
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
last_activity_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
INDEX (user_id),
|
||||
INDEX (expires_at)
|
||||
);
|
||||
```
|
||||
|
||||
확인:
|
||||
|
||||
```sql
|
||||
-- 테이블 목록 확인
|
||||
SHOW TABLES;
|
||||
|
||||
-- 사용자 테이블 구조 확인
|
||||
DESCRIBE users;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4️⃣ 프로젝트 폴더 구조 확인
|
||||
|
||||
```bash
|
||||
# 필요한 폴더 생성
|
||||
mkdir -p common_util
|
||||
mkdir -p config
|
||||
mkdir -p logs
|
||||
mkdir -p storage
|
||||
|
||||
# .env 파일 위치 확인
|
||||
ls -la config/.env
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5️⃣ 개발 서버 실행
|
||||
|
||||
### FastAPI 서버 시작
|
||||
|
||||
```bash
|
||||
# 프로젝트 루트에서
|
||||
cd your-project
|
||||
|
||||
# Uvicorn 서버 실행
|
||||
uvicorn main:app --reload --host 127.0.0.1 --port 8000
|
||||
```
|
||||
|
||||
확인:
|
||||
|
||||
```
|
||||
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
INFO: Started server process [12345]
|
||||
```
|
||||
|
||||
### 테스트 엔드포인트 확인
|
||||
|
||||
```bash
|
||||
# 다른 터미널에서
|
||||
curl http://127.0.0.1:8000/api/auth/health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6️⃣ 이메일 발송 테스트
|
||||
|
||||
### Python 테스트 스크립트 작성
|
||||
|
||||
```python
|
||||
# test_email.py
|
||||
|
||||
import asyncio
|
||||
from common_util.common_util_email import EmailService
|
||||
from common_util.common_util_email_templates import get_otp_email_html
|
||||
|
||||
async def test_send_otp():
|
||||
"""OTP 이메일 발송 테스트"""
|
||||
html_body, text_body = get_otp_email_html("123456")
|
||||
|
||||
result = await EmailService.send_email(
|
||||
to_email="umsangdon@gmail.com", # 수신 테스트 이메일
|
||||
subject="[Aislo] 테스트 - 이메일 인증 코드",
|
||||
body_html=html_body,
|
||||
body_text=text_body
|
||||
)
|
||||
|
||||
if result:
|
||||
print("✅ 이메일 발송 성공!")
|
||||
else:
|
||||
print("❌ 이메일 발송 실패")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_send_otp())
|
||||
```
|
||||
|
||||
실행:
|
||||
|
||||
```bash
|
||||
python test_email.py
|
||||
```
|
||||
|
||||
결과:
|
||||
|
||||
```
|
||||
✅ 이메일 발송 성공!
|
||||
```
|
||||
|
||||
Google 계정에서 수신 확인:
|
||||
|
||||
```
|
||||
- 받는사람: umsangdon@gmail.com
|
||||
- 발신: "Aislo Support <umsangdon@gmail.com>"
|
||||
- 제목: "[Aislo] 테스트 - 이메일 인증 코드"
|
||||
- 본문: HTML 템플릿 + OTP 코드 "123456"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7️⃣ 문제 해결 (Troubleshooting)
|
||||
|
||||
### 문제 1: "SMTP 연결 실패"
|
||||
|
||||
```
|
||||
Error: [Errno 11001] getaddrinfo failed
|
||||
```
|
||||
|
||||
**해결**:
|
||||
1. 인터넷 연결 확인
|
||||
2. .env 파일의 SMTP_HOST, SMTP_PORT 확인
|
||||
3. 방화벽에서 포트 587 허용 확인
|
||||
|
||||
### 문제 2: "앱 비밀번호 인증 실패"
|
||||
|
||||
```
|
||||
Error: b'5.7.8 Username and password not accepted'
|
||||
```
|
||||
|
||||
**해결**:
|
||||
1. Google 계정 2단계 인증 활성화 확인
|
||||
2. 앱 비밀번호 정확히 입력 (띄어쓰기 포함)
|
||||
3. .env 파일에서 SMTP_PASSWORD 재확인
|
||||
|
||||
### 문제 3: "메일이 스팸 폴더로 들어감"
|
||||
|
||||
**해결**:
|
||||
1. 발신자 이름 명확히 하기: `EMAIL_FROM_NAME=Aislo Support`
|
||||
2. 이메일 템플릿에 스팸 키워드 제거
|
||||
3. Phase 2에서 AWS SES로 마이그레이션
|
||||
|
||||
---
|
||||
|
||||
## ✅ 체크리스트
|
||||
|
||||
- [ ] Google 계정 2단계 인증 활성화
|
||||
- [ ] 앱 비밀번호 생성 (16자리)
|
||||
- [ ] `config/.env` 파일 작성
|
||||
- [ ] `.gitignore`에 `.env` 추가
|
||||
- [ ] Python 라이브러리 설치 (pip install -r requirements.txt)
|
||||
- [ ] MariaDB 데이터베이스 및 테이블 생성
|
||||
- [ ] 필요한 폴더 생성 (common_util, config, logs, storage)
|
||||
- [ ] FastAPI 서버 실행 확인
|
||||
- [ ] 이메일 발송 테스트 성공
|
||||
|
||||
---
|
||||
|
||||
## 📚 다음 단계
|
||||
|
||||
1. **로그인 페이지 구현** (A06_Login, A07_Register, A09_Security)
|
||||
2. **회원가입 API 작성** (A07_Register_Router.py)
|
||||
3. **로그인 API 작성** (A06_Login_Router.py)
|
||||
4. **프론트엔드 연결** (A06_Login_Api_Fetch.ts, A07_Register_Api_Fetch.ts)
|
||||
5. **테스트 및 검증**
|
||||
|
||||
자세한 내용은 다음 문서를 참고하세요:
|
||||
- `.agent/login_security_plan.md` — 전체 보안 계획
|
||||
- `.agent/email_infrastructure_plan.md` — 이메일 인프라 상세
|
||||
- `.agent/backend.md` — 백엔드 API 명세
|
||||
|
||||
---
|
||||
|
||||
**작성일**: 2026-07-07
|
||||
**상태**: 초안 (v1.0)
|
||||
Reference in New Issue
Block a user