feat(admin): 예전/최신 어드민 통합 — 실데이터 복원 + 인증 아키텍처 정리

예전 배포본(HEAD)의 상세 기능을 새 아키텍처(인증=.NET 백엔드, 데이터=Supabase)
위에 실데이터로 복원. 예전 HEAD는 서명 쿠키를 거부하고 위조 쿠키는 통과시키는
인증 결함이 있었고, 삭제된 페이지 다수는 백엔드 호출 0인 하드코딩 목업이었음.

인증/세션
- 로그인 이메일 전용화(username 폐지), 에러 키별 안내 메시지
- ADMIN_COOKIE_SECURE 옵션: TLS 없는 LAN HTTP 배포에서 Secure 쿠키 유실로
  로그인이 유지되지 않던 문제 해결 (login/logout route, admin-session, compose, .env.example)
- Supabase 미설정 시 우아한 저하: isSupabaseAdminConfigured + UnavailableAdminPanel

기능 복원 (실데이터)
- Release Hub: Forgejo API 실데이터(다운로드 수/SHA-256 체크섬/릴리스 이력)
- Ad Monetization: 데스크톱 미디에이션 10개 어댑터 로스터(fail-closed) + ad_reward_claims 통계
- License Issuer: 서버사이드 Ed25519 서명(/api/admin/license, super_admin 전용),
  개인키는 ADMIN_LICENSE_PRIVATE_KEY env로만, 발급 감사를 .NET AdminAuditEntries에 기록
- Service Models: STT 7종/LLM 5종 프리셋 드롭다운 + 자동채움
- 대시보드 ARR/MRR KPI: Supabase 구독 실집계(티어 월단가 기반)
- 사용자 상세 티어별 기능 배지(pro_plus 조건부)

.NET
- SuperAdminOnly 정책 추가, /api/admin/license-audit 엔드포인트, LicenseAuditDto
This commit is contained in:
Yun Chan 2026-08-23 23:38:08 +09:00
parent a9c9a1ca6e
commit 5a34f66981
66 changed files with 4471 additions and 3501 deletions

72
.env.example Normal file
View file

@ -0,0 +1,72 @@
# Copy this file to .env for local operations only. Never commit populated values.
# NAS deployment target
DSM_HOST=
DSM_SSH_PORT=22
DSM_SSH_USER=
DSM_SSH_PASSWORD=
NAS_DEPLOY_ROOT=/volume1/docker/d3ro-voice
NAS_ADMIN_PORT=5000
NAS_LANDING_PORT=4321
# Git/Forgejo operations
GIT_SERVER_URL=https://git.example.invalid
GIT_USERNAME=
GIT_PASSWORD=
GIT_REPO_NAME=d3ro-voice
FORGEJO_TOKEN=
FORGEJO_USERNAME=
FORGEJO_PASSWORD=
# Public service addresses
APP_DOMAIN=voice.example.invalid
ADMIN_DOMAIN=admin.voice.example.invalid
API_SERVER_URL=https://voice.example.invalid
SITE_URL=https://voice.example.invalid
CORS_ALLOWED_ORIGINS=https://voice.example.invalid,https://admin.voice.example.invalid
ALLOWED_HOSTS=voice.example.invalid;admin.voice.example.invalid
# API authentication. Generate unique random values for every environment.
# JWT_SECRET and ADMIN_SESSION_SECRET must each be at least 32 UTF-8 bytes.
JWT_SECRET=
JWT_ISSUER=https://voice.example.invalid
JWT_AUDIENCE=d3ro-admin
ADMIN_BOOTSTRAP_TOKEN=
ADMIN_SESSION_SECRET=
# TLS 없이 LAN HTTP(예: http://NAS_IP:3001)로 직접 접속하는 배포에서만 false로 설정.
# Secure 쿠키는 HTTP에서 브라우저가 버려 로그인이 유지되지 않는다. 기본값 true 유지 권장.
ADMIN_COOKIE_SECURE=true
# Shared only between Supabase stt-proxy and the .NET provider orchestrator.
# Use a distinct random value with at least 32 UTF-8 bytes.
D3RO_API_TOKEN=
# Supabase
SUPABASE_URL=
SUPABASE_SERVICE_ROLE_KEY=
# Google OAuth and browser automation
GOOGLE_OAUTH_CLIENT_ID=
GOOGLE_OAUTH_SECRET=
D3RO_PORTAL_OPERATOR_EMAIL=
# Android release signing. Prefer Gradle properties or a protected CI secret store.
D3RO_RELEASE_STORE_FILE=
D3RO_RELEASE_STORE_PASSWORD=
D3RO_RELEASE_KEY_ALIAS=
D3RO_RELEASE_KEY_PASSWORD=
# Google Mobile Ads production identifiers. These are identifiers, not secrets,
# but release builds intentionally fail when they are absent or use test IDs.
D3RO_ADMOB_APP_ID=
D3RO_ADMOB_BANNER_UNIT_ID=
D3RO_ADMOB_REWARDED_UNIT_ID=
# Email and push providers
RESEND_FROM=
RESEND_API_KEY=
FCM_SERVICE_ACCOUNT_JSON=
FCM_PROJECT_ID=
# Optional authenticated mobile E2E account, stored only in CI secrets or local .env.
MOBILE_E2E_EMAIL=
MOBILE_E2E_PASSWORD=