From 52f86acda76cbcd09959d4f42b4a855917743102 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Wed, 13 May 2026 12:24:18 +0900 Subject: [PATCH] feat(auth): voice-memo bot 365d access token (PoC v1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bot 계정(`voice-memo-bot`) 한정 long-expiry access token 발급 경로 추가. 일반 사용자 흐름 영향 0 (env gate default false). - core/auth.py: create_voice_memo_bot_token() 신규 (env gate + username hard-match) - api/auth.py: login route 에 bot 분기 (bot 이면 long token 반환, 일반은 기존 흐름) - docker-compose.yml: 3 env (VOICE_MEMO_BOT_TOKEN_ENABLED/_USERNAME/_EXPIRE_DAYS) default false OpenClaw `/voice-memo` plugin → DS `/memos/` Bearer proxy 의 auth 기반. 정식 service-account/api_keys 테이블은 Phase 2 (multi-service 인입 추가 시점). plan: ~/.claude/plans/rosy-launching-otter.md project: ~/.claude/projects/-Users-hyungiahn/memory/project_voice_memo_pipeline.md Co-Authored-By: Claude Opus 4.7 (1M context) --- app/api/auth.py | 6 ++++++ app/core/auth.py | 13 +++++++++++++ docker-compose.yml | 5 +++++ 3 files changed, 24 insertions(+) diff --git a/app/api/auth.py b/app/api/auth.py index 4d8edb0..811f3e8 100644 --- a/app/api/auth.py +++ b/app/api/auth.py @@ -16,6 +16,7 @@ from core.auth import ( REFRESH_TOKEN_EXPIRE_DAYS, create_access_token, create_refresh_token, + create_voice_memo_bot_token, decode_token, get_current_user, hash_password, @@ -117,6 +118,11 @@ async def login( user.last_login_at = datetime.now(timezone.utc) await session.commit() + # Voice Memo PoC v1 — bot 계정 한정 long-expiry token (env gate). 일반 사용자 흐름 영향 0. + bot_token = create_voice_memo_bot_token(user.username) + if bot_token is not None: + return AccessTokenResponse(access_token=bot_token) + # refresh token → HttpOnly cookie _set_refresh_cookie(response, create_refresh_token(user.username)) diff --git a/app/core/auth.py b/app/core/auth.py index 80a8a95..de34b2c 100644 --- a/app/core/auth.py +++ b/app/core/auth.py @@ -1,5 +1,6 @@ """JWT + TOTP 2FA 인증""" +import os from datetime import datetime, timedelta, timezone from typing import Annotated @@ -37,6 +38,18 @@ def create_access_token(subject: str, expires_minutes: int | None = None) -> str return jwt.encode(payload, settings.jwt_secret, algorithm=ALGORITHM) +def create_voice_memo_bot_token(username: str) -> str | None: + # Voice Memo PoC v1 — bot 계정 한정 long-expiry access token (env gate + username hard-match). + # 일반 사용자 호출 시 None 반환. 정식 service-account/api_keys 는 Phase 2. + if os.getenv("VOICE_MEMO_BOT_TOKEN_ENABLED", "false").lower() != "true": + return None + bot_username = os.getenv("VOICE_MEMO_BOT_USERNAME", "voice-memo-bot") + if username != bot_username: + return None + expire_days = int(os.getenv("VOICE_MEMO_BOT_TOKEN_EXPIRE_DAYS", "365")) + return create_access_token(username, expires_minutes=expire_days * 24 * 60) + + def create_refresh_token(subject: str) -> str: expire = datetime.now(timezone.utc) + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS) payload = {"sub": subject, "exp": expire, "type": "refresh"} diff --git a/docker-compose.yml b/docker-compose.yml index 1313a43..40612b1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -194,6 +194,11 @@ services: - STT_ENDPOINT=http://stt-service:3300 # KGS Code 등 외부 학습 자료 추가 스캔 경로 (host .env 에서 주입). 빈 값이면 비활성. - ADDITIONAL_WATCH_TARGETS=${ADDITIONAL_WATCH_TARGETS:-} + # Voice Memo PoC v1 — bot 계정 한정 long-expiry access token. default false → 일반 운영 영향 0. + # 활성화: host .env 에 VOICE_MEMO_BOT_TOKEN_ENABLED=true. plan: rosy-launching-otter.md + - VOICE_MEMO_BOT_TOKEN_ENABLED=${VOICE_MEMO_BOT_TOKEN_ENABLED:-false} + - VOICE_MEMO_BOT_USERNAME=${VOICE_MEMO_BOT_USERNAME:-voice-memo-bot} + - VOICE_MEMO_BOT_TOKEN_EXPIRE_DAYS=${VOICE_MEMO_BOT_TOKEN_EXPIRE_DAYS:-365} restart: unless-stopped frontend: