HIGH severity: Users can participate in an infinite number of games they haven't joined, bypassing all entry fee requirements while still becoming winners and claiming prizes. **Description:** `SessionManager::commitReaction` only checks that the user has joined `_gameId`, but doesn't verify that `_questionId` belongs to `_gameId`:

Then in the strategy contracts `commitReaction` also doesn't validate this; it just saves the user's reaction into...

Vulnerable code:
function commitReaction(uint256 _gameId, uint256 _questionId, bytes32 _commit)
        external
        onlyState(_gameId, SessionState.Ongoing)
    {
        require(contestants[_gameId][msg.sender], NotJoined(msg.sender, _gameId));
        IPromptStrategy(questionCommitment[_questionId].promptStrategy).commitReaction(
            _gameId, _questionId, _commit, msg.sender
        );
    }

function commitReaction(uint256 _gameId, uint256 _questionId, bytes32 _commit, address _user) external {
        require(revealedAt[_questionId] != 0, QuestionNotRevealed(_questionId));
        require(
            revealedQuestions[_questionId].sessionManager == msg.sender,
            OnlySessionManager(revealedQuestions[_questionId].sessionManager, msg.sender)
        );
        require(
            revealedAt[_questionId] + revealedQuestions[_questionId].reactionDeadline > block.timestamp, /
            ReactionDeadlinePassed(_user, _questionId)
        );
        Reaction storage r = reactions[_questionId][_user];
        require(r.baseReaction.timestamp == 0, AnswerAlreadyCommitted(_user, _gameId, _questionId));

        r.baseReaction.commit = _commit; <------
        r.baseReaction.timestamp = block.timestamp;

        emit AnswerCommitted(_gameId, _questionId, _user, _commit);
    }

function revealReaction(
        uint256 _gameId,
        uint256 _questionId,
        bytes calldata _selection,
        uint256 salt,
        address _user
    ) external {
        uint16 selection = abi.decode(_selection, (uint16));
        require(
            revealedQuestions[_questionId].sessionManager == msg.sender,
            OnlySessionManager(revealedQuestions[_questionId].sessionManager, msg.sender)
        );
        Reaction storage r = reactions[_questionId][_user];
        require(r.baseReaction.timestamp != 0, AnswerNotCommitted(_user, _gameId, _questionId));
        require(!r.baseReaction.revealed, AnswerAlreadyRevealed(_user, _gameId, _questionId));
        require(
            keccak256(abi.encodePacked(_gameId, _questionId, selection, salt)) == r.baseReaction.commit,
            RevealMismatch(_gameId, _questionId, selection, salt, r.baseReaction.commit)
        );

        r.answer = selection;<------
        r.baseReaction.revealed = true;<-------
        address[][] storage choiceCounter = choiceCounters[_questionId];
        uint256 choicesLength = revealedQuestions[_questionId].choices.length;
        if (choiceCounter.length == 0) {
            // set length of choiceCounter to choicesLength
            assembly {
                sstore(choiceCounter.slot, add(sload(choiceCounter.slot), choicesLength))
            }
        }
        choiceCounters[_questionId][selection].push(_user); <-------
        emit AnswerRevealed(_gameId, _questionId, _user, selection);
    }