From 6ddb61b231b94a178b722bfd51d7e6a09d664aa9 Mon Sep 17 00:00:00 2001 From: Akhil Date: Wed, 24 Jul 2024 21:10:48 +0530 Subject: [PATCH 01/18] install vue-hcaptcha --- package-lock.json | 15 +++++++++++++++ package.json | 1 + 2 files changed, 16 insertions(+) diff --git a/package-lock.json b/package-lock.json index 6a1bd3d6..fbfa795e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "ecloud-accounts", "version": "3.1.0", "dependencies": { + "@hcaptcha/vue-hcaptcha": "^1.3.0", "@nextcloud/axios": "^2.1.0", "@nextcloud/dialogs": "^3.2.0", "@nextcloud/initial-state": "^2.0.0", @@ -1786,6 +1787,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@hcaptcha/vue-hcaptcha": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@hcaptcha/vue-hcaptcha/-/vue-hcaptcha-1.3.0.tgz", + "integrity": "sha512-aUSWyhRucgFeBOBUC3nWBZuE0TkeoSH5QIVFwiTLnNsYpIaxD1tKBbI5Tdoy0TdpkuXKsB4KqyElbvoMZ9reGw==", + "peerDependencies": { + "vue": "^2.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", @@ -13001,6 +13010,12 @@ } } }, + "@hcaptcha/vue-hcaptcha": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@hcaptcha/vue-hcaptcha/-/vue-hcaptcha-1.3.0.tgz", + "integrity": "sha512-aUSWyhRucgFeBOBUC3nWBZuE0TkeoSH5QIVFwiTLnNsYpIaxD1tKBbI5Tdoy0TdpkuXKsB4KqyElbvoMZ9reGw==", + "requires": {} + }, "@humanwhocodes/config-array": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", diff --git a/package.json b/package.json index 30435c15..d52e8a92 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "stylelint:fix": "stylelint {src,css}/**/{*.scss,*.css} --fix --allow-empty-input" }, "dependencies": { + "@hcaptcha/vue-hcaptcha": "^1.3.0", "@nextcloud/axios": "^2.1.0", "@nextcloud/dialogs": "^3.2.0", "@nextcloud/initial-state": "^2.0.0", -- GitLab From e1af03d91dcf6552f380e1b0ead6e988cd7cfe25 Mon Sep 17 00:00:00 2001 From: Akhil Date: Wed, 24 Jul 2024 21:46:06 +0530 Subject: [PATCH 02/18] Add setup code for a new component --- lib/Controller/AccountController.php | 24 ++++++++++++++++++++++-- src/signup/HCaptchaForm.vue | 17 +++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 src/signup/HCaptchaForm.vue diff --git a/lib/Controller/AccountController.php b/lib/Controller/AccountController.php index 48d5117f..2f0ff41c 100644 --- a/lib/Controller/AccountController.php +++ b/lib/Controller/AccountController.php @@ -26,6 +26,9 @@ use OCP\ISession; use OCP\IURLGenerator; use OCP\IUserSession; use OCP\L10N\IFactory; +use OCP\AppFramework\Http\ContentSecurityPolicy; +use OCP\AppFramework\Services\IInitialState; + class AccountController extends Controller { protected $appName; @@ -37,10 +40,13 @@ class AccountController extends Controller { private $session; private $userSession; private $urlGenerator; + private $initialState; /** @var IConfig */ private IConfig $config; private const SESSION_USERNAME_CHECK = 'username_check_passed'; private const CAPTCHA_VERIFIED_CHECK = 'captcha_verified'; + private const HCAPTCHA_DOMAINS = ['https://hcaptcha.com', 'https://*.hcaptcha.com']; + private ILogger $logger; public function __construct( $AppName, @@ -53,7 +59,8 @@ class AccountController extends Controller { IURLGenerator $urlGenerator, ISession $session, IConfig $config, - ILogger $logger + ILogger $logger, + IInitialState $initialState ) { parent::__construct($AppName, $request); $this->appName = $AppName; @@ -67,6 +74,7 @@ class AccountController extends Controller { $this->urlGenerator = $urlGenerator; $this->logger = $logger; $this->request = $request; + $this->initialState = $initialState; } /** @@ -84,12 +92,24 @@ class AccountController extends Controller { $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $lang; - return new TemplateResponse( + $response = new TemplateResponse( Application::APP_ID, 'signup', ['appName' => Application::APP_ID, 'lang' => $lang], TemplateResponse::RENDER_AS_GUEST ); + + $csp = new ContentSecurityPolicy(); + foreach (self::HCAPTCHA_DOMAINS as $domain) { + $csp->addAllowedScriptDomain($domain); + $csp->addAllowedFrameDomain($domain); + $csp->addAllowedStyleDomain($domain); + $csp->addAllowedConnectDomain($domain); + } + $response->setContentSecurityPolicy($csp); + $hcaptchaSiteKey = $this->config->getSystemValue(Application::APP_ID . '.hcaptcha_site_key'); + $this->initialState->provideInitialState('hCaptchaSiteKey', $hcaptchaSiteKey); + return $response; } /** diff --git a/src/signup/HCaptchaForm.vue b/src/signup/HCaptchaForm.vue new file mode 100644 index 00000000..20ddfd87 --- /dev/null +++ b/src/signup/HCaptchaForm.vue @@ -0,0 +1,17 @@ + + + -- GitLab From b3dcbcc4e523b7e1371a35a18da6be7e0bc74214 Mon Sep 17 00:00:00 2001 From: Akhil Date: Wed, 24 Jul 2024 22:26:38 +0530 Subject: [PATCH 03/18] Add captcha verify method --- appinfo/routes.php | 4 +- lib/Controller/AccountController.php | 42 +++++++++++++++++++-- lib/Listeners/BeforeUserDeletedListener.php | 2 +- lib/Service/CurlService.php | 10 ++--- lib/Service/HCaptchaService.php | 34 +++++++++++++++++ lib/Service/UserService.php | 8 ++-- package.json | 2 +- src/Signup.vue | 7 +++- src/signup/HCaptchaForm.vue | 17 +++++++-- 9 files changed, 105 insertions(+), 21 deletions(-) create mode 100644 lib/Service/HCaptchaService.php diff --git a/appinfo/routes.php b/appinfo/routes.php index e89d7d76..af8a5565 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -10,7 +10,7 @@ return ['routes' => [ ['name' => 'shop_account#check_shop_email_post_delete', 'url' => '/shop-accounts/check_shop_email_post_delete', 'verb' => 'GET'], [ 'name' => 'user#preflighted_cors', 'url' => '/api/{path}', - 'verb' => 'OPTIONS', 'requirements' => array('path' => '.+') + 'verb' => 'OPTIONS', 'requirements' => ['path' => '.+'] ], [ 'name' => 'beta_user#remove_user_in_group', @@ -29,7 +29,7 @@ return ['routes' => [ ['name' => 'account#index', 'url' => '/accounts/signup', 'verb' => 'GET', 'postfix' => 'signwithoutlang'], ['name' => 'account#create', 'url' => '/accounts/create', 'verb' => 'POST'], ['name' => 'account#captcha', 'url' => '/accounts/captcha', 'verb' => 'GET'], - ['name' => 'account#verify_captcha', 'url' => '/accounts/verify_captcha', 'verb' => 'POST'], + ['name' => 'account#verify_captcha', 'url' => '/accounts/verify_hcaptcha', 'verb' => 'POST'], ['name' => 'account#verify_hcaptcha', 'url' => '/accounts/verify_captcha', 'verb' => 'POST'], ['name' => 'account#check_username_available', 'url' => '/accounts/check_username_available', 'verb' => 'POST'], ]]; diff --git a/lib/Controller/AccountController.php b/lib/Controller/AccountController.php index 2f0ff41c..5f046080 100644 --- a/lib/Controller/AccountController.php +++ b/lib/Controller/AccountController.php @@ -12,13 +12,16 @@ use OCA\EcloudAccounts\Exception\AddUsernameToCommonStoreException; use OCA\EcloudAccounts\Exception\LDAPUserCreationException; use OCA\EcloudAccounts\Exception\RecoveryEmailValidationException; use OCA\EcloudAccounts\Service\CaptchaService; +use OCA\EcloudAccounts\Service\HCaptchaService; use OCA\EcloudAccounts\Service\NewsLetterService; use OCA\EcloudAccounts\Service\UserService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; +use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Services\IInitialState; use OCP\IConfig; use OCP\ILogger; use OCP\IRequest; @@ -26,9 +29,6 @@ use OCP\ISession; use OCP\IURLGenerator; use OCP\IUserSession; use OCP\L10N\IFactory; -use OCP\AppFramework\Http\ContentSecurityPolicy; -use OCP\AppFramework\Services\IInitialState; - class AccountController extends Controller { protected $appName; @@ -36,6 +36,7 @@ class AccountController extends Controller { private $userService; private $newsletterService; private $captchaService; + private HCaptchaService $hCaptchaService; protected $l10nFactory; private $session; private $userSession; @@ -54,6 +55,7 @@ class AccountController extends Controller { UserService $userService, NewsLetterService $newsletterService, CaptchaService $captchaService, + HCaptchaService $hCaptchaService, IFactory $l10nFactory, IUserSession $userSession, IURLGenerator $urlGenerator, @@ -67,6 +69,7 @@ class AccountController extends Controller { $this->userService = $userService; $this->newsletterService = $newsletterService; $this->captchaService = $captchaService; + $this->hCaptchaService = $hCaptchaService; $this->l10nFactory = $l10nFactory; $this->session = $session; $this->userSession = $userSession; @@ -214,7 +217,7 @@ class AccountController extends Controller { * * @return string|null If validation fails, a string describing the error; otherwise, null. */ - public function validateInput(string $inputName, string $value, int $maxLength = null) : ?string { + public function validateInput(string $inputName, string $value, ?int $maxLength = null) : ?string { if ($value === '') { return "$inputName is required."; } @@ -298,4 +301,35 @@ class AccountController extends Controller { return $response; } + /** + * Verify against hCaptcha Service + * + * @NoAdminRequired + * @PublicPage + * @NoCSRFRequired + * + * @param string $captchaInput The user-provided human verification input. + * + * @return \OCP\AppFramework\Http\DataResponse + */ + public function verifyHcaptcha(string $token = '', string $ekey = '') : DataResponse { + $response = new DataResponse(); + $captchaToken = $this->config->getSystemValue('bypass_captcha_token', ''); + // Initialize the default status to 400 (Bad Request) + $response->setStatus(400); + // Check if the input matches the bypass token or the stored captcha result + $captchaResult = (string) $this->session->get(CaptchaService::CAPTCHA_RESULT_KEY, ''); + if ((!empty($captchaToken) && $bypassToken === $captchaToken) || (!empty($captchaResult) && $captchaInput === $captchaResult)) { + $this->session->set(self::CAPTCHA_VERIFIED_CHECK, true); + $response->setStatus(200); + } + + if ($this->hCaptchaService->verify($token)) { + $this->session->set(self::CAPTCHA_VERIFIED_CHECK, true); + $response->setStatus(200); + } + + return $response; + } + } diff --git a/lib/Listeners/BeforeUserDeletedListener.php b/lib/Listeners/BeforeUserDeletedListener.php index aa4a21eb..a2ab5007 100644 --- a/lib/Listeners/BeforeUserDeletedListener.php +++ b/lib/Listeners/BeforeUserDeletedListener.php @@ -46,7 +46,7 @@ class BeforeUserDeletedListener implements IEventListener { $isUserOnLDAP = $this->LDAPConnectionService->isUserOnLDAPBackend($user); try { - $this->logger->info("PostDelete user {user}", array('user' => $uid)); + $this->logger->info("PostDelete user {user}", ['user' => $uid]); $this->userService->deleteEmailAccount($email); } catch (Exception $e) { $this->logger->error('Error deleting mail folder for user '. $uid . ' :' . $e->getMessage()); diff --git a/lib/Service/CurlService.php b/lib/Service/CurlService.php index 2c6678da..090e7744 100644 --- a/lib/Service/CurlService.php +++ b/lib/Service/CurlService.php @@ -25,7 +25,7 @@ class CurlService { * @param array $userOptions * @return mixed */ - public function get($url, $params = array(), $headers = array(), $userOptions = array()) { + public function get($url, $params = [], $headers = [], $userOptions = []) { return $this->request('GET', $url, $params, $headers, $userOptions); } @@ -38,7 +38,7 @@ class CurlService { * @param array $userOptions * @return mixed */ - public function post($url, $params = array(), $headers = array(), $userOptions = array()) { + public function post($url, $params = [], $headers = [], $userOptions = []) { return $this->request('POST', $url, $params, $headers, $userOptions); } @@ -88,13 +88,13 @@ class CurlService { * @return mixed * @throws Exception */ - private function request($method, $url, $params = array(), $headers = array(), $userOptions = array()) { + private function request($method, $url, $params = [], $headers = [], $userOptions = []) { $ch = curl_init(); $method = strtoupper($method); - $options = array( + $options = [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers - ); + ]; foreach ($userOptions as $key => $value) { $options[$key] = $value; } diff --git a/lib/Service/HCaptchaService.php b/lib/Service/HCaptchaService.php new file mode 100644 index 00000000..6668dd08 --- /dev/null +++ b/lib/Service/HCaptchaService.php @@ -0,0 +1,34 @@ +session = $session; + $this->config = $config; + $this->curl = $curlService; + } + + public function verify(string $token) : bool { + $secret = $this->config->getSystemValue('ecloud-accounts.hcaptcha_secret'); + $data = [ + 'response' => $token, + 'secret' => $secret + ]; + + $data = http_build_query($data); + $response = $this->curl->post(self::VERIFY_URL, $data); + $response = json_decode($response, true); + + return $response['success']; + } +} diff --git a/lib/Service/UserService.php b/lib/Service/UserService.php index fea70cb8..7cca2d81 100644 --- a/lib/Service/UserService.php +++ b/lib/Service/UserService.php @@ -311,9 +311,9 @@ class UserService { $endpoint = $commonApiVersion . '/aliases/hide-my-email/'; $url = $commonServicesURL . $endpoint . $resultmail; - $data = array( + $data = [ "domain" => $aliasDomain - ); + ]; $headers = [ "Authorization: Bearer $token" ]; @@ -348,10 +348,10 @@ class UserService { $endpoint = $commonApiVersion . '/aliases/'; $url = $commonServicesURL . $endpoint . $userEmail; - $data = array( + $data = [ "alias" => $username, "domain" => $domain - ); + ]; $headers = [ "Authorization: Bearer $token" ]; diff --git a/package.json b/package.json index d52e8a92..7d599379 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ecloud-accounts", - "version": "3.1.0", + "version": "6.1.1", "description": "App for ecloud account management.", "repository": { "type": "git", diff --git a/src/Signup.vue b/src/Signup.vue index 82e3158c..a40db032 100644 --- a/src/Signup.vue +++ b/src/Signup.vue @@ -3,7 +3,10 @@
- + @@ -18,6 +21,7 @@ import Axios from '@nextcloud/axios' import { showSuccess, showError } from '@nextcloud/dialogs' import { generateUrl } from '@nextcloud/router' import RegistrationForm from './signup/RegistrationForm.vue' +import HCaptchaForm from './signup/HCaptchaForm.vue' import CaptchaForm from './signup/CaptchaForm.vue' import RecoveryEmailForm from './signup/RecoveryEmailForm.vue' import SuccessSection from './signup/SuccessSection.vue' @@ -31,6 +35,7 @@ export default { CaptchaForm, RecoveryEmailForm, SuccessSection, + HCaptchaForm, }, data() { return { diff --git a/src/signup/HCaptchaForm.vue b/src/signup/HCaptchaForm.vue index 20ddfd87..69a03c0e 100644 --- a/src/signup/HCaptchaForm.vue +++ b/src/signup/HCaptchaForm.vue @@ -1,17 +1,28 @@ -- GitLab From 9d0b911cc4c31e8e92cfae7f9b82ca5e7623d7ea Mon Sep 17 00:00:00 2001 From: Akhil Date: Wed, 24 Jul 2024 23:01:39 +0530 Subject: [PATCH 04/18] Fix component declaration for hcaptcha --- src/signup/HCaptchaForm.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/signup/HCaptchaForm.vue b/src/signup/HCaptchaForm.vue index 69a03c0e..1c468eb5 100644 --- a/src/signup/HCaptchaForm.vue +++ b/src/signup/HCaptchaForm.vue @@ -1,15 +1,19 @@ + + -- GitLab From 292a279d3d7022063a91fad32b3069c07293504a Mon Sep 17 00:00:00 2001 From: Akhil Date: Thu, 25 Jul 2024 14:26:38 +0530 Subject: [PATCH 09/18] Add captcha provider config option --- lib/Controller/AccountController.php | 59 +++++++++++++++++++--------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/lib/Controller/AccountController.php b/lib/Controller/AccountController.php index 5f046080..aefd660a 100644 --- a/lib/Controller/AccountController.php +++ b/lib/Controller/AccountController.php @@ -17,7 +17,6 @@ use OCA\EcloudAccounts\Service\NewsLetterService; use OCA\EcloudAccounts\Service\UserService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; -use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\TemplateResponse; @@ -46,6 +45,9 @@ class AccountController extends Controller { private IConfig $config; private const SESSION_USERNAME_CHECK = 'username_check_passed'; private const CAPTCHA_VERIFIED_CHECK = 'captcha_verified'; + private const ALLOWED_CAPTCHA_PROVIDERS = ['image', 'hcaptcha']; + private const DEFAULT_CAPTCHA_PROVIDER = 'image'; + private const HCAPTCHA_PROVIDER = 'hcaptcha'; private const HCAPTCHA_DOMAINS = ['https://hcaptcha.com', 'https://*.hcaptcha.com']; private ILogger $logger; @@ -102,16 +104,21 @@ class AccountController extends Controller { TemplateResponse::RENDER_AS_GUEST ); - $csp = new ContentSecurityPolicy(); - foreach (self::HCAPTCHA_DOMAINS as $domain) { - $csp->addAllowedScriptDomain($domain); - $csp->addAllowedFrameDomain($domain); - $csp->addAllowedStyleDomain($domain); - $csp->addAllowedConnectDomain($domain); + $captchaProvider = $this->getCaptchaProvider(); + $this->initialState->provideInitialState('captchaProvider', $captchaProvider); + + if ($captchaProvider === self::HCAPTCHA_PROVIDER) { + $csp = $response->getContentSecurityPolicy(); + foreach (self::HCAPTCHA_DOMAINS as $domain) { + $csp->addAllowedScriptDomain($domain); + $csp->addAllowedFrameDomain($domain); + $csp->addAllowedStyleDomain($domain); + $csp->addAllowedConnectDomain($domain); + } + $response->setContentSecurityPolicy($csp); + $hcaptchaSiteKey = $this->config->getSystemValue(Application::APP_ID . '.hcaptcha_site_key'); + $this->initialState->provideInitialState('hCaptchaSiteKey', $hcaptchaSiteKey); } - $response->setContentSecurityPolicy($csp); - $hcaptchaSiteKey = $this->config->getSystemValue(Application::APP_ID . '.hcaptcha_site_key'); - $this->initialState->provideInitialState('hCaptchaSiteKey', $hcaptchaSiteKey); return $response; } @@ -287,6 +294,11 @@ class AccountController extends Controller { */ public function verifyCaptcha(string $captchaInput = '', string $bypassToken = '') : DataResponse { $response = new DataResponse(); + if ($this->getCaptchaProvider() !== self::DEFAULT_CAPTCHA_PROVIDER) { + $response->setStatus(400); + return $response; + } + $captchaToken = $this->config->getSystemValue('bypass_captcha_token', ''); // Initialize the default status to 400 (Bad Request) $response->setStatus(400); @@ -312,19 +324,19 @@ class AccountController extends Controller { * * @return \OCP\AppFramework\Http\DataResponse */ - public function verifyHcaptcha(string $token = '', string $ekey = '') : DataResponse { + public function verifyHcaptcha(string $token = '', string $bypassToken = '') : DataResponse { $response = new DataResponse(); + + if ($this->getCaptchaProvider() !== self::HCAPTCHA_PROVIDER) { + $response->setStatus(400); + return $response; + } + $captchaToken = $this->config->getSystemValue('bypass_captcha_token', ''); // Initialize the default status to 400 (Bad Request) $response->setStatus(400); - // Check if the input matches the bypass token or the stored captcha result - $captchaResult = (string) $this->session->get(CaptchaService::CAPTCHA_RESULT_KEY, ''); - if ((!empty($captchaToken) && $bypassToken === $captchaToken) || (!empty($captchaResult) && $captchaInput === $captchaResult)) { - $this->session->set(self::CAPTCHA_VERIFIED_CHECK, true); - $response->setStatus(200); - } - - if ($this->hCaptchaService->verify($token)) { + // Check if the input matches the bypass token + if ((!empty($captchaToken) && $bypassToken === $captchaToken) || $this->hCaptchaService->verify($token)) { $this->session->set(self::CAPTCHA_VERIFIED_CHECK, true); $response->setStatus(200); } @@ -332,4 +344,13 @@ class AccountController extends Controller { return $response; } + private function getCaptchaProvider() : string { + $captchaProvider = $this->config->getSystemValue('ecloud-accounts.captcha_provider', self::DEFAULT_CAPTCHA); + + if (!in_array($captchaProvider, self::ALLOWED_CAPTCHA_PROVIDERS)) { + $captchaProvider = self::DEFAULT_CAPTCHA_PROVIDER; + } + return $captchaProvider; + } + } -- GitLab From 716b7cf6810469d80144d40da8b3292f8c30f9bc Mon Sep 17 00:00:00 2001 From: Akhil Date: Thu, 25 Jul 2024 14:40:45 +0530 Subject: [PATCH 10/18] Keep only verify_captcha route --- appinfo/routes.php | 1 - lib/Controller/AccountController.php | 60 ++++++++++------------------ src/signup/CaptchaForm.vue | 2 +- src/signup/HCaptchaForm.vue | 4 +- 4 files changed, 24 insertions(+), 43 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index 87b0f67a..eeeec68a 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -30,7 +30,6 @@ return ['routes' => [ ['name' => 'account#create', 'url' => '/accounts/create', 'verb' => 'POST'], ['name' => 'account#captcha', 'url' => '/accounts/captcha', 'verb' => 'GET'], ['name' => 'account#verify_captcha', 'url' => '/accounts/verify_captcha', 'verb' => 'POST'], - ['name' => 'account#verify_hcaptcha', 'url' => '/accounts/verify_hcaptcha', 'verb' => 'POST'], ['name' => 'account#check_username_available', 'url' => '/accounts/check_username_available', 'verb' => 'POST'], ]]; diff --git a/lib/Controller/AccountController.php b/lib/Controller/AccountController.php index aefd660a..1b7ec1ec 100644 --- a/lib/Controller/AccountController.php +++ b/lib/Controller/AccountController.php @@ -288,62 +288,44 @@ class AccountController extends Controller { * @PublicPage * @NoCSRFRequired * - * @param string $captchaInput The user-provided human verification input. + * @param string $token The user-provided human verification input. + * @param string $bypassToken Token to bypass captcha for automation testing * * @return \OCP\AppFramework\Http\DataResponse */ - public function verifyCaptcha(string $captchaInput = '', string $bypassToken = '') : DataResponse { + public function verifyCaptcha(string $userToken = '', string $bypassToken = '') : DataResponse { $response = new DataResponse(); - if ($this->getCaptchaProvider() !== self::DEFAULT_CAPTCHA_PROVIDER) { - $response->setStatus(400); - return $response; - } - - $captchaToken = $this->config->getSystemValue('bypass_captcha_token', ''); - // Initialize the default status to 400 (Bad Request) - $response->setStatus(400); - // Check if the input matches the bypass token or the stored captcha result - $captchaResult = (string) $this->session->get(CaptchaService::CAPTCHA_RESULT_KEY, ''); - if ((!empty($captchaToken) && $bypassToken === $captchaToken) || (!empty($captchaResult) && $captchaInput === $captchaResult)) { + + // Check if the input matches the bypass token + $bypassTokenInConfig = $this->config->getSystemValue('bypass_captcha_token', ''); + if ((!empty($bypassTokenInConfig) && $bypassToken === $bypassTokenInConfig)) { $this->session->set(self::CAPTCHA_VERIFIED_CHECK, true); $response->setStatus(200); } - $this->session->remove(CaptchaService::CAPTCHA_RESULT_KEY); - return $response; - } - - /** - * Verify against hCaptcha Service - * - * @NoAdminRequired - * @PublicPage - * @NoCSRFRequired - * - * @param string $captchaInput The user-provided human verification input. - * - * @return \OCP\AppFramework\Http\DataResponse - */ - public function verifyHcaptcha(string $token = '', string $bypassToken = '') : DataResponse { - $response = new DataResponse(); + $response->setStatus(400); + $captchaProvider = $this->getCaptchaProvider(); - if ($this->getCaptchaProvider() !== self::HCAPTCHA_PROVIDER) { - $response->setStatus(400); - return $response; + // Check for default captcha provider + if ($captchaProvider === self::DEFAULT_CAPTCHA_PROVIDER && $this->verifyImageCaptcha($userToken)) { + $this->session->set(self::CAPTCHA_VERIFIED_CHECK, true); + $this->session->remove(CaptchaService::CAPTCHA_RESULT_KEY); + $response->setStatus(200); } - $captchaToken = $this->config->getSystemValue('bypass_captcha_token', ''); - // Initialize the default status to 400 (Bad Request) - $response->setStatus(400); - // Check if the input matches the bypass token - if ((!empty($captchaToken) && $bypassToken === $captchaToken) || $this->hCaptchaService->verify($token)) { + // Check for hcaptcha provider + if ($captchaProvider === self::HCAPTCHA_PROVIDER && $this->hCaptchaService->verify($userToken)) { $this->session->set(self::CAPTCHA_VERIFIED_CHECK, true); $response->setStatus(200); } - return $response; } + private function verifyImageCaptcha(string $captchaInput = '') : bool { + $captchaResult = (string) $this->session->get(CaptchaService::CAPTCHA_RESULT_KEY, ''); + return (!empty($captchaResult) && $captchaInput === $captchaResult); + } + private function getCaptchaProvider() : string { $captchaProvider = $this->config->getSystemValue('ecloud-accounts.captcha_provider', self::DEFAULT_CAPTCHA); diff --git a/src/signup/CaptchaForm.vue b/src/signup/CaptchaForm.vue index 5085b8d8..a919ba32 100644 --- a/src/signup/CaptchaForm.vue +++ b/src/signup/CaptchaForm.vue @@ -88,7 +88,7 @@ export default { const urlParams = new URLSearchParams(window.location.search) const bypassToken = urlParams.get('bypassToken') const data = { - captchaInput: this.formData.captchaInput, + userToken: this.formData.captchaInput, bypassToken: bypassToken || null, } const url = generateUrl(`/apps/${this.appName}/accounts/verify_captcha`) diff --git a/src/signup/HCaptchaForm.vue b/src/signup/HCaptchaForm.vue index be462bc7..0ec2796c 100644 --- a/src/signup/HCaptchaForm.vue +++ b/src/signup/HCaptchaForm.vue @@ -28,8 +28,8 @@ export default { }, methods: { async onVerify(token, ekey) { - const url = generateUrl(`/apps/${APPLICATION_NAME}/accounts/verify_hcaptcha`) - await Axios.post(url, { token, ekey }) + const url = generateUrl(`/apps/${APPLICATION_NAME}/accounts/verify_captcha`) + await Axios.post(url, { userToken: token }) const isFormValid = true this.$emit('form-submitted', { isFormValid }) -- GitLab From 142a0769cf24c82a69bc0c4e760f6a8c448d0124 Mon Sep 17 00:00:00 2001 From: Akhil Date: Thu, 25 Jul 2024 14:46:43 +0530 Subject: [PATCH 11/18] Change signup.vue to use provider set in state --- src/Signup.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Signup.vue b/src/Signup.vue index a40db032..125ca934 100644 --- a/src/Signup.vue +++ b/src/Signup.vue @@ -3,10 +3,10 @@
- - @@ -51,6 +51,7 @@ export default { newsletterProduct: false, selectedLanguage: 'en', }, + captchaProvider: loadState(APPLICATION_NAME, 'captchaProvider'), appName: APPLICATION_NAME, showRegistrationForm: true, showCaptchaForm: false, -- GitLab From 4119e0475fb10ee63d75f5d0f4e88fccd4ae3472 Mon Sep 17 00:00:00 2001 From: Akhil Date: Thu, 25 Jul 2024 14:49:08 +0530 Subject: [PATCH 12/18] Don't allow requests to image captcha if provider is different --- lib/Controller/AccountController.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/Controller/AccountController.php b/lib/Controller/AccountController.php index 1b7ec1ec..433579b5 100644 --- a/lib/Controller/AccountController.php +++ b/lib/Controller/AccountController.php @@ -275,10 +275,15 @@ class AccountController extends Controller { * @NoCSRFRequired */ public function captcha(): Http\DataDisplayResponse { - $captchaValue = $this->captchaService->generateCaptcha(); + // Don't allow requests to image captcha if different provider is set + if ($this->getCaptchaProvider() !== self::DEFAULT_CAPTCHA_PROVDER) { + $response = new DataResponse(); + $response->setStatus(400); + return $response; + } + $captchaValue = $this->captchaService->generateCaptcha(); $response = new Http\DataDisplayResponse($captchaValue, Http::STATUS_OK, ['Content-Type' => 'image/png']); - return $response; } /** -- GitLab From d361a4c3a64ec4910effa45b136266afdfb38351 Mon Sep 17 00:00:00 2001 From: Akhil Date: Thu, 25 Jul 2024 14:50:37 +0530 Subject: [PATCH 13/18] import loadState in Signup --- src/Signup.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Signup.vue b/src/Signup.vue index 125ca934..c7b08215 100644 --- a/src/Signup.vue +++ b/src/Signup.vue @@ -19,6 +19,7 @@