-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
1,103 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?php | ||
|
||
// This file is part of Bileto. | ||
// Copyright 2022-2024 Probesys | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
|
||
declare(strict_types=1); | ||
|
||
namespace DoctrineMigrations; | ||
|
||
use Doctrine\DBAL\Platforms\MariaDBPlatform; | ||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform; | ||
use Doctrine\DBAL\Schema\Schema; | ||
use Doctrine\Migrations\AbstractMigration; | ||
|
||
// phpcs:disable Generic.Files.LineLength | ||
final class Version20240814123823AddToken extends AbstractMigration | ||
{ | ||
public function getDescription(): string | ||
{ | ||
return 'Add the token table and the users.resetPasswordToken column'; | ||
} | ||
|
||
public function up(Schema $schema): void | ||
{ | ||
$dbPlatform = $this->connection->getDatabasePlatform(); | ||
if ($dbPlatform instanceof PostgreSQLPlatform) { | ||
$this->addSql('CREATE TABLE token (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, created_at TIMESTAMP(0) WITH TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITH TIME ZONE NOT NULL, value VARCHAR(250) NOT NULL, expired_at TIMESTAMP(0) WITH TIME ZONE NOT NULL, description VARCHAR(250) NOT NULL, created_by_id INT DEFAULT NULL, updated_by_id INT DEFAULT NULL, PRIMARY KEY(id))'); | ||
$this->addSql('CREATE UNIQUE INDEX UNIQ_5F37A13B1D775834 ON token (value)'); | ||
$this->addSql('CREATE INDEX IDX_5F37A13BB03A8386 ON token (created_by_id)'); | ||
$this->addSql('CREATE INDEX IDX_5F37A13B896DBBDE ON token (updated_by_id)'); | ||
$this->addSql('ALTER TABLE token ADD CONSTRAINT FK_5F37A13BB03A8386 FOREIGN KEY (created_by_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||
$this->addSql('ALTER TABLE token ADD CONSTRAINT FK_5F37A13B896DBBDE FOREIGN KEY (updated_by_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||
$this->addSql('ALTER TABLE users ADD reset_password_token_id INT DEFAULT NULL'); | ||
$this->addSql('ALTER TABLE users ADD CONSTRAINT FK_1483A5E92C3F5AA9 FOREIGN KEY (reset_password_token_id) REFERENCES token (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E92C3F5AA9 ON users (reset_password_token_id)'); | ||
} elseif ($dbPlatform instanceof MariaDBPlatform) { | ||
$this->addSql('CREATE TABLE token (id INT AUTO_INCREMENT NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, value VARCHAR(250) NOT NULL, expired_at DATETIME NOT NULL, description VARCHAR(250) NOT NULL, created_by_id INT DEFAULT NULL, updated_by_id INT DEFAULT NULL, UNIQUE INDEX UNIQ_5F37A13B1D775834 (value), INDEX IDX_5F37A13BB03A8386 (created_by_id), INDEX IDX_5F37A13B896DBBDE (updated_by_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci`'); | ||
$this->addSql('ALTER TABLE token ADD CONSTRAINT FK_5F37A13BB03A8386 FOREIGN KEY (created_by_id) REFERENCES `users` (id)'); | ||
$this->addSql('ALTER TABLE token ADD CONSTRAINT FK_5F37A13B896DBBDE FOREIGN KEY (updated_by_id) REFERENCES `users` (id)'); | ||
$this->addSql('ALTER TABLE users ADD reset_password_token_id INT DEFAULT NULL'); | ||
$this->addSql('ALTER TABLE users ADD CONSTRAINT FK_1483A5E92C3F5AA9 FOREIGN KEY (reset_password_token_id) REFERENCES token (id) ON DELETE SET NULL'); | ||
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E92C3F5AA9 ON users (reset_password_token_id)'); | ||
} | ||
} | ||
|
||
public function down(Schema $schema): void | ||
{ | ||
$dbPlatform = $this->connection->getDatabasePlatform(); | ||
if ($dbPlatform instanceof PostgreSQLPlatform) { | ||
$this->addSql('ALTER TABLE token DROP CONSTRAINT FK_5F37A13BB03A8386'); | ||
$this->addSql('ALTER TABLE token DROP CONSTRAINT FK_5F37A13B896DBBDE'); | ||
$this->addSql('DROP TABLE token'); | ||
$this->addSql('ALTER TABLE "users" DROP CONSTRAINT FK_1483A5E92C3F5AA9'); | ||
$this->addSql('DROP INDEX UNIQ_1483A5E92C3F5AA9'); | ||
$this->addSql('ALTER TABLE "users" DROP reset_password_token_id'); | ||
} elseif ($dbPlatform instanceof MariaDBPlatform) { | ||
$this->addSql('ALTER TABLE token DROP FOREIGN KEY FK_5F37A13BB03A8386'); | ||
$this->addSql('ALTER TABLE token DROP FOREIGN KEY FK_5F37A13B896DBBDE'); | ||
$this->addSql('DROP TABLE token'); | ||
$this->addSql('ALTER TABLE `users` DROP FOREIGN KEY FK_1483A5E92C3F5AA9'); | ||
$this->addSql('DROP INDEX UNIQ_1483A5E92C3F5AA9 ON `users`'); | ||
$this->addSql('ALTER TABLE `users` DROP reset_password_token_id'); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
<?php | ||
|
||
// This file is part of Bileto. | ||
// Copyright 2022-2024 Probesys | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
|
||
namespace App\Controller; | ||
|
||
use App\Entity; | ||
use App\Form; | ||
use App\Message; | ||
use App\Repository; | ||
use Symfony\Component\DependencyInjection\Attribute\Autowire; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\Messenger\MessageBusInterface; | ||
use Symfony\Component\Routing\Annotation\Route; | ||
|
||
class PasswordsController extends BaseController | ||
{ | ||
#[Route('/passwords/reset', name: 'reset password')] | ||
public function reset( | ||
Request $request, | ||
Repository\UserRepository $userRepository, | ||
MessageBusInterface $bus, | ||
): Response { | ||
$sent = $request->query->getBoolean('sent'); | ||
|
||
$form = $this->createNamedForm('reset_password', Form\Password\ResetForm::class); | ||
$form->handleRequest($request); | ||
|
||
if ($form->isSubmitted() && $form->isValid()) { | ||
$data = $form->getData(); | ||
$user = $data['user']; | ||
|
||
$token = Entity\Token::create( | ||
2, | ||
'hours', | ||
length: 20, | ||
description: "Reset password token for {$user->getEmail()}" | ||
); | ||
$user->setResetPasswordToken($token); | ||
|
||
$userRepository->save($user, true); | ||
|
||
$bus->dispatch(new Message\SendResetPasswordEmail($user->getId())); | ||
|
||
return $this->redirectToRoute('reset password', ['sent' => true]); | ||
} | ||
|
||
return $this->render('passwords/reset.html.twig', [ | ||
'form' => $form, | ||
'emailSent' => $sent, | ||
]); | ||
} | ||
|
||
#[Route('/passwords/{token}/edit', name: 'edit password')] | ||
public function edit( | ||
string $token, | ||
Request $request, | ||
Repository\TokenRepository $tokenRepository, | ||
Repository\UserRepository $userRepository, | ||
#[Autowire(env: 'bool:LDAP_ENABLED')] | ||
bool $ldapEnabled, | ||
): Response { | ||
$user = $userRepository->findOneByResetPasswordToken($token); | ||
|
||
if (!$user) { | ||
throw $this->createNotFoundException('The token does not exist.'); | ||
} | ||
|
||
$resetPasswordToken = $user->getResetPasswordToken(); | ||
|
||
if (!$resetPasswordToken || !$resetPasswordToken->isValid()) { | ||
throw $this->createNotFoundException('The token does not exist.'); | ||
} | ||
|
||
$managedByLdap = $ldapEnabled && $user->getAuthType() === 'ldap'; | ||
|
||
if ($managedByLdap) { | ||
throw $this->createNotFoundException('The user is managed by LDAP.'); | ||
} | ||
|
||
$form = $this->createNamedForm('edit_password', Form\Password\EditForm::class, $user); | ||
$form->handleRequest($request); | ||
|
||
if ($form->isSubmitted() && $form->isValid()) { | ||
$user = $form->getData(); | ||
|
||
$user->setResetPasswordToken(null); | ||
|
||
$userRepository->save($user, true); | ||
|
||
$tokenRepository->remove($resetPasswordToken, true); | ||
|
||
$this->addFlash('password_changed', true); | ||
|
||
return $this->redirectToRoute('login'); | ||
} | ||
|
||
return $this->render('passwords/edit.html.twig', [ | ||
'form' => $form, | ||
'user' => $user, | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
<?php | ||
|
||
// This file is part of Bileto. | ||
// Copyright 2022-2024 Probesys | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
|
||
namespace App\Entity; | ||
|
||
use App\ActivityMonitor; | ||
use App\Repository; | ||
use App\Utils; | ||
use Doctrine\DBAL\Types\Types; | ||
use Doctrine\ORM\Mapping as ORM; | ||
use Symfony\Component\Translation\TranslatableMessage; | ||
use Symfony\Component\Validator\Constraints as Assert; | ||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; | ||
|
||
#[ORM\Entity(repositoryClass: Repository\TokenRepository::class)] | ||
#[UniqueEntity( | ||
fields: 'value', | ||
message: new TranslatableMessage('token.value.already_used', [], 'errors'), | ||
)] | ||
class Token implements ActivityMonitor\MonitorableEntityInterface | ||
{ | ||
use ActivityMonitor\MonitorableEntityTrait; | ||
|
||
#[ORM\Id] | ||
#[ORM\GeneratedValue] | ||
#[ORM\Column] | ||
private ?int $id = null; | ||
|
||
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE)] | ||
private ?\DateTimeImmutable $createdAt = null; | ||
|
||
#[ORM\ManyToOne] | ||
private ?User $createdBy = null; | ||
|
||
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE)] | ||
private ?\DateTimeImmutable $updatedAt = null; | ||
|
||
#[ORM\ManyToOne] | ||
private ?User $updatedBy = null; | ||
|
||
#[ORM\Column(length: 250, unique: true)] | ||
private ?string $value = null; | ||
|
||
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE)] | ||
private ?\DateTimeImmutable $expiredAt = null; | ||
|
||
#[ORM\Column(length: 250)] | ||
private ?string $description = null; | ||
|
||
public static function create(int $number, string $unit, int $length = 20, string $description = ''): self | ||
{ | ||
$token = new self(); | ||
|
||
$value = Utils\Random::hex($length); | ||
$token->setValue($value); | ||
|
||
$expiredAt = Utils\Time::fromNow($number, $unit); | ||
$token->setExpiredAt($expiredAt); | ||
|
||
$token->setDescription($description); | ||
|
||
return $token; | ||
} | ||
|
||
public function __construct() | ||
{ | ||
$this->value = ''; | ||
$this->description = ''; | ||
$this->expiredAt = Utils\Time::now(); | ||
} | ||
|
||
public function getValue(): ?string | ||
{ | ||
return $this->value; | ||
} | ||
|
||
public function setValue(string $value): static | ||
{ | ||
$this->value = $value; | ||
|
||
return $this; | ||
} | ||
|
||
public function getExpiredAt(): ?\DateTimeImmutable | ||
{ | ||
return $this->expiredAt; | ||
} | ||
|
||
public function setExpiredAt(\DateTimeImmutable $expiredAt): static | ||
{ | ||
$this->expiredAt = $expiredAt; | ||
|
||
return $this; | ||
} | ||
|
||
public function isValid(): bool | ||
{ | ||
return Utils\Time::now() < $this->expiredAt; | ||
} | ||
|
||
public function getDescription(): ?string | ||
{ | ||
return $this->description; | ||
} | ||
|
||
public function setDescription(string $description): static | ||
{ | ||
$this->description = $description; | ||
|
||
return $this; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<?php | ||
|
||
// This file is part of Bileto. | ||
// Copyright 2022-2024 Probesys | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
|
||
namespace App\Form\Password; | ||
|
||
use App\Entity; | ||
use Symfony\Component\Form\AbstractType; | ||
use Symfony\Component\Form\Extension\Core\Type; | ||
use Symfony\Component\Form\FormBuilderInterface; | ||
use Symfony\Component\OptionsResolver\OptionsResolver; | ||
|
||
class EditForm extends AbstractType | ||
{ | ||
public function buildForm(FormBuilderInterface $builder, array $options): void | ||
{ | ||
$builder->add('plainPassword', Type\PasswordType::class, [ | ||
'empty_data' => '', | ||
'hash_property_path' => 'password', | ||
'mapped' => false, | ||
]); | ||
} | ||
|
||
public function configureOptions(OptionsResolver $resolver): void | ||
{ | ||
$resolver->setDefaults([ | ||
'data_class' => Entity\User::class, | ||
'csrf_token_id' => 'edit password', | ||
'csrf_message' => 'csrf.invalid', | ||
]); | ||
} | ||
} |
Oops, something went wrong.