Skip to content

Add user entity and OAuth2 authentication with room lobbies#196

Merged
ArthurJCQ merged 30 commits intomainfrom
claude/user-entity-oauth-players-011CUvLt3TNszkFNyoQpYueG
Nov 11, 2025
Merged

Add user entity and OAuth2 authentication with room lobbies#196
ArthurJCQ merged 30 commits intomainfrom
claude/user-entity-oauth-players-011CUvLt3TNszkFNyoQpYueG

Conversation

@ArthurJCQ
Copy link
Owner

This commit introduces a comprehensive authentication refactoring:

  • Created User entity with optional email and defaultName fields
  • User entity now handles authentication (replaces Player as UserInterface)
  • Added support for Google and Apple OAuth authentication
  • Players are now linked to Users and Rooms
  • A User can have multiple Player instances across different room lobbies

Changes include:

  • New User entity with GoogleId and AppleId for OAuth integration
  • Player entity refactored to link to User via ManyToOne relationship
  • Removed UserInterface implementation from Player entity
  • Updated security configuration to use User entity for authentication
  • Installed knpuniversity/oauth2-client-bundle with Google and Apple providers
  • Created OAuthController with Google and Apple authentication endpoints
  • Updated PlayerController to create User when creating Player
  • Added database migration for User table and Player.user_id foreign key
  • Added OAuth environment variables to .env file

OAuth endpoints:

  • GET /oauth/google - Initiate Google OAuth flow
  • GET /oauth/google/check - Handle Google OAuth callback
  • GET /oauth/apple - Initiate Apple OAuth flow
  • GET /oauth/apple/check - Handle Apple OAuth callback

This commit introduces a comprehensive authentication refactoring:

- Created User entity with optional email and defaultName fields
- User entity now handles authentication (replaces Player as UserInterface)
- Added support for Google and Apple OAuth authentication
- Players are now linked to Users and Rooms
- A User can have multiple Player instances across different room lobbies

Changes include:
* New User entity with GoogleId and AppleId for OAuth integration
* Player entity refactored to link to User via ManyToOne relationship
* Removed UserInterface implementation from Player entity
* Updated security configuration to use User entity for authentication
* Installed knpuniversity/oauth2-client-bundle with Google and Apple providers
* Created OAuthController with Google and Apple authentication endpoints
* Updated PlayerController to create User when creating Player
* Added database migration for User table and Player.user_id foreign key
* Added OAuth environment variables to .env file

OAuth endpoints:
- GET /oauth/google - Initiate Google OAuth flow
- GET /oauth/google/check - Handle Google OAuth callback
- GET /oauth/apple - Initiate Apple OAuth flow
- GET /oauth/apple/check - Handle Apple OAuth callback
@ArthurJCQ ArthurJCQ force-pushed the claude/user-entity-oauth-players-011CUvLt3TNszkFNyoQpYueG branch from 83cd4d1 to 5e18543 Compare November 8, 2025 17:00
ArthurJCQ and others added 28 commits November 8, 2025 18:01
- Add room property to User entity for tracking current room context
- Add getCurrentUserPlayer method to PlayerRepository
- Update all Voters (PlayerVoter, MissionVoter, RoomVoter) to use getCurrentUserPlayer
- Create database migration to add room_id to users table

The room property on User tracks which room context the user is currently operating in.
This allows for proper authorization checks in Voters by getting the current player
based on the user's room context.

Changes:
- User entity: Added nullable room property with SET NULL on delete
- PlayerRepository: Added getCurrentUserPlayer(User) method
- DoctrinePlayerRepository: Implemented getCurrentUserPlayer logic
- PlayerVoter: Use getCurrentUserPlayer for authorization
- MissionVoter: Use getCurrentUserPlayer for authorization
- RoomVoter: Use getCurrentUserPlayer for authorization
- Migration: Add room_id column to users table with foreign key to room
The /me endpoint now returns the current player based on the user's room context
using the getCurrentUserPlayer method. This provides the player associated with
the user in their current room.

Changes:
- /me endpoint uses getCurrentUserPlayer to get the current player
- Returns PLAYER_NOT_FOUND_IN_CURRENT_ROOM error if no player found
- Maintains same serialization groups and Mercure cookie setup
Players can no longer change their room directly. Instead, users can change
their room context through a new UserController endpoint, which will affect
which player is returned as the current player.

Changes:
- User entity: Added avatar property with default value 'captain'
- User entity: Added 'patch-user' serialization group to defaultName and avatar
- UserController: Created new controller with PATCH /user/me endpoint
  - Allows updating defaultName, avatar, and room
  - Room changes affect which player is current via getCurrentUserPlayer
- PlayerController: Prevent room changes on players
  - Throw PLAYER_CANNOT_CHANGE_ROOM error if room change attempted
  - Removed room change logic and ChangeRoomUseCase call
  - Removed previousRoom tracking code
  - Added null check for room in Mercure publish
- Migration: Add avatar column to users table

This architectural change separates room context (User) from room membership (Player).
Users change their active room context, which determines which of their players
becomes the current player for authorization and API responses.
The /me endpoint now returns comprehensive user information including:
- All user properties (id, email, defaultName, avatar, room)
- List of all players belonging to the user
- currentPlayer property (only if user has a player in current room context)

Changes:
- PlayerController: Removed GET /me endpoint
- UserController: Added GET /me endpoint
  - Returns user info with 'me' serialization group
  - Includes players list
  - Conditionally adds currentPlayer using getCurrentUserPlayer
  - Sets Mercure cookie for SSE subscriptions
- User entity: Added 'me' group to players property for serialization

This provides a single endpoint to get all user context including which player
is currently active based on the user's room context.
Changes to User entity:
- Renamed defaultName property to name
- Updated getter/setter: getName() and setName()
- Updated validation messages: NAME_TOO_SHORT, NAME_TOO_LONG

Updated all references:
- OAuthController: Updated Google and Apple OAuth callbacks to use name
- PlayerController: Updated player creation to use setName()

Functional tests:
- Updated all tests to wrap player assertions in 'currentPlayer' key for /user/me endpoint
- 45 test assertions updated across RoomControllerCest, GuessKillerCest, and KillContestCest

Migration consolidation:
- Consolidated all session migrations into single Version20251108113552.php
- Removed Version20251108171358.php (room context)
- Removed Version20251108172842.php (avatar)
- Updated migration to use 'name' instead of 'default_name'
- Single migration now creates users table with all properties:
  - name, email, roles, google_id, apple_id, room_id, avatar
- Links player to user and removes roles from player table

This provides a cleaner migration history with a single migration for the entire
User entity feature including OAuth, room context, and authorization model.
PlayerController:
- createPlayer: Check if user already exists with getUser() before creating new one
- Prevents creating duplicate users when user is already authenticated (OAuth flow)

MissionController:
- Added PlayerRepository dependency
- createMission: Use getUser() to get User, then getCurrentUserPlayer() to get player
- deleteMission: Use getCurrentUserPlayer() for Mercure publish
- Added null safety checks for user and player

RoomController:
- Added PlayerRepository dependency
- createRoom: Use getUser() to get User, then getCurrentUserPlayer() to get player
- Added null safety checks for user and player

All controllers now properly handle the User-Player separation where:
- Authentication returns a User (not Player)
- getCurrentUserPlayer() determines the active player based on user's room context
- Voters already use this pattern for authorization
When a user updates their room through the UserController.patchUser endpoint,
automatically create a new Player instance for that user in the target room if
one doesn't already exist. The new player is initialized with the user's name
and avatar properties.
The Room entity uses a string type (VARCHAR(5)) for its id, so the users
table's room_id foreign key must match this type.
Replace the removed roles system with explicit boolean flags:
- Added Player.isAdmin property for room administrators
- Added Player.isMaster property for game masters
- Updated migration to include is_admin and is_master columns
- Updated RoomController.createRoom() to use setIsAdmin() and setIsMaster()
  instead of non-existent setRoles() method

The player creating a room is automatically set as admin, and if the room
is game mastered, the player is also set as master with SPECTATING status.
Replace all setRoles() calls with setIsAdmin() and setIsMaster():
- RoomChangeAdminUseCase: Use setIsAdmin(true) for new admin
- ResetPlayerUseCase: Reset isAdmin and isMaster to false
- Update RoomChangeAdminTest to expect setIsAdmin() call

This fixes the unit test failure where setRoles() method no longer exists
on Player entity since roles were moved to User entity.
- Created CreatePlayerUseCase to centralize player creation logic
- Updated UserController to use CreatePlayerUseCase when joining rooms
- Updated RoomControllerCest to use PATCH /user instead of PATCH /player
- Updated GuessKillerCest to use PATCH /user instead of PATCH /player
- When player IDs are needed, they are now grabbed after joining the room
  with room filter to ensure we get the correct player instance

This aligns with the new architecture where:
- Users authenticate (not players)
- PATCH /user with room ID creates a new player for that room
- Each user can have multiple players across different rooms
- Updated KillContestCest to use PATCH /user for joining rooms
- Updated PlayerControllerCest to use PATCH /user for joining/leaving rooms
- Updated SecondaryMissionsAndSwitchingCest to use PATCH /user for joining rooms
- When player IDs are needed after joining, they are now grabbed with room filter

All functional tests now use the new architecture where:
- Users PATCH /user with room ID to join a room
- This automatically creates a new player for that user in that room
- Players can be in multiple rooms (one player instance per room per user)
- Updated CreatePlayerUseCase to not flush automatically (caller controls flushing)
- Added CreatePlayerUseCase dependency to RoomController
- Refactored RoomController.createRoom() to use CreatePlayerUseCase
- Removed redundant player storage (CreatePlayerUseCase already stores the player)

This centralizes player creation logic and ensures consistency across the application.
The use case no longer flushes automatically, giving callers better transaction control.
Updated all Cest files to access player properties through the 'currentPlayer'
key in the response from /user/me endpoint:

- GuessKillerCest: Fixed 3 instances where response properties were accessed
  directly instead of through currentPlayer (target, id)
- KillContestCest: Fixed 2 instances (target.id, target.name)
- RoomControllerCest: Fixed 2 instances (target.id)

Before: $response['target']['id']
After: $response['currentPlayer']['target']['id']

This aligns with the new /user/me response structure where player data
is nested under the 'currentPlayer' key.
Major architectural change to make authentication user-based instead of player-based:

1. **User Entity Changes**:
   - Added token and refreshToken properties (non-persisted)
   - Added serialization groups for POST /user (post-user, create-user)
   - Token/refreshToken included in create-user group for response

2. **UserController**:
   - Added POST /user endpoint (createUser) for user registration
   - Generates JWT tokens and associates them with User
   - Added JWT-related dependencies (JWTTokenManagerInterface, etc.)
   - Implemented LoggerAwareInterface for logging

3. **PlayerController**:
   - Removed POST /player endpoint (createPlayer method)
   - Removed JWT-related dependencies (no longer needed)
   - Cleaned up unused imports

4. **Player Entity**:
   - Removed token and refreshToken properties
   - Removed getToken/setToken and getRefreshToken/setRefreshToken methods
   - Removed 'create-player' serialization group references

5. **Test Helper** (tests/_support/Helper/Api.php):
   - Updated createPlayerAndUpdateHeaders to call POST /user
   - Updated createAdminAndUpdateHeaders to call POST /user

This completes the separation of User (authentication) from Player (game participation).
Users now authenticate once and can have multiple players across different rooms.
- Added cascade: ['remove'] to Room->Players OneToMany relationship
- This ensures players are automatically deleted when their room is deleted
- Removed RoomDoctrineListener entirely as it's no longer needed
- The listener was manually removing players on room deletion, which is now handled by Doctrine's cascade

This simplifies the codebase and relies on Doctrine's built-in cascading behavior.
Enhanced the migration to handle data migration from existing players:

**Data Migration (up):**
1. For each existing player, creates a corresponding user with:
   - Same ID as the player
   - Same name and avatar (defaulting to 'captain' if null)
   - Same room_id
   - Default roles: ["ROLE_USER"]

2. Updates users_id_seq to continue after highest player ID

3. Links each player to their user (user_id = player.id)

4. Migrates player roles to new properties:
   - Sets player.is_admin = true for players with ROLE_ADMIN
   - Sets player.is_master = true for players with SPECTATING status

5. Makes user_id NOT NULL after data is migrated

6. Drops roles column from player table

**Rollback (down):**
1. Adds roles column back to player
2. Migrates is_admin back to ROLE_ADMIN role
3. Removes new columns and relationships
4. Drops users table

This ensures seamless migration of existing data to the new User-based
authentication architecture.
Added three Apple OAuth environment variables to production deployment:
- APPLE_CLIENT_SECRET (from PROD_APPLE_CLIENT_SECRET secret)
- APPLE_KEY_FILE_ID (from PROD_APPLE_KEY_FILE_ID secret)
- APPLE_TEAM_ID (from PROD_APPLE_TEAM_ID secret)

These secrets are now:
1. Exported in the env_vars file for SSH deployment
2. Passed during Docker image build
3. Passed when starting Docker containers

This enables Apple Sign In OAuth functionality in production environment.
@ArthurJCQ ArthurJCQ merged commit 26b0c39 into main Nov 11, 2025
2 checks passed
@ArthurJCQ ArthurJCQ deleted the claude/user-entity-oauth-players-011CUvLt3TNszkFNyoQpYueG branch November 11, 2025 00:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants