6 Commits

Author SHA1 Message Date
gitea-actions
44e1e4a293 chore: bump version to v0.1.5
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 18s
2026-04-03 11:12:34 +00:00
ad92a4c434 feat : commande app:create-user pour créer des utilisateurs
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Permet de créer un user en prod sans SQL :
  php bin/console app:create-user <username> <password> --admin

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:12:28 +02:00
gitea-actions
be12175e17 chore: bump version to v0.1.4
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 35s
2026-04-03 11:09:26 +00:00
e8fc85c173 fix : correctifs de sécurité et robustesse post-review
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
- MeProvider : guard null user avec AccessDeniedHttpException
- MaintenanceToggleProcessor : vérification des opérations filesystem
- User : restreindre Get/GetCollection aux ROLE_ADMIN
- useAppVersion : corriger le path relatif '/version'

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:09:14 +02:00
gitea-actions
b39e6f81d8 chore: bump version to v0.1.3
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 46s
2026-04-03 10:46:11 +00:00
28690be509 revert(build) : retire le contournement ipv4 du dockerfile
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
2026-04-03 12:46:00 +02:00
7 changed files with 87 additions and 10 deletions

View File

@@ -1,2 +1,2 @@
parameters:
app.version: '0.1.2'
app.version: '0.1.5'

View File

@@ -6,7 +6,7 @@ export function useAppVersion() {
if (version.value) {
return version.value
}
const response = await api.get<{ version: string }>('version', {}, {
const response = await api.get<{ version: string }>('/version', {}, {
toast: false
})
version.value = response.version

View File

@@ -11,7 +11,7 @@ COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /app
COPY composer.json composer.lock symfony.lock ./
RUN APP_ENV=prod APP_DEBUG=0 COMPOSER_IPRESOLVE=4 composer install --no-dev --no-scripts --no-interaction
RUN APP_ENV=prod APP_DEBUG=0 composer install --no-dev --no-scripts --no-interaction
COPY bin bin/
COPY config config/
@@ -26,7 +26,6 @@ FROM node:lts-alpine AS frontend-build
WORKDIR /app/frontend
COPY frontend/package.json frontend/package-lock.json ./
ENV NODE_OPTIONS=--dns-result-order=ipv4first
RUN npm ci
COPY frontend/ ./

View File

@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace App\Command;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
#[AsCommand(
name: 'app:create-user',
description: 'Create a new user',
)]
final class CreateUserCommand extends Command
{
public function __construct(
private readonly EntityManagerInterface $em,
private readonly UserPasswordHasherInterface $passwordHasher,
) {
parent::__construct();
}
protected function configure(): void
{
$this
->addArgument('username', InputArgument::REQUIRED, 'Username')
->addArgument('password', InputArgument::REQUIRED, 'Plain text password')
->addOption('admin', 'a', InputOption::VALUE_NONE, 'Grant ROLE_ADMIN');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$username = $input->getArgument('username');
$plainPassword = $input->getArgument('password');
$existing = $this->em->getRepository(User::class)->findOneBy(['username' => $username]);
if ($existing) {
$io->error(sprintf('User "%s" already exists.', $username));
return Command::FAILURE;
}
$user = new User();
$user->setUsername($username);
$user->setPassword($this->passwordHasher->hashPassword($user, $plainPassword));
$user->setRoles($input->getOption('admin') ? ['ROLE_ADMIN'] : []);
$this->em->persist($user);
$this->em->flush();
$io->success(sprintf('User "%s" created%s.', $username, $input->getOption('admin') ? ' (admin)' : ''));
return Command::SUCCESS;
}
}

View File

@@ -28,9 +28,11 @@ use Symfony\Component\Serializer\Attribute\Groups;
normalizationContext: ['groups' => ['me:read']],
),
new Get(
security: "is_granted('ROLE_ADMIN')",
normalizationContext: ['groups' => ['user:list']],
),
new GetCollection(
security: "is_granted('ROLE_ADMIN')",
normalizationContext: ['groups' => ['user:list']],
),
new Post(security: "is_granted('ROLE_ADMIN')", processor: UserPasswordHasherProcessor::class),

View File

@@ -44,13 +44,17 @@ final readonly class MaintenanceToggleProcessor implements ProcessorInterface
if ($data->maintenance) {
$directory = dirname($maintenancePath);
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
if (!is_dir($directory) && !mkdir($directory, 0755, true)) {
throw new \RuntimeException(sprintf('Cannot create directory "%s".', $directory));
}
touch($maintenancePath);
if (!touch($maintenancePath)) {
throw new \RuntimeException(sprintf('Cannot create maintenance file at "%s".', $maintenancePath));
}
} elseif (file_exists($maintenancePath)) {
unlink($maintenancePath);
if (!unlink($maintenancePath)) {
throw new \RuntimeException(sprintf('Cannot remove maintenance file at "%s".', $maintenancePath));
}
}
$dto = new ManagedApplication();

View File

@@ -8,6 +8,7 @@ use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Entity\User;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* @implements ProviderInterface<User>
@@ -20,7 +21,12 @@ final readonly class MeProvider implements ProviderInterface
public function provide(Operation $operation, array $uriVariables = [], array $context = []): User
{
// @var User $user
return $this->security->getUser();
$user = $this->security->getUser();
if (!$user instanceof User) {
throw new AccessDeniedHttpException('User not authenticated.');
}
return $user;
}
}