From 4a2d2d09e929f030af71fc548e4162df06d06dea Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Fri, 28 Nov 2025 15:38:26 +0100 Subject: [PATCH] Fix account creation when a captcha is required Query Mediawiki API to get a list of available fields and then build the form accordingly. If a captcha is required, add it to the form. Tested with ConfirmEdit (SimpleCaptcha and QuestyCaptcha) https://www.mediawiki.org/wiki/Extension:ConfirmEdit#SimpleCaptcha_(calculation) https://www.mediawiki.org/wiki/Extension:ConfirmEdit#QuestyCaptcha This only works with text-based captchas --- config/module.config.php | 1 + src/Controller/PublicApp/IndexController.php | 5 +- src/Form/CreateAccountForm.php | 191 +++++++++++------- src/Mediawiki/ApiClient.php | 38 ++-- src/Service/Form/CreateAccountFormFactory.php | 18 ++ 5 files changed, 168 insertions(+), 85 deletions(-) create mode 100644 src/Service/Form/CreateAccountFormFactory.php diff --git a/config/module.config.php b/config/module.config.php index e3d63f51..8b7cbf8a 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -33,6 +33,7 @@ 'factories' => [ 'Scripto\Form\Element\MediaTypeSelect' => Scripto\Service\Form\Element\MediaTypeSelectFactory::class, 'Scripto\Form\ModuleConfigForm' => Scripto\Service\Form\ModuleConfigFormFactory::class, + 'Scripto\Form\CreateAccountForm' => Scripto\Service\Form\CreateAccountFormFactory::class, ], ], 'controllers' => [ diff --git a/src/Controller/PublicApp/IndexController.php b/src/Controller/PublicApp/IndexController.php index ba4eadbc..2ca15496 100644 --- a/src/Controller/PublicApp/IndexController.php +++ b/src/Controller/PublicApp/IndexController.php @@ -44,10 +44,7 @@ public function createAccountAction() if ($form->isValid()) { $formData = $form->getData(); try { - $this->scripto()->apiClient()->createAccount( - $formData['username'], $formData['password'], $formData['retype'], - $formData['email'], $formData['realname'] - ); + $this->scripto()->apiClient()->createAccount($formData); $this->messenger()->addSuccess('Your Scripto account has been created! Please check your email for a link to activate your account.'); // @translate return $this->redirect()->toRoute('scripto'); } catch (CreateaccountException $e) { diff --git a/src/Form/CreateAccountForm.php b/src/Form/CreateAccountForm.php index 2d140814..1388132b 100644 --- a/src/Form/CreateAccountForm.php +++ b/src/Form/CreateAccountForm.php @@ -7,53 +7,94 @@ class CreateAccountForm extends Form { public function init() { - $this->add([ - 'type' => 'text', - 'name' => 'username', - 'options' => [ - 'label' => 'Username', // @translate - ], - 'attributes' => [ - 'required' => true, - ], - ]); - $this->add([ - 'type' => 'password', - 'name' => 'password', - 'options' => [ - 'label' => 'Password', // @translate - ], - 'attributes' => [ - 'required' => true, - ], - ]); - $this->add([ - 'type' => 'password', - 'name' => 'retype', - 'options' => [ - 'label' => 'Confirm password', // @translate - ], - 'attributes' => [ - 'required' => true, - ], - ]); - $this->add([ - 'type' => 'email', - 'name' => 'email', - 'options' => [ - 'label' => 'Email address', // @translate - ], - 'attributes' => [ - 'required' => true, - ], - ]); - $this->add([ - 'type' => 'text', - 'name' => 'realname', - 'options' => [ - 'label' => 'Real name', // @translate - ], - ]); + $fields = $this->getOption('fields') ?? []; + + if (isset($fields['username'])) { + $this->add([ + 'type' => 'text', + 'name' => 'username', + 'options' => [ + 'label' => 'Username', // @translate + ], + 'attributes' => [ + 'required' => true, + ], + ]); + } + + if (isset($fields['password'])) { + $this->add([ + 'type' => 'password', + 'name' => 'password', + 'options' => [ + 'label' => 'Password', // @translate + ], + 'attributes' => [ + 'required' => true, + ], + ]); + } + + if (isset($fields['retype'])) { + $this->add([ + 'type' => 'password', + 'name' => 'retype', + 'options' => [ + 'label' => 'Confirm password', // @translate + ], + 'attributes' => [ + 'required' => true, + ], + ]); + } + + if (isset($fields['email'])) { + $this->add([ + 'type' => 'email', + 'name' => 'email', + 'options' => [ + 'label' => 'Email address', // @translate + ], + 'attributes' => [ + 'required' => true, + ], + ]); + } + + if (isset($fields['realname'])) { + $this->add([ + 'type' => 'text', + 'name' => 'realname', + 'options' => [ + 'label' => 'Real name', // @translate + ], + ]); + } + + if (isset($fields['captchaId'])) { + $captchaId = $fields['captchaId']['value']; + $this->add([ + 'name' => 'captchaId', + 'type' => 'hidden', + 'attributes' => [ + 'value' => $captchaId, + ], + ]); + } + + if (isset($fields['captchaWord'])) { + $this->add([ + 'name' => 'captchaWord', + 'type' => 'text', + 'options' => [ + 'label' => sprintf('CAPTCHA: %s', $fields['captchaInfo']['value']), + ], + 'attributes' => [ + 'required' => true, + ], + ]); + } + $this->add([ 'name' => 'submit', 'type' => 'submit', @@ -63,29 +104,41 @@ public function init() ]); $inputFilter = $this->getInputFilter(); - $inputFilter->add([ - 'name' => 'username', - 'required' => true, - ]); - $inputFilter->add([ - 'name' => 'password', - 'required' => true, - ]); - $inputFilter->add([ - 'name' => 'retype', - 'required' => true, - 'validators' => [ - [ - 'name' => 'identical', - 'options' => [ - 'token' => 'password', + + if (isset($fields['username'])) { + $inputFilter->add([ + 'name' => 'username', + 'required' => true, + ]); + } + + if (isset($fields['password'])) { + $inputFilter->add([ + 'name' => 'password', + 'required' => true, + ]); + } + + if (isset($fields['retype'])) { + $inputFilter->add([ + 'name' => 'retype', + 'required' => true, + 'validators' => [ + [ + 'name' => 'identical', + 'options' => [ + 'token' => 'password', + ], ], ], - ], - ]); - $inputFilter->add([ - 'name' => 'email', - 'required' => true, - ]); + ]); + } + + if (isset($fields['email'])) { + $inputFilter->add([ + 'name' => 'email', + 'required' => true, + ]); + } } } diff --git a/src/Mediawiki/ApiClient.php b/src/Mediawiki/ApiClient.php index ab276bf1..687fef4c 100644 --- a/src/Mediawiki/ApiClient.php +++ b/src/Mediawiki/ApiClient.php @@ -901,34 +901,48 @@ public function queryUserInfo() return $this->userInfo; } + /** + * Retrieve the list of fields available to build the user creation form + * + * @return array An associative array of all the available fields + */ + public function getCreateAccountFields() + { + $res = $this->request([ + 'action' => 'query', + 'meta' => 'authmanagerinfo', + 'amirequestsfor' => 'create', + 'amimergerequestfields' => '1', + ]); + + // Some Mediawiki captcha stores need cookies to be set (CaptchaSessionStore) + $this->session->cookies = $this->httpClient->getCookies(); + + return $res['query']['authmanagerinfo']['fields']; + } + /** * Create a MediaWiki account using the default requests. * * @link https://www.mediawiki.org/wiki/API:Account_creation - * @param string $username Username for authentication - * @param string $password Password for authentication - * @param string $retype Retype password - * @param string $email Email address - * @param string $realname Real name of the user + * @param array $data Data to be sent to API for the 'createaccount' + * action. Keys should be the same as the result of + * getCreateAccountFields * @return array The successful create account result */ - public function createAccount($username, $password, $retype, $email, $realname) + public function createAccount(array $data): array { $query = $this->request([ 'action' => 'query', 'meta' => 'tokens', 'type' => 'createaccount', ]); - $createaccount = $this->request([ + $data = array_merge($data, [ 'action' => 'createaccount', 'createreturnurl' => 'http://example.com/', // currently unused but required 'createtoken' => $query['query']['tokens']['createaccounttoken'], - 'username' => $username, - 'password' => $password, - 'retype' => $password, - 'email' => $email, - 'realname' => $realname, ]); + $createaccount = $this->request($data); if (isset($createaccount['error'])) { throw new Exception\CreateaccountException($createaccount['error']['info']); } diff --git a/src/Service/Form/CreateAccountFormFactory.php b/src/Service/Form/CreateAccountFormFactory.php new file mode 100644 index 00000000..60e46d2e --- /dev/null +++ b/src/Service/Form/CreateAccountFormFactory.php @@ -0,0 +1,18 @@ +get('Scripto\Mediawiki\ApiClient'); + + $form = new CreateAccountForm(null, ['fields' => $apiClient->getCreateAccountFields()]); + + return $form; + } +}