{"id":523,"date":"2020-05-24T11:52:43","date_gmt":"2020-05-24T03:52:43","guid":{"rendered":"http:\/\/www.brainfart.sg\/?p=523"},"modified":"2020-05-28T14:38:14","modified_gmt":"2020-05-28T06:38:14","slug":"hcaptcha-on-owncloud","status":"publish","type":"post","link":"https:\/\/www.brainfart.sg\/index.php\/2020\/05\/hcaptcha-on-owncloud\/","title":{"rendered":"hCaptcha on OwnCloud"},"content":{"rendered":"\n<p>A couple of days back I logged into <a rel=\"noreferrer noopener\" href=\"https:\/\/www.cloudflare.com\/\" target=\"_blank\">CloudFlare<\/a> and noticed that they are using a new captcha system on the login page, <a rel=\"noreferrer noopener\" href=\"https:\/\/www.hcaptcha.com\/\" target=\"_blank\">hCaptcha<\/a>.<\/p>\n\n\n\n<p>Now if you have been on the internet long enough, you would have encountered Google&#8217;s <a rel=\"noreferrer noopener\" href=\"https:\/\/www.google.com\/recaptcha\/i\" target=\"_blank\">reCaptcha<\/a> (from CAPTCHA &#8211; Completely Automated Public Turing test to tell Computers and Humans Apart), that annoying pop-up where you either need to type in some squiggly text (last time), click a bunch of picture or just click a checkbox before you can submit that funny cat photo. This is to prove that you are really a human posting the above-mentioned cat photo and not some bot trying to spam or probe the funny cat website.<\/p>\n\n\n\n<p>The idea of hCaptcha is to reduce the footprint of Google on users, considering they have their finger in pretty much every pie. Supposedly, hCaptcha captures a minimal amount of user information since they are not interested in advertising or selling personal data. They tout that they have a better experience but as of now, that doesn&#8217;t seem to be the case, since I trigger the captcha pretty much all the time&#8230; (Maybe I am a bot&#8230;) But I believe as more users use the service, they should be able to better tell bots from humans.<\/p>\n\n\n\n<p>Now back to the meat of this post. After seeing Cloudflare&#8217;s switch to hCaptcha, I figured I could make use of CAPTCHAs to reduce the impact of bots on the services I have exposed to the internet, such as WordPress here and Owncloud and a few other services. Since I&#8217;m the main user of the services, it doesn&#8217;t frustrate me to click on CAPTCHAs. Besides, for every CAPTCHA solved, I get paid (though pretty much next to nothing :P)<\/p>\n\n\n\n<p>WordPress was pretty easy since there is already a <a rel=\"noreferrer noopener\" href=\"https:\/\/wordpress.org\/plugins\/hcaptcha-for-forms-and-more\/\" target=\"_blank\">plugin<\/a> developed by the hCaptcha and the community.<\/p>\n\n\n\n<p>Next was to add hCaptcha in the OwnCloud service that I am running. This is where my problem arise. Apparently, not many people have done CAPTCHA integration on Owncloud, since I couldn&#8217;t find much resource online. The only one I found was this <a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/inwinstack\/owncloud-reCAPTCHA\" target=\"_blank\">github repo<\/a> by a Taiwanese IT Service Provider. However, it seems like they the are self-hosting the reCaptcha javascript and validating the somewhere using javascript. Problem is, I&#8217;m not a developer, much less a JS developer, so for the life of me, I can&#8217;t tell how they are validating the CAPTCHA with reCaptcha. Also, they are self-hosting reCaptcha code, so if they are any updates, they will have to manually update the JS file. Good for air-gapped network, not so good for lazy people like me. So, taking reference from that repo and also the <a rel=\"noreferrer noopener\" href=\"https:\/\/medium.com\/@hCaptcha\/using-hcaptcha-with-php-fc31884aa9ea\" target=\"_blank\">hCaptcha blog post<\/a> on PHP integration , I made my own changes to OwnCloud to enable hCaptcha. Here are the steps to save you some frustration and hours.<br>*Disclaimer: I am a systems administrator by trade and not a developer. What I lack in coding skills, I make up in hacking things to work. So my solution will be very ugly to PHP developers out there. Also, I&#8217;m making changes to OwnCloud core files, so the next update, *poof* all the customisations will be gone.<\/p>\n\n\n\n<p>First things first, sign up for a <a rel=\"noreferrer noopener\" href=\"https:\/\/hCaptcha.com\/?r=2dfd3f9a5e40\" target=\"_blank\">hCaptcha account<\/a> and take note of the site key and secret key. Guard your secret key and don&#8217;t expose it on any public facing page\/script.<\/p>\n\n\n\n<p>Next is to add hCaptcha to the login, this step is pretty simple, just add two lines of HTML to <code>login.php<\/code><br>Take note to input the site key from hCaptcha.<br>I&#8217;ve placed the hCaptcha button after the <code>&lt;fieldset&gt;<\/code> stanza so as not to mess up the existing CSS.<\/p>\n\n\n<pre class=\"brush: xml; highlight: [12,13,18,19]; title: owncloud\/core\/templates\/login.php; notranslate\" title=\"owncloud\/core\/templates\/login.php\">\n&lt;?php \/** @var $l \\OCP\\IL10N *\/ ?&gt;\n&lt;?php\nvendor_script('jsTimezoneDetect\/jstz');\nscript('core', [\n        'visitortimezone',\n        'lostpassword',\n        'login',\n        'browser-update'\n]);\n?&gt;\n\n&lt;!-- Add hCaptcha JavaScript Library --&gt;\n&lt;script src='https:\/\/www.hCaptcha.com\/1\/api.js' async defer&gt;&lt;\/script&gt;\n&lt;!--[if IE 8]&gt;&lt;style&gt;input[type=&quot;checkbox&quot;]{padding:0;}&lt;\/style&gt;&lt;![endif]--&gt;\n&lt;form method=&quot;post&quot; name=&quot;login&quot;&gt;\n...\n        &lt;\/fieldset&gt;\n        &lt;!-- Add hCaptcha Button --&gt;\n        &lt;div class=&quot;h-captcha&quot; data-sitekey=&quot;SITE KEY&quot;&gt;&lt;\/div&gt;\n&lt;\/form&gt;\n<\/pre>\n\n\n\n<p>After adding the hCaptcha, you will find that OwnCloud wouldn&#8217;t load the hCaptcha button, this is due to the OwnCloud CSRF Policy. So the next thing is to grant the hCaptcha domain permission to load scripts. Edit the <code>LoginController.php<\/code> file to add the policy in the <code>showLoginForm()<\/code> function.<\/p>\n\n\n<pre class=\"brush: php; highlight: [10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]; title: owncloud\/core\/Controller\/LoginController.php; notranslate\" title=\"owncloud\/core\/Controller\/LoginController.php\">\n        public function showLoginForm($user, $redirect_url, $remember_login) {\n...\n                if (!empty($redirect_url) &amp;&amp; ($remember_login === null) &amp;&amp;\n                        ($this-&gt;userSession-&gt;isLoggedIn() === false) &amp;&amp;\n                        (\\strpos($this-&gt;urlGenerator-&gt;getAbsoluteURL(\\urldecode($redirect_url)),\n                                        $this-&gt;urlGenerator-&gt;getAbsoluteURL('\/index.php\/f\/')) !== false)) {\n                        $parameters['accessLink'] = true;\n                }\n\n                \/\/ CSRF Policy for hCaptcha\n                $response = new TemplateResponse($this-&gt;appName, 'login', $parameters, 'guest');\n                $csp = new ContentSecurityPolicy();\n                        $csp-&gt;addAllowedImageDomain('*.hcaptcha.com');\n                        $csp-&gt;addAllowedImageDomain('hcaptcha.com');\n                        $csp-&gt;addAllowedMediaDomain('*.hcaptcha.com');\n                        $csp-&gt;addAllowedMediaDomain('hcaptcha.com');\n                        $csp-&gt;addAllowedFrameDomain('*.hcaptcha.com');\n                        $csp-&gt;addAllowedFrameDomain('hcaptcha.com');\n                        $csp-&gt;addAllowedStyleDomain('*.hcaptcha.com');\n                        $csp-&gt;addAllowedStyleDomain('hcaptcha.com');\n                        $csp-&gt;addAllowedScriptDomain('*.hcaptcha.com');\n                        $csp-&gt;addAllowedScriptDomain('hcaptcha.com');\n                $csp-&gt;allowInlineScript(true);\n\n                $response-&gt;setContentSecurityPolicy($csp);\n                return $response;\n\n                \/\/return new TemplateResponse(\n                \/\/      $this-&gt;appName, 'login', $parameters, 'guest'\n                \/\/);\n<\/pre>\n\n\n\n<p>Now that we can load the hCaptcha button, next is to validate the hCaptcha response during login. After performing CAPTCHA check, hCaptcha will include the <code>h-captcha-response<\/code> in form data, so we will validate this response with hCaptcha&#8217;s <code>siteverify<\/code> API. Again, edit the <code>LoginController.php<\/code> file, but this time we are interested in the <code>tryLogin()<\/code> function.<br>Take note to input the secret key from hCaptcha.<\/p>\n\n\n<pre class=\"brush: php; highlight: [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30]; title: owncloud\/core\/Controller\/LoginController.php; notranslate\" title=\"owncloud\/core\/Controller\/LoginController.php\">\n        public function tryLogin($user, $password, $redirect_url, $timezone = null) {\n                \/\/ Check if h-captcha-response is present and validate hCaptcha response\n                if(isset($_POST['h-captcha-response']) &amp;&amp; !empty($_POST['h-captcha-response'])){\n                        $data = array(\n                                'secret' =&gt; &quot;SECRET KEY&quot;,\n                                'response' =&gt; $_POST['h-captcha-response']\n                        );\n                        $verify = curl_init();\n                        curl_setopt($verify, CURLOPT_URL, &quot;https:\/\/hcaptcha.com\/siteverify&quot;);\n                        curl_setopt($verify, CURLOPT_POST, true);\n                        curl_setopt($verify, CURLOPT_POSTFIELDS, http_build_query($data));\n                        curl_setopt($verify, CURLOPT_RETURNTRANSFER, true);\n                        $verifyResponse = curl_exec($verify);\n                        $responseData = json_decode($verifyResponse);\n\n                        if($responseData-&gt;success !== true) {\n                                $this-&gt;session-&gt;set('loginMessages', [\n                                        ['invalidCaptcha'], []\n                                ]);\n                                $args = [];\n                                return new RedirectResponse($this-&gt;urlGenerator-&gt;linkToRoute('core.login.showLoginForm', $args));\n                        }\n                }\n                else {\n                        $this-&gt;session-&gt;set('loginMessages', [\n                                ['invalidCaptcha'], []\n                        ]);\n                        $args = [];\n                        return new RedirectResponse($this-&gt;urlGenerator-&gt;linkToRoute('core.login.showLoginForm', $args));\n                }\n                $originalUser = $user;\n                \/\/ TODO: Add all the insane error handling\n                $loginResult = $this-&gt;userSession-&gt;login($user, $password);\n<\/pre>\n\n\n\n<p>Notice the &#8216;invalidCaptcha&#8217; error handling? We need to code this into <code>login.php<\/code>. Edit <code>login.php<\/code> again.<\/p>\n\n\n<pre class=\"brush: xml; highlight: [8,9,10,11,12,13,14]; title: owncloud\/core\/templates\/login.php; notranslate\" title=\"owncloud\/core\/templates\/login.php\">\n                &lt;?php if (!empty($_['accessLink'])) {\n                ?&gt;\n                        &lt;p class=&quot;warning&quot;&gt;\n                                &lt;?php p($l-&gt;t(&quot;You are trying to access a private link. Please log in first.&quot;)) ?&gt;\n                        &lt;\/p&gt;\n                &lt;?php\n        } ?&gt;\n                &lt;?php if (!empty($_['invalidCaptcha'])) {\n                ?&gt;\n                        &lt;p class=&quot;warning&quot;&gt;\n                                &lt;?php p($l-&gt;t(&quot;Invalid\/Missing Captcha. Please try again.&quot;)) ?&gt;\n                        &lt;\/p&gt;\n                &lt;?php\n        } ?&gt;\n                &lt;?php if ($_['rememberLoginAllowed'] === true) : ?&gt;\n                &lt;div class=&quot;remember-login-container&quot;&gt;\n                        &lt;?php if ($_['rememberLoginState'] === 0) {\n                ?&gt;\n<\/pre>\n\n\n\n<p class=\"has-text-align-left\">And done! Now OwnCloud is protected with hCaptcha!<br><img loading=\"lazy\" width=\"464\" height=\"482\" class=\"wp-image-539\" style=\"width: 400px;\" src=\"http:\/\/www.brainfart.sg\/wp-content\/uploads\/2020\/05\/Owncloud-hCaptcha.png\" alt=\"\"><br>To change the look and feel of the hCaptcha button, you can play with the <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.hcaptcha.com\/configuration#themes\" target=\"_blank\">themes<\/a> of the button.<br>Remember to backup the edited files so that they are not lost when you upgrade OwnCloud.<\/p>\n\n\n\n<p>I have also shared my php files on <a href=\"https:\/\/github.com\/wongkeewee\/owncloud-hcaptcha\" target=\"_blank\" rel=\"noreferrer noopener\">GitHub<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A couple of days back I logged into CloudFlare and noticed that they are using a new captcha system on the login page, hCaptcha. Now if you have been on the internet long enough, you would have encountered Google&#8217;s reCaptcha&hellip; <a href=\"https:\/\/www.brainfart.sg\/index.php\/2020\/05\/hcaptcha-on-owncloud\/\" class=\"more-link\">Continue Reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"aside","meta":{"spay_email":""},"categories":[17],"tags":[104,103,105,54],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p1T66h-8r","jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/www.brainfart.sg\/index.php\/wp-json\/wp\/v2\/posts\/523"}],"collection":[{"href":"https:\/\/www.brainfart.sg\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.brainfart.sg\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.brainfart.sg\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.brainfart.sg\/index.php\/wp-json\/wp\/v2\/comments?post=523"}],"version-history":[{"count":37,"href":"https:\/\/www.brainfart.sg\/index.php\/wp-json\/wp\/v2\/posts\/523\/revisions"}],"predecessor-version":[{"id":565,"href":"https:\/\/www.brainfart.sg\/index.php\/wp-json\/wp\/v2\/posts\/523\/revisions\/565"}],"wp:attachment":[{"href":"https:\/\/www.brainfart.sg\/index.php\/wp-json\/wp\/v2\/media?parent=523"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.brainfart.sg\/index.php\/wp-json\/wp\/v2\/categories?post=523"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.brainfart.sg\/index.php\/wp-json\/wp\/v2\/tags?post=523"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}