-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/auth #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/auth #8
Conversation
Walkthrough새로운 인증 인프라(토큰/리프레시/게스트/OAuth2, PKCE, 콜백 핸들러, DTO/모델, 유틸)와 예제 스크립트가 대량 추가되었고, 네트워크 클라이언트가 세션 기반에서 토큰 기반으로 전환되었습니다. WebSocket 연결도 토큰 쿼리 방식으로 변경되었습니다. 일부 자동 동작(해상도 스케일링, 콘솔 표시)이 조정되었고, SessionManager 및 에디터 메뉴가 제거되었습니다. .gitignore가 확장되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant UI
participant AuthManager
participant GuestAuthService
participant HttpApiClient
participant TokenManager
UI->>AuthManager: LoginAsGuestAsync()
AuthManager->>GuestAuthService: LoginAsGuestAsync()
GuestAuthService->>HttpApiClient: POST /api/v1/auth/guest-login (requiresAuth: false)
HttpApiClient-->>GuestAuthService: GuestLoginResponse
GuestAuthService->>TokenManager: SaveTokens(TokenSet)
TokenManager-->>GuestAuthService: OnTokensUpdated
GuestAuthService-->>AuthManager: Success(TokenSet)
AuthManager-->>UI: OnLoginSuccess(TokenSet)
sequenceDiagram
autonumber
participant UI
participant AuthManager
participant ServerOAuth2Provider
participant Browser
participant CallbackHandler
participant TokenManager
UI->>AuthManager: LoginWithOAuth2Async()
AuthManager->>ServerOAuth2Provider: LoginWithServerOAuth2Async()
ServerOAuth2Provider->>ServerOAuth2Provider: GeneratePKCE
ServerOAuth2Provider->>Browser: Open(AuthUrl)
Browser-->>CallbackHandler: Callback URL (success, state)
CallbackHandler-->>ServerOAuth2Provider: WaitForCallbackAsync()
ServerOAuth2Provider->>ServerOAuth2Provider: RequestTokenAsync(state)
ServerOAuth2Provider->>TokenManager: SaveTokens(TokenSet)
TokenManager-->>AuthManager: OnTokensUpdated
AuthManager-->>UI: OnLoginSuccess(TokenSet)
sequenceDiagram
autonumber
participant Runtime
participant TokenManager
participant TokenRefreshService
participant HttpApiClient
participant Consumers
Runtime->>TokenManager: GetAccessToken()
TokenManager-->>Runtime: (expired)
TokenManager-->>TokenRefreshService: OnTokensExpired
TokenRefreshService->>HttpApiClient: POST /api/v1/auth/oauth2/refresh (requiresAuth: false)
HttpApiClient-->>TokenRefreshService: TokenSet
TokenRefreshService->>TokenManager: SaveTokens(TokenSet)
TokenManager-->>Consumers: OnTokensUpdated(new access token)
sequenceDiagram
autonumber
participant App
participant WebSocketManager
participant TokenManager
participant Server
App->>WebSocketManager: ConnectAsync()
WebSocketManager->>TokenManager: GetAccessToken()
TokenManager-->>WebSocketManager: accessToken
WebSocketManager->>Server: WS connect w/ ?token=accessToken
Server-->>WebSocketManager: Messages
WebSocketManager-->>App: OnMessageReceived(payload)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes Possibly related PRs
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 36
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
Assets/Infrastructure/Network/Configs/NetworkConfig.cs (2)
65-76: 런타임 강제 환경 설정(ApplyRuntimeGuard) 미적용 — 의도된 보호 장치가 동작하지 않습니다모바일 빌드에서 Production을 강제하는
ApplyRuntimeGuard가 정의되어 있으나 어디서도 호출되지 않아 보호 장치가 무력화됩니다. 에디터 외 런타임에서 개발/테스트 서버로 접속할 수 있는 리스크가 있습니다. 인스턴스 반환 전에 호출하도록 수정 제안합니다.아래 패치 적용 제안:
@@ { _instance = Resources.Load<NetworkConfig>("NetworkConfig"); if (_instance == null) { Debug.LogError("NetworkConfig를 찾을 수 없습니다. Resources 폴더에 NetworkConfig.asset 파일을 생성하세요."); _instance = CreateDefaultInstance(); } } - return _instance; + // 런타임 안전장치 적용(모바일 빌드에서 Production 강제) + ApplyRuntimeGuard(_instance); + return _instance;
172-176: NetworkConfig.cs의 세션 기반 메서드에 [Obsolete] 속성 추가 및 토큰 기반 메서드 도입 필요세션 ID 기반 쿼리 파라미터(
?sessionId=)를 사용하는GetWebSocketUrlWithSession메서드는 시스템 전반에서 여전히 참조되고(코드: NetworkConfig.cs, docs: README.md) 있으나, PR 요약에서는 토큰 쿼리 방식으로 전환되었다고 명시되어 있어 불일치합니다.
아래 수정 사항을 적용해 주시기 바랍니다.
- Assets/Infrastructure/Network/Configs/NetworkConfig.cs (172–176 라인)
GetWebSocketUrlWithSession에[Obsolete]속성 추가- 신규 토큰 기반 빌더 메서드
GetWebSocketUrlWithToken(string accessToken)추가- Assets/Infrastructure/Network/README.md (84 라인)
- 예시 코드에서 세션 기반 메서드 → 토큰 기반 메서드 사용 예시로 변경
제안하는 diff:
@@ Assets/Infrastructure/Network/Configs/NetworkConfig.cs - public static string GetWebSocketUrlWithSession(string sessionId) + [System.Obsolete("Use GetWebSocketUrlWithToken(accessToken) instead. Will be removed in vNext.")] + public static string GetWebSocketUrlWithSession(string sessionId) { var baseWsUrl = GetWebSocketUrlWithVersion(); return $"{baseWsUrl}?sessionId={sessionId}"; } + /// <summary> + /// 토큰 기반 WebSocket URL 생성 + /// </summary> + public static string GetWebSocketUrlWithToken(string accessToken) + { + var baseWsUrl = GetWebSocketUrlWithVersion(); + // 서버와 합의된 파라미터 키(access_token 등)를 사용하세요. + return $"{baseWsUrl}?access_token={UnityWebRequest.EscapeURL(accessToken)}"; + }또한 README.md의 사용 예시를 아래처럼 업데이트해 주세요:
@@ Assets/Infrastructure/Network/README.md - var wsUrlWithSession = NetworkConfig.GetWebSocketUrlWithSession("session-123"); + var wsUrlWithToken = NetworkConfig.GetWebSocketUrlWithToken("your-access-token");이 변경으로 기존 세션 기반 호출 호환성을 유지하는 한편, 토큰 기반으로의 완전 전환 계획을 명확히 할 수 있습니다.
🧹 Nitpick comments (85)
Assets/Infrastructure/Auth/OAuth2/Handlers.meta (2)
4-8: AssetBundle/Label 계획이 있으면 폴더 단위 설정은 지양하세요.
- DefaultImporter 하위의 userData/assetBundleName/Variant가 비어있는 상태는 무해합니다.
- 만약 번들링 전략을 적용할 예정이라면, 폴더 레벨이 아닌 개별 에셋 단위로 설정(또는 Addressables 활용)을 권장합니다. 현 변경은 그대로 유지해도 문제 없습니다.
1-8: .meta 누락 검사 스크립트 수정 제안기존에 제안된 스크립트(
bash -lc사용) 실행 시 로그인 셸 로딩(/etc/profile) 오류로 인해 실제 검사가 수행되지 않았습니다. 아래처럼 단순한find혹은fd기반 스크립트로 교체하여,Assets/Infrastructure/Auth/OAuth2/Handlers폴더 하위의 파일 중 .meta가 없는 항목을 정확히 탐지하시길 권장드립니다.• 대상 폴더:
- Assets/Infrastructure/Auth/OAuth2/Handlers
• 개선된 스크립트 예시 1 (find 사용)
#!/usr/bin/env bash set -euo pipefail BASE="Assets/Infrastructure/Auth/OAuth2/Handlers" echo "[Handlers 폴더 .meta 누락 검사] $BASE" find "$BASE" -type f ! -name '*.meta' | while IFS= read -r file; do if [[ ! -f "${file}.meta" ]]; then echo "missing meta: ${file}.meta" fi done• 개선된 스크립트 예시 2 (fd + while 사용)
#!/usr/bin/env bash set -euo pipefail BASE="Assets/Infrastructure/Auth/OAuth2/Handlers" echo "[Handlers 폴더 .meta 누락 검사] $BASE" fd --type f --hidden --no-ignore '' "$BASE" | while IFS= read -r file; do # .meta 파일이 아니고, .meta 파일이 실제로 존재하지 않으면 출력 if [[ "${file##*.}" != "meta" ]] && [[ ! -f "${file}.meta" ]]; then echo "missing meta: ${file}.meta" fi done위 스크립트들 중 편한 도구를 선택하여 실행한 뒤, 출력된 항목이 없으면 모든 핸들러 에셋에 .meta가 정상 커밋된 것입니다.
.gitignore (2)
56-64: 과도한 와일드카드와 중복 패턴 정리 제안현재 규칙들은 다음 이슈가 있습니다:
- 중복:
CLAUDE.md와*CLAUDE.md(또는claude.md/*claude.md)는 의미가 사실상 동일합니다.- 범위 과대:
*claude*는 경로 어디든 이름에claude가 포함되면 전부 무시되어 의도치 않은 자산(예: 서드파티 패키지/에셋 이름에 포함)까지 제외될 수 있습니다.- 크로스플랫폼 대소문자 문제: OS/설정별 대소문자 민감도 차이를 안전하게 커버하려면 대괄호 패턴으로 정규화하는 편이 좋습니다.
- 주석 언어 일관성: 파일 전반의 주석이 한국어 위주인 만큼 이 블록도 한국어로 통일하는 것이 가독성에 유리합니다.
권장 리팩터(루트 앵커링, 대소문자 커버, 중복 제거, 과도한 캐치올 제거):
-# Claude AI related files -CLAUDE.md -claude.md -*CLAUDE.md -*claude.md -.claude/ -claude-* -*claude* +# Claude AI 관련 산출물(문서/캐시/실행 로그) +/.claude/ +/[cC][lL][aA][uU][dD][eE].md +/claude-* +/CLAUDE-* +# 필요 시 특정 문서를 보존하려면(예: 루트가 아닌 docs/ 경로) 아래 예외를 추가하세요. +# !/docs/[cC][lL][aA][uU][dD][eE].md +# 주의: 의도치 않은 자산 누락을 막기 위해 광범위한 '*claude*' 규칙은 사용하지 않습니다.비고:
/로 루트 고정하여 다른 디렉터리의 비의도치 매치를 줄였습니다.[cC][lL][aA][uU][dD][eE].md로 대소문자 변종을 한 줄로 처리했습니다.- 캐치올
*claude*는 제거했습니다. 실제 필요 시, 더 구체적인 확장자/접두사(예:/claude-*.json,/claude-*.md)만 허용적으로 추가하세요.
56-56: 주석 언어 일관성파일 전반이 한국어 주석을 사용하고 있으므로 이 블록의 제목도 한국어로 맞추는 것이 좋습니다. 위 리팩터 diff에 포함되어 있습니다.
Assets/Domain/Character/Script/Component/CameraResolutionScaler.cs (3)
27-30: Start()에서 자동 초기화 비활성화: 외부에서 Initialize() 호출이 보장되는지 확인 필요Start()의
Initialize()호출을 주석 처리하면서, 현재 컴포넌트는 외부에서 명시적으로Initialize()를 호출하지 않으면 아무 동작도 하지 않습니다. 인스펙터에 노출된applyOnStart값도 사실상 무력화됩니다. 의도라면 좋습니다만, 씬/프리팹 단에서 이 컴포넌트를 붙여만 두고 쓴 곳이 있었다면 런타임 동작이 바뀌어 회귀가 발생할 수 있습니다. 최소한 마이그레이션 기간 동안 경고 로그를 1회 표시해 혼선을 줄이는 것을 권장합니다.아래처럼 Start() 내에 안내 로그를 추가하면, 디버그 모드에서만 1회 경고가 떠 사용자가 쉽게 원인을 파악할 수 있습니다.
- // Initialize(); + // 자동 초기화는 PR #8에서 비활성화되었습니다(수동 호출 필요). + if (applyOnStart && showDebugInfo) + { + Debug.LogWarning("[CameraResolutionScaler] Start() 자동 초기화가 비활성화되었습니다. 외부에서 Initialize()를 호출하세요."); + }추가 제안(옵션):
- 에디터 편의용으로 [ContextMenu("Initialize")] 메서드를 추가하면, 프리팹/씬에서 수동 초기화를 쉽게 호출할 수 있습니다.
- 필드 혼선을 줄이기 위해
applyOnStart에[Obsolete("PR #8 이후 사용되지 않습니다. Initialize()를 수동 호출하세요.", true/false)]또는[Tooltip]으로 명시를 고려해 주세요.
32-38: Update()에서 자동 스케일 해제: 미사용 필드(applyOnResolutionChange) 혼선 가능성프레임 단위 해상도 감지/자동 스케일 블록이 비활성화되면서
applyOnResolutionChange가 더 이상 동작하지 않습니다. 의도된 변경이라면, 필드의 의미와 실제 동작의 괴리를 줄이기 위해 1회성 경고 로그를 두거나 필드를 폐기/Deprecated 처리하는 편이 안전합니다.경고 1회 표시 예시(필요 시 전용 플래그를 추가해야 합니다):
- // if (applyOnResolutionChange && HasResolutionChanged()) - // { - // ApplyCameraScale(); - // } + // 자동 런타임 스케일은 PR #8에서 비활성화되었습니다. + if (applyOnResolutionChange && showDebugInfo && !_warnedUpdateAutoScaleDisabled) + { + Debug.LogWarning("[CameraResolutionScaler] Update() 자동 스케일이 비활성화되었습니다. 외부 트리거나 매니저에서 ApplyCameraScale()을 호출하세요. (이 메시지는 1회만 표시)"); + _warnedUpdateAutoScaleDisabled = true; + }위 diff를 적용하려면 클래스 필드에 다음을 추가해야 합니다(추가 코드):
private bool _warnedUpdateAutoScaleDisabled;장점:
- 런타임 CPU 사용량 감소(폴링 제거).
- 다만 외부 트리거(예: ResolutionManager, UI CanvasScaler 이벤트, 윈도우 리사이즈 훅 등)에서
ApplyCameraScale()호출이 반드시 보장되어야 합니다.
181-196: 해상도 비율 계산 시 0으로 나누기(안전성) 및 불필요 변수 정리
CalculateResolutionRatio()에서baseResolution.y또는baseResolution.x가 0이면 분모 0 위험이 있습니다(인스펙터 오입력 가능성). 또한baseAspect는 계산 후 사용되지 않습니다. 간단한 가드와 불필요 변수 제거를 제안합니다.- var currentAspect = resolution.x / resolution.y; - var baseAspect = baseResolution.x / baseResolution.y; + var currentAspect = resolution.y != 0 ? (resolution.x / resolution.y) : 0f; + if (baseResolution.x <= 0f || baseResolution.y <= 0f) + { + Debug.LogWarning("[CameraResolutionScaler] baseResolution이 0 이하입니다. 1.0f 스케일을 반환합니다."); + return 1.0f; + } // 세로 모드인 경우 높이 기준, 가로 모드인 경우 너비 기준 if (currentAspect < 1.0f) // 세로 모드 { return resolution.y / baseResolution.y; } else // 가로 모드 { return resolution.x / baseResolution.x; }Assets/Domain/Character/Script/Component/ResolutionManager.cs (2)
27-30: Start()에서 자동 초기화 주석 처리: 매니저 생명주기와 초기 스케일 적용 보장 확인 필요
Initialize()를 Start()에서 호출하지 않으면서, 싱글톤 인스턴스 생성만으로는 초기화가 보장되지 않습니다. 그 결과ApplyScaleToAllModels()가 외부에서 호출되지 않으면 어떤 모델에도 스케일이 적용되지 않을 수 있습니다. 실제로ApplyScaleToAllModels()는scaleConfig == null이면 조용히 반환하므로, 이전에 자동으로 로드되던ResolutionModelScaleConfig가 더 이상 로드되지 않는 점도 주의가 필요합니다.마이그레이션 완화를 위해 Start()에 1회성 안내 로그를 추가하는 것을 권장합니다.
- // Initialize(); + // 자동 초기화는 PR #8에서 비활성화되었습니다(수동 호출 필요). + if (showDebugInfo) + { + Debug.LogWarning("[ResolutionManager] Start() 자동 Initialize()가 비활성화되었습니다. 외부 부팅 시퀀스에서 Initialize() 호출을 보장하세요."); + }대안(옵션):
- 싱글톤 생성 시점(Instance getter)에서 “자동 초기화 비활성화” 플래그를 검사해 조건부로
Initialize()를 수행하는 하이브리드 전략을 고려할 수 있습니다(기본값: 수동).
32-38: Update() 자동 감지 비활성화: 폴링 제거는 바람직하나, 외부 트리거 설계가 필요해상도 변경 폴링을 중단한 것은 성능 면에서 이득입니다. 다만
applyOnResolutionChange는 더 이상 의미를 가지지 않으므로(주석 처리), 필드 정리 또는 Deprecated 표시가 필요합니다. 또한 외부 트리거(예: 윈도우 리사이즈 이벤트 래퍼, UI 루트에서의 SafeArea 변경 통지, 사용자 설정 변경 이벤트 등)에서ApplyScaleToAllModels()를 호출하는 경로가 실제로 연결되어야 합니다.혼선을 줄이기 위한 1회성 경고 예시:
- // if (applyOnResolutionChange && HasResolutionChanged()) - // { - // ApplyScaleToAllModels(); - // } + // 자동 런타임 스케일은 PR #8에서 비활성화되었습니다. + if (applyOnResolutionChange && showDebugInfo && !_warnedUpdateAutoScaleDisabled) + { + Debug.LogWarning("[ResolutionManager] Update() 자동 스케일이 비활성화되었습니다. 외부 이벤트에서 ApplyScaleToAllModels()를 호출하세요. (이 메시지는 1회만 표시)"); + _warnedUpdateAutoScaleDisabled = true; + }필요한 추가 필드(추가 코드):
private bool _warnedUpdateAutoScaleDisabled;추가로, 외부 이벤트에서 대량 모델에 스케일을 적용할 때 GC/할당을 줄이기 위해
FindAllModelScalers()재탐색 빈도를 조절하거나(이미autoDetectScalers가 이를 제어) 변경 알림 기반 등록/해제(현재 제공되는 Register/Unregister)를 적극 활용하는 것을 권장합니다.Assets/Core/DebugConsole/GameDebugConsoleManager.cs (4)
55-60: Start()에서의 진단 호출은 에디터/디버그 빌드로 한정하세요런타임 초기에 검증 로그가 출력되는 건 유용하지만, 프로덕션 빌드에서 불필요한 로그 소음이 생길 수 있습니다. 에디터/디버그 빌드에서만 실행되도록 가드하는 것을 권장합니다.
적용 diff:
- ValidateButtonSetup(); + #if UNITY_EDITOR || DEVELOPMENT_BUILD + ValidateButtonSetup(); + #endif
119-149: SetupUI의 상세 로그는 좋지만, 릴리스에서 소음이 되지 않도록 가드하세요진단 로그 자체는 유용합니다. 다만 릴리스 빌드에서는 불필요한 로그가 늘어날 수 있으므로 컴파일 가드를 권장합니다.
적용 diff(해당 로그만 에디터/디버그에서 출력):
- Debug.Log("[DEBUG_CONSOLE] SetupUI called"); + #if UNITY_EDITOR || DEVELOPMENT_BUILD + Debug.Log("[DEBUG_CONSOLE] SetupUI called"); + #endif @@ - Debug.Log("[DEBUG_CONSOLE] Clear button listener added"); + #if UNITY_EDITOR || DEVELOPMENT_BUILD + Debug.Log("[DEBUG_CONSOLE] Clear button listener added"); + #endif @@ - Debug.LogWarning("[DEBUG_CONSOLE] _clearButton is null!"); + #if UNITY_EDITOR || DEVELOPMENT_BUILD + Debug.LogWarning("[DEBUG_CONSOLE] _clearButton is null!"); + #endif @@ - Debug.Log("[DEBUG_CONSOLE] Toggle button listener added"); + #if UNITY_EDITOR || DEVELOPMENT_BUILD + Debug.Log("[DEBUG_CONSOLE] Toggle button listener added"); + #endif @@ - Debug.LogWarning("[DEBUG_CONSOLE] _toggleButton is null!"); + #if UNITY_EDITOR || DEVELOPMENT_BUILD + Debug.LogWarning("[DEBUG_CONSOLE] _toggleButton is null!"); + #endif @@ - Debug.Log("[DEBUG_CONSOLE] Filter input listener added"); + #if UNITY_EDITOR || DEVELOPMENT_BUILD + Debug.Log("[DEBUG_CONSOLE] Filter input listener added"); + #endif @@ - Debug.LogWarning("[DEBUG_CONSOLE] _filterInput is null!"); + #if UNITY_EDITOR || DEVELOPMENT_BUILD + Debug.LogWarning("[DEBUG_CONSOLE] _filterInput is null!"); + #endif
362-374: _consolePanel이 null일 때 내부 상태만 뒤집히는 불일치가 생깁니다현재는 패널이 null이어도
_isConsoleVisible만 토글됩니다. 이 경우 UI는 없는데 “표시 중”으로 간주되어, 로그 수집/표시 정책이 뒤틀릴 수 있습니다. 패널 미할당 시엔 토글 자체를 중단하는 편이 일관됩니다.적용 diff:
- Debug.Log($"[DEBUG_CONSOLE] ToggleConsole called. Current state: {_isConsoleVisible}"); - - _isConsoleVisible = !_isConsoleVisible; - - if (_consolePanel != null) - { - _consolePanel.SetActive(_isConsoleVisible); - Debug.Log($"[DEBUG_CONSOLE] Console panel set to: {_isConsoleVisible}"); - } - else - { - Debug.LogWarning("[DEBUG_CONSOLE] _consolePanel is null!"); - } + Debug.Log($"[DEBUG_CONSOLE] ToggleConsole called. Current state: {_isConsoleVisible}"); + + if (_consolePanel == null) + { + Debug.LogWarning("[DEBUG_CONSOLE] _consolePanel is null! Toggle aborted."); + return; + } + + _isConsoleVisible = !_isConsoleVisible; + _consolePanel.SetActive(_isConsoleVisible); + Debug.Log($"[DEBUG_CONSOLE] Console panel set to: {_isConsoleVisible}");추가 참고(별도 PR 권장): 풀링을 사용하지 않는 경우
DestroyImmediate(Line 337)는 런타임에서 비권장입니다.Destroy로 전환하는 리팩터를 고려해 주세요.
545-573: ValidateButtonSetup: 영속 이벤트 수와 런타임 리스너 혼동 방지, 에디터/디버그 전용, 조기 리턴 제거
GetPersistentEventCount()는 인스펙터에 바인딩된 영속 이벤트만 카운트합니다. 런타임에AddListener로 추가한 리스너는 집계되지 않아 오해 소지가 있습니다. 로그 문구를 명확히 해주세요.- 프로덕션 빌드에서는 불필요하므로 에디터/디버그 전용으로 가드하는 걸 권장합니다.
_toggleButton이 null이어도_consolePanel검증까지 계속 진행하도록 조기 리턴을 제거하면 진단에 유리합니다.적용 diff:
- public void ValidateButtonSetup() - { - Debug.Log("[DEBUG_CONSOLE] === Button Setup Validation ==="); - - if (_toggleButton == null) - { - Debug.LogError("[DEBUG_CONSOLE] _toggleButton is null! Please assign it in the inspector."); - return; - } - - Debug.Log($"[DEBUG_CONSOLE] Toggle button found: {_toggleButton.name}"); - Debug.Log($"[DEBUG_CONSOLE] Toggle button active: {_toggleButton.gameObject.activeInHierarchy}"); - Debug.Log($"[DEBUG_CONSOLE] Toggle button interactable: {_toggleButton.interactable}"); - Debug.Log($"[DEBUG_CONSOLE] Toggle button onClick event count: {_toggleButton.onClick.GetPersistentEventCount()}"); - - if (_consolePanel == null) - { - Debug.LogError("[DEBUG_CONSOLE] _consolePanel is null! Please assign it in the inspector."); - } - else - { - Debug.Log($"[DEBUG_CONSOLE] Console panel found: {_consolePanel.name}"); - Debug.Log($"[DEBUG_CONSOLE] Console panel active: {_consolePanel.activeInHierarchy}"); - } - - Debug.Log($"[DEBUG_CONSOLE] Console visible state: {_isConsoleVisible}"); - Debug.Log("[DEBUG_CONSOLE] === End Validation ==="); - } + #if UNITY_EDITOR || DEVELOPMENT_BUILD + [ContextMenu("Validate Button Setup")] + public void ValidateButtonSetup() + { + Debug.Log("[DEBUG_CONSOLE] === Button Setup Validation ==="); + + if (_toggleButton == null) + { + Debug.LogError("[DEBUG_CONSOLE] _toggleButton is null! Please assign it in the inspector."); + } + else + { + Debug.Log($"[DEBUG_CONSOLE] Toggle button found: {_toggleButton.name}"); + Debug.Log($"[DEBUG_CONSOLE] Toggle button active: {_toggleButton.gameObject.activeInHierarchy}"); + Debug.Log($"[DEBUG_CONSOLE] Toggle button interactable: {_toggleButton.interactable}"); + var persistentCount = _toggleButton.onClick.GetPersistentEventCount(); + Debug.Log($"[DEBUG_CONSOLE] Toggle button onClick persistent event count: {persistentCount} (runtime listeners는 포함되지 않음)"); + } + + if (_consolePanel == null) + { + Debug.LogError("[DEBUG_CONSOLE] _consolePanel is null! Please assign it in the inspector."); + } + else + { + Debug.Log($"[DEBUG_CONSOLE] Console panel found: {_consolePanel.name}"); + Debug.Log($"[DEBUG_CONSOLE] Console panel active: {_consolePanel.activeInHierarchy}"); + } + + Debug.Log($"[DEBUG_CONSOLE] Console visible state: {_isConsoleVisible}"); + Debug.Log("[DEBUG_CONSOLE] === End Validation ==="); + } + #endifAssets/Infrastructure/Auth/Examples/AuthManagerExample.cs.meta (1)
1-2: .cs.meta 파일에 MonoImporter 섹션 추가 필요
Unity 에디터가 C# 스크립트(.cs)에 대해 자동으로 MonoImporter 설정을 관리하므로, 해당 섹션이 없으면 에디터가 재작성하며 불필요한 변경(diff)과 충돌이 발생할 수 있습니다. 아래 경로의 모든.cs.meta파일에 MonoImporter 섹션을 추가해주세요.
- Assets/Infrastructure/Auth//*.cs.meta
• AuthManager.cs.meta
• Examples/AuthManagerExample.cs.meta
• Examples/GuestLoginExample.cs.meta
• Examples/ServerOAuth2Example.cs.meta
• JwtTokenParser.cs.meta
• Models/AccessToken.cs.meta, Models/RefreshToken.cs.meta, Models/TokenSet.cs.meta
• OAuth2//.cs.meta 일체
• Services/GuestAuthService.cs.meta
• TokenManager.cs.meta, TokenRefreshService.cs.meta
• Utils/DeviceIdProvider.cs.meta적용 스니펫 (
fileFormatVersion과guid바로 다음 줄에 삽입):fileFormatVersion: 2 guid: 9f606a2fb3216f042a46e97bfca2f735 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant:추가 후, 아래 명령어로 누락된 파일이 없는지 확인할 수 있습니다:
rg -nP --glob 'Assets/Infrastructure/Auth/**/*.cs.meta' '^\s*MonoImporter:' -LAssets/Infrastructure/Auth/TokenManager.cs.meta (1)
1-2: TokenManager 스크립트 .meta도 MonoImporter 블록 추가 권장이 파일도 C# 스크립트용 표준 .meta 포맷(MonoImporter 섹션)이 비어 있습니다. 에디터 최초 임포트 시 자동 보정으로 불필요한 diff가 생길 수 있습니다.
아래와 같이 보완을 제안합니다.
fileFormatVersion: 2 guid: af60fb15aa1dc0947aae32b10c6583e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant:추가로, 해당 guid가 이미 프로젝트 내 다른 자산과 중복되지 않는지 위의 검증 스크립트로 확인 부탁드립니다.
Assets/Infrastructure/Auth/Examples/ServerOAuth2Example.cs.meta (1)
1-2: 예제 스크립트 .meta 최소 필드만 존재 — 표준화 필요예제 코드도 팀/머신 간 일관성을 위해 MonoImporter 섹션을 포함시키는 편이 안전합니다.
제안 패치:
fileFormatVersion: 2 guid: 59d58c6b995b5494abd828406d93edee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant:변경 후 Unity에서 임포트했을 때 .meta가 재작성되지 않는지(추가 diff 없음)까지 확인해 주세요.
Assets/Infrastructure/Auth/OAuth2/Models/PKCEParameters.cs.meta (1)
1-2: PKCEParameters 모델 .meta 정합성모델 .cs의 .meta 역시 MonoImporter가 누락되어 있습니다. 향후 임포트 시 자동 보정으로 인한 잡음을 예방하기 위해 표준 블록을 추가해 주세요.
권장 수정:
fileFormatVersion: 2 guid: 297b4b48ad8a852469f2dff27f6e0f14 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant:Assets/Infrastructure/Auth/OAuth2/Handlers/MobileCallbackHandler.cs.meta (1)
1-2: 모바일 콜백 핸들러 .meta — MonoImporter 추가 및 EOF 개행 필요 (검증 완료)검증 결과: Assets/Infrastructure/Auth/OAuth2/Handlers/MobileCallbackHandler.cs.meta에 MonoImporter 섹션이 없고 파일 끝에 개행(LF, 0x0A)이 없습니다. 리포지토리 내 다수의 .meta 파일에서도 EOF 개행 누락이 확인되었습니다.
조치 권장:
- Assets/Infrastructure/Auth/OAuth2/Handlers/MobileCallbackHandler.cs.meta — MonoImporter 추가 및 파일 끝에 개행 추가
권장 패치:
fileFormatVersion: 2 guid: ca02408ec03f4064aa227b1c43b8b99d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant:(패치 적용 후 파일 끝에 newline(LF) 한 줄이 반드시 있어야 합니다.)
추가 권장: 스캔으로 확인된 다른 .meta 파일들에 대해서도 EOF 개행을 일괄 보정하세요(이미 실행한 스크립트 출력 참조).
Assets/Infrastructure/Auth/TokenRefreshService.cs.meta (1)
1-2: 토큰 리프레시 서비스 .meta 파일 개행 추가 권장검증 결과:
- Assets/Infrastructure/Auth/TokenRefreshService.cs.meta 파일의 마지막에 개행(뉴라인)이 빠져 있습니다.
→ 일부 도구에서 “No newline at end of file” 경고가 발생할 수 있으므로 파일 끝에 빈 줄 하나를 추가해주세요.- MonoImporter 블록이 없는 것은 Unity .meta의 최소 포맷으로 의도된 사항이므로 문제가 없습니다.
적용 예시:
file: Assets/Infrastructure/Auth/TokenRefreshService.cs.meta guid: 19627176d6dfc384a8a5a4f76bdda34f +Assets/Infrastructure/Network/Configs/NetworkConfig.cs (6)
109-123: 프로덕션에서 HTTPS 강제 및 스킴 자동판별 필요현재 항상
http://로 구성됩니다. 토큰 기반 인증 도입 이후 프로덕션에서는https가 필수에 가깝습니다. 또한 서버 문자열이 이미 스킴을 포함할 경우 중복을 피해야 합니다.아래처럼 환경에 따라 스킴을 선택하고, 스킴이 이미 포함된 경우 그대로 사용하도록 제안합니다.
@@ - return $"http://{server}"; + var scheme = CurrentEnvironment == EnvironmentType.Production ? "https" : "http"; + return server.StartsWith("http", System.StringComparison.OrdinalIgnoreCase) + ? server + : $"{scheme}://{server}";추가로, 프로덕션에서 자체 서명 인증서 등을 사용할 경우 HTTP 클라이언트 설정과의 호환성(인증서 검증)도 함께 확인이 필요합니다.
125-139: WebSocket도 wss 지원 및 스킴 자동판별 필요현재
ws://고정입니다. 프로덕션에서는wss사용이 권장됩니다. 또한 스킴이 포함된 주소는 그대로 사용해야 합니다.두 메서드 모두 동일 패턴으로 개선 제안:
@@ - return $"ws://{server}"; + var scheme = CurrentEnvironment == EnvironmentType.Production ? "wss" : "ws"; + return server.StartsWith("ws", System.StringComparison.OrdinalIgnoreCase) + ? server + : $"{scheme}://{server}";동일 로직이 중복되므로 내부 헬퍼(
GetServerByEnv,ComposeUrl(server, schemePrefix))로 중복 제거도 고려해 주세요.Also applies to: 141-152
166-170: WebSocket 버전 경로 하드코딩(API Path 무시) — 설정 불일치
GetWebSocketUrlWithVersion에서"api"문자열이 하드코딩되어ApiPath설정과 불일치합니다. 설정값을 사용하도록 수정해 주세요.@@ - return $"{baseUrl.TrimEnd('/')}/api/{Instance.apiVersion.TrimStart('/').TrimEnd('/')}/{Instance.wsPath.TrimStart('/').TrimEnd('/')}"; + return $"{baseUrl.TrimEnd('/')}/{Instance.apiPath.Trim('/')}/{Instance.apiVersion.Trim('/')}/{Instance.wsPath.Trim('/')}";
203-205: 문자열 비교 시 문화권 영향 제거 및 할당 최소화
ToLower()할당 없이 대/소문자 무시 비교로 교체해 주세요.@@ - public static bool IsJsonMessageType => Instance.wsMessageType?.ToLower() == "json"; - public static bool IsBinaryMessageType => Instance.wsMessageType?.ToLower() == "binary"; + public static bool IsJsonMessageType => + string.Equals(Instance.wsMessageType, "json", System.StringComparison.OrdinalIgnoreCase); + public static bool IsBinaryMessageType => + string.Equals(Instance.wsMessageType, "binary", System.StringComparison.OrdinalIgnoreCase);
221-254: 기본 인스턴스 생성 경로에서도 런타임 가드 적용 누락
CreateDefaultInstance()경로에서는 여전히ApplyRuntimeGuard가 호출되지 않습니다. 일관성 확보를 위해 반환 전 적용을 권장합니다.@@ Debug.LogWarning("기본 NetworkConfig를 생성했습니다. Resources 폴더에 NetworkConfig.asset 파일을 생성하는 것을 권장합니다."); - return instance; + ApplyRuntimeGuard(instance); + return instance;
17-20: 프로덕션 서버 IP 하드코딩 제거 권고레포 전체를 확인한 결과, 프로덕션 서버 주소(
122.153.130.223:7900)가 아래 위치들에 중복 노출되고 있습니다. 보안 및 유연성 측면에서 코드와 문서 내 하드코딩을 제거하고, 환경별 설정 파일 또는 CI 환경 변수를 통해 관리할 것을 권고드립니다.
- Assets/Infrastructure/Network/Configs/NetworkConfig.cs
• 19번째 줄:[SerializeField] private string productionServer = "122.153.130.223:7900";
• 228번째 줄:instance.productionServer = "122.153.130.223:7900";- Assets/Resources/NetworkConfig.asset
• 18번째 줄:productionServer: 122.153.130.223:7900- Assets/Infrastructure/Network/README.md
• 65번째 줄:NetworkConfig.SetProductionEnvironment(); // 122.153.130.223:7900
• 188번째 줄:- **프로덕션 환경**:122.153.130.223:7900``제안 사항:
- 프로덕션 서버 주소를 ScriptableObject 에셋이나 외부 설정 파일(예: JSON, YAML)로 분리
- 빌드/배포 파이프라인(CI) 또는 런타임 시 환경변수로 주입
- 문서에도 별도 예시가 아니라 참조 가이드 혹은 템플릿으로만 노출
이렇게 분리 관리하면 IP 변경 시 코드 수정 없이 설정만 교체 가능하며, 민감 정보 노출 위험도 낮출 수 있습니다.
Assets/Infrastructure/Auth/OAuth2/Handlers/IOAuth2CallbackHandler.cs (2)
10-11: 수명 주기 명시를 위해 IDisposable 상속 권장핸들러가 소켓/리스너 등 언매니지드 리소스를 가질 수 있어
Cleanup()만으로는 사용 측의using패턴을 유도하기 어렵습니다.IDisposable상속을 추가해 수명 주기를 명확히 하길 권장합니다. 현재 파일 상단에using System;이 있으므로 즉시 적용 가능합니다.@@ - public interface IOAuth2CallbackHandler + public interface IOAuth2CallbackHandler : IDisposable참고: 구현체에서
Cleanup()은Dispose()에서 호출하도록 정리하면 사용성이 좋아집니다.
12-18: 취소/타임아웃 제어를 위한 CancellationToken 지원 검토
InitializeAsync(expectedState, timeoutSeconds)와WaitForCallbackAsync()에CancellationToken이 없어 상위 레이어에서 일관된 취소 제어가 어렵습니다. 플랫폼별 구현에 이미 내부CancellationTokenSource가 있더라도, 퍼블릭 API에 토큰을 노출해 호출자가 제어할 수 있도록 확장하는 것을 제안합니다. 대규모 시그니처 변경이므로 단계적 도입을 권장합니다.문서/코드 전반에서 OAuth2 플로우 취소 시의 사용자 경험(재시도, UI 해제, 포커스 복원)이 정의되어 있는지 확인 부탁드립니다.
Also applies to: 20-24
Assets/Infrastructure/Network/DTOs/Auth/GuestLoginRequest.cs (2)
22-25: 생성자에서 null 검사는 적절하지만 빈 문자열도 차단 고려현재
null만 예외입니다. DTO 수준에서 강하게 보장하려면 빈 문자열/공백도 거부하는 선택지가 있습니다. 단, JSON 역직렬화 호환성을 고려해 유지할 수도 있습니다.가능한 대안:
- 생성자에서는
string.IsNullOrWhiteSpace(guestId)시ArgumentException발생- 또는 현재처럼 기본 생성자 +
IsValid()에 검증 위임(현 구조 유지)
38-44: 마스킹 표기 일관성 개선 (프로젝트 전반과 통일)
GuestAuthService.MaskDeviceId()와DeviceIdProvider의 마스킹 표기(특히 길이<8일 때***)가 이 클래스(****)와 다릅니다. 로그 분석 시 혼선이 생깁니다. 동일 규칙으로 통일을 권장합니다.- var maskedGuestId = string.IsNullOrEmpty(GuestId) ? "null" : - GuestId.Length > 8 ? $"{GuestId.Substring(0, 4)}****{GuestId.Substring(GuestId.Length - 4)}" : "****"; + var maskedGuestId = string.IsNullOrEmpty(GuestId) ? "null" : + GuestId.Length > 8 ? $"{GuestId.Substring(0, 4)}****{GuestId.Substring(GuestId.Length - 4)}" : "***";또는 공용 유틸(예:
MaskingUtils.MaskMiddle(string))로 중복 제거.Assets/Infrastructure/Auth/OAuth2/Handlers/MobileCallbackHandler.cs (3)
1-5: 스레드 안전성:_lastCustomUrl교환 시 Interlocked 사용 권장네이티브 플러그인이 다른 스레드에서 호출할 가능성을 고려하면 원자적 교환이 안전합니다.
CheckCustomSchemeUrl에서Interlocked.Exchange로 값을 읽고 비우세요. 네임스페이스 추가도 필요합니다.using System; using System.Threading.Tasks; using UnityEngine; using Cysharp.Threading.Tasks; +using System.Threading;
80-86: 원자적 URL 소비로 중복 처리 방지현재는 단순 null-할당으로 클리어합니다. 원자적 교환으로 레이스를 방지하세요.
- if (Application.isFocused && !string.IsNullOrEmpty(_lastCustomUrl)) - { - var url = _lastCustomUrl; - _lastCustomUrl = null; // 한 번만 사용 - return url; - } + if (Application.isFocused && !string.IsNullOrEmpty(_lastCustomUrl)) + { + var url = Interlocked.Exchange(ref _lastCustomUrl, null); + return url; + }
23-31: 초기화 시 기준 시각 기록 및 상태 초기화 제안후속 정책(예: 외부 타임아웃 기준)에서 활용하려면 초기화 때 기준 시각을 기록해 두는 것이 유용합니다. 또한
_isDisposed를 false로 재설정하여 재사용 시 안전성을 높일 수 있습니다.public async Task InitializeAsync(string expectedState, float timeoutSeconds) { _expectedState = expectedState; _timeoutSeconds = timeoutSeconds; _isInitialized = true; + _isDisposed = false; + // 기준 시각 기록(필요 시 활용) + // _initAt = DateTime.UtcNow; Debug.Log($"[MobileCallbackHandler] 초기화 완료 - State: {expectedState}, Timeout: {timeoutSeconds}초"); await UniTask.CompletedTask; }Assets/Infrastructure/Auth/Utils/DeviceIdProvider.cs (3)
119-131: WebGL 식별자 생성에서GetHashCode()사용은 비결정적임(.NET의 랜덤 시드 영향)
string.GetHashCode()는 프로세스마다 달라질 수 있어, 동일 입력이라도 런타임마다 결과가 달라집니다. 최초 생성 후 PlayerPrefs에 저장되긴 하지만, 초기 생성값 자체가 비결정적일 필요는 없습니다. 또한userAgent변수에는 실제 UA가 아니라Application.platform.ToString()이 들어가 오해의 소지가 있습니다.안정적 구성요소만 사용하도록 단순화하세요.
- string browserId = SystemInfo.deviceUniqueIdentifier; - - // 추가로 브라우저 정보 포함 - string userAgent = Application.platform.ToString(); - string screenInfo = $"{Screen.width}x{Screen.height}"; - - return $"{browserId}-{userAgent.GetHashCode()}-{screenInfo.GetHashCode()}"; + var browserId = SystemInfo.deviceUniqueIdentifier; + var platform = Application.platform.ToString(); + var screenInfo = $"{Screen.width}x{Screen.height}"; + return $"{browserId}-{platform}-{screenInfo}";향후 실제 User-Agent가 필요하면 WebGL용 JS 플러그인 연동으로 주입하는 방식을 권장합니다.
175-187: 에디터용 ID에 사용자명/머신명이 포함됨 — PII 최소화 고려
editor-{MachineName}-{UserName}는 내부 개발 환경이라도 식별 정보가 로그/스토리지에 남습니다. 에디터 전용이라도 난수 GUID 기반(최초 1회 생성 후 저장)으로 대체하는 것이 더 안전합니다.가능한 대안:
Guid.NewGuid().ToString("N")를 최초 1회 생성하여 PlayerPrefs에 저장- 또는 OS별 안정 ID가 있다면 그것을 우선 사용
189-201: 마스킹 로직의 중복 제거 및 공용화 제안
GuestAuthService등 유사 마스킹 로직이 분산되어 있습니다. 공용 유틸로 노출(예:public static string Mask(string, int show=4))하여 일관성을 보장하세요.Assets/Infrastructure/Network/DTOs/Auth/GuestLoginResponse.cs.meta (1)
1-2: Unity .meta 파일 스키마 검증 완료 — MonoImporter 블록 미포함 확인
- [1] GUID 중복: 없음
- [2]
.cs.meta→ 대응.cs파일: 모두 존재- [3] MonoImporter 블록: 모든
.cs.meta에서 누락현재 모든 스크립트
.meta파일이guid+fileFormatVersion만 포함하는 최소 스키마 형태입니다.
Unity 에디터가.meta를 로드할 때MonoImporter섹션을 자동으로 추가하며, 이로 인해 불필요한 diff가 발생할 수 있습니다.
따라서 아래 사항에 대해 팀 차원의 정책·합의를 권장합니다:
- 최소 스키마(현재 상태) 유지
- 또는 표준
MonoImporter블록을 포함하도록.meta정규화필요 시, 모든
.cs.meta파일에MonoImporter블록을 일괄 삽입하는 스크립트 제공이 가능합니다.Assets/Infrastructure/Auth/OAuth2/Handlers/DesktopCallbackHandler.cs.meta (1)
1-2: 플랫폼 핸들러 스크립트 .meta — 에디터/플랫폼별 임포트 설정 필요 시 MonoImporter로 관리현재 최소 스키마로도 동작하나, 데스크톱 전용 핸들러라면 플랫폼별 아이콘/실행순서/플랫폼 임포트 설정이 필요할 수 있습니다. 향후 설정 변경 계획이 있다면 MonoImporter 블록을 명시적으로 포함해 형상관리하는 방안을 고려해 주세요. 중복 GUID/대응 .cs 존재는 상단 스크립트로 점검 가능합니다.
Assets/Infrastructure/Auth/Services/GuestAuthService.cs.meta (1)
1-2: 스크립트 .meta에 MonoImporter 블록 누락 — Unity가 자동 재생성하더라도 명시적으로 포함하는 편이 안전합니다.현재 두 줄 형식(fileFormatVersion/guid)만 존재합니다. Unity가 가져오면서 기본값을 채우긴 하지만, 팀/CI 환경에 따라 불필요한 diff가 생기거나 실행 순서(executionOrder) 등의 메타가 의도치 않게 초기화될 수 있습니다. 기본 MonoImporter 섹션을 포함하는 것을 권장합니다.
아래 기본 블록을 추가해 안정성을 높여주세요:
fileFormatVersion: 2 guid: e47475a6323a8e94bbf28c623daa3b87 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 0} + userData: + assetBundleName: + assetBundleVariant:Assets/Infrastructure/Auth/Models/AccessToken.cs.meta (1)
1-2: 스크립트 메타 최소 필드만 존재 — 팀/CI 간 불필요한 재생성 방지를 위해 표준 블록 추가 권장AccessToken.cs.meta에도 MonoImporter 섹션이 없습니다. Unity 버전/설정 차이로 자동 보정되며 diff 노이즈가 발생할 수 있습니다. 아래 기본 블록을 포함해 주세요.
fileFormatVersion: 2 guid: 2b8e8d468f9dc2346b7637200a36f19a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 0} + userData: + assetBundleName: + assetBundleVariant:Assets/Infrastructure/Auth/Models/TokenSet.cs.meta (1)
1-2: TokenSet 스크립트 메타 정규화 권장장기간 유지될 핵심 모델이므로 MonoImporter 블록을 명시하여 실행 순서/아이콘 등 추후 편집 시에도 안정적으로 추적되도록 하세요.
fileFormatVersion: 2 guid: fc2ca688c549a0b4f81b25db48667d3e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 0} + userData: + assetBundleName: + assetBundleVariant:Assets/Infrastructure/Auth/OAuth2/Utils/OAuth2CallbackParser.cs.meta (2)
1-2: OAuth 유틸 스크립트 메타 최소화 — 표준 MonoImporter 추가 제안콜백 파서 유틸은 실행 순서 영향은 없지만, 프로젝트 전반의 .meta 일관성을 위해 표준 블록을 포함하는 것을 추천합니다.
fileFormatVersion: 2 guid: f711e0bc0a0086743b170059d5e421c7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 0} + userData: + assetBundleName: + assetBundleVariant:
1-2: 메타 파일 말미 개행(Newline) 규칙 확인일부 에디터/CI에서 파일 말미 개행 유무로 포맷팅 diff가 생길 수 있습니다. 저장소 규칙에 맞춰 통일해 주세요.
Assets/Infrastructure/Auth/Utils/DeviceIdProvider.cs.meta (2)
1-2: DeviceIdProvider 스크립트 메타 — 기본 MonoImporter 블록 포함 권장게스트 인증 경로에서 자주 참조될 수 있는 유틸이므로 메타 정규화를 통해 향후 아이콘/실행 순서 정책 변경 시 안정성 확보를 권장합니다.
fileFormatVersion: 2 guid: 6e444377c5386384481442b3dd4f2105 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 0} + userData: + assetBundleName: + assetBundleVariant:
1-2: 프로젝트 전반 .meta 정책 통일 제안이번 PR에서 추가된 모든 .cs.meta가 동일한 스키마/필드 순서를 따르도록 에디터 설정(Force Text, Visible Meta Files)과 .editorconfig/.gitattributes 조합을 점검해 주세요. 불필요한 메타 diff를 크게 줄일 수 있습니다.
Assets/Infrastructure/Network/Services/ChatApiService.cs (4)
17-18: 엔드포인트 상수의 중앙집중화 제안여러 서비스에서 동일 규약(/api/v1/…)을 반복 선언하면 변경 시 누락 위험이 큽니다. 공용 정적 클래스(예: ApiRoutes.V1.Chat)로 모아두는 방안을 고려해 주세요.
- private const string CHAT_ENDPOINT = "/api/v1/chat"; - private const string DEFAULT_ACTION = "chat"; + // TODO: 공용 라우트 상수로 이전(ApiRoutes.V1.Chat 등) + private const string CHAT_ENDPOINT = "/api/v1/chat"; + private const string DEFAULT_ACTION = "chat";
125-127: 요청 로깅 메서드가 비어 있음 — 최소 안전 로깅 구현 또는 호출 제거 권장운영 트러블슈팅에 도움이 되도록 PII 최소화/마스킹 기준으로 가벼운 구조화 로그를 남기거나, 로깅이 불필요하다면 호출부 제거가 더 낫습니다.
- private void LogRequestDetails(ChatRequest request) - { - } + private void LogRequestDetails(ChatRequest request) + { +#if UNITY_EDITOR || DEVELOPMENT_BUILD + if (request == null) return; + var uid = string.IsNullOrEmpty(request.userId) + ? "(null)" + : $"***{request.userId.Substring(Math.Max(0, request.userId.Length - 4))}"; + Debug.Log($"[ChatApiService] chat request: characterId={request.characterId}, userId={uid}, actor={(request.actor ?? "(null)")}, messageLen={(request.message?.Length ?? 0)}, action={request.action}, requestedAt={request.requestedAt}"); +#endif + }
7-7: 불필요한 using 제거 제안현재 Newtonsoft.Json은 사용되지 않습니다. 로깅에 사용할 계획이 없다면 제거해 빌드 경고를 줄이는 것이 좋습니다.
-using Newtonsoft.Json;
111-116: sessionId 빈 문자열 대신 null 지정해 직렬화 제외ChatRequest DTO에
[JsonProperty]의 Required 설정이 없어 null 값을 전송해도 서버 계약상 문제가 없으며, 빈 문자열("") 전송 시 “값이 있으나 유효하지 않음”으로 오해될 수 있습니다.
먼저 HttpApiClient 내부 JSON 직렬화 설정에서NullValueHandling = Ignore로 null 프로퍼티를 생략하도록 구성돼 있는지 확인해 주세요. 이후 아래와 같이 optional refactor를 적용하시길 권장드립니다.- sessionId = "", // HttpApiClient에서 자동 주입 + // 토큰 기반 인증 시 sessionId 미사용으로 null 지정해 직렬화 제외 + sessionId = null, message = message,Assets/Infrastructure/Network/Services/CharacterApiService.cs (4)
47-56: 입력값 검증 보강 제안: characterId 유효성 체크서버 왕복 전에 즉시 실패시키면 디버깅과 사용자 피드백 품질이 좋아집니다.
public async UniTask<CharacterData> GetCharacterAsync(string characterId, CancellationToken cancellationToken = default) { + if (string.IsNullOrEmpty(characterId)) + { + Debug.LogError("[CharacterApiService] characterId가 비어있습니다."); + return null; + } if (_httpClient == null) { Debug.LogError("[CharacterApiService] HttpApiClient가 null입니다. 초기화를 확인해주세요."); return null; }
103-106: UpdateCharacterAsync: characterId/request 유효성 체크 권장널/빈 값 방어 코드는 버그를 크게 줄입니다.
public async UniTask<CharacterData> UpdateCharacterAsync(string characterId, UpdateCharacterRequest request, CancellationToken cancellationToken = default) { + if (string.IsNullOrEmpty(characterId)) + { + Debug.LogError("[CharacterApiService] characterId가 비어있습니다."); + return null; + } + if (request == null) + { + Debug.LogError("[CharacterApiService] request가 null입니다."); + return null; + } return await _httpClient.PutAsync<CharacterData>($"/api/v1/character/{characterId}", request, cancellationToken: cancellationToken); }
142-147: DeleteCharacterAsync: characterId 유효성 체크 및 에러 로그 보강 제안즉시 실패와 예외 메시지 로그를 추가하면 원인 추적이 쉬워집니다.
public async UniTask<bool> DeleteCharacterAsync(string characterId, CancellationToken cancellationToken = default) { + if (string.IsNullOrEmpty(characterId)) + { + Debug.LogError("[CharacterApiService] characterId가 비어있습니다."); + return false; + } try { - await _httpClient.DeleteAsync<object>($"/api/v1/character/{characterId}", cancellationToken: cancellationToken); + await _httpClient.DeleteAsync<object>($"/api/v1/character/{characterId}", cancellationToken: cancellationToken); return true; } catch (System.Exception ex) { - return false; + Debug.LogError($"[CharacterApiService] 캐릭터 삭제 실패: {ex.Message}"); + return false; } }
64-67: CreateCharacterAsync(request): HttpClient null 및 request null 방어 처리 제안다른 메서드와 일관되게 방어 로직을 두는 것이 안전합니다.
public async UniTask<CharacterData> CreateCharacterAsync(CreateCharacterRequest request, CancellationToken cancellationToken = default) { - return await _httpClient.PostAsync<CharacterData>("/api/v1/character", request, cancellationToken: cancellationToken); + if (_httpClient == null) + { + Debug.LogError("[CharacterApiService] HttpApiClient가 null입니다. 초기화를 확인해주세요."); + return null; + } + if (request == null) + { + Debug.LogError("[CharacterApiService] CreateCharacterRequest가 null입니다."); + return null; + } + return await _httpClient.PostAsync<CharacterData>("/api/v1/character", request, cancellationToken: cancellationToken); }Assets/Infrastructure/Network/Services/STTService.cs (1)
68-68: 언어 파라미터를 하드코딩하지 말고 메서드 인자를 사용하세요.현재
forcedLanguage = "ko"를 사용해 URL에 고정값을 넣고 있어,ConvertSpeechToTextAsync(..., language)인자가 무시됩니다. 또한 미래에ko-KR같은 값이 들어올 수 있으니 URL 인코딩을 권장합니다.아래처럼 라인 68만 수정하면 호출부 호환성을 유지하면서 즉시 개선됩니다:
- string endpoint = $"/api/v1/stt/transcribe?language={forcedLanguage}"; + string endpoint = $"/api/v1/stt/transcribe?language={Uri.EscapeDataString(string.IsNullOrEmpty(language) ? "ko" : language)}";별도로, 이제
forcedLanguage변수가 사용되지 않으니 제거하는 것이 깔끔합니다.Assets/Core/Managers/SystemManager.cs (1)
230-231: 필수 매니저 체크는 적절하나, 실패 시 복구 전략 부재.현재는 즉시
InvalidOperationException을 던집니다. 실사용 환경에서 코어 매니저의 Lazy-Init 지연이나 재시도를 한 번 정도 제공하면 회복력이 올라갑니다. 로딩 UI가 이미 켜지므로 “한 번 재시도 후 실패 시 예외” 패턴을 고려해 보세요.Assets/Infrastructure/Auth/Models/AccessToken.cs (2)
24-27: 음수 입력 방어 로직 추가 권장.
IsExpiringSoon(int minutesBeforeExpiry)에 음수 값이 들어오면 의도치 않은 true/false가 나올 수 있습니다. 최소 0으로 클램프하세요.- public bool IsExpiringSoon(int minutesBeforeExpiry) + public bool IsExpiringSoon(int minutesBeforeExpiry) { - return DateTime.UtcNow.AddMinutes(minutesBeforeExpiry) >= ExpiresAt; + var buffer = Math.Max(0, minutesBeforeExpiry); + return DateTime.UtcNow.AddMinutes(buffer) >= ExpiresAt; }
8-9: JSON 역직렬화 영향 검토 결과
JsonConvert.DeserializeObject<AccessToken>호출 없음.JsonSerializer.Deserialize<AccessToken>호출도 없음.- Unity
JsonUtility.FromJson<AccessToken>호출 역시 발견되지 않음.- 서버 응답은 DTO(
GuestLoginResponse,ServerOAuth2Models등)에서 문자열로 먼저 파싱한 뒤new AccessToken(string)생성자를 통해AccessToken객체를 만듦.위 검토 결과, 외부 JSON 역직렬화 경로가 없으므로 프로퍼티의
set접근자를private으로 변경해도 역직렬화에는 영향이 없습니다.
안전성을 높이기 위해 아래와 같이 수정을 적용하세요:- public string Token { get; set; } - public DateTime ExpiresAt { get; set; } + public string Token { get; private set; } + public DateTime ExpiresAt { get; private set; }Assets/Infrastructure/Auth/OAuth2/Models/PKCEParameters.cs (3)
36-42: 생성자 입력값 유효성 검사 추가.필수 필드에 대해 null/empty 검사를 추가하고, 길이 조건(Verifier 43–128)을 즉시 보장하면 이후
IsValid()의존도를 줄일 수 있습니다.- public PKCEParameters(string codeVerifier, string codeChallenge, string state) + public PKCEParameters(string codeVerifier, string codeChallenge, string state) { - CodeVerifier = codeVerifier; - CodeChallenge = codeChallenge; - State = state; + if (string.IsNullOrEmpty(codeVerifier)) throw new ArgumentNullException(nameof(codeVerifier)); + if (codeVerifier.Length < 43 || codeVerifier.Length > 128) throw new ArgumentOutOfRangeException(nameof(codeVerifier), "PKCE code_verifier length must be 43–128."); + if (string.IsNullOrEmpty(codeChallenge)) throw new ArgumentNullException(nameof(codeChallenge)); + if (string.IsNullOrEmpty(state)) throw new ArgumentNullException(nameof(state)); + CodeVerifier = codeVerifier; + CodeChallenge = codeChallenge; + State = state; CreatedAt = DateTime.UtcNow; }
47-53: PKCE 문자 집합 검증 누락(규격 미준수 가능성).PKCE code_verifier는
ALPHA / DIGIT / "-" / "." / "_" / "~"만 허용됩니다(RFC 7636). 현재는 길이만 검사하고 있어 이후 서버 검증에서 실패할 수 있습니다. 간단한 정규식으로 보강하세요.- return !string.IsNullOrEmpty(CodeVerifier) && + bool IsValidVerifierChars(string s) => System.Text.RegularExpressions.Regex.IsMatch(s, "^[A-Za-z0-9\\-._~]+$"); + return !string.IsNullOrEmpty(CodeVerifier) && !string.IsNullOrEmpty(CodeChallenge) && !string.IsNullOrEmpty(State) && - CodeVerifier.Length >= 43 && CodeVerifier.Length <= 128; + CodeVerifier.Length >= 43 && CodeVerifier.Length <= 128 && + IsValidVerifierChars(CodeVerifier);
58-63: 만료 기준(10분) 하드코딩 — 설정 연동 고려.서버 측 OAuth2 타임아웃과 동기화되지 않으면 디버깅이 어려워집니다.
ServerOAuth2Config.TimeoutSeconds와 연동하거나, 호출부에서 전달받은 값을 우선하도록 가이드하세요.의존성 순환을 피하려면 “기본값 생성을 외부에서” 하는 패턴(예: Provider에서
TimeSpan.FromSeconds(ServerOAuth2Config.Instance.TimeoutSeconds)전달)을 권장합니다.Assets/Infrastructure/Auth/OAuth2/Handlers/OAuth2CallbackHandlerFactory.cs (2)
64-68: 지원 여부 확인 시 인스턴스 생성 부하 최소화 검토.현재
IsPlatformSupported()는 핸들러를 실제 생성합니다. 현재 구현에서 생성자 부하는 거의 없지만, 향후 생성 시 리스너/리소스 확보가 생길 경우를 대비해 “플랫폼 상수 기반 평가”로 대체를 고려할 수 있습니다. 영향도 낮아 선택 사안입니다.
45-58: 플랫폼 전처리기 조건 불일치로 인한 에디터 빌드 혼동 가능성CreateHandler() 메서드에서는 각 플랫폼 분기마다
&& !UNITY_EDITOR조건을 사용하지만, GetCurrentPlatformName() 메서드에는!UNITY_EDITOR가 빠져 있어 에디터에서 실제 플랫폼 이름만 “WebGL” 등으로 리턴되고, 핸들러 생성 시에는 에디터 분기로 빠져 기본 핸들러(데스크톱)로 돌아갑니다. 이로 인해 에디터에서 WebGL 빌드를 테스트할 때 이름은 WebGL인데 핸들러는 Desktop 으로 동작해 혼란이 발생할 수 있습니다.대상 위치
- Assets/Infrastructure/Auth/OAuth2/Handlers/OAuth2CallbackHandlerFactory.cs
- CreateHandler (15–24행)
- GetCurrentPlatformName (43–58행)
제안하는 리팩터링
- 두 메서드의 전처리기 조건을 일치시키기
- 또는 GetCurrentPlatformName에도
&& !UNITY_EDITOR추가- 에디터 환경에서는 항상
"Editor"를 반환하도록 명확히 분기 처리예시 diff
--- a/Assets/Infrastructure/Auth/OAuth2/Handlers/OAuth2CallbackHandlerFactory.cs @@ public static string GetCurrentPlatformName() -#if UNITY_WEBGL +#if UNITY_WEBGL && !UNITY_EDITOR return "WebGL"; @@ -#else - return "Editor"; +#elif UNITY_EDITOR + return "Editor"; +#else + // 그 외 플랫폼 + return "Unknown"; #endifAssets/Infrastructure/Auth/OAuth2/IServerOAuth2Client.cs (3)
11-12: 네이밍 정합성: Provider 구현과 인터페이스 이름이 어긋납니다구현체가
ServerOAuth2Provider인데 인터페이스는IServerOAuth2Client입니다. 팀 내 명명 규칙에 맞춰IServerOAuth2Provider로 정렬하는 편이 탐색성과 가독성에 유리합니다.가능한 최소 수정안:
- public interface IServerOAuth2Client + public interface IServerOAuth2Provider변경 시 구현체/등록 지점도 함께 치환해야 합니다. 원하시면 일괄 치환 스크립트 제공하겠습니다.
1-2: 취소 가능성(CancellationToken) 노출 제안대부분의 메서드가 네트워크/외부 상호작용을 포함하므로 호출자가 타임아웃·사용자 취소를 제어할 수 있도록
CancellationToken을 인자로 노출하는 것을 권장합니다. Unity에서도CancellationToken은 표준적입니다.using System; using System.Threading.Tasks; +using System.Threading; ... - Task<PKCEParameters> GeneratePKCEAsync(); + Task<PKCEParameters> GeneratePKCEAsync(CancellationToken ct = default); ... - Task<string> StartServerOAuth2Async(PKCEParameters pkce); + Task<string> StartServerOAuth2Async(PKCEParameters pkce, CancellationToken ct = default); ... - Task<(bool success, string state)> HandleOAuth2CallbackAsync(string callbackUrl); + Task<(bool success, string state)> HandleOAuth2CallbackAsync(string callbackUrl, CancellationToken ct = default); ... - Task<TokenSet> RequestTokenAsync(string state); + Task<TokenSet> RequestTokenAsync(string state, CancellationToken ct = default); ... - Task<TokenSet> LoginWithServerOAuth2Async(); + Task<TokenSet> LoginWithServerOAuth2Async(CancellationToken ct = default);검증 제안:
- 구현체(
ServerOAuth2Provider) 메서드 시그니처를 동기화하고, 상위 호출부(예: Example, TokenRefreshService)에서 필요 시 토큰 전달 여부를 점검.Also applies to: 21-41
31-36: Tuple 반환 대신 의미 있는 결과형 고려
(bool success, string state)Tuple은 빠르지만 맥락 손실이 있습니다.OAuth2CallbackResult { bool Success; string State; string Error; }같은 명시적 DTO가 디버깅과 확장이 용이합니다.Assets/Infrastructure/Auth/OAuth2/Config/ServerOAuth2Config.cs (5)
49-49: NetworkConfig 접근 일관성 및 NRE 방지
ServerUrl은 정적 프로퍼티를,IsValid()는Instance를 참조합니다. 하나로 통일하고 널 안전하게 접근하는 편이 안전합니다.- public string ServerUrl => ProjectVG.Infrastructure.Network.Configs.NetworkConfig.HttpServerAddress; + public string ServerUrl => ProjectVG.Infrastructure.Network.Configs.NetworkConfig.Instance?.HttpServerAddress;이렇게 하면
IsValid()에서NetworkConfig미초기화 시에도ServerUrl접근으로 인한 NRE를 예방할 수 있습니다.Also applies to: 94-99
149-161: 플랫폼 이름 불일치(Editor → Unknown)현재
UNITY_EDITOR에서"Unknown"을 반환합니다. 다른 곳(예:OAuth2CallbackHandlerFactory.GetCurrentPlatformName)은"Editor"를 사용하므로 통일을 권장합니다.#elif UNITY_STANDALONE_OSX return "macOS"; +#elif UNITY_EDITOR + return "Editor"; #else return "Unknown"; #endif
101-123: 리다이렉트 URI 중복 호출/로깅 최적화
GetCurrentPlatformRedirectUri()를 여러 번 호출하면서 로깅도 반복됩니다. 한 번만 호출해 지역 변수로 재사용하면 로그 정합성과 가독성이 좋아집니다.- var hasRequiredFields = !string.IsNullOrEmpty(GetCurrentPlatformRedirectUri()); + var redirect = GetCurrentPlatformRedirectUri(); + var hasRequiredFields = !string.IsNullOrEmpty(redirect); ... - if (string.IsNullOrEmpty(GetCurrentPlatformRedirectUri())) + if (string.IsNullOrEmpty(redirect)) Debug.LogError($" - redirectUri: 비어있음"); - else if (GetCurrentPlatformRedirectUri().Contains("your-domain")) - Debug.LogError($" - redirectUri: '{GetCurrentPlatformRedirectUri()}' (기본값입니다. 실제 도메인으로 변경하세요)"); + else if (redirect.Contains("your-domain")) + Debug.LogError($" - redirectUri: '{redirect}' (기본값입니다. 실제 도메인으로 변경하세요)"); else - Debug.LogError($" - redirectUri: '{GetCurrentPlatformRedirectUri()}'"); + Debug.LogError($" - redirectUri: '{redirect}'"); ... - Debug.Log($" - 리다이렉트 URI: {GetCurrentPlatformRedirectUri()}"); + Debug.Log($" - 리다이렉트 URI: {redirect}");
171-179: 주석 정밀도: '64바이트' → '64자'PKCE Code Verifier는 길이(문자 수) 조건(43–128자)입니다. 주석의 '바이트' 표기는 혼동 소지가 있습니다.
- instance.pkceCodeVerifierLength = 64; // PKCE 표준에 맞게 64바이트 + instance.pkceCodeVerifierLength = 64; // PKCE 표준 권장 예시(43–128자 범위 내)
55-71: 에디터 리다이렉트 URI 기본값 정책 재검토에디터에서 Windows URI를 강제하는 주석/동작이 있습니다. WebGL 플로우를 에디터에서 시뮬레이션할 때는 WebGL URI가 더 자연스러울 수 있습니다. 팀 워크플로우에 맞춰 선택지를 옵션화(예: Editor 전용 플래그)하는 것을 고려해 보세요.
Assets/Infrastructure/Auth/OAuth2/Handlers/WebGLCallbackHandler.cs (2)
1-5: 성공 플래그 검증 및 파서 중복 제거
- 현재는
"success="존재만 검사합니다. 실제 값이"true"인지 확인하도록 개선하세요.- 파라미터 파싱 로직은 공용 유틸(
OAuth2CallbackParser) 재사용을 권장합니다.using System; using System.Threading.Tasks; using UnityEngine; using Cysharp.Threading.Tasks; +using ProjectVG.Infrastructure.Auth.OAuth2.Utils; ... - if (currentUrl.Contains("success=") && currentUrl.Contains("state=")) + if (currentUrl.Contains("success=") && currentUrl.Contains("state=")) { - // URL에서 state 파라미터 추출 - var stateParam = ExtractParameter(currentUrl, "state"); + // URL에서 파라미터 추출 + var successParam = OAuth2CallbackParser.ExtractParameter(currentUrl, "success"); + var stateParam = OAuth2CallbackParser.ExtractParameter(currentUrl, "state"); - if (!string.IsNullOrEmpty(stateParam) && stateParam == _expectedState) + if (string.Equals(successParam, "true", StringComparison.OrdinalIgnoreCase) && + !string.IsNullOrEmpty(stateParam) && stateParam == _expectedState) { return currentUrl; } }참고: 아래의 로컬
ExtractParameter메서드는 제거하거나, 유틸을 호출하는 단순 래퍼로 변경하세요.Also applies to: 82-92
103-119: 중복 파서 제거(선택) 또는 System.Web 의존성 축소WebGL/IL2CPP 환경에서
System.Web.HttpUtility사용은 빌드 환경에 따라 제약이 있을 수 있습니다. 현 PR에는 동일 용도의 유틸이 이미 존재하므로 본 메서드는 제거를 권장합니다.- /// <summary> - /// URL에서 파라미터 추출 - /// </summary> - private string ExtractParameter(string url, string parameterName) - { - try - { - var uri = new Uri(url); - var query = System.Web.HttpUtility.ParseQueryString(uri.Query); - return query[parameterName]; - } - catch (Exception ex) - { - Debug.LogError($"[WebGLCallbackHandler] 파라미터 추출 중 오류: {ex.Message}"); - return null; - } - } + // 파라미터 파싱은 OAuth2CallbackParser를 사용합니다.확인 필요:
- 현재 빌드 설정에서
System.Web의존이 문제 없는지(특히 WebGL/IL2CPP). 문제가 있다면 유틸 내 로직도HttpUtility비의존 방식으로 교체해야 합니다.Assets/Infrastructure/Auth/Examples/ServerOAuth2Example.cs (2)
88-105: HttpApiClient 초기화 강제 실패 처리 재검토
HttpApiClient는 공개 메서드 시작 시EnsureInitialized()를 수행하도록 설계되어 있습니다. 현재 예제는 초기화가 false면 즉시 실패 처리하는데, 자동 초기화 흐름을 신뢰하거나, 명시적으로Initialize()를 호출하는 방식으로 사용자 경험을 개선할 수 있습니다.
419-424: ‘토큰 삭제’ 버튼은 로그인 여부와 무관하게 활성화하는 편이 실용적테스트/복구를 위해 로그인 상태가 아니어도 토큰 정리 기능을 허용하는 것이 유용합니다.
- if (clearButton != null) - clearButton.interactable = _isLoggedIn; + if (clearButton != null) + clearButton.interactable = true;Assets/Infrastructure/Auth/Examples/GuestLoginExample.cs (1)
175-224: 디버그 UI를 에디터/개발 빌드로 한정
OnGUI기반 디버그 UI는 프로덕션 빌드에서 숨기는 것이 안전합니다. 전처리기를 추가하세요.- private void OnGUI() - { - if (!showDebugUI) return; + private void OnGUI() + { +#if UNITY_EDITOR || DEVELOPMENT_BUILD + if (!showDebugUI) return; GUILayout.BeginArea(new Rect(10, 10, 400, 600)); GUILayout.Label("=== Guest Login Debug UI ===", GUI.skin.box); ... GUILayout.EndArea(); +#else + // no-op in production +#endif - } + }Assets/Infrastructure/Auth/OAuth2/Utils/PKCEGenerator.cs (1)
119-124: 중복 검증 로직 제거 필요
pkce.IsValid()가 이미CodeVerifier길이를 43-128자로 검사하고 있으므로, Line 120-124의 중복된 길이 검사는 불필요합니다.// 만료 검사 if (pkce.IsExpired()) return false; -// Code Verifier 길이 검사 (43-128자) -if (pkce.CodeVerifier.Length < 43 || pkce.CodeVerifier.Length > 128) -{ - Debug.LogError($"[PKCEGenerator] Code Verifier 길이 검사 실패: {pkce.CodeVerifier.Length} (43-128 사이여야 함)"); - return false; -} - // Base64Url 형식 검사Assets/Infrastructure/Network/DTOs/Auth/GuestLoginResponse.cs (1)
65-87: 디버그 로그의 식별자 노출 최소화 제안
GetDebugInfo()에서UserId를 그대로 출력합니다. 운영 환경에서는 사용자 식별자 최소 노출이 안전합니다. 앞/뒤 몇 글자만 출력하도록 마스킹을 권장합니다.Assets/Infrastructure/Auth/OAuth2/Utils/OAuth2CallbackParser.cs (1)
1-12: System.Web(HttpUtility) 의존성: Unity/IL2CPP 호환성 검토 요청Unity/IL2CPP 환경에서
System.Web은 종종 사용할 수 없거나 AOT 이슈를 유발합니다. 가능하면 경량 쿼리 파서(URI + 수동 split +Uri.UnescapeDataString)로 교체하거나, 플랫폼별 전처리로 대체 경로를 두는 것을 권장합니다.원하시면
HttpUtility없이 동작하는 내부ParseQuery헬퍼 추가 패치를 제공하겠습니다.Assets/Infrastructure/Auth/OAuth2/ServerOAuth2Provider.cs (1)
25-33: 사용되지 않는 필드 제거
_currentPKCE는readonly로 선언되었지만 설정·사용되지 않습니다. 정리하여 가독성과 유지보수성을 높이세요.- private readonly PKCEParameters _currentPKCE; ... - _currentPKCE = null;Assets/Infrastructure/Auth/OAuth2/Handlers/DesktopCallbackHandler.cs (2)
21-22: 미사용 필드/자원 정리
CancellationTokenSource를 생성하지만 사용하지 않습니다. 제거하거나,GetContextAsync취소 로직에 실제로 연결하세요. 미사용이면 아래처럼 정리 가능합니다.- private CancellationTokenSource _cancellationTokenSource; ... - _cancellationTokenSource = new CancellationTokenSource(); ... - _cancellationTokenSource?.Cancel();Also applies to: 34-35, 89-90
106-113: 고정 포트(3000) 바인딩은 충돌/권한 문제를 유발할 수 있음
http://localhost:3000/에 고정 바인딩하면 다른 프로세스와 충돌하거나 권한이 필요할 수 있습니다.ServerOAuth2Config의 데스크톱 리다이렉트 URI에서 호스트/포트를 파싱해 동적으로 바인딩하거나, 사용 불가 시 다른 포트로 폴백하는 로직을 권장합니다.Assets/Infrastructure/Auth/TokenManager.cs (1)
294-307: 비동기 처리 개선 필요코루틴에서 UniTask의 Status를 polling하는 것은 비효율적입니다. UniTask.ToCoroutine()을 사용하거나 완전히 async/await로 전환하는 것이 좋습니다.
- private IEnumerator TryRefreshTokenCoroutine() - { - var refreshTask = TokenRefreshService.Instance.RefreshAccessTokenAsync(); - yield return new WaitUntil(() => refreshTask.Status != Cysharp.Threading.Tasks.UniTaskStatus.Pending); - - if (refreshTask.Status == Cysharp.Threading.Tasks.UniTaskStatus.Succeeded) - { - bool success = refreshTask.GetAwaiter().GetResult(); - if (!success) - { - Debug.LogWarning("[TokenManager] 앱 시작 시 AccessToken 자동 복구 실패"); - } - } - } + private IEnumerator TryRefreshTokenCoroutine() + { + yield return TokenRefreshService.Instance.RefreshAccessTokenAsync().ToCoroutine( + onResult: success => + { + if (!success) + { + Debug.LogWarning("[TokenManager] 앱 시작 시 AccessToken 자동 복구 실패"); + } + } + ); + }Assets/Infrastructure/Auth/AuthManager.cs (2)
318-324: 주석 처리된 코드 제거 필요사용하지 않는 주석 코드는 제거하는 것이 좋습니다.
- // 이미 유효한 토큰이 있고 만료가 임박하지 않은 경우 - /* - var currentToken = _tokenManager.GetAccessToken(); - if (IsLoggedIn && currentToken != null && !currentToken.IsExpiringSoon(minutesBeforeExpiry)) - { - return true; - } - */ - // 토큰이 만료되었거나 곧 만료될 경우 갱신 시도
117-151: 초기화 에러 처리 개선 필요
InitializeAsync에서 예외 발생 시_isInitialized가 false로 유지되지만, 이후 메서드들이 재초기화를 시도하지 않습니다.private async UniTaskVoid InitializeAsync() { try { Debug.Log("[AuthManager] 초기화 시작"); // 의존성 초기화 _tokenManager = TokenManager.Instance; _tokenRefreshService = TokenRefreshService.Instance; _guestAuthService = GuestAuthService.Instance; // TokenManager 이벤트 구독 _tokenManager.OnTokensUpdated += HandleTokensUpdated; _tokenManager.OnTokensExpired += HandleTokensExpired; _tokenManager.OnTokensCleared += HandleTokensCleared; // TokenRefreshService 이벤트 구독 _tokenRefreshService.OnTokenRefreshed += HandleTokenRefreshed; _tokenRefreshService.OnTokenRefreshFailed += HandleTokenRefreshFailed; // GuestAuthService 이벤트 구독 _guestAuthService.OnGuestLoginSuccess += HandleGuestLoginSuccess; _guestAuthService.OnGuestLoginFailed += HandleGuestLoginFailed; _isInitialized = true; Debug.Log("[AuthManager] 초기화 완료"); // 앱 시작 시 자동 로그인 시도 await TryAutoLoginAsync(); } catch (Exception ex) { Debug.LogError($"[AuthManager] 초기화 실패: {ex.Message}"); + // 초기화 실패 이벤트 발생 고려 + OnInitializationFailed?.Invoke(ex.Message); } }Assets/Infrastructure/Network/Http/HttpApiClient.cs (2)
73-98: 토큰 관리자 의존성 개선 필요
EnsureAuthToken메서드가TokenManager.Instance에 직접 의존하고 있어 결합도가 높습니다. 의존성 주입 패턴을 고려해보세요.토큰 제공자 인터페이스를 통한 의존성 주입:
public interface ITokenProvider { string GetAccessToken(); } private ITokenProvider _tokenProvider; public void SetTokenProvider(ITokenProvider provider) { _tokenProvider = provider; } private void EnsureAuthToken(bool requiresAuth) { if (!requiresAuth || _tokenProvider == null) return; var accessToken = _tokenProvider.GetAccessToken(); // ... }
203-216: 파일 크기 검증 로직 개선
ValidateFileSize메서드가 모든 byte[] 데이터를 파일로 간주하는데, 이는 정확하지 않을 수 있습니다.private void ValidateFileSize(Dictionary<string, object> formData) { if (!NetworkConfig.EnableFileSizeCheck) return; foreach (var kvp in formData) { - if (kvp.Value is byte[] byteData && byteData.Length > NetworkConfig.MaxFileSize) + // 파일 필드 확인 (예: "file", "image", "document" 등) + bool isFileField = kvp.Key.ToLower().Contains("file") || + kvp.Key.ToLower().Contains("image") || + kvp.Key.ToLower().Contains("document"); + + if (isFileField && kvp.Value is byte[] byteData && byteData.Length > NetworkConfig.MaxFileSize) { var fileSizeMB = byteData.Length / 1024.0 / 1024.0; var maxSizeMB = NetworkConfig.MaxFileSize / 1024.0 / 1024.0; throw new FileSizeExceededException(fileSizeMB, maxSizeMB); } } }
| _consolePanel.SetActive(true); | ||
| _isConsoleVisible = true; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
초기 가시성을 강제로 true로 만드는 변경은 릴리스 UX를 깨뜨릴 수 있습니다
Awake 시점에 콘솔 패널을 무조건 활성화하면(특히 프로덕션) 게임 화면을 가리는 회귀가 발생할 수 있습니다. 개발/디버그 빌드에서는 켜두고, 릴리스에서는 씬의 초기 상태를 존중하도록 분기하는 편이 안전합니다.
적용 diff(개발/디버그=표시, 릴리스=씬 상태 유지):
- _consolePanel.SetActive(true);
- _isConsoleVisible = true;
+ #if UNITY_EDITOR || DEVELOPMENT_BUILD
+ _consolePanel.SetActive(true);
+ _isConsoleVisible = true;
+ #else
+ // 씬/프리팹의 설정을 그대로 따릅니다.
+ _isConsoleVisible = _consolePanel.activeInHierarchy;
+ #endif검증 포인트:
- 의도적으로 “기본 표시”가 요구사항인지 확인 부탁드립니다. 만약 전 빌드에서 기본 비표시였다면 이 변경은 사용자 체감 회귀입니다(특히 모바일/콘솔).
검증을 위해 아래 쉘 스크립트를 실행해 주세요:
🏁 Script executed:
#!/bin/bash
set -e
# GameDebugConsoleManager.cs 파일 경로 확인
file=$(fd GameDebugConsoleManager.cs -t f)
echo "파일 위치: $file"
# Awake 메서드 및 SetActive 호출 부분 확인
echo "----- Awake 메서드 -----"
rg -n "void Awake" -C5 "$file"
echo "----- SetActive 호출 -----"
rg -n "SetActive" -C3 "$file"Length of output: 743
검증을 위해 아래 쉘 스크립트를 실행해 주세요:
🏁 Script executed:
#!/bin/bash
set -e
# .cs 파일 경로만 선택
file=$(fd '^GameDebugConsoleManager\.cs$' -t f | head -n 1)
echo "파일 위치: $file"
# Awake 메서드와 SetActive 호출 위치 출력
echo "----- Awake 메서드 ±5줄 -----"
rg -n -C5 'void Awake' "$file"
echo "----- _consolePanel.SetActive 호출 ±3줄 -----"
rg -n -C3 '_consolePanel\.SetActive' "$file"
echo "----- _isConsoleVisible 설정 ±3줄 -----"
rg -n -C3 '_isConsoleVisible' "$file"Length of output: 3630
릴리스 빌드에서 콘솔 패널 초기 강제 활성화 방지 필요
현재 Assets/Core/DebugConsole/GameDebugConsoleManager.cs에서 Awake/Start 초기화 시
_consolePanel.SetActive(true)와 _isConsoleVisible = true를 모든 빌드에 적용하고 있어,
기본값(false)으로 동작하던 릴리스 환경에서 의도치 않게 콘솔이 항상 화면을 가리는 회귀가 발생할 수 있습니다.
• 파일 위치:
Assets/Core/DebugConsole/GameDebugConsoleManager.cs- 라인 106–108
• 제안된 수정(diff):
- _consolePanel.SetActive(true);
- _isConsoleVisible = true;
+#if UNITY_EDITOR || DEVELOPMENT_BUILD
+ _consolePanel.SetActive(true);
+ _isConsoleVisible = true;
+#else
+ // 씬/프리팹의 초기 설정(visible 상태)을 그대로 따릅니다.
+ _isConsoleVisible = _consolePanel.activeInHierarchy;
+#endif• 검증 포인트:
- “초기 비표시(false)”가 릴리스 UX 요구사항인지 확인
- 이전 릴리스(특히 모바일/콘솔)에서 콘솔이 기본 비표시 상태였는지 검토
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| _consolePanel.SetActive(true); | |
| _isConsoleVisible = true; | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD | |
| _consolePanel.SetActive(true); | |
| _isConsoleVisible = true; | |
| #else | |
| // 씬/프리팹의 초기 설정(visible 상태)을 그대로 따릅니다. | |
| _isConsoleVisible = _consolePanel.activeInHierarchy; | |
| #endif |
🤖 Prompt for AI Agents
In Assets/Core/DebugConsole/GameDebugConsoleManager.cs around lines 106–108 the
code unconditionally calls _consolePanel.SetActive(true) and sets
_isConsoleVisible = true during initialization, causing the debug console to
always appear in release builds; change this so the console is only forced
visible in non-release/debug contexts by wrapping the activation and state
assignment in a build-config check (e.g. #if UNITY_EDITOR || DEVELOPMENT_BUILD
or a serialized/Config debug flag) or by reading a serialized defaultVisible
boolean, leaving release builds to keep the console hidden by default; ensure
the preprocessor/flag is used consistently and add a comment explaining why
release builds should not auto-enable the panel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
개발 단계에서 배포시 첫 로그를 확인하기 위함
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ImGdevel 개발/배포 단계에서 첫 로그 확인의 필요성을 이해했습니다!
다만 프로덕션에서 최종 사용자에게도 콘솔이 기본적으로 보이게 되는 점을 고려하여, 더 유연한 접근 방식을 제안드립니다:
[Header("Deployment Settings")]
[SerializeField] private bool _showOnStartup = true; // Inspector에서 설정 가능
private void InitializeConsole()
{
if (_consolePanel == null)
{
return;
}
// 개발자가 설정한 초기 표시 여부에 따라 결정
_consolePanel.SetActive(_showOnStartup);
_isConsoleVisible = _showOnStartup;
SetupLayoutGroup();
// ...
}이렇게 하면:
- 개발/배포 단계:
_showOnStartup = true로 설정하여 첫 로그 확인 가능 - 프로덕션 릴리스:
_showOnStartup = false로 설정하여 사용자 경험 보호 - 유연성: 상황에 따라 Inspector에서 쉽게 조정 가능
현재 방식도 F12나 터치로 토글할 수 있지만, 초기 표시 여부를 설정 가능하게 하는 것이 더 범용적일 것 같습니다. 어떻게 생각하시나요?
✏️ Learnings added
Learnt from: ImGdevel
PR: ProjectVG/ProjectVG-Client#8
File: Assets/Core/DebugConsole/GameDebugConsoleManager.cs:106-108
Timestamp: 2025-08-27T01:15:29.669Z
Learning: ImGdevel은 개발/배포 단계에서 첫 로그를 확인하기 위해 GameDebugConsoleManager에서 콘솔을 초기에 표시되도록 설정하는 것을 선호한다.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
| } | ||
|
|
||
| [ContextMenu("Test Auto Login Simulation")] | ||
| public async void TestAutoLoginSimulation() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
비동기 메서드 시그니처 개선 필요
async void는 예외 처리가 어렵고 테스트가 불가능합니다. Unity의 컨텍스트 메뉴 제약이 있더라도 더 안전한 패턴을 사용하는 것이 좋습니다.
[ContextMenu("Test Auto Login Simulation")]
-public async void TestAutoLoginSimulation()
+public void TestAutoLoginSimulation()
+{
+ TestAutoLoginSimulationAsync().Forget();
+}
+
+private async UniTaskVoid TestAutoLoginSimulationAsync()
{
try
{📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public async void TestAutoLoginSimulation() | |
| [ContextMenu("Test Auto Login Simulation")] | |
| public void TestAutoLoginSimulation() | |
| { | |
| // Fire-and-forget the async logic safely via UniTask | |
| TestAutoLoginSimulationAsync().Forget(); | |
| } | |
| private async UniTaskVoid TestAutoLoginSimulationAsync() | |
| { | |
| try | |
| { | |
| // ... original TestAutoLoginSimulation implementation goes here ... |
🤖 Prompt for AI Agents
In Assets/Infrastructure/Auth/Examples/AuthManagerExample.cs around line 352,
the method is declared as "public async void TestAutoLoginSimulation()" which
should be converted to an async Task to allow proper exception propagation and
testability; change the signature to "public async Task
TestAutoLoginSimulation()" and add/update call sites accordingly, and if Unity's
ContextMenu requires a void entry point, add a separate non-async wrapper like
"public void TestAutoLoginSimulation_ContextMenu() { _ =
TestAutoLoginSimulation(); }" and import System.Threading.Tasks as needed.
| fileFormatVersion: 2 | ||
| guid: b273f87c4feba7c45b007339de53f770 No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
메타 파일 추가 적절 — GUID 안정성만 확인하면 됩니다
Unity 스크립트 메타(.meta)로서 필수 키(fileFormatVersion: 2, guid)가 정상입니다. 신규 GUID가 중복되지 않고, 해당 .cs 스크립트를 참조하는 프리팹/씬/에셋에서 GUID로 정상 바인딩되는지만 확인해 주세요.
다음 스크립트로 중복 GUID 및 참조 존재 여부를 빠르게 점검할 수 있습니다.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
GUID="b273f87c4feba7c45b007339de53f770"
META_FILE="Assets/Infrastructure/Auth/Examples/GuestLoginExample.cs.meta"
CS_FILE="Assets/Infrastructure/Auth/Examples/GuestLoginExample.cs"
echo "[1] 메타/스크립트 페어 존재 여부"
test -f "$META_FILE" && echo " - OK: $META_FILE 있음" || { echo " - NG: $META_FILE 없음"; exit 1; }
test -f "$CS_FILE" && echo " - OK: $CS_FILE 있음" || { echo " - NG: $CS_FILE 없음"; exit 1; }
echo "[2] GUID 중복 여부"
DUP_COUNT=$(rg -nP '^guid:\s*[0-9a-f]+' -g '!**/Library/**' -S --no-line-number | awk '{print $2}' | sort | uniq -d | wc -l | tr -d ' ')
if [ "$DUP_COUNT" -eq 0 ]; then
echo " - OK: 중복 GUID 없음"
else
echo " - NG: 중복 GUID 존재"; rg -nP '^guid:\s*[0-9a-f]+' -g '!**/Library/**' -S
exit 1
fi
echo "[3] GUID 참조 역검색"
REFS=$(rg -n "$GUID" -g '!**/Library/**' -g '!**/Logs/**' -S || true)
if [ -n "$REFS" ]; then
echo " - 참고: 다음 파일에서 해당 GUID가 참조됩니다"
echo "$REFS"
else
echo " - 참고: 현재 GUID 직접 참조 없음(신규 추가 직후라면 정상일 수 있음)"
fi
echo "검증 완료"Length of output: 98569
다음 스크립트로 해당 GUID가 프로젝트 내에서 정확히 1회만 등장하는지, 그리고 참조 여부를 재검증해 주세요.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
GUID="b273f87c4feba7c45b007339de53f770"
META_FILE="Assets/Infrastructure/Auth/Examples/GuestLoginExample.cs.meta"
CS_FILE="Assets/Infrastructure/Auth/Examples/GuestLoginExample.cs"
echo "[1] 메타/스크립트 페어 존재 여부"
test -f "$META_FILE" && echo " - OK: $META_FILE 있음" || { echo " - NG: $META_FILE 없음"; exit 1; }
test -f "$CS_FILE" && echo " - OK: $CS_FILE 있음" || { echo " - NG: $CS_FILE 없음"; exit 1; }
echo "[2] 특정 GUID 등장 횟수 확인"
OCC=$(rg -g '!**/Library/**' -g '!**/Logs/**' -o "$GUID" | wc -l | tr -d ' ')
if [ "$OCC" -eq 1 ]; then
echo " - OK: GUID 단일 회수 (1회)"
else
echo " - NG: GUID 중복 또는 누락 ($OCC회)"
echo "Occurrences:"; rg -n "$GUID" -g '!**/Library/**'
exit 1
fi
echo "[3] GUID 참조 검색"
REFS=$(rg -n "$GUID" -g '!**/Library/**' -g '!**/Logs/**')
if [ -n "$REFS" ]; then
echo " - 참고: 다음 파일들에서 GUID 참조됨"
echo "$REFS"
else
echo " - 참고: 현재 GUID 직접 참조 없음 (신규 추가 직후라면 정상)"
fi
echo "검증 완료"Length of output: 1249
검증 스크립트 수정 요청: GUID 정의와 참조를 분리해 검증하세요
현재 스크립트는 메타 파일 정의(.meta)와 씬/에셋 참조를 합산해 등장 횟수가 1이 아니면 NG 처리합니다. 그러나 정상적인 흐름에서는
.meta내 GUID 정의는 딱 1회- 씬/프리팹/스크립트 참조는 1회 이상
존재해야 하므로, 두 가지를 분리해 검증하도록 스크립트를 수정해야 합니다.
• 검증 로직 수정 요약
.meta파일 중 해당 GUID 정의 라인(^guid:)이 정확히 1회인지 확인.meta파일이 아닌 나머지 에셋(씬, 프리팹, 스크립트 등)에 GUID가 1회 이상 참조되는지 확인
• 예시 수정안
- OCC=$(rg -g '!**/Library/**' -g '!**/Logs/**' -o "$GUID" | wc -l)
- if [ "$OCC" -eq 1 ]; then
- echo " - OK: GUID 단일 회수 (1회)"
- else
- echo " - NG: GUID 중복 또는 누락 ($OCC회)"
- ...
- fi
+ # 1) .meta 정의 카운트
+ DEF_COUNT=$(rg -g '*.meta' -g '!**/Library/**' -o "^guid:\s*$GUID" | wc -l | tr -d ' ')
+ if [ "$DEF_COUNT" -eq 1 ]; then
+ echo " - OK: .meta 내 GUID 정의 단일 (1회)"
+ else
+ echo " - NG: .meta 정의 중복 또는 누락 (정의 $DEF_COUNT회)"
+ rg -n "^guid:\s*$GUID" -g '*.meta' -g '!**/Library/**'
+ exit 1
+ fi
+
+ # 2) 참조 여부 확인
+ REF_COUNT=$(rg -g '!**/Library/**' -g '!*.meta' -o "$GUID" | wc -l | tr -d ' ')
+ if [ "$REF_COUNT" -ge 1 ]; then
+ echo " - OK: 에셋 참조 존재 ($REF_COUNT회)"
+ rg -n "$GUID" -g '!**/Library/**' -g '!*.meta'
+ else
+ echo " - NG: 에셋/씬 내 GUID 참조 없음"
+ exit 1
+ fi위처럼 정의(definition)와 참조(reference)를 분리해 각각의 기대값(1회 정의, 1회 이상 참조)을 검증하도록 스크립트를 수정해 주세요.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fileFormatVersion: 2 | |
| guid: b273f87c4feba7c45b007339de53f770 | |
| # 1) .meta 정의 카운트 | |
| DEF_COUNT=$(rg -g '*.meta' -g '!**/Library/**' -o "^guid:\s*$GUID" | wc -l | tr -d ' ') | |
| if [ "$DEF_COUNT" -eq 1 ]; then | |
| echo " - OK: .meta 내 GUID 정의 단일 (1회)" | |
| else | |
| echo " - NG: .meta 정의 중복 또는 누락 (정의 $DEF_COUNT회)" | |
| rg -n "^guid:\s*$GUID" -g '*.meta' -g '!**/Library/**' | |
| exit 1 | |
| fi | |
| # 2) 참조 여부 확인 | |
| REF_COUNT=$(rg -g '!**/Library/**' -g '!*.meta' -o "$GUID" | wc -l | tr -d ' ') | |
| if [ "$REF_COUNT" -ge 1 ]; then | |
| echo " - OK: 에셋 참조 존재 ($REF_COUNT회)" | |
| rg -n "$GUID" -g '!**/Library/**' -g '!*.meta' | |
| else | |
| echo " - NG: 에셋/씬 내 GUID 참조 없음" | |
| exit 1 | |
| fi |
🤖 Prompt for AI Agents
In Assets/Infrastructure/Auth/Examples/GuestLoginExample.cs.meta around lines 1
to 2, the validation script currently aggregates .meta GUID definitions and
asset references and fails when total occurrences != 1; instead, change the
logic to treat definition and references separately: ensure the .meta file
contains exactly one line matching the GUID definition pattern (e.g., '^guid:')
and separately scan all other asset files (scenes, prefabs, scripts, etc.) to
assert the same GUID appears at least once; update the checks to report a
failure if the .meta definition count != 1 or if the reference count < 1,
keeping error messages specific to definition vs reference failures.
| private async UniTaskVoid StartAutoTokenRefresh() | ||
| { | ||
| while (_isLoggedIn && _currentTokenSet?.HasRefreshToken() == true) | ||
| { | ||
| try | ||
| { | ||
| // 토큰 갱신이 필요한지 확인 | ||
| if (_currentTokenSet.NeedsRefresh()) | ||
| { | ||
| Debug.Log("[ServerOAuth2Example] 토큰 갱신 시작"); | ||
|
|
||
| // 현재는 전체 재로그인 플로우 실행 | ||
| var newTokenSet = await _oauth2Provider.LoginWithServerOAuth2Async(); | ||
|
|
||
| if (newTokenSet?.HasRefreshToken() == true) | ||
| { | ||
| _currentTokenSet = newTokenSet; | ||
| Debug.Log("[ServerOAuth2Example] 토큰 갱신 성공"); | ||
| DisplayTokenInfo(); | ||
| } | ||
| else | ||
| { | ||
| Debug.LogWarning("[ServerOAuth2Example] 토큰 갱신 실패"); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| // 1분 대기 | ||
| await UniTask.Delay(60000); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
자동 갱신 흐름이 ‘전체 재로그인’으로 구현됨 — Refresh 토큰 기반 갱신으로 교체 권장
현재는 만료 임박 시 브라우저 열기 등 풀 OAuth2 플로우를 반복 실행합니다. 이는 UX 저하와 불필요한 리스크를 유발합니다. 이미 TokenRefreshService가 있으므로 이를 사용하세요.
- Debug.Log("[ServerOAuth2Example] 토큰 갱신 시작");
-
- // 현재는 전체 재로그인 플로우 실행
- var newTokenSet = await _oauth2Provider.LoginWithServerOAuth2Async();
-
- if (newTokenSet?.HasRefreshToken() == true)
- {
- _currentTokenSet = newTokenSet;
- Debug.Log("[ServerOAuth2Example] 토큰 갱신 성공");
- DisplayTokenInfo();
- }
- else
- {
- Debug.LogWarning("[ServerOAuth2Example] 토큰 갱신 실패");
- break;
- }
+ Debug.Log("[ServerOAuth2Example] 토큰 갱신 시작");
+ var refreshed = await _refreshService.EnsureValidTokenAsync();
+ if (refreshed)
+ {
+ _currentTokenSet = _tokenManager.LoadTokens();
+ Debug.Log("[ServerOAuth2Example] 토큰 갱신 성공");
+ DisplayTokenInfo();
+ }
+ else
+ {
+ Debug.LogWarning("[ServerOAuth2Example] 토큰 갱신 실패");
+ break;
+ }Also applies to: 259-266
| Debug.Log("=== 🎉 OAuth2 로그인 성공 - 토큰 정보 ==="); | ||
|
|
||
| // Access Token 정보 | ||
| if (_currentTokenSet.AccessToken != null) | ||
| { | ||
| Debug.Log($"✅ Access Token: {_currentTokenSet.AccessToken.Token}"); | ||
| Debug.Log($" - 만료 시간: {_currentTokenSet.AccessToken.ExpiresAt:yyyy-MM-dd HH:mm:ss}"); | ||
| Debug.Log($" - 만료까지: {(_currentTokenSet.AccessToken.ExpiresAt - DateTime.UtcNow).TotalMinutes:F1}분"); | ||
| } | ||
| else | ||
| { | ||
| Debug.LogError("❌ Access Token이 null입니다."); | ||
| } | ||
|
|
||
| // Refresh Token 정보 | ||
| if (_currentTokenSet.RefreshToken != null) | ||
| { | ||
| Debug.Log($"✅ Refresh Token: {_currentTokenSet.RefreshToken.Token}"); | ||
| Debug.Log($" - 만료 시간: {_currentTokenSet.RefreshToken.ExpiresAt:yyyy-MM-dd HH:mm:ss}"); | ||
| Debug.Log($" - 만료까지: {(_currentTokenSet.RefreshToken.ExpiresAt - DateTime.UtcNow).TotalDays:F1}일"); | ||
| Debug.Log($" - 디바이스 ID: {_currentTokenSet.RefreshToken.DeviceId}"); | ||
| } | ||
| else | ||
| { | ||
| Debug.LogWarning("⚠️ Refresh Token이 null입니다."); | ||
| } | ||
|
|
||
| // 토큰 세트 상태 | ||
| Debug.Log($"📊 토큰 세트 상태:"); | ||
| Debug.Log($" - 갱신 필요: {_currentTokenSet.NeedsRefresh()}"); | ||
| Debug.Log($" - Refresh Token 보유: {_currentTokenSet.HasRefreshToken()}"); | ||
|
|
||
| Debug.Log("=== 토큰 정보 끝 ==="); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
중대 보안 문제: Access/Refresh 토큰 전체 값을 로그로 출력
토큰 원문 로그는 심각한 보안 리스크입니다. 길이·만료 등 메타데이터만 출력하거나, 부분 마스킹을 적용하세요.
- Debug.Log($"✅ Access Token: {_currentTokenSet.AccessToken.Token}");
+ Debug.Log($"✅ Access Token: {MaskToken(_currentTokenSet.AccessToken.Token)}");
Debug.Log($" - 만료 시간: {_currentTokenSet.AccessToken.ExpiresAt:yyyy-MM-dd HH:mm:ss}");
Debug.Log($" - 만료까지: {(_currentTokenSet.AccessToken.ExpiresAt - DateTime.UtcNow).TotalMinutes:F1}분");
...
- Debug.Log($"✅ Refresh Token: {_currentTokenSet.RefreshToken.Token}");
+ Debug.Log($"✅ Refresh Token: {MaskToken(_currentTokenSet.RefreshToken.Token)}");
Debug.Log($" - 만료 시간: {_currentTokenSet.RefreshToken.ExpiresAt:yyyy-MM-dd HH:mm:ss}");파일 내 보조 메서드 추가(선택 위치):
private static string MaskToken(string token)
{
if (string.IsNullOrEmpty(token)) return "(null)";
var head = Math.Min(6, token.Length);
var tail = Math.Min(4, Math.Max(0, token.Length - head));
var masked = new string('*', Math.Max(0, token.Length - head - tail));
return $"{token.Substring(0, head)}{masked}{token.Substring(token.Length - tail)}";
}🤖 Prompt for AI Agents
Assets/Infrastructure/Auth/Examples/ServerOAuth2Example.cs lines 471-503:
current code logs full Access/Refresh token strings which is a critical security
risk; replace these full-token Debug.Log calls with logging that only emits
token metadata (expiry, time-to-expiry, device id) and a masked token string
instead; add a private helper MaskToken(string token) in this class (use simple
head/tail reveal with middle replaced by asterisks) and call MaskToken when
logging token values, and change Debug.LogError/Warning to not include raw
tokens.
| public TokenSet ToTokenSet() | ||
| { | ||
| if (Tokens == null) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| var accessToken = new AccessToken(Tokens.AccessToken); | ||
|
|
||
| RefreshToken refreshToken = null; | ||
| if (!string.IsNullOrEmpty(Tokens.RefreshToken)) | ||
| { | ||
| refreshToken = new RefreshToken( | ||
| Tokens.RefreshToken, | ||
| Tokens.RefreshExpiresIn, | ||
| User?.UserId ?? "guest" | ||
| ); | ||
| } | ||
|
|
||
| return new TokenSet(accessToken, refreshToken); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Null/만료 처리 보강: AccessToken 미존재·JWT exp 파싱 실패시 안전 가드 추가 제안
Tokens만 null 체크하고 Tokens.AccessToken이 빈 값이면 AccessToken 생성자에서 ArgumentNullException이 납니다. 또한 JWT에 exp가 없을 때 ExpiresAt가 DateTime.MinValue로 남을 수 있어 즉시 만료로 취급됩니다. 아래처럼 가드를 추가하고 서버가 내려주는 ExpiresIn을 보조 신뢰원으로 사용하면 견고해집니다.
public TokenSet ToTokenSet()
{
if (Tokens == null)
{
return null;
}
- var accessToken = new AccessToken(Tokens.AccessToken);
+ if (string.IsNullOrWhiteSpace(Tokens.AccessToken))
+ {
+ return null;
+ }
+ var accessToken = new AccessToken(Tokens.AccessToken);
+ // JWT exp 파싱 실패 대비: 서버의 expiresIn으로 보조 설정
+ if (accessToken.ExpiresAt == DateTime.MinValue && Tokens.ExpiresIn > 0)
+ {
+ accessToken.ExpiresAt = DateTime.UtcNow.AddSeconds(Tokens.ExpiresIn);
+ }
RefreshToken refreshToken = null;
if (!string.IsNullOrEmpty(Tokens.RefreshToken))
{
refreshToken = new RefreshToken(
Tokens.RefreshToken,
Tokens.RefreshExpiresIn,
User?.UserId ?? "guest"
);
}
return new TokenSet(accessToken, refreshToken);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public TokenSet ToTokenSet() | |
| { | |
| if (Tokens == null) | |
| { | |
| return null; | |
| } | |
| var accessToken = new AccessToken(Tokens.AccessToken); | |
| RefreshToken refreshToken = null; | |
| if (!string.IsNullOrEmpty(Tokens.RefreshToken)) | |
| { | |
| refreshToken = new RefreshToken( | |
| Tokens.RefreshToken, | |
| Tokens.RefreshExpiresIn, | |
| User?.UserId ?? "guest" | |
| ); | |
| } | |
| return new TokenSet(accessToken, refreshToken); | |
| } | |
| public TokenSet ToTokenSet() | |
| { | |
| if (Tokens == null) | |
| { | |
| return null; | |
| } | |
| if (string.IsNullOrWhiteSpace(Tokens.AccessToken)) | |
| { | |
| return null; | |
| } | |
| var accessToken = new AccessToken(Tokens.AccessToken); | |
| // JWT exp 파싱 실패 대비: 서버의 expiresIn으로 보조 설정 | |
| if (accessToken.ExpiresAt == DateTime.MinValue && Tokens.ExpiresIn > 0) | |
| { | |
| accessToken.ExpiresAt = DateTime.UtcNow.AddSeconds(Tokens.ExpiresIn); | |
| } | |
| RefreshToken refreshToken = null; | |
| if (!string.IsNullOrEmpty(Tokens.RefreshToken)) | |
| { | |
| refreshToken = new RefreshToken( | |
| Tokens.RefreshToken, | |
| Tokens.RefreshExpiresIn, | |
| User?.UserId ?? "guest" | |
| ); | |
| } | |
| return new TokenSet(accessToken, refreshToken); | |
| } |
🤖 Prompt for AI Agents
In Assets/Infrastructure/Network/DTOs/Auth/GuestLoginResponse.cs around lines
40–60, add guards so we don't construct an AccessToken when Tokens.AccessToken
is null/empty (return null TokenSet or handle accordingly) and make JWT exp
parsing resilient: if parsing the token's exp fails or yields DateTime.MinValue,
use the server-provided Tokens.ExpiresIn to compute ExpiresAt (e.g.,
DateTime.UtcNow + TimeSpan.FromSeconds(Tokens.ExpiresIn)) as a fallback; ensure
RefreshToken creation similarly uses its provided expiry fallback and that no
token is returned that is already immediately expired.
| var tokenManager = ProjectVG.Infrastructure.Auth.TokenManager.Instance; | ||
| var accessToken = tokenManager.GetAccessToken(); | ||
|
|
||
| Debug.Log($"[HttpApiClient] Access Token: {accessToken}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
민감 정보 로깅 제거
Access Token을 로그에 출력하면 보안 위험이 있습니다. 프로덕션 환경에서는 토큰이 노출되지 않도록 주의해야 합니다.
- Debug.Log($"[HttpApiClient] Access Token: {accessToken}");
+ Debug.Log($"[HttpApiClient] Access Token 설정: {!string.IsNullOrEmpty(accessToken)}");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Debug.Log($"[HttpApiClient] Access Token: {accessToken}"); | |
| Debug.Log($"[HttpApiClient] Access Token 설정: {!string.IsNullOrEmpty(accessToken)}"); |
🤖 Prompt for AI Agents
Assets/Infrastructure/Network/Http/HttpApiClient.cs around line 81: the code
logs the raw access token which leaks sensitive credentials; remove the
Debug.Log that prints the token, or replace it with a non-sensitive message
(e.g., log that a token is present/expired or log only a masked/hashed
representation), and ensure any remaining diagnostic logs do not include the
full token (use conditional compilation or environment check to avoid emitting
token info in production).
| return await _httpClient.GetAsync<CharacterData[]>("/api/v1/character", cancellationToken: cancellationToken); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
토큰 기반 전환 반영 누락 가능성 — requiresAuth: true 지정 필요 여부 확인
PR 전반에서 세션 → 토큰 기반으로 이행되었는데, 캐릭터 API 호출에는 requiresAuth 지정이 없습니다. 서버가 인증을 요구한다면 현 상태는 Authorization 헤더 미첨부로 401을 유발합니다. 반드시 계약을 확인하고 필요 시 아래와 같이 수정하세요.
- return await _httpClient.GetAsync<CharacterData[]>("/api/v1/character", cancellationToken: cancellationToken);
+ return await _httpClient.GetAsync<CharacterData[]>("/api/v1/character", requiresAuth: true, cancellationToken: cancellationToken);
- return await _httpClient.GetAsync<CharacterData>($"/api/v1/character/{characterId}", cancellationToken: cancellationToken);
+ return await _httpClient.GetAsync<CharacterData>($"/api/v1/character/{characterId}", requiresAuth: true, cancellationToken: cancellationToken);
- return await _httpClient.PostAsync<CharacterData>("/api/v1/character", request, cancellationToken: cancellationToken);
+ return await _httpClient.PostAsync<CharacterData>("/api/v1/character", request, requiresAuth: true, cancellationToken: cancellationToken);
- return await _httpClient.PutAsync<CharacterData>($"/api/v1/character/{characterId}", request, cancellationToken: cancellationToken);
+ return await _httpClient.PutAsync<CharacterData>($"/api/v1/character/{characterId}", request, requiresAuth: true, cancellationToken: cancellationToken);
- await _httpClient.DeleteAsync<object>($"/api/v1/character/{characterId}", cancellationToken: cancellationToken);
+ await _httpClient.DeleteAsync<object>($"/api/v1/character/{characterId}", requiresAuth: true, cancellationToken: cancellationToken);아래 스크립트로 HttpApiClient 시그니처 및 requiresAuth 미지정 호출을 빠르게 점검할 수 있습니다.
Also applies to: 55-56, 66-67, 105-106, 146-147
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== HttpApiClient 메서드 시그니처 점검 =="
rg -nC2 -P 'public\s+async\s+UniTask<.*?>\s+(GetAsync|PostAsync|PutAsync|DeleteAsync)\s*<.*?>\s*\(' Assets/Infrastructure/Network/Http/HttpApiClient.cs || true
echo "== Services에서 requiresAuth 누락 가능 호출 검색 =="
rg -n -P '_httpClient\.(GetAsync|PostAsync|PutAsync|DeleteAsync)<.*?>\(' Assets/Infrastructure/Network/Services \
| rg -nPv 'requiresAuth\s*:\s*true' -n -C1 || trueLength of output: 2768
requiresAuth 설정 누락 – 인증 헤더 전송 보장 필요
HttpApiClient의 GetAsync/PostAsync/PutAsync/DeleteAsync 시그니처에 보면 requiresAuth 파라미터(default: false)가 존재하며, true로 지정해야만 Authorization 헤더가 자동 첨부됩니다.
CharacterApiService의 모든 API 호출에 requiresAuth: true가 빠져 있어, 서버가 인증을 요구하는 경우 401 에러가 발생할 수 있습니다.
다음 위치에서 requiresAuth: true를 지정해 주십시오.
- Assets/Infrastructure/Network/Services/CharacterApiService.cs
• 38행
• 55행
• 66행
• 105행
• 146행
- return await _httpClient.GetAsync<CharacterData[]>("/api/v1/character", cancellationToken: cancellationToken);
+ return await _httpClient.GetAsync<CharacterData[]>("/api/v1/character", requiresAuth: true, cancellationToken: cancellationToken);
- return await _httpClient.GetAsync<CharacterData>($"/api/v1/character/{characterId}", cancellationToken: cancellationToken);
+ return await _httpClient.GetAsync<CharacterData>($"/api/v1/character/{characterId}", requiresAuth: true, cancellationToken: cancellationToken);
- return await _httpClient.PostAsync<CharacterData>("/api/v1/character", request, cancellationToken: cancellationToken);
+ return await _httpClient.PostAsync<CharacterData>("/api/v1/character", request, requiresAuth: true, cancellationToken: cancellationToken);
- return await _httpClient.PutAsync<CharacterData>($"/api/v1/character/{characterId}", request, cancellationToken: cancellationToken);
+ return await _httpClient.PutAsync<CharacterData>($"/api/v1/character/{characterId}", request, requiresAuth: true, cancellationToken: cancellationToken);
- await _httpClient.DeleteAsync<object>($"/api/v1/character/{characterId}", cancellationToken: cancellationToken);
+ await _httpClient.DeleteAsync<object>($"/api/v1/character/{characterId}", requiresAuth: true, cancellationToken: cancellationToken);🤖 Prompt for AI Agents
In Assets/Infrastructure/Network/Services/CharacterApiService.cs around lines
38, 55, 66, 105, and 146, the HttpApiClient calls are missing the requiresAuth:
true parameter so the Authorization header is not being attached; update each
call at those lines (GetAsync/PostAsync/PutAsync/DeleteAsync as applicable) to
pass requiresAuth: true in the method call so authenticated requests include the
Authorization header.
| Console.WriteLine($"[WebSocket] ConnectAsync 호출 - 연결 상태: {_isConnected}, 연결 중: {_isConnecting}"); | ||
| Console.WriteLine($"[WebSocket] 토큰 상태 - AccessToken: {_tokenManager.GetAccessToken()?.Substring(0, 10) ?? "null"}, RefreshToken: {_tokenManager.GetRefreshToken()?.Substring(0, 10) ?? "null"}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
민감한 토큰 정보 로깅 제거 필요
Console.WriteLine으로 토큰의 일부를 출력하는 것은 보안상 위험합니다. 프로덕션 환경에서 민감한 정보가 노출될 수 있습니다.
-Console.WriteLine($"[WebSocket] ConnectAsync 호출 - 연결 상태: {_isConnected}, 연결 중: {_isConnecting}");
-Console.WriteLine($"[WebSocket] 토큰 상태 - AccessToken: {_tokenManager.GetAccessToken()?.Substring(0, 10) ?? "null"}, RefreshToken: {_tokenManager.GetRefreshToken()?.Substring(0, 10) ?? "null"}");
+Debug.Log($"[WebSocket] ConnectAsync 호출 - 연결 상태: {_isConnected}, 연결 중: {_isConnecting}");
+Debug.Log($"[WebSocket] 토큰 상태 - AccessToken 존재: {_tokenManager.GetAccessToken() != null}, RefreshToken 존재: {_tokenManager.GetRefreshToken() != null}");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Console.WriteLine($"[WebSocket] ConnectAsync 호출 - 연결 상태: {_isConnected}, 연결 중: {_isConnecting}"); | |
| Console.WriteLine($"[WebSocket] 토큰 상태 - AccessToken: {_tokenManager.GetAccessToken()?.Substring(0, 10) ?? "null"}, RefreshToken: {_tokenManager.GetRefreshToken()?.Substring(0, 10) ?? "null"}"); | |
| Debug.Log($"[WebSocket] ConnectAsync 호출 - 연결 상태: {_isConnected}, 연결 중: {_isConnecting}"); | |
| Debug.Log($"[WebSocket] 토큰 상태 - AccessToken 존재: {_tokenManager.GetAccessToken() != null}, RefreshToken 존재: {_tokenManager.GetRefreshToken() != null}"); |
🤖 Prompt for AI Agents
In Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs around lines 99
to 100, remove the Console.WriteLine that prints parts of access/refresh tokens;
instead log only non-sensitive state (e.g., _isConnected, _isConnecting) and if
needed a boolean or masked indicator like "hasAccessToken: true/false" without
exposing characters, and replace Console.WriteLine with the application's logger
at an appropriate level.
|
|
||
| if (!string.IsNullOrEmpty(sessionId)) | ||
| if (!string.IsNullOrEmpty(accessToken)) | ||
| { | ||
| return $"{baseUrl}?sessionId={sessionId}"; | ||
| return $"{baseUrl}?token={accessToken}"; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
❓ Verification inconclusive
URL에 토큰을 쿼리 파라미터로 전달하는 보안 문제
Access Token을 URL 쿼리 파라미터로 전달하는 것은 보안상 위험합니다. 토큰이 서버 로그, 브라우저 히스토리, 프록시 로그 등에 노출될 수 있습니다. WebSocket의 경우 Authorization 헤더를 사용하거나 연결 후 별도 인증 메시지로 전송하는 것이 안전합니다.
🌐 Web query:
WebSocket authentication best practices security risks query parameters
Error during web search
URL 쿼리 파라미터 토큰 전달 보안 위험
WebSocket 연결 시 URL 쿼리 파라미터에 액세스 토큰을 포함하면 서버 로그, 브라우저 히스토리, 프록시 로그 등에 토큰이 노출될 수 있습니다. 반드시 다음 중 하나의 방식을 사용해 토큰을 전송하시기 바랍니다:
- HTTP
Authorization헤더에Bearer {accessToken}형식으로 전달 - 연결 수립 후 별도 인증 메시지(payload)로 토큰 전송
코드 수정 필요 위치:
- Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs 줄 255–259:
// 기존 if (!string.IsNullOrEmpty(accessToken)) { return $"{baseUrl}?token={accessToken}"; } // 권장 예시 (의사 코드) if (!string.IsNullOrEmpty(accessToken)) { webSocket.Options.SetRequestHeader("Authorization", $"Bearer {accessToken}"); } return baseUrl;
🤖 Prompt for AI Agents
Assets/Infrastructure/Network/WebSocket/WebSocketManager.cs lines 255-259:
currently the access token is appended to the URL query string which exposes it
in logs/history; remove the token from the URL and instead attach it to the
WebSocket connection via an Authorization header (Bearer {accessToken}) using
the WebSocket client/options API if available, or if the client library does not
support headers for the handshake, send the token immediately after connection
as a dedicated authentication message/payload and treat the connection as
unauthenticated until that succeeds; ensure the method returns the baseUrl (no
token query) and that header-setting or post-connect auth is only executed when
accessToken is non-empty, and update any callers/tests accordingly.
Summary by CodeRabbit
New Features
Changes
Removed
Chores