Internal: Make password edition a single independent page - refs BT#21546

pull/5597/head
christianbeeznst 5 months ago
parent fbdcc5415b
commit 2e4ab279d2
  1. 12
      assets/vue/components/social/UserProfileCard.vue
  2. 44
      src/CoreBundle/Controller/AccountController.php
  3. 43
      src/CoreBundle/Form/ChangePasswordType.php
  4. 5
      src/CoreBundle/Repository/Node/UserRepository.php
  5. 3
      src/CoreBundle/Resources/config/services.yml
  6. 48
      src/CoreBundle/Resources/views/Account/change_password.html.twig

@ -104,6 +104,14 @@
type="primary"
@click="editProfile"
/>
<BaseButton
v-if="isCurrentUser || securityStore.isAdmin"
:label="t('Change Password')"
class="mt-2"
icon="lock"
type="secondary"
@click="changePassword"
/>
</div>
</BaseCard>
</template>
@ -140,6 +148,10 @@ const editProfile = () => {
window.location = "/account/edit"
}
const changePassword = () => {
window.location = "/account/change-password"
}
async function fetchUserProfile(userId) {
try {
const { data } = await axios.get(`/social-network/user-profile/${userId}`)

@ -7,6 +7,7 @@ declare(strict_types=1);
namespace Chamilo\CoreBundle\Controller;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Form\ChangePasswordType;
use Chamilo\CoreBundle\Form\ProfileType;
use Chamilo\CoreBundle\Repository\Node\IllustrationRepository;
use Chamilo\CoreBundle\Repository\Node\UserRepository;
@ -18,6 +19,9 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
/**
* @author Julio Montoya <gugli100@gmail.com>
@ -69,4 +73,44 @@ class AccountController extends BaseController
'user' => $user,
]);
}
#[Route('/change-password', name: 'chamilo_core_account_change_password', methods: ['GET', 'POST'])]
public function changePassword(Request $request, UserRepository $userRepository, CsrfTokenManagerInterface $csrfTokenManager): Response
{
$user = $this->getUser();
if (!\is_object($user) || !$user instanceof UserInterface) {
throw $this->createAccessDeniedException('This user does not have access to this section');
}
$form = $this->createForm(ChangePasswordType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$submittedToken = $request->request->get('_token');
if (!$csrfTokenManager->isTokenValid(new CsrfToken('change_password', $submittedToken))) {
$form->addError(new FormError('CSRF token is invalid. Please try again.'));
} else {
$currentPassword = $form->get('currentPassword')->getData();
$newPassword = $form->get('newPassword')->getData();
$confirmPassword = $form->get('confirmPassword')->getData();
if (!$userRepository->isPasswordValid($user, $currentPassword)) {
$form->get('currentPassword')->addError(new FormError('Current password is incorrect.'));
} elseif ($newPassword !== $confirmPassword) {
$form->get('confirmPassword')->addError(new FormError('Passwords do not match.'));
} else {
$user->setPlainPassword($newPassword);
$userRepository->updateUser($user);
$this->addFlash('success', 'Password changed successfully.');
return $this->redirectToRoute('chamilo_core_account_home');
}
}
}
return $this->render('@ChamiloCore/Account/change_password.html.twig', [
'form' => $form->createView(),
]);
}
}

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/* For licensing terms, see /license.txt */
namespace Chamilo\CoreBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
class ChangePasswordType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('currentPassword', PasswordType::class, [
'label' => 'Current Password',
'required' => true,
])
->add('newPassword', PasswordType::class, [
'label' => 'New Password',
'required' => true,
])
->add('confirmPassword', PasswordType::class, [
'label' => 'Confirm New Password',
'required' => true,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'csrf_protection' => true,
'csrf_field_name' => '_token',
'csrf_token_id' => 'change_password',
]);
}
}

@ -105,6 +105,11 @@ class UserRepository extends ResourceRepository implements PasswordUpgraderInter
}
}
public function isPasswordValid(User $user, string $plainPassword): bool
{
return $this->hasher->isPasswordValid($user, $plainPassword);
}
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
/** @var User $user */

@ -5,6 +5,9 @@ services:
public: true
autoconfigure: true
csrf.token_manager:
class: Symfony\Component\Security\Csrf\CsrfTokenManager
chamilo_core.translation.loader.po:
class: Symfony\Component\Translation\Loader\PoFileLoader
tags:

@ -0,0 +1,48 @@
{% extends "@ChamiloCore/Layout/layout_one_col.html.twig" %}
{% block content %}
<section id="change-password" class="py-8">
<div class="mx-auto w-full">
<h2 class="text-2xl font-semibold text-center mb-6">{{ "Change Password"|trans }}</h2>
{{ form_start(form, {'attr': {'class': 'bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4'}}) }}
{% for message in app.flashes('success') %}
<div class="alert alert-success">
{{ message }}
</div>
{% endfor %}
{% if form.vars.errors|length > 0 %}
<div class="alert alert-danger">
{{ form_errors(form) }}
</div>
{% endif %}
<div class="mb-4">
{{ form_label(form.currentPassword) }}
{{ form_widget(form.currentPassword, {'attr': {'class': 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'}}) }}
{{ form_errors(form.currentPassword) }}
</div>
<div class="mb-4">
{{ form_label(form.newPassword) }}
{{ form_widget(form.newPassword, {'attr': {'class': 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'}}) }}
{{ form_errors(form.newPassword) }}
</div>
<div class="mb-4">
{{ form_label(form.confirmPassword) }}
{{ form_widget(form.confirmPassword, {'attr': {'class': 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'}}) }}
{{ form_errors(form.confirmPassword) }}
</div>
<div class="flex items-center justify-center">
<input type="hidden" name="_token" value="{{ csrf_token('change_password') }}">
<button type="submit" class="btn btn--primary mt-4">{{ "Change Password"|trans }}</button>
</div>
{{ form_end(form) }}
</div>
</section>
{% endblock %}
Loading…
Cancel
Save