Initial commit: Todo Project with dashboard, classification center, and upload functionality
- 📱 PWA 지원: 홈화면 추가 가능한 Progressive Web App - 🎨 M-Project 색상 스키마: 하늘색, 주황색, 회색, 흰색 일관된 디자인 - 📊 대시보드: 데스크톱 캘린더 뷰 + 모바일 일일 뷰 반응형 디자인 - 📥 분류 센터: Gmail 스타일 받은편지함으로 스마트 분류 시스템 - 🤖 AI 분류 제안: 키워드 기반 자동 분류 제안 및 일괄 처리 - 📷 업로드 모달: 데스크톱(파일 선택) + 모바일(카메라/갤러리) 최적화 - 🏷️ 3가지 분류: Todo(시작일), 캘린더(마감일), 체크리스트(무기한) - 📋 체크리스트: 진행률 표시 및 완료 토글 기능 - 🔄 시놀로지 연동 준비: 메일플러스 연동을 위한 구조 설계 - 📱 반응형 UI: 모든 페이지 모바일 최적화 완료
This commit is contained in:
484
docs/API.md
Normal file
484
docs/API.md
Normal file
@@ -0,0 +1,484 @@
|
||||
# API 문서 (API.md)
|
||||
|
||||
## 🚀 API 개요
|
||||
|
||||
Todo-Project REST API는 간결하고 직관적인 할일 관리 기능을 제공합니다.
|
||||
|
||||
### 기본 정보
|
||||
- **Base URL**: `http://localhost:9000/api`
|
||||
- **인증 방식**: JWT Bearer Token
|
||||
- **응답 형식**: JSON
|
||||
- **API 버전**: v1
|
||||
|
||||
### 포트 설정
|
||||
- **Frontend**: http://localhost:4000
|
||||
- **Backend API**: http://localhost:9000
|
||||
- **Database**: localhost:5434
|
||||
|
||||
## 🔐 인증
|
||||
|
||||
### 로그인
|
||||
```http
|
||||
POST /api/auth/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "password123",
|
||||
"remember_me": false
|
||||
}
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
|
||||
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
|
||||
"token_type": "bearer",
|
||||
"expires_in": 1800
|
||||
}
|
||||
```
|
||||
|
||||
### 기기 등록 (개인용 최적화)
|
||||
```http
|
||||
POST /api/auth/register-device
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {access_token}
|
||||
|
||||
{
|
||||
"device_name": "내 iPhone",
|
||||
"fingerprint": "abc123def456",
|
||||
"platform": "mobile"
|
||||
}
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"device_token": "long-term-device-token-here",
|
||||
"expires_at": "2024-02-15T10:30:00Z",
|
||||
"device_id": "device-uuid"
|
||||
}
|
||||
```
|
||||
|
||||
### 기기 토큰 로그인
|
||||
```http
|
||||
POST /api/auth/device-login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"device_token": "long-term-device-token-here"
|
||||
}
|
||||
```
|
||||
|
||||
## 📋 할일 관리
|
||||
|
||||
### 할일 목록 조회
|
||||
```http
|
||||
GET /api/todos?status=active&limit=50&offset=0
|
||||
Authorization: Bearer {access_token}
|
||||
```
|
||||
|
||||
**쿼리 파라미터:**
|
||||
- `status`: `draft`, `scheduled`, `active`, `completed`, `delayed`
|
||||
- `limit`: 페이지당 항목 수 (기본: 50)
|
||||
- `offset`: 시작 위치 (기본: 0)
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"todos": [
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"user_id": "550e8400-e29b-41d4-a716-446655440001",
|
||||
"content": "프로젝트 기획서 작성",
|
||||
"status": "active",
|
||||
"created_at": "2024-01-15T09:00:00Z",
|
||||
"start_date": "2024-01-15T10:00:00Z",
|
||||
"estimated_minutes": 120,
|
||||
"completed_at": null,
|
||||
"delayed_until": null,
|
||||
"parent_id": null,
|
||||
"split_order": null,
|
||||
"comment_count": 2
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"has_more": false
|
||||
}
|
||||
```
|
||||
|
||||
### 할일 생성
|
||||
```http
|
||||
POST /api/todos
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {access_token}
|
||||
|
||||
{
|
||||
"content": "새로운 할일 내용"
|
||||
}
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440002",
|
||||
"user_id": "550e8400-e29b-41d4-a716-446655440001",
|
||||
"content": "새로운 할일 내용",
|
||||
"status": "draft",
|
||||
"created_at": "2024-01-15T09:30:00Z",
|
||||
"start_date": null,
|
||||
"estimated_minutes": null,
|
||||
"completed_at": null,
|
||||
"delayed_until": null,
|
||||
"parent_id": null,
|
||||
"split_order": null,
|
||||
"comment_count": 0
|
||||
}
|
||||
```
|
||||
|
||||
### 할일 일정 설정
|
||||
```http
|
||||
POST /api/todos/{todo_id}/schedule
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {access_token}
|
||||
|
||||
{
|
||||
"start_date": "2024-01-16T14:00:00Z",
|
||||
"estimated_minutes": 90
|
||||
}
|
||||
```
|
||||
|
||||
**응답:** 업데이트된 할일 객체
|
||||
|
||||
### 할일 완료 처리
|
||||
```http
|
||||
PUT /api/todos/{todo_id}/complete
|
||||
Authorization: Bearer {access_token}
|
||||
```
|
||||
|
||||
**응답:** 완료된 할일 객체 (status: "completed", completed_at 설정됨)
|
||||
|
||||
### 할일 지연 처리
|
||||
```http
|
||||
PUT /api/todos/{todo_id}/delay
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {access_token}
|
||||
|
||||
{
|
||||
"delayed_until": "2024-01-17T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 할일 분할
|
||||
```http
|
||||
POST /api/todos/{todo_id}/split
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {access_token}
|
||||
|
||||
{
|
||||
"subtasks": [
|
||||
"1단계: 요구사항 분석",
|
||||
"2단계: 설계 문서 작성",
|
||||
"3단계: 검토 및 수정"
|
||||
],
|
||||
"estimated_minutes_per_task": [30, 60, 30]
|
||||
}
|
||||
```
|
||||
|
||||
**응답:** 생성된 하위 할일들의 배열
|
||||
|
||||
### 할일 상세 조회 (댓글 포함)
|
||||
```http
|
||||
GET /api/todos/{todo_id}
|
||||
Authorization: Bearer {access_token}
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"user_id": "550e8400-e29b-41d4-a716-446655440001",
|
||||
"content": "프로젝트 기획서 작성",
|
||||
"status": "active",
|
||||
"created_at": "2024-01-15T09:00:00Z",
|
||||
"start_date": "2024-01-15T10:00:00Z",
|
||||
"estimated_minutes": 120,
|
||||
"completed_at": null,
|
||||
"delayed_until": null,
|
||||
"parent_id": null,
|
||||
"split_order": null,
|
||||
"comment_count": 2,
|
||||
"comments": [
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440003",
|
||||
"todo_item_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"user_id": "550e8400-e29b-41d4-a716-446655440001",
|
||||
"content": "진행 상황 메모",
|
||||
"created_at": "2024-01-15T11:00:00Z",
|
||||
"updated_at": "2024-01-15T11:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 💬 댓글/메모 관리
|
||||
|
||||
### 댓글 추가
|
||||
```http
|
||||
POST /api/todos/{todo_id}/comments
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {access_token}
|
||||
|
||||
{
|
||||
"content": "진행 상황 업데이트"
|
||||
}
|
||||
```
|
||||
|
||||
### 댓글 목록 조회
|
||||
```http
|
||||
GET /api/todos/{todo_id}/comments
|
||||
Authorization: Bearer {access_token}
|
||||
```
|
||||
|
||||
## 📊 통계 및 대시보드
|
||||
|
||||
### 할일 통계
|
||||
```http
|
||||
GET /api/todos/stats
|
||||
Authorization: Bearer {access_token}
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"total_count": 25,
|
||||
"draft_count": 5,
|
||||
"scheduled_count": 8,
|
||||
"active_count": 7,
|
||||
"completed_count": 4,
|
||||
"delayed_count": 1,
|
||||
"completion_rate": 16.0
|
||||
}
|
||||
```
|
||||
|
||||
### 활성 할일 조회 (시간 기반 자동 활성화)
|
||||
```http
|
||||
GET /api/todos/active
|
||||
Authorization: Bearer {access_token}
|
||||
```
|
||||
|
||||
**기능:** scheduled 상태의 할일 중 시작 시간이 지난 것들을 자동으로 active로 변경하고 반환
|
||||
|
||||
## 👤 사용자 관리
|
||||
|
||||
### 현재 사용자 정보
|
||||
```http
|
||||
GET /api/users/me
|
||||
Authorization: Bearer {access_token}
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440001",
|
||||
"email": "user@example.com",
|
||||
"full_name": "사용자 이름",
|
||||
"is_active": true,
|
||||
"is_admin": false,
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2024-01-15T09:00:00Z",
|
||||
"last_login_at": "2024-01-15T09:00:00Z",
|
||||
"timezone": "Asia/Seoul",
|
||||
"language": "ko"
|
||||
}
|
||||
```
|
||||
|
||||
### 사용자 정보 수정
|
||||
```http
|
||||
PUT /api/users/me
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {access_token}
|
||||
|
||||
{
|
||||
"full_name": "새로운 이름",
|
||||
"timezone": "Asia/Seoul",
|
||||
"language": "ko"
|
||||
}
|
||||
```
|
||||
|
||||
## 🔗 시놀로지 연동 (1단계)
|
||||
|
||||
### 시놀로지 설정 테스트
|
||||
```http
|
||||
POST /api/synology/test-connection
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {access_token}
|
||||
|
||||
{
|
||||
"dsm_url": "https://your-nas.synology.me:5001",
|
||||
"username": "todo_user",
|
||||
"password": "password123"
|
||||
}
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"dsm_connection": "success",
|
||||
"calendar_connection": "success",
|
||||
"mail_connection": "success",
|
||||
"available_services": ["Calendar", "MailPlus"]
|
||||
}
|
||||
```
|
||||
|
||||
### 캘린더 동기화 수동 실행
|
||||
```http
|
||||
POST /api/synology/sync-calendar/{todo_id}
|
||||
Authorization: Bearer {access_token}
|
||||
```
|
||||
|
||||
### 메일 알림 발송
|
||||
```http
|
||||
POST /api/synology/send-notification/{todo_id}
|
||||
Authorization: Bearer {access_token}
|
||||
```
|
||||
|
||||
## 🔧 시스템 관리
|
||||
|
||||
### 헬스 체크
|
||||
```http
|
||||
GET /api/health
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"timestamp": "2024-01-15T12:00:00Z",
|
||||
"version": "0.1.0",
|
||||
"database": "connected",
|
||||
"synology_integration": "enabled"
|
||||
}
|
||||
```
|
||||
|
||||
### API 정보
|
||||
```http
|
||||
GET /api/info
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"name": "Todo Project API",
|
||||
"version": "0.1.0",
|
||||
"description": "간결하고 스마트한 개인용 할일 관리 시스템",
|
||||
"docs_url": "/docs",
|
||||
"redoc_url": "/redoc"
|
||||
}
|
||||
```
|
||||
|
||||
## 📝 오류 응답
|
||||
|
||||
### 표준 오류 형식
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "입력 데이터가 유효하지 않습니다.",
|
||||
"details": {
|
||||
"field": "content",
|
||||
"issue": "최소 1자 이상 입력해야 합니다."
|
||||
}
|
||||
},
|
||||
"timestamp": "2024-01-15T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 주요 오류 코드
|
||||
- `AUTHENTICATION_ERROR`: 인증 실패
|
||||
- `AUTHORIZATION_ERROR`: 권한 없음
|
||||
- `VALIDATION_ERROR`: 입력 데이터 검증 실패
|
||||
- `NOT_FOUND`: 리소스를 찾을 수 없음
|
||||
- `CONFLICT`: 데이터 충돌
|
||||
- `RATE_LIMIT_EXCEEDED`: 요청 한도 초과
|
||||
- `SYNOLOGY_CONNECTION_ERROR`: 시놀로지 연동 오류
|
||||
|
||||
### HTTP 상태 코드
|
||||
- `200`: 성공
|
||||
- `201`: 생성 성공
|
||||
- `400`: 잘못된 요청
|
||||
- `401`: 인증 필요
|
||||
- `403`: 권한 없음
|
||||
- `404`: 찾을 수 없음
|
||||
- `409`: 충돌
|
||||
- `422`: 검증 실패
|
||||
- `429`: 요청 한도 초과
|
||||
- `500`: 서버 오류
|
||||
|
||||
## 🚀 SDK 및 클라이언트
|
||||
|
||||
### JavaScript 클라이언트 예제
|
||||
```javascript
|
||||
class TodoAPI {
|
||||
constructor(baseURL = 'http://localhost:9000/api') {
|
||||
this.baseURL = baseURL;
|
||||
this.token = localStorage.getItem('access_token');
|
||||
}
|
||||
|
||||
async createTodo(content) {
|
||||
const response = await fetch(`${this.baseURL}/todos`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.token}`
|
||||
},
|
||||
body: JSON.stringify({ content })
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async getTodos(status = null) {
|
||||
const params = status ? `?status=${status}` : '';
|
||||
const response = await fetch(`${this.baseURL}/todos${params}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.token}`
|
||||
}
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async completeTodo(todoId) {
|
||||
const response = await fetch(`${this.baseURL}/todos/${todoId}/complete`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.token}`
|
||||
}
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
// 사용 예제
|
||||
const api = new TodoAPI();
|
||||
|
||||
// 할일 생성
|
||||
const newTodo = await api.createTodo('새로운 할일');
|
||||
|
||||
// 할일 목록 조회
|
||||
const activeTodos = await api.getTodos('active');
|
||||
|
||||
// 할일 완료
|
||||
await api.completeTodo(newTodo.id);
|
||||
```
|
||||
|
||||
## 📚 추가 리소스
|
||||
|
||||
- **Swagger UI**: http://localhost:9000/docs
|
||||
- **ReDoc**: http://localhost:9000/redoc
|
||||
- **OpenAPI Spec**: http://localhost:9000/openapi.json
|
||||
|
||||
이 API 문서를 통해 Todo-Project의 모든 기능을 활용할 수 있습니다!
|
||||
469
docs/DEPLOYMENT.md
Normal file
469
docs/DEPLOYMENT.md
Normal file
@@ -0,0 +1,469 @@
|
||||
# 배포 가이드 (DEPLOYMENT.md)
|
||||
|
||||
## 🚀 배포 개요
|
||||
|
||||
Todo-Project는 Docker를 사용한 컨테이너 기반 배포를 지원하며, 개인용 환경에 최적화되어 있습니다.
|
||||
|
||||
## 📋 사전 요구사항
|
||||
|
||||
### 시스템 요구사항
|
||||
- **OS**: Linux, macOS, Windows (Docker 지원)
|
||||
- **RAM**: 최소 2GB, 권장 4GB
|
||||
- **Storage**: 최소 10GB 여유 공간
|
||||
- **Network**: 인터넷 연결 (시놀로지 연동 시)
|
||||
|
||||
### 필수 소프트웨어
|
||||
- Docker 20.10+
|
||||
- Docker Compose 2.0+
|
||||
- Git (소스 코드 다운로드용)
|
||||
|
||||
## 🐳 Docker 배포
|
||||
|
||||
### 1. 소스 코드 다운로드
|
||||
```bash
|
||||
git clone https://github.com/your-username/Todo-Project.git
|
||||
cd Todo-Project
|
||||
```
|
||||
|
||||
### 2. 환경 설정
|
||||
```bash
|
||||
# 환경 변수 파일 생성
|
||||
cp .env.example .env
|
||||
|
||||
# 환경 변수 편집
|
||||
nano .env
|
||||
```
|
||||
|
||||
#### 기본 환경 변수 설정
|
||||
```bash
|
||||
# 데이터베이스 설정
|
||||
DATABASE_URL=postgresql://todo_user:todo_password@database:5432/todo_db
|
||||
POSTGRES_USER=todo_user
|
||||
POSTGRES_PASSWORD=your_secure_password_here
|
||||
POSTGRES_DB=todo_db
|
||||
|
||||
# JWT 설정 (반드시 변경!)
|
||||
SECRET_KEY=your-very-long-and-random-secret-key-here-change-this-in-production
|
||||
ALGORITHM=HS256
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
||||
|
||||
# 애플리케이션 설정
|
||||
DEBUG=false
|
||||
CORS_ORIGINS=["http://localhost:4000", "http://your-domain.com:4000"]
|
||||
|
||||
# 서버 설정
|
||||
HOST=0.0.0.0
|
||||
PORT=9000
|
||||
|
||||
# 시놀로지 연동 (선택사항)
|
||||
SYNOLOGY_DSM_URL=https://your-nas.synology.me:5001
|
||||
SYNOLOGY_USERNAME=todo_user
|
||||
SYNOLOGY_PASSWORD=your_synology_password
|
||||
ENABLE_SYNOLOGY_INTEGRATION=true
|
||||
```
|
||||
|
||||
### 3. Docker Compose 실행
|
||||
```bash
|
||||
# 백그라운드에서 실행
|
||||
docker-compose up -d
|
||||
|
||||
# 로그 확인
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
### 4. 서비스 확인
|
||||
```bash
|
||||
# 컨테이너 상태 확인
|
||||
docker-compose ps
|
||||
|
||||
# 헬스 체크
|
||||
curl http://localhost:9000/api/health
|
||||
|
||||
# 프론트엔드 접속
|
||||
open http://localhost:4000
|
||||
```
|
||||
|
||||
## 🔧 Docker Compose 구성
|
||||
|
||||
### docker-compose.yml
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "4000:80"
|
||||
depends_on:
|
||||
- backend
|
||||
environment:
|
||||
- API_BASE_URL=http://localhost:9000/api
|
||||
volumes:
|
||||
- ./frontend/static:/usr/share/nginx/html/static
|
||||
restart: unless-stopped
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "9000:9000"
|
||||
depends_on:
|
||||
- database
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://todo_user:${POSTGRES_PASSWORD}@database:5432/todo_db
|
||||
- SECRET_KEY=${SECRET_KEY}
|
||||
- DEBUG=${DEBUG:-false}
|
||||
volumes:
|
||||
- ./backend/uploads:/app/uploads
|
||||
restart: unless-stopped
|
||||
|
||||
database:
|
||||
image: postgres:15-alpine
|
||||
ports:
|
||||
- "5434:5432"
|
||||
environment:
|
||||
- POSTGRES_USER=${POSTGRES_USER}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=${POSTGRES_DB}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./database/init:/docker-entrypoint-initdb.d
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
### 프로덕션용 docker-compose.prod.yml
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile.prod
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./ssl:/etc/nginx/ssl
|
||||
- ./nginx/nginx.prod.conf:/etc/nginx/nginx.conf
|
||||
restart: always
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile.prod
|
||||
expose:
|
||||
- "9000"
|
||||
environment:
|
||||
- DEBUG=false
|
||||
- DATABASE_URL=postgresql://todo_user:${POSTGRES_PASSWORD}@database:5432/todo_db
|
||||
restart: always
|
||||
|
||||
database:
|
||||
image: postgres:15-alpine
|
||||
expose:
|
||||
- "5432"
|
||||
environment:
|
||||
- POSTGRES_USER=${POSTGRES_USER}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=${POSTGRES_DB}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./backups:/backups
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
## 🌐 프로덕션 배포
|
||||
|
||||
### 1. SSL 인증서 설정
|
||||
```bash
|
||||
# Let's Encrypt 인증서 생성 (Certbot 사용)
|
||||
sudo certbot certonly --standalone -d your-domain.com
|
||||
|
||||
# 인증서 파일 복사
|
||||
sudo cp /etc/letsencrypt/live/your-domain.com/fullchain.pem ./ssl/
|
||||
sudo cp /etc/letsencrypt/live/your-domain.com/privkey.pem ./ssl/
|
||||
```
|
||||
|
||||
### 2. Nginx 설정 (프로덕션)
|
||||
```nginx
|
||||
# nginx/nginx.prod.conf
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name your-domain.com;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
|
||||
|
||||
# Frontend
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Backend API
|
||||
location /api/ {
|
||||
proxy_pass http://backend:9000/api/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# 보안 헤더
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 프로덕션 배포 실행
|
||||
```bash
|
||||
# 프로덕션 환경으로 배포
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
|
||||
# 로그 모니터링
|
||||
docker-compose -f docker-compose.prod.yml logs -f
|
||||
```
|
||||
|
||||
## 🔄 업데이트 및 유지보수
|
||||
|
||||
### 애플리케이션 업데이트
|
||||
```bash
|
||||
# 소스 코드 업데이트
|
||||
git pull origin main
|
||||
|
||||
# 컨테이너 재빌드 및 재시작
|
||||
docker-compose down
|
||||
docker-compose build --no-cache
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 데이터베이스 백업
|
||||
```bash
|
||||
# 백업 스크립트 생성
|
||||
cat > backup.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="todo_backup_${DATE}.sql"
|
||||
|
||||
docker-compose exec database pg_dump -U todo_user todo_db > ./backups/${BACKUP_FILE}
|
||||
echo "백업 완료: ${BACKUP_FILE}"
|
||||
|
||||
# 7일 이상 된 백업 파일 삭제
|
||||
find ./backups -name "todo_backup_*.sql" -mtime +7 -delete
|
||||
EOF
|
||||
|
||||
chmod +x backup.sh
|
||||
|
||||
# 백업 실행
|
||||
./backup.sh
|
||||
```
|
||||
|
||||
### 데이터베이스 복원
|
||||
```bash
|
||||
# 백업에서 복원
|
||||
docker-compose exec database psql -U todo_user -d todo_db < ./backups/todo_backup_20240115_120000.sql
|
||||
```
|
||||
|
||||
### 로그 관리
|
||||
```bash
|
||||
# 로그 확인
|
||||
docker-compose logs backend
|
||||
docker-compose logs frontend
|
||||
docker-compose logs database
|
||||
|
||||
# 로그 로테이션 설정
|
||||
cat > /etc/logrotate.d/docker-compose << 'EOF'
|
||||
/var/lib/docker/containers/*/*.log {
|
||||
rotate 7
|
||||
daily
|
||||
compress
|
||||
size=1M
|
||||
missingok
|
||||
delaycompress
|
||||
copytruncate
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
## 📊 모니터링
|
||||
|
||||
### 헬스 체크 스크립트
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# health_check.sh
|
||||
|
||||
API_URL="http://localhost:9000/api/health"
|
||||
FRONTEND_URL="http://localhost:4000"
|
||||
|
||||
# API 헬스 체크
|
||||
if curl -f -s $API_URL > /dev/null; then
|
||||
echo "✅ API 서버 정상"
|
||||
else
|
||||
echo "❌ API 서버 오류"
|
||||
# 알림 발송 (예: 이메일, Slack 등)
|
||||
fi
|
||||
|
||||
# 프론트엔드 체크
|
||||
if curl -f -s $FRONTEND_URL > /dev/null; then
|
||||
echo "✅ 프론트엔드 정상"
|
||||
else
|
||||
echo "❌ 프론트엔드 오류"
|
||||
fi
|
||||
|
||||
# 데이터베이스 체크
|
||||
if docker-compose exec database pg_isready -U todo_user > /dev/null; then
|
||||
echo "✅ 데이터베이스 정상"
|
||||
else
|
||||
echo "❌ 데이터베이스 오류"
|
||||
fi
|
||||
```
|
||||
|
||||
### 시스템 리소스 모니터링
|
||||
```bash
|
||||
# 컨테이너 리소스 사용량 확인
|
||||
docker stats
|
||||
|
||||
# 디스크 사용량 확인
|
||||
df -h
|
||||
|
||||
# 메모리 사용량 확인
|
||||
free -h
|
||||
```
|
||||
|
||||
## 🔐 보안 설정
|
||||
|
||||
### 방화벽 설정 (Ubuntu/CentOS)
|
||||
```bash
|
||||
# UFW (Ubuntu)
|
||||
sudo ufw allow 22/tcp # SSH
|
||||
sudo ufw allow 80/tcp # HTTP
|
||||
sudo ufw allow 443/tcp # HTTPS
|
||||
sudo ufw enable
|
||||
|
||||
# firewalld (CentOS)
|
||||
sudo firewall-cmd --permanent --add-service=ssh
|
||||
sudo firewall-cmd --permanent --add-service=http
|
||||
sudo firewall-cmd --permanent --add-service=https
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
### 자동 보안 업데이트
|
||||
```bash
|
||||
# Ubuntu
|
||||
sudo apt install unattended-upgrades
|
||||
sudo dpkg-reconfigure -plow unattended-upgrades
|
||||
|
||||
# CentOS
|
||||
sudo yum install yum-cron
|
||||
sudo systemctl enable yum-cron
|
||||
sudo systemctl start yum-cron
|
||||
```
|
||||
|
||||
## 🚨 문제 해결
|
||||
|
||||
### 일반적인 문제들
|
||||
|
||||
#### 1. 컨테이너 시작 실패
|
||||
```bash
|
||||
# 로그 확인
|
||||
docker-compose logs backend
|
||||
|
||||
# 포트 충돌 확인
|
||||
netstat -tulpn | grep :9000
|
||||
|
||||
# 권한 문제 확인
|
||||
ls -la ./backend/uploads
|
||||
```
|
||||
|
||||
#### 2. 데이터베이스 연결 실패
|
||||
```bash
|
||||
# 데이터베이스 컨테이너 상태 확인
|
||||
docker-compose exec database pg_isready -U todo_user
|
||||
|
||||
# 연결 테스트
|
||||
docker-compose exec database psql -U todo_user -d todo_db -c "SELECT 1;"
|
||||
```
|
||||
|
||||
#### 3. 시놀로지 연동 문제
|
||||
```bash
|
||||
# 네트워크 연결 테스트
|
||||
curl -k https://your-nas.synology.me:5001/webapi/auth.cgi
|
||||
|
||||
# DNS 해결 확인
|
||||
nslookup your-nas.synology.me
|
||||
```
|
||||
|
||||
### 성능 최적화
|
||||
|
||||
#### 1. 데이터베이스 최적화
|
||||
```sql
|
||||
-- 인덱스 확인
|
||||
SELECT schemaname, tablename, attname, n_distinct, correlation
|
||||
FROM pg_stats
|
||||
WHERE tablename = 'todo_items';
|
||||
|
||||
-- 쿼리 성능 분석
|
||||
EXPLAIN ANALYZE SELECT * FROM todo_items WHERE user_id = 'uuid';
|
||||
```
|
||||
|
||||
#### 2. 캐싱 설정
|
||||
```bash
|
||||
# Redis 추가 (선택사항)
|
||||
docker run -d --name redis -p 6379:6379 redis:alpine
|
||||
```
|
||||
|
||||
## 📱 모바일 PWA 배포
|
||||
|
||||
### PWA 설정 확인
|
||||
```javascript
|
||||
// manifest.json 검증
|
||||
{
|
||||
"name": "Todo Project",
|
||||
"short_name": "Todo",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#6366f1",
|
||||
"theme_color": "#6366f1",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Service Worker 등록
|
||||
```javascript
|
||||
// sw.js 등록 확인
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js');
|
||||
}
|
||||
```
|
||||
|
||||
이 배포 가이드를 통해 안정적이고 확장 가능한 Todo-Project를 배포할 수 있습니다!
|
||||
387
docs/SECURITY.md
Normal file
387
docs/SECURITY.md
Normal file
@@ -0,0 +1,387 @@
|
||||
# 보안 가이드 (SECURITY.md)
|
||||
|
||||
## 🔐 보안 철학
|
||||
|
||||
Todo-Project는 **개인용 도구**로 설계되어 **편의성**과 **적절한 보안** 사이의 균형을 추구합니다.
|
||||
|
||||
### 보안 원칙
|
||||
- **Trust but Verify**: 신뢰할 수 있는 기기에서는 간편하게, 의심스러운 접근은 차단
|
||||
- **최소 권한**: 필요한 최소한의 권한만 부여
|
||||
- **개인 최적화**: 개인 사용에 최적화된 보안 모델
|
||||
|
||||
## 🛡️ 보안 레벨
|
||||
|
||||
### 1. Minimal (개인용 권장)
|
||||
```python
|
||||
SECURITY_MINIMAL = {
|
||||
"device_remember_days": 30, # 30일간 기기 기억
|
||||
"require_password": False, # 기기 등록 후 비밀번호 불필요
|
||||
"session_timeout": 0, # 무제한 세션
|
||||
"biometric_optional": True, # 생체 인증 선택사항
|
||||
"auto_login": True # 자동 로그인 활성화
|
||||
}
|
||||
```
|
||||
|
||||
**적합한 환경**: 개인 기기 (내 폰, 내 컴퓨터)에서만 사용
|
||||
|
||||
### 2. Balanced (일반 권장)
|
||||
```python
|
||||
SECURITY_BALANCED = {
|
||||
"device_remember_days": 7, # 7일간 기기 기억
|
||||
"require_password": True, # 주기적 비밀번호 확인
|
||||
"session_timeout": 24*60, # 24시간 세션
|
||||
"biometric_optional": True, # 생체 인증 선택사항
|
||||
"auto_login": False # 수동 로그인
|
||||
}
|
||||
```
|
||||
|
||||
**적합한 환경**: 가끔 다른 기기에서도 접근하는 경우
|
||||
|
||||
### 3. Secure (높은 보안)
|
||||
```python
|
||||
SECURITY_SECURE = {
|
||||
"device_remember_days": 1, # 1일간만 기기 기억
|
||||
"require_password": True, # 매번 비밀번호 확인
|
||||
"session_timeout": 60, # 1시간 세션
|
||||
"biometric_required": True, # 생체 인증 필수
|
||||
"auto_login": False # 수동 로그인
|
||||
}
|
||||
```
|
||||
|
||||
**적합한 환경**: 민감한 정보가 포함된 경우
|
||||
|
||||
## 🔑 인증 시스템
|
||||
|
||||
### 기기 등록 방식
|
||||
|
||||
#### 기기 식별
|
||||
```python
|
||||
class DeviceFingerprint:
|
||||
"""기기 고유 식별자 생성"""
|
||||
|
||||
def generate_fingerprint(self, request):
|
||||
"""브라우저 fingerprint 생성"""
|
||||
components = [
|
||||
request.headers.get('User-Agent', ''),
|
||||
request.headers.get('Accept-Language', ''),
|
||||
request.headers.get('Accept-Encoding', ''),
|
||||
self.get_screen_resolution(), # JavaScript에서 전송
|
||||
self.get_timezone(), # JavaScript에서 전송
|
||||
self.get_platform_info() # JavaScript에서 전송
|
||||
]
|
||||
|
||||
fingerprint = hashlib.sha256(
|
||||
'|'.join(components).encode('utf-8')
|
||||
).hexdigest()
|
||||
|
||||
return fingerprint[:16] # 16자리 축약
|
||||
```
|
||||
|
||||
#### 기기 등록 프로세스
|
||||
```python
|
||||
class DeviceRegistration:
|
||||
"""기기 등록 관리"""
|
||||
|
||||
async def register_device(self, user_id, device_info, user_confirmation):
|
||||
"""새 기기 등록"""
|
||||
|
||||
# 1. 사용자 확인 (비밀번호 또는 기존 기기에서 승인)
|
||||
if not await self.verify_user_identity(user_id, user_confirmation):
|
||||
raise AuthenticationError("사용자 확인 실패")
|
||||
|
||||
# 2. 기기 정보 생성
|
||||
device_id = self.generate_device_id(device_info)
|
||||
device_name = device_info.get('name', '알 수 없는 기기')
|
||||
|
||||
# 3. 장기 토큰 생성 (30일 유효)
|
||||
device_token = self.create_device_token(user_id, device_id)
|
||||
|
||||
# 4. 기기 정보 저장
|
||||
device_record = {
|
||||
"device_id": device_id,
|
||||
"user_id": user_id,
|
||||
"device_name": device_name,
|
||||
"fingerprint": device_info['fingerprint'],
|
||||
"registered_at": datetime.now(),
|
||||
"last_used": datetime.now(),
|
||||
"token": device_token,
|
||||
"expires_at": datetime.now() + timedelta(days=30),
|
||||
"is_trusted": True
|
||||
}
|
||||
|
||||
await self.save_device_record(device_record)
|
||||
return device_token
|
||||
```
|
||||
|
||||
### 토큰 관리
|
||||
|
||||
#### JWT 토큰 구조
|
||||
```python
|
||||
class TokenManager:
|
||||
"""토큰 생성 및 관리"""
|
||||
|
||||
def create_device_token(self, user_id, device_id):
|
||||
"""장기간 유효한 기기 토큰 생성"""
|
||||
payload = {
|
||||
"user_id": str(user_id),
|
||||
"device_id": device_id,
|
||||
"token_type": "device",
|
||||
"issued_at": datetime.utcnow(),
|
||||
"expires_at": datetime.utcnow() + timedelta(days=30)
|
||||
}
|
||||
|
||||
return jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
|
||||
|
||||
def create_session_token(self, user_id, device_id):
|
||||
"""세션 토큰 생성"""
|
||||
payload = {
|
||||
"user_id": str(user_id),
|
||||
"device_id": device_id,
|
||||
"token_type": "session",
|
||||
"issued_at": datetime.utcnow(),
|
||||
"expires_at": datetime.utcnow() + timedelta(hours=24)
|
||||
}
|
||||
|
||||
return jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
|
||||
```
|
||||
|
||||
#### 토큰 검증
|
||||
```python
|
||||
class TokenValidator:
|
||||
"""토큰 검증"""
|
||||
|
||||
async def validate_device_token(self, token):
|
||||
"""기기 토큰 검증"""
|
||||
try:
|
||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
|
||||
|
||||
# 토큰 타입 확인
|
||||
if payload.get("token_type") != "device":
|
||||
return None
|
||||
|
||||
# 만료 시간 확인
|
||||
expires_at = datetime.fromisoformat(payload["expires_at"])
|
||||
if datetime.utcnow() > expires_at:
|
||||
return None
|
||||
|
||||
# 기기 정보 확인
|
||||
device_record = await self.get_device_record(
|
||||
payload["user_id"],
|
||||
payload["device_id"]
|
||||
)
|
||||
|
||||
if not device_record or not device_record["is_trusted"]:
|
||||
return None
|
||||
|
||||
return payload
|
||||
|
||||
except jwt.JWTError:
|
||||
return None
|
||||
```
|
||||
|
||||
## 🔒 데이터 보안
|
||||
|
||||
### 데이터베이스 보안
|
||||
|
||||
#### 비밀번호 해싱
|
||||
```python
|
||||
from passlib.context import CryptContext
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
"""비밀번호 해싱 (bcrypt)"""
|
||||
return pwd_context.hash(password)
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
"""비밀번호 검증"""
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
```
|
||||
|
||||
#### 민감 정보 암호화
|
||||
```python
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
class DataEncryption:
|
||||
"""민감 정보 암호화"""
|
||||
|
||||
def __init__(self):
|
||||
self.key = settings.ENCRYPTION_KEY.encode()
|
||||
self.cipher = Fernet(self.key)
|
||||
|
||||
def encrypt_sensitive_data(self, data: str) -> str:
|
||||
"""민감 정보 암호화 (시놀로지 비밀번호 등)"""
|
||||
return self.cipher.encrypt(data.encode()).decode()
|
||||
|
||||
def decrypt_sensitive_data(self, encrypted_data: str) -> str:
|
||||
"""민감 정보 복호화"""
|
||||
return self.cipher.decrypt(encrypted_data.encode()).decode()
|
||||
```
|
||||
|
||||
### 네트워크 보안
|
||||
|
||||
#### HTTPS 강제
|
||||
```python
|
||||
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
|
||||
|
||||
# 프로덕션에서 HTTPS 강제
|
||||
if not settings.DEBUG:
|
||||
app.add_middleware(HTTPSRedirectMiddleware)
|
||||
```
|
||||
|
||||
#### CORS 설정
|
||||
```python
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.ALLOWED_ORIGINS, # 특정 도메인만 허용
|
||||
allow_credentials=True,
|
||||
allow_methods=["GET", "POST", "PUT", "DELETE"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
```
|
||||
|
||||
## 🚨 보안 모니터링
|
||||
|
||||
### 로그인 시도 모니터링
|
||||
```python
|
||||
class SecurityMonitor:
|
||||
"""보안 모니터링"""
|
||||
|
||||
def __init__(self):
|
||||
self.failed_attempts = {} # IP별 실패 횟수
|
||||
self.blocked_ips = set() # 차단된 IP
|
||||
|
||||
async def record_login_attempt(self, ip_address, success):
|
||||
"""로그인 시도 기록"""
|
||||
if success:
|
||||
# 성공 시 실패 횟수 초기화
|
||||
self.failed_attempts.pop(ip_address, None)
|
||||
else:
|
||||
# 실패 시 횟수 증가
|
||||
self.failed_attempts[ip_address] = \
|
||||
self.failed_attempts.get(ip_address, 0) + 1
|
||||
|
||||
# 5회 실패 시 30분 차단
|
||||
if self.failed_attempts[ip_address] >= 5:
|
||||
self.block_ip(ip_address, minutes=30)
|
||||
|
||||
def block_ip(self, ip_address, minutes=30):
|
||||
"""IP 주소 차단"""
|
||||
self.blocked_ips.add(ip_address)
|
||||
|
||||
# 일정 시간 후 차단 해제
|
||||
asyncio.create_task(
|
||||
self.unblock_ip_after(ip_address, minutes)
|
||||
)
|
||||
```
|
||||
|
||||
### 의심스러운 활동 감지
|
||||
```python
|
||||
class AnomalyDetection:
|
||||
"""이상 활동 감지"""
|
||||
|
||||
async def detect_suspicious_activity(self, user_id, activity):
|
||||
"""의심스러운 활동 감지"""
|
||||
|
||||
# 1. 비정상적인 시간대 접근
|
||||
if self.is_unusual_time(activity.timestamp):
|
||||
await self.alert_unusual_time_access(user_id, activity)
|
||||
|
||||
# 2. 새로운 기기에서 접근
|
||||
if not await self.is_known_device(user_id, activity.device_info):
|
||||
await self.alert_new_device_access(user_id, activity)
|
||||
|
||||
# 3. 비정상적인 API 호출 패턴
|
||||
if await self.is_unusual_api_pattern(user_id, activity):
|
||||
await self.alert_unusual_api_pattern(user_id, activity)
|
||||
```
|
||||
|
||||
## 🔧 보안 설정
|
||||
|
||||
### 환경 변수 보안
|
||||
```bash
|
||||
# .env 파일 보안 설정
|
||||
SECRET_KEY=your-very-long-and-random-secret-key-here
|
||||
ENCRYPTION_KEY=your-32-byte-encryption-key-here
|
||||
|
||||
# 시놀로지 인증 정보 (암호화 저장)
|
||||
SYNOLOGY_USERNAME=encrypted_username
|
||||
SYNOLOGY_PASSWORD=encrypted_password
|
||||
|
||||
# 보안 레벨 설정
|
||||
SECURITY_LEVEL=minimal # minimal, balanced, secure
|
||||
ENABLE_DEVICE_REGISTRATION=true
|
||||
ENABLE_BIOMETRIC_AUTH=true
|
||||
ENABLE_SECURITY_MONITORING=true
|
||||
|
||||
# 세션 설정
|
||||
SESSION_TIMEOUT_MINUTES=1440 # 24시간
|
||||
DEVICE_REMEMBER_DAYS=30
|
||||
```
|
||||
|
||||
### 보안 헤더
|
||||
```python
|
||||
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
||||
from fastapi.responses import Response
|
||||
|
||||
# 신뢰할 수 있는 호스트만 허용
|
||||
app.add_middleware(
|
||||
TrustedHostMiddleware,
|
||||
allowed_hosts=["localhost", "127.0.0.1", "your-domain.com"]
|
||||
)
|
||||
|
||||
@app.middleware("http")
|
||||
async def add_security_headers(request, call_next):
|
||||
"""보안 헤더 추가"""
|
||||
response = await call_next(request)
|
||||
|
||||
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||
response.headers["X-Frame-Options"] = "DENY"
|
||||
response.headers["X-XSS-Protection"] = "1; mode=block"
|
||||
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
|
||||
|
||||
return response
|
||||
```
|
||||
|
||||
## 🛠️ 보안 체크리스트
|
||||
|
||||
### 개발 환경
|
||||
- [ ] `.env` 파일이 `.gitignore`에 포함되어 있는가?
|
||||
- [ ] 기본 비밀번호가 변경되었는가?
|
||||
- [ ] 디버그 모드가 비활성화되어 있는가? (프로덕션)
|
||||
- [ ] 로그에 민감 정보가 포함되지 않는가?
|
||||
|
||||
### 인증 시스템
|
||||
- [ ] 비밀번호가 안전하게 해싱되어 있는가?
|
||||
- [ ] JWT 토큰에 민감 정보가 포함되지 않는가?
|
||||
- [ ] 토큰 만료 시간이 적절하게 설정되어 있는가?
|
||||
- [ ] 기기 등록 프로세스가 안전한가?
|
||||
|
||||
### 네트워크 보안
|
||||
- [ ] HTTPS가 활성화되어 있는가? (프로덕션)
|
||||
- [ ] CORS 설정이 적절한가?
|
||||
- [ ] 보안 헤더가 설정되어 있는가?
|
||||
- [ ] 불필요한 포트가 차단되어 있는가?
|
||||
|
||||
### 데이터 보안
|
||||
- [ ] 민감 정보가 암호화되어 있는가?
|
||||
- [ ] 데이터베이스 접근이 제한되어 있는가?
|
||||
- [ ] 백업 데이터가 안전하게 보관되어 있는가?
|
||||
- [ ] 로그 파일이 안전하게 관리되어 있는가?
|
||||
|
||||
## 🚨 보안 사고 대응
|
||||
|
||||
### 사고 대응 절차
|
||||
1. **즉시 조치**: 의심스러운 접근 차단
|
||||
2. **영향 평가**: 피해 범위 확인
|
||||
3. **복구 작업**: 시스템 정상화
|
||||
4. **사후 분석**: 원인 분석 및 재발 방지
|
||||
|
||||
### 비상 연락처
|
||||
- **시스템 관리자**: [연락처]
|
||||
- **보안 담당자**: [연락처]
|
||||
- **시놀로지 지원**: [연락처]
|
||||
|
||||
이 보안 가이드를 통해 안전하고 편리한 Todo 시스템을 구축할 수 있습니다.
|
||||
Reference in New Issue
Block a user