2020-07-22 01:58:25 +00:00
|
|
|
<?php
|
|
|
|
|
2021-10-21 14:45:18 +01:00
|
|
|
declare(strict_types = 1);
|
2021-10-10 09:26:18 +01:00
|
|
|
|
2020-07-22 01:58:25 +00:00
|
|
|
namespace App\Controller;
|
|
|
|
|
2020-07-23 14:08:31 +00:00
|
|
|
use App\Core\Controller;
|
2020-07-25 02:06:55 +00:00
|
|
|
use App\Core\DB\DB;
|
2021-10-18 13:22:02 +01:00
|
|
|
use App\Core\Event;
|
2020-07-25 02:06:55 +00:00
|
|
|
use App\Core\Form;
|
2021-11-26 23:31:53 +00:00
|
|
|
use function App\Core\I18n\_m;
|
2021-07-28 21:38:27 +00:00
|
|
|
use App\Core\Log;
|
2021-12-18 04:24:27 +00:00
|
|
|
use App\Core\UserRoles;
|
2021-09-18 03:22:27 +01:00
|
|
|
use App\Entity\Actor;
|
2021-11-26 23:31:53 +00:00
|
|
|
use App\Entity\Feed;
|
2020-07-25 02:06:55 +00:00
|
|
|
use App\Entity\LocalUser;
|
|
|
|
use App\Security\Authenticator;
|
2021-09-06 19:49:03 +01:00
|
|
|
use App\Security\EmailVerifier;
|
|
|
|
use App\Util\Common;
|
2021-10-21 14:45:18 +01:00
|
|
|
use App\Util\Exception\DuplicateFoundException;
|
|
|
|
use App\Util\Exception\EmailTakenException;
|
2021-09-18 03:22:27 +01:00
|
|
|
use App\Util\Exception\NicknameEmptyException;
|
2021-10-18 13:22:02 +01:00
|
|
|
use App\Util\Exception\NicknameException;
|
|
|
|
use App\Util\Exception\NicknameInvalidException;
|
2021-10-10 17:41:30 +01:00
|
|
|
use App\Util\Exception\NicknameNotAllowedException;
|
2021-04-11 11:03:32 +00:00
|
|
|
use App\Util\Exception\NicknameTakenException;
|
2021-09-18 03:22:27 +01:00
|
|
|
use App\Util\Exception\NicknameTooLongException;
|
2021-11-14 23:15:24 +00:00
|
|
|
use App\Util\Exception\NotFoundException;
|
2021-07-28 21:38:27 +00:00
|
|
|
use App\Util\Exception\ServerException;
|
2021-08-06 11:12:10 +00:00
|
|
|
use App\Util\Form\FormFields;
|
2020-07-25 02:06:55 +00:00
|
|
|
use App\Util\Nickname;
|
2022-01-04 23:13:41 +00:00
|
|
|
use Component\Language\Entity\ActorLanguage;
|
2022-01-02 20:37:15 +00:00
|
|
|
use Component\Subscription\Entity\Subscription;
|
2021-04-11 11:03:32 +00:00
|
|
|
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
2021-10-18 13:22:02 +01:00
|
|
|
use LogicException;
|
2020-07-25 02:06:55 +00:00
|
|
|
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
|
|
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
|
|
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
|
|
|
use Symfony\Component\HttpFoundation\Request;
|
2021-09-18 03:22:27 +01:00
|
|
|
use Symfony\Component\HttpFoundation\Response;
|
2021-11-16 14:48:18 +00:00
|
|
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
2020-07-25 02:06:55 +00:00
|
|
|
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
|
2020-07-22 01:58:25 +00:00
|
|
|
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
2020-07-25 02:06:55 +00:00
|
|
|
use Symfony\Component\Validator\Constraints\Length;
|
|
|
|
use Symfony\Component\Validator\Constraints\NotBlank;
|
2020-07-22 01:58:25 +00:00
|
|
|
|
2020-07-23 14:08:31 +00:00
|
|
|
class Security extends Controller
|
2020-07-22 01:58:25 +00:00
|
|
|
{
|
2020-11-06 19:47:15 +00:00
|
|
|
/**
|
|
|
|
* Log a user in
|
|
|
|
*/
|
2020-07-23 17:55:06 +00:00
|
|
|
public function login(AuthenticationUtils $authenticationUtils)
|
2020-07-22 01:58:25 +00:00
|
|
|
{
|
2021-11-16 14:48:18 +00:00
|
|
|
// Skip if already logged in
|
2020-07-22 01:58:25 +00:00
|
|
|
if ($this->getUser()) {
|
2021-12-23 13:27:31 +00:00
|
|
|
return $this->redirectToRoute('root');
|
2020-07-22 01:58:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// get the login error if there is one
|
|
|
|
$error = $authenticationUtils->getLastAuthenticationError();
|
|
|
|
// last username entered by the user
|
2021-07-28 21:38:27 +00:00
|
|
|
$last_login_id = $authenticationUtils->getLastUsername();
|
2020-07-22 01:58:25 +00:00
|
|
|
|
2021-07-28 21:38:27 +00:00
|
|
|
return [
|
2021-10-21 14:45:18 +01:00
|
|
|
'_template' => 'security/login.html.twig',
|
2021-07-28 21:38:27 +00:00
|
|
|
'last_login_id' => $last_login_id,
|
2021-10-21 14:45:18 +01:00
|
|
|
'error' => $error,
|
2021-07-28 21:38:27 +00:00
|
|
|
];
|
2020-07-22 01:58:25 +00:00
|
|
|
}
|
|
|
|
|
2021-07-28 21:38:27 +00:00
|
|
|
/**
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
*/
|
2020-07-22 01:58:25 +00:00
|
|
|
public function logout()
|
|
|
|
{
|
2021-10-10 17:41:30 +01:00
|
|
|
throw new LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
|
2020-07-22 01:58:25 +00:00
|
|
|
}
|
2020-07-25 02:06:55 +00:00
|
|
|
|
2020-11-06 19:47:15 +00:00
|
|
|
/**
|
|
|
|
* Register a user, making sure the nickname is not reserved and
|
|
|
|
* possibly sending a confirmation email
|
2021-09-15 00:25:16 +01:00
|
|
|
*
|
2021-10-10 09:26:18 +01:00
|
|
|
* @throws DuplicateFoundException
|
|
|
|
* @throws EmailTakenException
|
2021-10-21 14:45:18 +01:00
|
|
|
* @throws EmailTakenException
|
2021-09-15 00:25:16 +01:00
|
|
|
* @throws NicknameEmptyException
|
2021-10-21 14:45:18 +01:00
|
|
|
* @throws NicknameException
|
2021-10-18 13:22:02 +01:00
|
|
|
* @throws NicknameInvalidException
|
2021-10-10 17:41:30 +01:00
|
|
|
* @throws NicknameNotAllowedException
|
2021-10-18 13:22:02 +01:00
|
|
|
* @throws NicknameTakenException
|
2021-09-15 00:25:16 +01:00
|
|
|
* @throws NicknameTooLongException
|
2021-10-18 13:22:02 +01:00
|
|
|
* @throws ServerException
|
2020-11-06 19:47:15 +00:00
|
|
|
*/
|
2021-10-21 14:45:18 +01:00
|
|
|
public function register(
|
|
|
|
Request $request,
|
2021-11-16 14:48:18 +00:00
|
|
|
UserPasswordHasherInterface $user_password_hasher,
|
2021-10-21 14:45:18 +01:00
|
|
|
Authenticator $authenticator,
|
2021-11-16 14:48:18 +00:00
|
|
|
GuardAuthenticatorHandler $guard,
|
|
|
|
): array|Response {
|
2020-07-25 02:06:55 +00:00
|
|
|
$form = Form::create([
|
|
|
|
['nickname', TextType::class, [
|
2021-10-21 14:45:18 +01:00
|
|
|
'label' => _m('Nickname'),
|
|
|
|
'help' => _m('Your desired nickname (e.g., j0hnD03)'),
|
2021-07-28 21:06:10 +00:00
|
|
|
'constraints' => [
|
|
|
|
new NotBlank(['message' => _m('Please enter a nickname')]),
|
|
|
|
new Length([
|
2021-10-21 14:45:18 +01:00
|
|
|
'max' => Nickname::MAX_LEN,
|
2021-11-16 14:48:18 +00:00
|
|
|
'maxMessage' => _m(['Your nickname must be at most # characters long'], ['count' => Nickname::MAX_LEN]),
|
|
|
|
]),
|
2020-07-25 02:06:55 +00:00
|
|
|
],
|
2021-10-21 14:45:18 +01:00
|
|
|
'block_name' => 'nickname',
|
|
|
|
'label_attr' => ['class' => 'section-form-label'],
|
2021-09-15 00:25:16 +01:00
|
|
|
'invalid_message' => _m('Nickname not valid. Please provide a valid nickname.'),
|
2020-07-25 02:06:55 +00:00
|
|
|
]],
|
2021-07-28 21:06:10 +00:00
|
|
|
['email', EmailType::class, [
|
2021-10-21 14:45:18 +01:00
|
|
|
'label' => _m('Email'),
|
|
|
|
'help' => _m('Desired email for this account (e.g., john@provider.com)'),
|
|
|
|
'constraints' => [new NotBlank(['message' => _m('Please enter an email')])],
|
|
|
|
'block_name' => 'email',
|
|
|
|
'label_attr' => ['class' => 'section-form-label'],
|
2021-09-15 00:25:16 +01:00
|
|
|
'invalid_message' => _m('Email not valid. Please provide a valid email.'),
|
2021-11-16 14:48:18 +00:00
|
|
|
'attr' => ['autocomplete' => 'email'],
|
2021-07-28 21:06:10 +00:00
|
|
|
]],
|
2021-11-16 14:48:18 +00:00
|
|
|
FormFields::repeated_password(['attr' => ['autocomplete' => 'new-password']]),
|
2020-07-25 02:06:55 +00:00
|
|
|
['register', SubmitType::class, ['label' => _m('Register')]],
|
2021-08-17 20:51:06 +01:00
|
|
|
], form_options: ['block_prefix' => 'register']);
|
2020-07-25 02:06:55 +00:00
|
|
|
|
|
|
|
$form->handleRequest($request);
|
|
|
|
|
|
|
|
if ($form->isSubmitted() && $form->isValid()) {
|
2021-10-21 14:45:18 +01:00
|
|
|
$data = $form->getData();
|
2020-07-25 02:06:55 +00:00
|
|
|
$data['password'] = $form->get('password')->getData();
|
|
|
|
|
2021-10-10 17:41:30 +01:00
|
|
|
// Already used is checked below
|
2021-11-16 14:48:18 +00:00
|
|
|
$nickname = Nickname::normalize($data['nickname'], check_already_used: false, which: Nickname::CHECK_LOCAL_USER, check_is_allowed: false);
|
2021-11-14 23:15:24 +00:00
|
|
|
|
|
|
|
try {
|
2021-11-16 14:48:18 +00:00
|
|
|
$found_user = DB::findOneBy('local_user', ['or' => ['nickname' => $nickname, 'outgoing_email' => $data['email']]]);
|
|
|
|
if ($found_user->getNickname() === $nickname) {
|
2021-11-14 23:15:24 +00:00
|
|
|
throw new NicknameTakenException($found_user->getActor());
|
|
|
|
} elseif ($found_user->getOutgoingEmail() === $data['email']) {
|
|
|
|
throw new EmailTakenException($found_user->getActor());
|
|
|
|
}
|
|
|
|
unset($found_user);
|
|
|
|
} catch (NotFoundException) {
|
|
|
|
// continue
|
|
|
|
}
|
2020-07-25 02:06:55 +00:00
|
|
|
|
2021-04-11 11:03:32 +00:00
|
|
|
try {
|
2021-07-28 21:38:27 +00:00
|
|
|
// This already checks if the nickname is being used
|
2021-12-18 04:24:27 +00:00
|
|
|
$actor = Actor::create([
|
|
|
|
'nickname' => $nickname,
|
|
|
|
'is_local' => true,
|
2021-12-23 13:27:31 +00:00
|
|
|
'type' => Actor::PERSON,
|
|
|
|
'roles' => UserRoles::USER,
|
2021-12-18 04:24:27 +00:00
|
|
|
]);
|
2021-12-23 13:27:31 +00:00
|
|
|
$user = LocalUser::create([
|
2021-11-16 14:48:18 +00:00
|
|
|
'nickname' => $nickname,
|
2021-04-11 11:03:32 +00:00
|
|
|
'outgoing_email' => $data['email'],
|
|
|
|
'incoming_email' => $data['email'],
|
|
|
|
]);
|
2021-11-16 14:48:18 +00:00
|
|
|
$user->setPassword($user_password_hasher->hashPassword($user, $data['password']));
|
2021-04-23 12:54:25 +00:00
|
|
|
DB::persistWithSameId(
|
|
|
|
$actor,
|
|
|
|
$user,
|
2021-11-26 23:31:53 +00:00
|
|
|
function (int $id) use ($user) {
|
2022-01-02 20:04:52 +00:00
|
|
|
// Self subscription for the Home feed and alike
|
2022-01-02 20:37:15 +00:00
|
|
|
DB::persist(Subscription::create(['subscriber_id' => $id, 'subscribed_id' => $id]));
|
2021-11-26 23:31:53 +00:00
|
|
|
Feed::createDefaultFeeds($id, $user);
|
2022-01-04 23:13:41 +00:00
|
|
|
DB::persist(ActorLanguage::create([
|
|
|
|
'actor_id' => $id,
|
|
|
|
'language_id' => Common::currentLanguage()->getId(),
|
|
|
|
'ordering' => 1,
|
|
|
|
]));
|
2021-11-26 23:31:53 +00:00
|
|
|
},
|
2021-04-23 12:54:25 +00:00
|
|
|
);
|
2021-10-18 13:22:02 +01:00
|
|
|
|
|
|
|
Event::handle('SuccessfulLocalUserRegistration', [$actor, $user]);
|
|
|
|
|
2021-04-23 12:54:25 +00:00
|
|
|
DB::flush();
|
2021-08-03 10:24:01 +00:00
|
|
|
// @codeCoverageIgnoreStart
|
2021-04-11 11:03:32 +00:00
|
|
|
} catch (UniqueConstraintViolationException $e) {
|
2021-07-28 21:38:27 +00:00
|
|
|
// _something_ was duplicated, but since we already check if nickname is in use, we can't tell what went wrong
|
2021-11-14 23:15:24 +00:00
|
|
|
$m = 'An error occurred while trying to register';
|
2021-11-16 14:48:18 +00:00
|
|
|
Log::critical($m . " with nickname: '{$nickname}' and email '{$data['email']}'");
|
2021-11-14 23:15:24 +00:00
|
|
|
throw new ServerException(_m($m), previous: $e);
|
2021-04-11 11:03:32 +00:00
|
|
|
}
|
2021-08-03 10:24:01 +00:00
|
|
|
// @codeCoverageIgnoreEnd
|
2020-07-25 02:06:55 +00:00
|
|
|
|
|
|
|
// generate a signed url and email it to the user
|
2021-08-04 17:48:00 +01:00
|
|
|
if ($_ENV['APP_ENV'] !== 'dev' && Common::config('site', 'use_email')) {
|
2021-08-03 10:24:01 +00:00
|
|
|
// @codeCoverageIgnoreStart
|
2021-11-16 14:48:18 +00:00
|
|
|
// TODO: Implement send confirmation email
|
|
|
|
// (new EmailVerifier())->sendEmailConfirmation($user);
|
2021-10-21 14:45:18 +01:00
|
|
|
// @codeCoverageIgnoreEnd
|
2020-07-25 02:06:55 +00:00
|
|
|
} else {
|
|
|
|
$user->setIsEmailVerified(true);
|
|
|
|
}
|
|
|
|
|
2021-11-16 14:48:18 +00:00
|
|
|
return $guard->authenticateUserAndHandleSuccess(
|
2020-07-25 02:06:55 +00:00
|
|
|
$user,
|
|
|
|
$request,
|
|
|
|
$authenticator,
|
2021-11-16 14:48:18 +00:00
|
|
|
'main',
|
2020-07-25 02:06:55 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return [
|
2021-10-21 14:45:18 +01:00
|
|
|
'_template' => 'security/register.html.twig',
|
2020-07-25 02:06:55 +00:00
|
|
|
'registration_form' => $form->createView(),
|
|
|
|
];
|
|
|
|
}
|
2020-07-22 01:58:25 +00:00
|
|
|
}
|