# HotPartners 2FA Package
[](https://badge.fury.io/py/hotpartners-twofa)
[](https://pypi.org/project/hotpartners-twofa/)
[](https://opensource.org/licenses/MIT)
Two-Factor Authentication (2FA) 패키지로 TOTP 기반 인증을 제공합니다.
## 주요 기능
- 🔐 **TOTP 기반 2FA**: Google Authenticator, Authy 등과 호환
- 📱 **QR 코드 생성**: 모바일 앱 연동을 위한 QR 코드 자동 생성
- 🔑 **백업 코드**: 일회용 백업 코드 생성 및 관리
- 🛡️ **암호화 저장**: 민감한 데이터의 안전한 암호화 저장
- 📊 **로그 관리**: 2FA 사용 이력 추적
- 🔄 **비동기 지원**: async/await 패턴 완전 지원
## 설치
```bash
pip install hotpartners-twofa
```
## 빠른 시작
### 1. 데이터베이스 스키마 설정
```bash
# CLI 도구로 스키마 생성
hotpartners-twofa-setup --connection-string "postgresql://user:pass@localhost/db"
# 또는 Python 코드로
from hotpartners_twofa.schema import TwoFASchemaManager
schema_manager = TwoFASchemaManager(connection_func)
await schema_manager.create_tables()
```
### 2. 기본 사용법
```python
import asyncio
from hotpartners_twofa import TwoFAService, AdminTwoFARepository, TwoFAConfig
async def main():
# 설정
config = TwoFAConfig()
repository = AdminTwoFARepository(config)
service = TwoFAService(repository)
# 2FA 설정
user_id = "user123"
setup_result = await service.setup_2fa(user_id)
print(f"QR 코드: {setup_result.qr_code}")
print(f"백업 코드: {setup_result.backup_codes}")
# 2FA 인증
token = "123456" # 사용자가 입력한 6자리 코드
auth_result = await service.authenticate_2fa(user_id, token)
if auth_result.success:
print("2FA 인증 성공!")
else:
print(f"인증 실패: {auth_result.message}")
asyncio.run(main())
```
## 데이터베이스 스키마
### PostgreSQL
```sql
-- 사용자 2FA 테이블
CREATE TABLE user_twofa (
user_id VARCHAR(255) PRIMARY KEY,
otp_secret TEXT,
backup_codes TEXT, -- JSON 문자열
is_2fa_enabled BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 2FA 사용 로그
CREATE TABLE user_twofa_logs (
id SERIAL PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
ip_address INET,
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
## API 문서
### TwoFAService
#### `setup_2fa(user_id: str) -> TwoFASetupResponse`
사용자의 2FA를 설정합니다.
**반환값:**
- `qr_code`: QR 코드 (Base64 인코딩된 이미지)
- `backup_codes`: 백업 코드 리스트
- `secret`: OTP 비밀키 (수동 입력용)
#### `authenticate_2fa(user_id: str, token: str) -> TwoFAAuthenticateResponse`
2FA 토큰을 검증합니다.
**매개변수:**
- `user_id`: 사용자 ID
- `token`: 6자리 TOTP 코드
#### `verify_backup_code(user_id: str, backup_code: str) -> TwoFAVerifyResponse`
백업 코드를 검증합니다.
#### `regenerate_backup_codes(user_id: str) -> TwoFARegenerateBackupCodesResponse`
새로운 백업 코드를 생성합니다.
#### `disable_2fa(user_id: str) -> TwoFADisableResponse`
2FA를 비활성화합니다.
### TwoFARepository
데이터베이스 작업을 담당하는 추상 클래스입니다.
#### 구현체
- `AdminTwoFARepository`: 관리자용 구현체
- `UserTwoFARepository`: 일반 사용자용 구현체 (확장 가능)
## 설정
환경 변수로 설정을 변경할 수 있습니다:
```bash
# 2FA 발급자 이름
export TWOFA_ISSUER_NAME="MyApp"
# 백업 코드 개수 (기본값: 8)
export TWOFA_BACKUP_CODES_COUNT="10"
# TOTP 윈도우 크기 (기본값: 1)
export TWOFA_WINDOW_SIZE="2"
# 최대 재시도 횟수 (기본값: 5)
export TWOFA_MAX_RETRY_ATTEMPTS="3"
# 재시도 윈도우 (분, 기본값: 5)
export TWOFA_RETRY_WINDOW_MINUTES="10"
```
## 보안 고려사항
1. **암호화**: OTP 비밀키와 백업 코드는 Fernet으로 암호화되어 저장됩니다.
2. **재시도 제한**: 무차별 대입 공격을 방지하기 위한 재시도 제한이 있습니다.
3. **로그 관리**: 2FA 사용 이력이 추적되어 보안 모니터링이 가능합니다.
4. **권한 분리**: 관리자와 일반 사용자 권한을 분리하여 관리합니다.
## 개발
### 개발 환경 설정
```bash
git clone https://github.com/hotpartners/hotpartners-twofa.git
cd hotpartners-twofa
pip install -e ".[dev]"
```
### 테스트 실행
```bash
pytest
```
### 코드 포맷팅
```bash
black hotpartners_twofa/
flake8 hotpartners_twofa/
mypy hotpartners_twofa/
```
## 라이선스
MIT License - 자세한 내용은 [LICENSE](LICENSE) 파일을 참조하세요.
## 기여하기
1. Fork the Project
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the Branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
## 지원
- 이슈 리포트: [GitHub Issues](https://github.com/hotpartners/hotpartners-twofa/issues)
- 문서: [Read the Docs](https://hotpartners-twofa.readthedocs.io/)
- 이메일: dev@hotpartners.com
## 변경 이력
### 1.0.0 (2024-12-02)
- 초기 릴리스
- TOTP 기반 2FA 지원
- QR 코드 생성
- 백업 코드 관리
- 암호화 저장
- 비동기 지원
Raw data
{
"_id": null,
"home_page": "https://pypi.org/project/hotpartners-twofa/",
"name": "hotpartners-twofa",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "2fa two-factor authentication otp totp qr-code security",
"author": "hotseller_devjun",
"author_email": "jypark@hotseller.co.kr",
"download_url": "https://files.pythonhosted.org/packages/a4/2f/1fbb4db2a04655f2f2e36077cdcecee4808cc1df66522ead7ce1e7cf6ca3/hotpartners_twofa-1.1.2.tar.gz",
"platform": null,
"description": "# HotPartners 2FA Package\n\n[](https://badge.fury.io/py/hotpartners-twofa)\n[](https://pypi.org/project/hotpartners-twofa/)\n[](https://opensource.org/licenses/MIT)\n\nTwo-Factor Authentication (2FA) \ud328\ud0a4\uc9c0\ub85c TOTP \uae30\ubc18 \uc778\uc99d\uc744 \uc81c\uacf5\ud569\ub2c8\ub2e4.\n\n## \uc8fc\uc694 \uae30\ub2a5\n\n- \ud83d\udd10 **TOTP \uae30\ubc18 2FA**: Google Authenticator, Authy \ub4f1\uacfc \ud638\ud658\n- \ud83d\udcf1 **QR \ucf54\ub4dc \uc0dd\uc131**: \ubaa8\ubc14\uc77c \uc571 \uc5f0\ub3d9\uc744 \uc704\ud55c QR \ucf54\ub4dc \uc790\ub3d9 \uc0dd\uc131\n- \ud83d\udd11 **\ubc31\uc5c5 \ucf54\ub4dc**: \uc77c\ud68c\uc6a9 \ubc31\uc5c5 \ucf54\ub4dc \uc0dd\uc131 \ubc0f \uad00\ub9ac\n- \ud83d\udee1\ufe0f **\uc554\ud638\ud654 \uc800\uc7a5**: \ubbfc\uac10\ud55c \ub370\uc774\ud130\uc758 \uc548\uc804\ud55c \uc554\ud638\ud654 \uc800\uc7a5\n- \ud83d\udcca **\ub85c\uadf8 \uad00\ub9ac**: 2FA \uc0ac\uc6a9 \uc774\ub825 \ucd94\uc801\n- \ud83d\udd04 **\ube44\ub3d9\uae30 \uc9c0\uc6d0**: async/await \ud328\ud134 \uc644\uc804 \uc9c0\uc6d0\n\n## \uc124\uce58\n\n```bash\npip install hotpartners-twofa\n```\n\n## \ube60\ub978 \uc2dc\uc791\n\n### 1. \ub370\uc774\ud130\ubca0\uc774\uc2a4 \uc2a4\ud0a4\ub9c8 \uc124\uc815\n\n```bash\n# CLI \ub3c4\uad6c\ub85c \uc2a4\ud0a4\ub9c8 \uc0dd\uc131\nhotpartners-twofa-setup --connection-string \"postgresql://user:pass@localhost/db\"\n\n# \ub610\ub294 Python \ucf54\ub4dc\ub85c\nfrom hotpartners_twofa.schema import TwoFASchemaManager\n\nschema_manager = TwoFASchemaManager(connection_func)\nawait schema_manager.create_tables()\n```\n\n### 2. \uae30\ubcf8 \uc0ac\uc6a9\ubc95\n\n```python\nimport asyncio\nfrom hotpartners_twofa import TwoFAService, AdminTwoFARepository, TwoFAConfig\n\nasync def main():\n # \uc124\uc815\n config = TwoFAConfig()\n repository = AdminTwoFARepository(config)\n service = TwoFAService(repository)\n\n # 2FA \uc124\uc815\n user_id = \"user123\"\n setup_result = await service.setup_2fa(user_id)\n\n print(f\"QR \ucf54\ub4dc: {setup_result.qr_code}\")\n print(f\"\ubc31\uc5c5 \ucf54\ub4dc: {setup_result.backup_codes}\")\n\n # 2FA \uc778\uc99d\n token = \"123456\" # \uc0ac\uc6a9\uc790\uac00 \uc785\ub825\ud55c 6\uc790\ub9ac \ucf54\ub4dc\n auth_result = await service.authenticate_2fa(user_id, token)\n\n if auth_result.success:\n print(\"2FA \uc778\uc99d \uc131\uacf5!\")\n else:\n print(f\"\uc778\uc99d \uc2e4\ud328: {auth_result.message}\")\n\nasyncio.run(main())\n```\n\n## \ub370\uc774\ud130\ubca0\uc774\uc2a4 \uc2a4\ud0a4\ub9c8\n\n### PostgreSQL\n\n```sql\n-- \uc0ac\uc6a9\uc790 2FA \ud14c\uc774\ube14\nCREATE TABLE user_twofa (\n user_id VARCHAR(255) PRIMARY KEY,\n otp_secret TEXT,\n backup_codes TEXT, -- JSON \ubb38\uc790\uc5f4\n is_2fa_enabled BOOLEAN DEFAULT FALSE,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\n-- 2FA \uc0ac\uc6a9 \ub85c\uadf8\nCREATE TABLE user_twofa_logs (\n id SERIAL PRIMARY KEY,\n user_id VARCHAR(255) NOT NULL,\n ip_address INET,\n user_agent TEXT,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n```\n\n## API \ubb38\uc11c\n\n### TwoFAService\n\n#### `setup_2fa(user_id: str) -> TwoFASetupResponse`\n\uc0ac\uc6a9\uc790\uc758 2FA\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.\n\n**\ubc18\ud658\uac12:**\n- `qr_code`: QR \ucf54\ub4dc (Base64 \uc778\ucf54\ub529\ub41c \uc774\ubbf8\uc9c0)\n- `backup_codes`: \ubc31\uc5c5 \ucf54\ub4dc \ub9ac\uc2a4\ud2b8\n- `secret`: OTP \ube44\ubc00\ud0a4 (\uc218\ub3d9 \uc785\ub825\uc6a9)\n\n#### `authenticate_2fa(user_id: str, token: str) -> TwoFAAuthenticateResponse`\n2FA \ud1a0\ud070\uc744 \uac80\uc99d\ud569\ub2c8\ub2e4.\n\n**\ub9e4\uac1c\ubcc0\uc218:**\n- `user_id`: \uc0ac\uc6a9\uc790 ID\n- `token`: 6\uc790\ub9ac TOTP \ucf54\ub4dc\n\n#### `verify_backup_code(user_id: str, backup_code: str) -> TwoFAVerifyResponse`\n\ubc31\uc5c5 \ucf54\ub4dc\ub97c \uac80\uc99d\ud569\ub2c8\ub2e4.\n\n#### `regenerate_backup_codes(user_id: str) -> TwoFARegenerateBackupCodesResponse`\n\uc0c8\ub85c\uc6b4 \ubc31\uc5c5 \ucf54\ub4dc\ub97c \uc0dd\uc131\ud569\ub2c8\ub2e4.\n\n#### `disable_2fa(user_id: str) -> TwoFADisableResponse`\n2FA\ub97c \ube44\ud65c\uc131\ud654\ud569\ub2c8\ub2e4.\n\n### TwoFARepository\n\n\ub370\uc774\ud130\ubca0\uc774\uc2a4 \uc791\uc5c5\uc744 \ub2f4\ub2f9\ud558\ub294 \ucd94\uc0c1 \ud074\ub798\uc2a4\uc785\ub2c8\ub2e4.\n\n#### \uad6c\ud604\uccb4\n- `AdminTwoFARepository`: \uad00\ub9ac\uc790\uc6a9 \uad6c\ud604\uccb4\n- `UserTwoFARepository`: \uc77c\ubc18 \uc0ac\uc6a9\uc790\uc6a9 \uad6c\ud604\uccb4 (\ud655\uc7a5 \uac00\ub2a5)\n\n## \uc124\uc815\n\n\ud658\uacbd \ubcc0\uc218\ub85c \uc124\uc815\uc744 \ubcc0\uacbd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4:\n\n```bash\n# 2FA \ubc1c\uae09\uc790 \uc774\ub984\nexport TWOFA_ISSUER_NAME=\"MyApp\"\n\n# \ubc31\uc5c5 \ucf54\ub4dc \uac1c\uc218 (\uae30\ubcf8\uac12: 8)\nexport TWOFA_BACKUP_CODES_COUNT=\"10\"\n\n# TOTP \uc708\ub3c4\uc6b0 \ud06c\uae30 (\uae30\ubcf8\uac12: 1)\nexport TWOFA_WINDOW_SIZE=\"2\"\n\n# \ucd5c\ub300 \uc7ac\uc2dc\ub3c4 \ud69f\uc218 (\uae30\ubcf8\uac12: 5)\nexport TWOFA_MAX_RETRY_ATTEMPTS=\"3\"\n\n# \uc7ac\uc2dc\ub3c4 \uc708\ub3c4\uc6b0 (\ubd84, \uae30\ubcf8\uac12: 5)\nexport TWOFA_RETRY_WINDOW_MINUTES=\"10\"\n```\n\n## \ubcf4\uc548 \uace0\ub824\uc0ac\ud56d\n\n1. **\uc554\ud638\ud654**: OTP \ube44\ubc00\ud0a4\uc640 \ubc31\uc5c5 \ucf54\ub4dc\ub294 Fernet\uc73c\ub85c \uc554\ud638\ud654\ub418\uc5b4 \uc800\uc7a5\ub429\ub2c8\ub2e4.\n2. **\uc7ac\uc2dc\ub3c4 \uc81c\ud55c**: \ubb34\ucc28\ubcc4 \ub300\uc785 \uacf5\uaca9\uc744 \ubc29\uc9c0\ud558\uae30 \uc704\ud55c \uc7ac\uc2dc\ub3c4 \uc81c\ud55c\uc774 \uc788\uc2b5\ub2c8\ub2e4.\n3. **\ub85c\uadf8 \uad00\ub9ac**: 2FA \uc0ac\uc6a9 \uc774\ub825\uc774 \ucd94\uc801\ub418\uc5b4 \ubcf4\uc548 \ubaa8\ub2c8\ud130\ub9c1\uc774 \uac00\ub2a5\ud569\ub2c8\ub2e4.\n4. **\uad8c\ud55c \ubd84\ub9ac**: \uad00\ub9ac\uc790\uc640 \uc77c\ubc18 \uc0ac\uc6a9\uc790 \uad8c\ud55c\uc744 \ubd84\ub9ac\ud558\uc5ec \uad00\ub9ac\ud569\ub2c8\ub2e4.\n\n## \uac1c\ubc1c\n\n### \uac1c\ubc1c \ud658\uacbd \uc124\uc815\n\n```bash\ngit clone https://github.com/hotpartners/hotpartners-twofa.git\ncd hotpartners-twofa\npip install -e \".[dev]\"\n```\n\n### \ud14c\uc2a4\ud2b8 \uc2e4\ud589\n\n```bash\npytest\n```\n\n### \ucf54\ub4dc \ud3ec\ub9f7\ud305\n\n```bash\nblack hotpartners_twofa/\nflake8 hotpartners_twofa/\nmypy hotpartners_twofa/\n```\n\n## \ub77c\uc774\uc120\uc2a4\n\nMIT License - \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [LICENSE](LICENSE) \ud30c\uc77c\uc744 \ucc38\uc870\ud558\uc138\uc694.\n\n## \uae30\uc5ec\ud558\uae30\n\n1. Fork the Project\n2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)\n3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)\n4. Push to the Branch (`git push origin feature/AmazingFeature`)\n5. Open a Pull Request\n\n## \uc9c0\uc6d0\n\n- \uc774\uc288 \ub9ac\ud3ec\ud2b8: [GitHub Issues](https://github.com/hotpartners/hotpartners-twofa/issues)\n- \ubb38\uc11c: [Read the Docs](https://hotpartners-twofa.readthedocs.io/)\n- \uc774\uba54\uc77c: dev@hotpartners.com\n\n## \ubcc0\uacbd \uc774\ub825\n\n### 1.0.0 (2024-12-02)\n- \ucd08\uae30 \ub9b4\ub9ac\uc2a4\n- TOTP \uae30\ubc18 2FA \uc9c0\uc6d0\n- QR \ucf54\ub4dc \uc0dd\uc131\n- \ubc31\uc5c5 \ucf54\ub4dc \uad00\ub9ac\n- \uc554\ud638\ud654 \uc800\uc7a5\n- \ube44\ub3d9\uae30 \uc9c0\uc6d0\n",
"bugtrack_url": null,
"license": null,
"summary": "Two-Factor Authentication (2FA) package for HotPartners",
"version": "1.1.2",
"project_urls": {
"Homepage": "https://pypi.org/project/hotpartners-twofa/"
},
"split_keywords": [
"2fa",
"two-factor",
"authentication",
"otp",
"totp",
"qr-code",
"security"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "fa1a9b6aadd5b0418b4eea98b00189d68036b036c2bfadad5c9c5408606b18d9",
"md5": "7f5fac4f1fceb0d6e1c7b8d0c9c89289",
"sha256": "06cd50530643dc20d151ad7491ddcc838ccd8966b9500a028b72bac2a27a9ef0"
},
"downloads": -1,
"filename": "hotpartners_twofa-1.1.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7f5fac4f1fceb0d6e1c7b8d0c9c89289",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 19580,
"upload_time": "2025-09-12T05:49:29",
"upload_time_iso_8601": "2025-09-12T05:49:29.980582Z",
"url": "https://files.pythonhosted.org/packages/fa/1a/9b6aadd5b0418b4eea98b00189d68036b036c2bfadad5c9c5408606b18d9/hotpartners_twofa-1.1.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "a42f1fbb4db2a04655f2f2e36077cdcecee4808cc1df66522ead7ce1e7cf6ca3",
"md5": "d548fe080048a5d1d2408d6564518330",
"sha256": "1931bff3772c198efdd35f0d39b20b117442ce5ad60873ab2a1624b77fc2dbcb"
},
"downloads": -1,
"filename": "hotpartners_twofa-1.1.2.tar.gz",
"has_sig": false,
"md5_digest": "d548fe080048a5d1d2408d6564518330",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 17626,
"upload_time": "2025-09-12T05:49:31",
"upload_time_iso_8601": "2025-09-12T05:49:31.277925Z",
"url": "https://files.pythonhosted.org/packages/a4/2f/1fbb4db2a04655f2f2e36077cdcecee4808cc1df66522ead7ce1e7cf6ca3/hotpartners_twofa-1.1.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-09-12 05:49:31",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "hotpartners-twofa"
}