mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 21:55:42 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ddd772ddc | ||
|
|
2fe92778c6 | ||
|
|
bb81d58b0f | ||
|
|
f896d42267 |
+107
-61
@@ -4,70 +4,77 @@
|
||||
|
||||
The OAuth implementation is functional but has several gaps that need addressing before it's production-ready. This document outlines the known issues, proposes a comprehensive user identity model, and provides a prioritized implementation plan.
|
||||
|
||||
## Current State
|
||||
## Current State (Updated January 2026)
|
||||
|
||||
### What Works
|
||||
### What Works ✅
|
||||
- Discord OAuth flow (server-mediated polling)
|
||||
- Google OAuth flow
|
||||
- JWT token generation and validation
|
||||
- User creation and display name setting
|
||||
- Auto-login with stored tokens
|
||||
- Basic game creation and play with OAuth users
|
||||
- Headshot fetching via public CDN (no auth required)
|
||||
- Logout button in lobby (preserves tokens for quick reconnect)
|
||||
- Environment (prod/qa) and user display in lobby
|
||||
- Game identity with userName = displayName (PR #4964 merged)
|
||||
|
||||
### Known Issues
|
||||
|
||||
#### 1. Game Identity Model Fragility (Critical - but PR #4964 is required now)
|
||||
**Problem**: PR #4964 sets `userName = displayName` for backwards compatibility. This works but creates a coupling: if a user changes their displayName later, their game history becomes orphaned.
|
||||
#### 1. Game Identity Model Fragility (Deferred)
|
||||
**Status**: Accepted for now. PR #4964 merged with `userName = displayName`.
|
||||
|
||||
**Current flow**:
|
||||
**Current behavior**:
|
||||
- Games store `userNameToFactionId: Map[String, Int]`
|
||||
- For JWT users, this maps displayName → factionId
|
||||
- displayName is technically mutable (users could change it)
|
||||
- No migration path when displayName changes
|
||||
|
||||
**Recommendation**: **Merge PR #4964 now.** It fixes the immediate crash (admin server NPE, games with null players). The fragility is acceptable because:
|
||||
**Why this is acceptable**:
|
||||
1. We don't currently have a "change display name" feature
|
||||
2. The alternative (using userId) requires more extensive changes
|
||||
3. We can migrate to userId-based identity in Phase 2
|
||||
3. Can migrate to userId-based identity later if needed
|
||||
|
||||
**Phase 2 fix**: Change `userName = userId` instead of `displayName`, add display name resolution layer for UI.
|
||||
#### 2. In-Game Headshot Fetching ✅ FIXED
|
||||
**Solution**: Made the `eagle0-headshots` S3 bucket public and enabled CDN.
|
||||
- Client now fetches directly from `https://eagle0-headshots.sfo3.cdn.digitaloceanspaces.com/`
|
||||
- No authentication required
|
||||
- Works for both OAuth and Basic Auth users
|
||||
- Simpler architecture, no dependency on home Mac server
|
||||
|
||||
#### 2. In-Game Headshot Fetching Broken (High)
|
||||
**Problem**: Hero/character portrait images can't be fetched by OAuth users.
|
||||
#### 3. Logout from Lobby ✅ FIXED
|
||||
**Solution**: Added logout button to lobby UI (PR #4967).
|
||||
- Button disconnects from server and returns to connection screen
|
||||
- Intentionally does NOT clear OAuth tokens
|
||||
- Allows quick reconnect with same account without full OAuth flow
|
||||
|
||||
**Architecture** (discovered via investigation):
|
||||
- Headshots are AI-generated character portraits stored in S3 bucket `eagle0-headshots`
|
||||
- Client fetches from `https://eagle0.net/assets/headshots/{path}` (HOME MAC, not prod!)
|
||||
- The nginx on `eagle0.net` validates auth and returns 302 redirect to S3 signed URL
|
||||
- Client follows redirect to S3 (no auth needed for signed URLs)
|
||||
|
||||
**The Problem**:
|
||||
- Game gRPC connects to `prod.eagle0.net` (DigitalOcean)
|
||||
- Headshot fetching goes to `eagle0.net` (home Mac) - **different server!**
|
||||
- The `eagle0.net` nginx likely only validates Basic Auth, not JWT Bearer tokens
|
||||
- Client code (ConnectionHandler.cs:579-581) correctly sets Bearer token, but server rejects it
|
||||
|
||||
**Why this worked before**: Basic Auth users had credentials for both game (prod.eagle0.net) and headshots (eagle0.net).
|
||||
|
||||
**Why it breaks with OAuth**: OAuth users have JWT for prod.eagle0.net but the eagle0.net headshot endpoint doesn't know how to validate JWTs.
|
||||
|
||||
**Note**: This is NOT about user avatars from Discord/Google. Those are a separate future feature.
|
||||
|
||||
#### 3. No Logout from Lobby (Medium)
|
||||
**Problem**: Once auto-logged in via OAuth, users have no way to log out and return to the connection screen to use a different account or auth method.
|
||||
|
||||
**Required**: A logout button in the lobby UI that clears tokens and returns to connection screen.
|
||||
|
||||
#### 4. Display Name Uniqueness Not Enforced (Medium)
|
||||
#### 4. Display Name Uniqueness Not Enforced (Medium) - OPEN
|
||||
**Problem**: User was able to set displayName "nolen" when that name was already taken.
|
||||
|
||||
**Root cause**: Either the uniqueness check is buggy, or the displayNameIndex wasn't populated correctly during user creation.
|
||||
**Root cause**: Unknown - needs investigation. Either:
|
||||
- The uniqueness check is buggy
|
||||
- The displayNameIndex wasn't populated correctly during user creation
|
||||
- Race condition during concurrent registrations
|
||||
|
||||
#### 5. Admin Server Crashes (Fixed)
|
||||
**Problem**: `GetRunningGames` threw NPE because game player info had null strings.
|
||||
#### 5. Admin Server Crashes ✅ FIXED
|
||||
**Solution**: PR #4964 sets `userName = displayName` for JWT users.
|
||||
|
||||
**Root cause**: JWT users had null `userName`, causing games to be created with null player identities.
|
||||
#### 6. Intermittent "Expired" Errors During Login (Medium) - INVESTIGATING
|
||||
**Problem**: Users occasionally get "OAuth session expired" errors even when server logs show the callback succeeded.
|
||||
|
||||
**Fix**: PR #4964 sets `userName = displayName` for JWT users.
|
||||
**Status**: Added diagnostic logging in PR #4974 to trace:
|
||||
- State creation in `getAuthUrl`
|
||||
- State lookup in `handleCallback`
|
||||
- Result lookup in `checkStatus`
|
||||
|
||||
**Possible causes**:
|
||||
- State mismatch between client and server
|
||||
- Race condition in polling
|
||||
- Cleanup running at wrong time
|
||||
|
||||
#### 7. Token Expiry Field Bug ✅ FIXED
|
||||
**Problem**: `CheckOAuthStatusResponse.expiresAt` was returning refresh token expiry (30 days) instead of access token expiry (7 days).
|
||||
|
||||
**Solution**: Fixed in PR #4974 to calculate correct access token expiry.
|
||||
|
||||
---
|
||||
|
||||
@@ -163,42 +170,81 @@ When a user logs in with a new OAuth provider:
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Stabilization (Immediate)
|
||||
### Phase 1: Stabilization ✅ COMPLETE
|
||||
|
||||
#### 1.1 Fix Display Name Uniqueness Bug
|
||||
#### 1.1 Fix Display Name Uniqueness Bug - OPEN
|
||||
- [ ] Investigate why "nolen" was allowed when it existed
|
||||
- [ ] Add logging to `setDisplayName` to trace the issue
|
||||
- [ ] Ensure `displayNameIndex` is correctly maintained
|
||||
- [ ] Add unit tests for uniqueness enforcement
|
||||
|
||||
#### 1.2 Add Logout Button to Lobby
|
||||
- [ ] Add "Logout" button to lobby UI (near connection status)
|
||||
- [ ] On click: call `OAuthManager.LogoutAsync()`
|
||||
- [ ] Clear `TokenStorage`
|
||||
- [ ] Disconnect from server
|
||||
- [ ] Navigate to connection screen
|
||||
#### 1.2 Add Logout Button to Lobby ✅ DONE
|
||||
- [x] Add "Logout" button to lobby UI
|
||||
- [x] Disconnect from server
|
||||
- [x] Navigate to connection screen
|
||||
- [x] Preserve OAuth tokens for quick reconnect (intentional change from original plan)
|
||||
|
||||
#### 1.3 Merge PR #4964 (userName = displayName)
|
||||
- [ ] This is a stopgap - games will break if displayName changes
|
||||
- [ ] Document this limitation
|
||||
- [ ] Plan Phase 2 migration to userId-based identity
|
||||
#### 1.3 Merge PR #4964 (userName = displayName) ✅ DONE
|
||||
- [x] Merged - games work with OAuth users
|
||||
- [x] Documented limitation (games break if displayName changes)
|
||||
|
||||
### Phase 2: Stable Identity (Next Sprint)
|
||||
#### 1.4 Fix Headshot Fetching ✅ DONE
|
||||
- [x] Made eagle0-headshots bucket public
|
||||
- [x] Client fetches directly from CDN
|
||||
- [x] No authentication required
|
||||
|
||||
#### 2.1 Migrate to userId-based Game Identity
|
||||
#### 1.5 Add Lobby Status Display ✅ DONE
|
||||
- [x] Show environment (prod/qa) in lobby
|
||||
- [x] Show current user in lobby (OAuth displayName or classic username)
|
||||
|
||||
### Phase 2: Remaining Work (Priority Order)
|
||||
|
||||
#### 2.1 Diagnose Intermittent "Expired" Errors - IN PROGRESS
|
||||
- [x] Add diagnostic logging (PR #4974)
|
||||
- [ ] Deploy and reproduce the issue
|
||||
- [ ] Analyze logs to identify root cause
|
||||
- [ ] Implement fix based on findings
|
||||
|
||||
#### 2.2 Fix Display Name Uniqueness
|
||||
- [ ] Investigate UserService.setDisplayName logic
|
||||
- [ ] Check displayNameIndex population
|
||||
- [ ] Add logging to trace the issue
|
||||
- [ ] Fix the bug and add tests
|
||||
|
||||
#### 2.3 Wire Up Lobby UI in Unity
|
||||
- [ ] Connect `lobbyEnvironmentText` to TextMeshProUGUI in scene
|
||||
- [ ] Connect `lobbyUserText` to TextMeshProUGUI in scene
|
||||
|
||||
#### 2.4 Implement Token Refresh During Gameplay
|
||||
- [ ] Implement `RefreshToken` RPC on server (currently throws UNIMPLEMENTED)
|
||||
- [ ] Store refresh tokens server-side for validation
|
||||
- [ ] Add proactive refresh in client before token expires
|
||||
- [ ] Handle refresh during reconnection attempts
|
||||
|
||||
### Phase 3: Nice-to-Haves (Future)
|
||||
|
||||
#### 3.1 Proactive Token Refresh
|
||||
- [ ] Monitor token expiry in client
|
||||
- [ ] Refresh automatically when < 5 minutes remaining
|
||||
- [ ] Update TokenStorage with new access token
|
||||
|
||||
#### 3.2 Better Error Messages
|
||||
- [ ] Distinguish between network errors and auth errors
|
||||
- [ ] Show user-friendly messages for OAuth failures
|
||||
- [ ] Add retry suggestions
|
||||
|
||||
#### 3.3 Session Persistence Across Server Restarts
|
||||
- [ ] Move pendingOAuth from in-memory TrieMap to Redis/database
|
||||
- [ ] Move completedOAuth to Redis with TTL
|
||||
- [ ] Server can restart without breaking in-flight OAuth flows
|
||||
|
||||
#### 3.4 Migrate to userId-based Game Identity (Deferred)
|
||||
- [ ] Change `AuthorizationUtils.userName` to return `userId` for JWT users
|
||||
- [ ] Add `UserService.resolveDisplayName(id: String): String` for UI display
|
||||
- [ ] Update game UI to resolve userIds to displayNames
|
||||
- [ ] Existing Basic Auth games continue to work (userName is literal)
|
||||
|
||||
#### 2.2 Headshot Fetching with OAuth
|
||||
- [ ] Investigate current headshot fetching implementation
|
||||
- [ ] Add avatar proxy endpoint: `GET /api/avatar/{userId}`
|
||||
- [ ] Proxy requests to OAuth provider CDN
|
||||
- [ ] Add caching layer (Redis or local file cache)
|
||||
- [ ] Update client to use new endpoint for OAuth users
|
||||
|
||||
#### 2.3 Display Name Change Support
|
||||
#### 3.5 Display Name Change Support (Requires 3.4)
|
||||
- [ ] Add `ChangeDisplayName` RPC
|
||||
- [ ] Validate new name is unique
|
||||
- [ ] Update user record
|
||||
|
||||
+21
@@ -112,6 +112,8 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
|
||||
[Header("Lobby Controls")]
|
||||
public Button logoutButton;
|
||||
public TextMeshProUGUI lobbyEnvironmentText;
|
||||
public TextMeshProUGUI lobbyUserText;
|
||||
|
||||
public GameObject runningGamesListArea;
|
||||
public GameObject runningGamesListItemPrefab;
|
||||
@@ -270,6 +272,22 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
if (logoutButton != null) { logoutButton.onClick.AddListener(OnLogoutClicked); }
|
||||
}
|
||||
|
||||
private void UpdateLobbyStatusDisplays() {
|
||||
// Show environment (prod/qa)
|
||||
if (lobbyEnvironmentText != null) {
|
||||
lobbyEnvironmentText.text = ConnectedEnvironmentName ?? "";
|
||||
}
|
||||
|
||||
// Show current user - prefer OAuth display name, fall back to classic login name
|
||||
if (lobbyUserText != null) {
|
||||
if (_useOAuth && !string.IsNullOrEmpty(TokenStorage.DisplayName)) {
|
||||
lobbyUserText.text = TokenStorage.DisplayName;
|
||||
} else {
|
||||
lobbyUserText.text = nameField.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnLogoutClicked() {
|
||||
Debug.Log("[ConnectionHandler] Logout button clicked");
|
||||
|
||||
@@ -523,6 +541,9 @@ public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
|
||||
connectionPanel.gameObject.SetActive(false);
|
||||
gameSelectionPanel.gameObject.SetActive(true);
|
||||
|
||||
// Update lobby status displays
|
||||
UpdateLobbyStatusDisplays();
|
||||
|
||||
// Set up running games table
|
||||
foreach (Transform row in runningGamesListArea.transform) { Destroy(row.gameObject); }
|
||||
foreach (var runningGame in lobbyResponse.RunningGames) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@ scala_library(
|
||||
deps = [
|
||||
":oauth_config",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:auth_scala_grpc",
|
||||
"//src/main/scala/net/eagle0/common:simple_timed_logger",
|
||||
"@maven//:org_json4s_json4s_ast_3",
|
||||
"@maven//:org_json4s_json4s_core_3",
|
||||
"@maven//:org_json4s_json4s_native_3",
|
||||
|
||||
Reference in New Issue
Block a user