initial commit

This commit is contained in:
boris
2025-09-30 09:24:25 +01:00
committed by boris
parent a783a12c97
commit c7770ea03b
4695 changed files with 525784 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
class <?= $class_name ?> implements UserProviderInterface, PasswordUpgraderInterface
{
/**
* Symfony calls this method if you use features like switch_user
* or remember_me.
*
* If you're not using these features, you do not need to implement
* this method.
*
* @throws UserNotFoundException if the user is not found
*/
public function loadUserByIdentifier($identifier): UserInterface
{
// Load a User object from your data source or throw UserNotFoundException.
// The $identifier argument may not actually be a username:
// it is whatever value is being returned by the getUserIdentifier()
// method in your User class.
throw new \Exception('TODO: fill in loadUserByIdentifier() inside '.__FILE__);
}
/**
* @deprecated since Symfony 5.3, loadUserByIdentifier() is used instead
*/
public function loadUserByUsername($username): UserInterface
{
return $this->loadUserByIdentifier($username);
}
/**
* Refreshes the user after being reloaded from the session.
*
* When a user is logged in, at the beginning of each request, the
* User object is loaded from the session and then this method is
* called. Your job is to make sure the user's data is still fresh by,
* for example, re-querying for fresh User data.
*
* If your firewall is "stateless: true" (for a pure API), this
* method is not called.
*/
public function refreshUser(UserInterface $user): UserInterface
{
if (!$user instanceof <?= $user_short_name ?>) {
throw new UnsupportedUserException(sprintf('Invalid user class "%s".', $user::class));
}
// Return a User object after making sure its data is "fresh".
// Or throw a UsernameNotFoundException if the user no longer exists.
throw new \Exception('TODO: fill in refreshUser() inside '.__FILE__);
}
/**
* Tells Symfony to use this provider for this User class.
*/
public function supportsClass(string $class): bool
{
return <?= $user_short_name ?>::class === $class || is_subclass_of($class, <?= $user_short_name ?>::class);
}
/**
* Upgrades the hashed password of a user, typically for using a better hash algorithm.
*/
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
// TODO: when hashed passwords are in use, this method should:
// 1. persist the new password in the user storage
// 2. update the $user object with $user->setPassword($newHashedPassword);
}
}

View File

@@ -0,0 +1,43 @@
<?= "<?php\n" ?>
namespace <?= $class_data->getNamespace(); ?>;
<?= $class_data->getUseStatements(); ?>
<?= $class_data->getClassDeclaration() ?>
{
public const EDIT = 'POST_EDIT';
public const VIEW = 'POST_VIEW';
protected function supports(string $attribute, mixed $subject): bool
{
// replace with your own logic
// https://symfony.com/doc/current/security/voters.html
return in_array($attribute, [self::EDIT, self::VIEW])
&& $subject instanceof \App\Entity\<?= str_replace('Voter', null, $class_data->getClassName()) ?>;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
// if the user is anonymous, do not grant access
if (!$user instanceof UserInterface) {
return false;
}
// ... (check conditions and return true to grant permission) ...
switch ($attribute) {
case self::EDIT:
// logic to determine if the user can EDIT
// return true or false
break;
case self::VIEW:
// logic to determine if the user can VIEW
// return true or false
break;
}
return false;
}
}

View File

@@ -0,0 +1,67 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
/**
* @see https://symfony.com/doc/current/security/custom_authenticator.html
*/
class <?= $class_short_name ?> extends AbstractAuthenticator
{
/**
* Called on every request to decide if this authenticator should be
* used for the request. Returning `false` will cause this authenticator
* to be skipped.
*/
public function supports(Request $request): ?bool
{
// return $request->headers->has('X-AUTH-TOKEN');
}
public function authenticate(Request $request): Passport
{
// $apiToken = $request->headers->get('X-AUTH-TOKEN');
// if (null === $apiToken) {
// The token header was empty, authentication fails with HTTP Status
// Code 401 "Unauthorized"
// throw new CustomUserMessageAuthenticationException('No API token provided');
// }
// implement your own logic to get the user identifier from `$apiToken`
// e.g. by looking up a user in the database using its API key
// $userIdentifier = /** ... */;
// return new SelfValidatingPassport(new UserBadge($userIdentifier));
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
// on success, let the request continue
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
$data = [
// you may want to customize or obfuscate the message first
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
// or to translate this message
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
// public function start(Request $request, ?AuthenticationException $authException = null): Response
// {
// /*
// * If you would like this class to control what happens when an anonymous user accesses a
// * protected page (e.g. redirect to /login), uncomment this method and make this class
// * implement Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface.
// *
// * For more details, see https://symfony.com/doc/current/security/experimental_authenticators.html#configuring-the-authentication-entry-point
// */
// }
}

View File

@@ -0,0 +1,23 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
class <?= $controller_name ?> extends AbstractController
{
#[Route(path: '/login', name: 'app_login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('<?= $template_path ?>/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
]);
}
}

View File

@@ -0,0 +1,79 @@
<?= "<?php\n" ?>
namespace App\Tests;
<?= $use_statements ?>
class LoginControllerTest extends WebTestCase
{
private KernelBrowser $client;
protected function setUp(): void
{
$this->client = static::createClient();
$container = static::getContainer();
$em = $container->get('doctrine.orm.entity_manager');
$userRepository = $em->getRepository(<?= $user_short_name ?>::class);
// Remove any existing users from the test database
foreach ($userRepository->findAll() as $user) {
$em->remove($user);
}
$em->flush();
// Create a <?= $user_short_name ?> fixture
/** @var UserPasswordHasherInterface $passwordHasher */
$passwordHasher = $container->get('security.user_password_hasher');
$user = (new <?= $user_short_name ?>())->setEmail('email@example.com');
$user->setPassword($passwordHasher->hashPassword($user, 'password'));
$em->persist($user);
$em->flush();
}
public function testLogin(): void
{
// Denied - Can't login with invalid email address.
$this->client->request('GET', '/login');
self::assertResponseIsSuccessful();
$this->client->submitForm('Sign in', [
'_username' => 'doesNotExist@example.com',
'_password' => 'password',
]);
self::assertResponseRedirects('/login');
$this->client->followRedirect();
// Ensure we do not reveal if the user exists or not.
self::assertSelectorTextContains('.alert-danger', 'Invalid credentials.');
// Denied - Can't login with invalid password.
$this->client->request('GET', '/login');
self::assertResponseIsSuccessful();
$this->client->submitForm('Sign in', [
'_username' => 'email@example.com',
'_password' => 'bad-password',
]);
self::assertResponseRedirects('/login');
$this->client->followRedirect();
// Ensure we do not reveal the user exists but the password is wrong.
self::assertSelectorTextContains('.alert-danger', 'Invalid credentials.');
// Success - Login with valid credentials is allowed.
$this->client->submitForm('Sign in', [
'_username' => 'email@example.com',
'_password' => 'password',
]);
self::assertResponseRedirects('/');
$this->client->followRedirect();
self::assertSelectorNotExists('.alert-danger');
self::assertResponseIsSuccessful();
}
}

View File

@@ -0,0 +1,40 @@
{% extends 'base.html.twig' %}
{% block title %}Log in!{% endblock %}
{% block body %}
<form method="post">
{% if error %}
<div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
<?php if ($logout_setup): ?>
{% if app.user %}
<div class="mb-3">
You are logged in as {{ app.user.userIdentifier }}, <a href="{{ path('app_logout') }}">Logout</a>
</div>
{% endif %}
<?php endif; ?>
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<label for="username"><?= $username_label; ?></label>
<input type="<?= $username_is_email ? 'email' : 'text'; ?>" value="{{ last_username }}" name="_username" id="username" class="form-control" autocomplete="<?= $username_is_email ? 'email' : 'username'; ?>" required autofocus>
<label for="password">Password</label>
<input type="password" name="_password" id="password" class="form-control" autocomplete="current-password" required>
<input type="hidden" name="_csrf_token" data-controller="csrf-protection" value="{{ csrf_token('authenticate') }}">
{#
Uncomment this section and add a remember_me option below your firewall to activate remember me functionality.
See https://symfony.com/doc/current/security/remember_me.html
<div class="checkbox mb-3">
<input type="checkbox" name="_remember_me" id="_remember_me">
<label for="_remember_me">Remember me</label>
</div>
#}
<button class="btn btn-lg btn-primary" type="submit">
Sign in
</button>
</form>
{% endblock %}