Private
Public Access
1
0

Compare commits

..

2 Commits

Author SHA1 Message Date
42c552c528 fix: usr: quickfix for https-only login - & add user data when the user is not logged in #4
All checks were successful
Deploy to Production / deploy (push) Successful in 1m55s
2026-04-18 08:49:10 +02:00
3b376e5386 chg: pkg: new version release !skipChangelog 2026-04-16 11:56:30 +02:00
7 changed files with 68 additions and 30 deletions

View File

@@ -1,6 +1,15 @@
# Changelog
## v2026.2.1-7 (2026-04-16)
### Changes
* Add consent checkbox to user's registration - and fix the sharing pics #4. [Lang]
* Add correct version numbering and CHANGELOG - and add the LICENSE #4. [Lang]
## v2026.2.1-6 (2026-04-16)
### Changes

View File

@@ -33,7 +33,11 @@ services:
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
MINIO_ENDPOINT: http://minio:9000
MINIO_PUBLIC_URL: ${MINIO_PUBLIC_URL:-http://localhost:9000}
TRUSTED_PROXIES: ${TRUSTED_PROXIES}
# IMPORTANT: Set TRUSTED_PROXIES to your reverse proxy IP in production.
# For Docker on same host, use: 172.17.0.1 (default bridge) or 172.16.0.0/12 (overlay network)
# For Kubernetes or external proxy, use the proxy's IP address.
# WARNING: Using 0.0.0.0/0 is insecure in production environments!
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1}
volumes:
- app_var:/app/var
- caddy_data:/data

View File

@@ -23,12 +23,14 @@ security:
auth_code_parameter_name: _auth_code
post_only: true
default_target_path: MineSeekerBundle_homepage
always_use_default_target_path: false
prepare_on_login: true
prepare_on_access_denied: true
form_login:
login_path: MineSeekerBundle_login
check_path: MineSeekerBundle_login
default_target_path: MineSeekerBundle_homepage
always_use_default_target_path: false
username_parameter: _username
password_parameter: _password
enable_csrf: true

View File

@@ -64,7 +64,7 @@ class MercureController extends AbstractController
#[Route('/api/game/join/{gameAssoc}', name: 'MineSeekerBundle_api_game_join', methods: ['POST'])]
public function join(string $gameAssoc, Request $request): JsonResponse
{
$this->topicManager->subscribe($gameAssoc, $this->resolveUserName($request), $this->getUser());
$this->topicManager->subscribe($gameAssoc, $this->resolveUserName($request), $this->getUser(), $request);
return $this->json(['success' => true]);
}

View File

@@ -17,6 +17,7 @@ use App\Form\ResetPasswordFormType;
use App\Repository\UserRepository;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use LogicException;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
@@ -64,7 +65,7 @@ class SecurityController extends AbstractController
#[Route('/logout', name: 'MineSeekerBundle_logout', methods: ['POST'])]
public function logout(): never
{
throw new \LogicException('This action is intercepted by the security firewall.');
throw new LogicException('This action is intercepted by the security firewall.');
}
#[Route('/register', name: 'MineSeekerBundle_register')]

View File

@@ -10,6 +10,7 @@
namespace App\Interfaces;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserInterface;
/**
@@ -24,7 +25,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
*/
interface TopicManagerInterface
{
public function subscribe(string $gameAssoc, string $userName, ?UserInterface $user): void;
public function subscribe(string $gameAssoc, string $userName, ?UserInterface $user, Request $request): void;
public function unSubscribe(string $gameAssoc, string $userName): void;

View File

@@ -18,14 +18,15 @@ use App\Entity\User;
use App\Interfaces\TopicManagerInterface;
use App\Repository\PlayedGameRepository;
use App\Repository\UserRepository;
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
use DateTimeInterface;
use DateTime;
use DateTimeInterface;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use JsonException;
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;
use Symfony\Component\Security\Core\User\UserInterface;
@@ -52,7 +53,7 @@ readonly class TopicManager implements TopicManagerInterface
) {
}
public function subscribe(string $gameAssoc, string $userName, ?UserInterface $user): void
public function subscribe(string $gameAssoc, string $userName, ?UserInterface $user, Request $request): void
{
$playedGame = $this->getPlayedGame($gameAssoc);
if (null === $playedGame) {
@@ -70,7 +71,7 @@ readonly class TopicManager implements TopicManagerInterface
/** Save the player to the database on a fresh join */
if (!$isKnown && $count < 2) {
$users = $this->saveUserToDb($gameAssoc, $userName, $user, $count + 1);
$users = $this->saveUserToDb($gameAssoc, $userName, $user, $count + 1, $request);
$count = $this->getPlayerCount($users);
}
@@ -168,9 +169,6 @@ readonly class TopicManager implements TopicManagerInterface
return $data;
}
// ------------------------------------------------------------------ //
// Normal move
// ------------------------------------------------------------------ //
$coords = $event['coords'];
$player = $event['player']; // 'red' | 'blue'
$isBomb = (bool)$event['bomb'];
@@ -243,10 +241,6 @@ readonly class TopicManager implements TopicManagerInterface
return $data;
}
// ------------------------------------------------------------------ //
// Grid helpers
// ------------------------------------------------------------------ //
/** Load the grid rows from the database as a 2-D array. */
private function loadGrid(string $gameAssoc): array
{
@@ -403,10 +397,6 @@ readonly class TopicManager implements TopicManagerInterface
return $mines;
}
// ------------------------------------------------------------------ //
// Database helpers
// ------------------------------------------------------------------ //
private function getPlayedGame(string $gameAssoc): ?PlayedGame
{
return $this->playedGameRepository->findOneByGameAssoc($gameAssoc);
@@ -462,13 +452,18 @@ readonly class TopicManager implements TopicManagerInterface
}
}
private function saveUserToDb(string $gameAssoc, string $userName, ?UserInterface $user, int $count): array
{
private function saveUserToDb(
string $gameAssoc,
string $userName,
?UserInterface $user,
int $count,
Request $request
): array {
$playedGame = $this->getPlayedGame($gameAssoc);
null !== $user
? $this->saveRegisteredUser($userName, $count, $playedGame)
: $this->saveAnonUser($userName, $count, $playedGame);
: $this->saveAnonUser($userName, $count, $playedGame, $request);
$this->entityManager->persist($playedGame);
$this->entityManager->flush();
@@ -495,11 +490,14 @@ readonly class TopicManager implements TopicManagerInterface
}
}
private function saveAnonUser(string $userName, int $count, PlayedGame $playedGame): void
private function saveAnonUser(string $userName, int $count, PlayedGame $playedGame, Request $request): void
{
try {
$anon = new Gamer();
$anon->setUsername($userName);
$anon->setUserName($userName);
$anon->setIp($request->getClientIp());
$anon->setCountry($this->extractCountry($request));
$anon->setUserAgent($request->headers->get('User-Agent'));
$anon->setConnTimestamp(new DateTime());
$this->entityManager->persist($anon);
@@ -518,8 +516,8 @@ readonly class TopicManager implements TopicManagerInterface
private function getUserCollection(PlayedGame $playedGame): array
{
$redUser = $playedGame->getRed();
$blueUser = $playedGame->getBlue();
$redUser = $playedGame->getRed();
$blueUser = $playedGame->getBlue();
return [
'red' => null !== $redUser ? $redUser->getUsername() : '',
@@ -527,11 +525,11 @@ readonly class TopicManager implements TopicManagerInterface
'redAnon' => null !== $playedGame->getRedAnon() ? $playedGame->getRedAnon()->getUserName() : '',
'blueAnon' => null !== $playedGame->getBlueAnon() ? $playedGame->getBlueAnon()->getUserName() : '',
'redAvatar' => null !== $redUser && null !== $redUser->getAvatarPath()
? $this->cacheManager->generateUrl($redUser->getAvatarPath(), 'avatar_thumb')
: null,
? $this->cacheManager->generateUrl($redUser->getAvatarPath(), 'avatar_thumb')
: null,
'blueAvatar' => null !== $blueUser && null !== $blueUser->getAvatarPath()
? $this->cacheManager->generateUrl($blueUser->getAvatarPath(), 'avatar_thumb')
: null,
? $this->cacheManager->generateUrl($blueUser->getAvatarPath(), 'avatar_thumb')
: null,
];
}
@@ -585,4 +583,27 @@ readonly class TopicManager implements TopicManagerInterface
$this->logger->error('Lobby publish error: ' . $e->getMessage());
}
}
private function extractCountry(Request $request): ?string
{
/** Common headers used by CDNs and proxies to pass country information */
$countryHeaders = [
'CF-IPCountry', // Cloudflare
'CloudFront-Viewer-Country', // AWS CloudFront
'X-Country-Code', // Custom header
'X-Geoip-Country', // Generic GeoIP header
];
foreach ($countryHeaders as $header) {
$country = $request->headers->get($header);
if (empty($country)) {
continue;
}
return substr($country, 0, 100);
}
return null;
}
}