# Things-Factory & Label Studio Custom 통합 설정 가이드

Things-Factory에서 Label Studio Custom (SSO Edition)을 iframe으로 통합하고 SSO를 사용하는 완전한 설정 가이드입니다.

---

## 📋 사전 요구사항

- Things-Factory 서버 (9.1.0+)
- Label Studio Custom Docker 이미지: `ghcr.io/aidoop/label-studio-custom:1.20.0-sso.38`
- Docker & Docker Compose 설치
- PostgreSQL 13+ (Docker로 실행 가능)
- 관리자 권한
- HTTPS 사용 권장 (프로덕션 필수)

---

## 🐳 0단계: Label Studio Custom Docker 설치

### Docker Compose로 빠른 설치

```bash
# 1. 프로젝트 디렉토리 생성
mkdir label-studio-deployment
cd label-studio-deployment

# 2. docker-compose.yml 작성
cat > docker-compose.yml << 'EOF'
version: "3.8"

services:
  postgres:
    image: postgres:13.18
    container_name: label-studio-postgres
    restart: unless-stopped

    environment:
      POSTGRES_DB: labelstudio
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: dev_postgres_2024
      PGDATA: /var/lib/postgresql/data/pgdata

    volumes:
      - postgres_data:/var/lib/postgresql/data

    ports:
      - "5432:5432"

    networks:
      - labelstudio

  labelstudio:
    image: ghcr.io/aidoop/label-studio-custom:1.20.0-sso.38
    container_name: label-studio-app
    restart: unless-stopped

    depends_on:
      - postgres

    environment:
      # Database
      DJANGO_DB: default
      POSTGRES_HOST: postgres
      POSTGRES_PORT: 5432
      POSTGRES_DB: labelstudio
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: dev_postgres_2024

      # Django
      DEBUG: false
      LOG_LEVEL: INFO

      # SSO Configuration
      JWT_SSO_NATIVE_USER_ID_CLAIM: user_id
      JWT_SSO_COOKIE_NAME: ls_auth_token
      JWT_SSO_TOKEN_PARAM: token
      SSO_TOKEN_EXPIRY: 600

      # Label Studio
      LABEL_STUDIO_HOST: http://label.hatiolab.localhost:8080

      # Cookie Settings (서브도메인 공유)
      SESSION_COOKIE_DOMAIN: .hatiolab.localhost
      CSRF_COOKIE_DOMAIN: .hatiolab.localhost

      # iframe Security Headers
      CSP_FRAME_ANCESTORS: "'self' http://localhost:3000 http://hatiolab.localhost:3000"

      # Other
      FEATURE_FLAGS_OFFLINE: true
      STORAGE_PERSISTENCE: true

    volumes:
      - labelstudio_data:/label-studio/data

    ports:
      - "8080:8080"

    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

    networks:
      - labelstudio

volumes:
  postgres_data:
    driver: local

  labelstudio_data:
    driver: local

networks:
  labelstudio:
    driver: bridge
EOF

# 3. 실행
docker compose up -d

# 4. 로그 확인
docker compose logs -f labelstudio

# 5. Admin 사용자 생성 (최초 1회)
docker compose exec labelstudio python manage.py createsuperuser
# → Email: admin@hatiolab.io
# → Username: admin
# → Password: admin (또는 원하는 패스워드)

# 6. API 토큰 발급
# http://label.hatiolab.localhost:8080 접속
# Account Settings → Access Token → Create new token
# 생성된 토큰을 복사 (예: 2c00d45b8318a11f59e04c7233d729f3f17664e8)

# 7. .env 파일에 API 토큰 추가 (선택사항)
cat > .env << 'EOF'
LABEL_STUDIO_API_TOKEN=2c00d45b8318a11f59e04c7233d729f3f17664e8
EOF
```

### 접속 확인

- **Label Studio**: http://label.hatiolab.localhost:8080
- **Health Check**: http://label.hatiolab.localhost:8080/health
- **PostgreSQL**: localhost:5432

---

## 🔧 1단계: Things-Factory 설정

### 1.1 모듈 빌드

```bash
# Things-Factory 프로젝트 디렉토리로 이동
cd /path/to/things-factory

# integration-label-studio 모듈 빌드
cd packages/integration-label-studio
yarn build

# 루트로 돌아가기
cd ../..
```

### 1.2 설정 파일 작성

`config/label-studio.config.js` 파일 생성:

```javascript
module.exports = {
  labelStudio: {
    serverUrl: process.env.LABEL_STUDIO_URL || 'http://localhost:8080',
    apiToken: process.env.LABEL_STUDIO_API_TOKEN || '',
    interfaces: 'panel,controls,annotations:menu' // 표시할 UI 요소
  }
}
```

### 1.3 환경 변수 설정

`.env` 파일에 추가:

```bash
# Label Studio 서버 URL
LABEL_STUDIO_URL=https://label-studio.example.com

# Label Studio API 토큰 (사용자 동기화용)
LABEL_STUDIO_API_TOKEN=your-api-token-here
```

### 1.4 Things-Factory 재시작

```bash
cd /path/to/things-factory
yarn workspace @things-factory/[APP_NAME] run serve:dev
# 또는 프로덕션
yarn workspace @things-factory/[APP_NAME] run serve
```

---

## 📦 2단계: Label Studio SSO 패키지 설치

### 2.1 label-studio-sso 패키지 설치

```bash
# Label Studio 가상환경 활성화
source /path/to/label-studio/venv/bin/activate

# 패키지 설치
pip install label-studio-sso

# 설치 확인
pip list | grep label-studio-sso
```

### 2.2 Label Studio 설정 수정

`label_studio/core/settings/base.py` 파일 수정:

```python
# INSTALLED_APPS에 추가
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # ... 기존 앱들 ...

    'label_studio_sso',  # ✅ 추가
]

# AUTHENTICATION_BACKENDS에 추가 (최상단에 배치)
AUTHENTICATION_BACKENDS = [
    'label_studio_sso.backends.JWTAuthenticationBackend',  # ✅ 추가 (최우선)
    'rules.permissions.ObjectPermissionBackend',
    'django.contrib.auth.backends.ModelBackend',
]

# MIDDLEWARE에 추가 (AuthenticationMiddleware 바로 다음)
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'core.middleware.DisableCSRF',
    'django.middleware.csrf.CsrfViewMiddleware',
    'core.middleware.XApiKeySupportMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'label_studio_sso.middleware.JWTAutoLoginMiddleware',  # ✅ 추가
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # ... 나머지 미들웨어 ...
]

# JWT SSO 설정
JWT_SSO_SECRET = os.getenv('JWT_SSO_SECRET')
JWT_SSO_ALGORITHM = 'HS256'
JWT_SSO_TOKEN_PARAM = 'token'
JWT_SSO_EMAIL_CLAIM = 'email'
JWT_SSO_AUTO_CREATE_USERS = False  # Things-Factory 사용자 동기화 사용
```

### 2.3 Label Studio 재시작

```bash
# systemd 사용 시
sudo systemctl restart label-studio

# 수동 실행 시
python label_studio/manage.py runserver 0.0.0.0:8080
```

---

## 👥 3단계: Label Studio API 토큰 생성

### 3.1 Label Studio에서 API 토큰 생성

1. Label Studio에 로그인
2. **Account Settings** → **Access Token** 메뉴 이동
3. **"Create new token"** 클릭
4. 생성된 토큰 복사 (예: `a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`)

### 3.2 Things-Factory 환경 변수에 추가

`.env` 파일의 `LABEL_STUDIO_API_TOKEN`에 복사한 토큰 추가:

```bash
LABEL_STUDIO_API_TOKEN=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
```

---

## 👥 4단계: 사용자 권한 설정 및 동기화

### 4.1 Label Studio 권한 부여

Things-Factory에서 사용자에게 `label-studio` 권한을 부여합니다.

**Things-Factory Admin UI**:

1. Users 메뉴 → 사용자 선택
2. "Granted Roles" 탭
3. `label-studio` 권한이 포함된 역할 추가

### 4.2 사용자 동기화

Things-Factory GraphQL Playground에서 사용자를 Label Studio로 동기화:

**전체 사용자 동기화 (관리자용)**:

```graphql
mutation {
  syncAllUsersToLabelStudio {
    total
    created
    updated
    deactivated
    skipped
    errors
    results {
      success
      email
      action
      lsUserId
      lsPermissions
      error
    }
  }
}
```

**현재 사용자만 동기화**:

```graphql
mutation {
  syncMyUserToLabelStudio {
    success
    email
    action
    lsUserId
    lsPermissions
    error
  }
}
```

**예상 출력**:

```json
{
  "data": {
    "syncAllUsersToLabelStudio": {
      "total": 10,
      "created": 5,
      "updated": 3,
      "deactivated": 2,
      "skipped": 0,
      "errors": 0,
      "results": [
        {
          "success": true,
          "email": "user@example.com",
          "action": "created",
          "lsUserId": "123",
          "lsPermissions": "Staff (Labeling only)",
          "error": null
        }
      ]
    }
  }
}
```

---

## ✅ 5단계: 통합 검증

### 5.1 Things-Factory에서 접근

1. Things-Factory 로그인
2. 메뉴에서 **"Label Studio"** 클릭
3. iframe에 Label Studio가 로드되어야 함
4. **자동으로 로그인되어야 함** (별도 로그인 없이)

### 5.2 JWT 토큰 확인

**브라우저 개발자 도구**에서 iframe URL 확인:

```
https://label-studio.example.com/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&interfaces=panel,controls,annotations:menu
```

**localStorage에서 토큰 확인**:

```javascript
// 브라우저 콘솔에서
localStorage.getItem('access-token')
```

### 5.3 Label Studio 로그 확인

```bash
tail -f /var/log/label-studio/label-studio.log | grep "JWT"
```

**성공 메시지**:

```
INFO: JWT token verified for email: user@example.com
INFO: User found: user@example.com
INFO: User auto-logged in: user@example.com
```

**실패 메시지**:

```
ERROR: JWT_SSO_SECRET is not configured
ERROR: JWT token signature verification failed
WARNING: User not found in Label Studio: user@example.com
```

---

## 🔍 문제 해결

### 문제 1: "JWT token signature verification failed"

**원인**: Things-Factory와 Label Studio의 `JWT_SSO_SECRET`이 다름

**해결**:

```bash
# 1. Things-Factory 시크릿 확인
cd /path/to/things-factory
cat .env | grep JWT_SSO_SECRET

# 2. Label Studio 시크릿 확인
echo $JWT_SSO_SECRET

# 3. 두 값이 동일한지 확인
# 다르면 둘 중 하나를 수정하고 양쪽 모두 재시작
```

### 문제 2: iframe에 "Refused to connect" 에러

**원인**: Label Studio의 X-Frame-Options 설정

**해결**:

```python
# Label Studio settings.py
X_FRAME_OPTIONS = 'ALLOWALL'  # 또는 특정 도메인 허용
```

### 문제 3: 사용자가 Label Studio에 없음

**원인**: 사용자 동기화가 안됨

**해결**:

```graphql
# Things-Factory GraphQL
mutation {
  syncMyUserToLabelStudio {
    success
    email
    action
    lsUserId
    error
  }
}
```

또는 Label Studio에서 수동 생성:

```bash
cd /path/to/label-studio
python manage.py createsuperuser --email user@example.com
```

### 문제 4: HTTPS에서만 작동

**원인**: JWT 토큰이 URL에 포함되므로 보안상 HTTPS 필요

**해결**:

- 개발: `http://localhost` 사용 (로컬호스트는 예외)
- 프로덕션: 반드시 HTTPS 사용

---

## 📊 설정 확인 체크리스트

### Things-Factory

```bash
# 1. 환경 변수 확인
cat .env | grep LABEL_STUDIO

# 2. 모듈 빌드 확인
ls -la packages/integration-label-studio/dist-server/
ls -la packages/integration-label-studio/dist-client/

# 3. 설정 파일 확인
cat config/label-studio.config.js

# 4. 서버 재시작
yarn workspace @things-factory/[APP_NAME] run serve:dev
```

### Label Studio

```bash
# 1. 패키지 설치 확인
pip list | grep label-studio-sso

# 2. 환경 변수 확인
echo $JWT_SSO_SECRET

# 3. 설정 파일 확인
grep -A 5 "label_studio_sso" label_studio/core/settings/base.py

# 4. 서버 재시작
sudo systemctl restart label-studio
```

---

## 🔐 보안 고려사항

### 1. JWT 시크릿 보안

- **절대 Git에 커밋하지 마세요**
- `.gitignore`에 `.env` 추가
- 프로덕션에서는 환경 변수 또는 비밀 관리 도구 사용

### 2. HTTPS 필수

- 프로덕션에서는 반드시 HTTPS 사용
- JWT 토큰이 URL에 포함되므로 중간자 공격 방지 필요

### 3. 토큰 유효 기간

- Things-Factory JWT 토큰 유효 기간: 5-10분 권장
- 만료 후 재로그인 필요

### 4. CORS 설정

```python
# Label Studio settings.py
CORS_ALLOWED_ORIGINS = [
    'https://things-factory.example.com',
]
```

---

## 📚 추가 문서

- [README.md](./README.md) - 모듈 개요
- [USER_SYNC_GUIDE.md](./USER_SYNC_GUIDE.md) - 사용자 동기화 가이드
- [label-studio-sso README](https://github.com/hatiolab/label-studio-sso) - SSO 패키지 문서
- [label-studio-sso CONFIGURATION](https://github.com/hatiolab/label-studio-sso/blob/main/CONFIGURATION.md) - 상세 설정

---

**문서 버전**: 1.0
**작성일**: 2025-10-02
**최종 업데이트**: 2025-10-02
