Skip to content

Commit

Permalink
Init admin login page (#82)
Browse files Browse the repository at this point in the history
  • Loading branch information
loic425 authored Oct 3, 2024
2 parents c830b50 + 2a43c12 commit 474799c
Show file tree
Hide file tree
Showing 40 changed files with 712 additions and 0 deletions.
2 changes: 2 additions & 0 deletions app/DataFixtures/AppFixtures.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use App\Story\DefaultConferencesStory;
use App\Story\DefaultSpeakersStory;
use App\Story\DefaultSyliusCon2024TalksStory;
use App\Story\DefaultUsersStory;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;

Expand All @@ -28,5 +29,6 @@ public function load(ObjectManager $manager): void
DefaultConferencesStory::load();
DefaultSpeakersStory::load();
DefaultSyliusCon2024TalksStory::load();
DefaultUsersStory::load();
}
}
109 changes: 109 additions & 0 deletions app/Entity/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace App\Entity;

use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;

#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;

#[ORM\Column(length: 180)]
private ?string $email = null;

/** @var list<string> The user roles */
#[ORM\Column]
private array $roles = [];

#[ORM\Column]
private ?string $password = null;

public function getId(): ?int
{
return $this->id;
}

public function getEmail(): ?string
{
return $this->email;
}

public function setEmail(?string $email): void
{
$this->email = $email;
}

/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->email;
}

/**
* @see UserInterface
*
* @return list<string>
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';

return array_unique($roles);
}

/**
* @param list<string> $roles
*/
public function setRoles(array $roles): void
{
$this->roles = $roles;
}

/**
* @see PasswordAuthenticatedUserInterface
*/
public function getPassword(): ?string
{
return $this->password;
}

public function setPassword(?string $password): void
{
$this->password = $password;
}

/**
* @see UserInterface
*/
public function eraseCredentials(): void
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
}
68 changes: 68 additions & 0 deletions app/Factory/UserFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace App\Factory;

use App\Entity\User;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;

/**
* @extends PersistentProxyObjectFactory<User>
*/
final class UserFactory extends PersistentProxyObjectFactory
{
public function __construct(
private UserPasswordHasherInterface $userPasswordHasher,
) {
parent::__construct();
}

public static function class(): string
{
return User::class;
}

public function withEmail(string $email): self
{
return $this->with(['email' => $email]);
}

public function admin(): self
{
return $this->with(['roles' => ['ROLE_ADMIN']]);
}

public function withPassword(string $password): self
{
return $this->with(['password' => $password]);
}

protected function defaults(): array|callable
{
return [
'email' => self::faker()->email(),
'password' => self::faker()->password(),
'roles' => [],
];
}

protected function initialize(): static
{
return $this
->afterInstantiate(function (User $user): void {
$user->setPassword($this->userPasswordHasher->hashPassword($user, $user->getPassword() ?? ''));
})
;
}
}
46 changes: 46 additions & 0 deletions app/Repository/UserRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace App\Repository;

use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;

/**
* @extends ServiceEntityRepository<User>
*/
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}

/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class));
}

$user->setPassword($newHashedPassword);
$this->getEntityManager()->persist($user);
$this->getEntityManager()->flush();
}
}
29 changes: 29 additions & 0 deletions app/Story/DefaultUsersStory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace App\Story;

use App\Factory\UserFactory;
use Zenstruck\Foundry\Story;

final class DefaultUsersStory extends Story
{
public function build(): void
{
UserFactory::new()
->admin()
->withEmail('[email protected]')
->withPassword('admin')
->create();
}
}
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
"symfony/dependency-injection": "^6.4 || ^7.0",
"symfony/expression-language": "^6.4 || ^7.0",
"symfony/http-kernel": "^6.4 || ^7.0",
"symfony/security-bundle": "^6.4 || ^7.0",
"symfony/security-http": "^6.4 || ^7.0",
"symfony/stopwatch": "^6.4 || ^7.0",
"symfony/twig-bundle": "^6.4 || ^7.0",
"symfony/ux-autocomplete": "^2.17",
Expand Down
1 change: 1 addition & 0 deletions config/bundles.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@
Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Symfony\UX\Autocomplete\AutocompleteBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
];
55 changes: 55 additions & 0 deletions config/packages/security.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
security:
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider

# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall

# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true

form_login:
# "app_login" is the name of the route created previously
login_path: sylius_admin_ui_login
check_path: sylius_admin_ui_login_check
default_target_path: app_admin_conference_index
logout:
path: sylius_admin_ui_logout
target: sylius_admin_ui_login

# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/admin/login, roles: PUBLIC_ACCESS }
- { path: ^/admin/logout, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: PUBLIC_ACCESS }
# - { path: ^/profile, roles: ROLE_USER }

when@test:
security:
password_hashers:
# By default, password hashers are resource intensive and take time. This is
# important to generate secure password hashes. In tests however, secure hashes
# are not important, waste resources and increase test times. The following
# reduces the work factor to the lowest possible values.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon
3 changes: 3 additions & 0 deletions config/routes/security.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
_security_logout:
resource: security.route_loader.logout
type: service
3 changes: 3 additions & 0 deletions config/routes/sylius_admin_ui.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
sylius_admin_ui:
resource: '@SyliusAdminUiBundle/config/routes.php'
prefix: /admin
2 changes: 2 additions & 0 deletions src/AdminUi/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"knplabs/knp-menu-bundle": "^3.0",
"sylius/twig-hooks": "^0.3 || dev-main",
"symfony/http-kernel": "^6.4 || ^7.0",
"symfony/security-bundle": "^6.4 || ^7.0",
"symfony/security-http": "^6.4 || ^7.0",
"symfony/twig-bundle": "^6.4 || ^7.0",
"sylius/resource-bundle": "^1.11 || ^1.12@alpha",
"twig/twig": "^2.15 || ^3.0"
Expand Down
26 changes: 26 additions & 0 deletions src/AdminUi/config/routes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;

return function (RoutingConfigurator $routes): void {
$routes->add('sylius_admin_ui_login', '/login')
->controller('sylius_admin_ui.controller.login')
;

$routes->add('sylius_admin_ui_login_check', '/login_check');
$routes->add('sylius_admin_ui_logout', '/logout')
->methods(['GET']);
};
2 changes: 2 additions & 0 deletions src/AdminUi/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use Sylius\AdminUi\TwigHooks\Hookable\Metadata\RoutingHookableMetadataFactory;

return function (ContainerConfigurator $configurator): void {
$configurator->import('./services/**/**.php');

$services = $configurator->services();

$services->set('sylius_admin_ui.knp.menu_builder', MenuBuilder::class)
Expand Down
Loading

0 comments on commit 474799c

Please sign in to comment.