banner
libxcnya.so

libxcnya.so

Nothing...
telegram
twitter
github
email

Add Cloudflare Turnstile in Typecho Backend

Preface#

Following the last episode, we added reCAPTCHA in the Typecho backend
Now we will add Cloudflare Turnstile in the Typecho backend

Acquisition#

First, open Cloudflare and find Turnstile on the left

![1][1]

Click on this section to Add Site

![2][2]

Once inside, you can write any site name, enter your domain, and select the widget mode as hosted

![3][3]

Then remember these two strings, you will need them later

![4][4]

Installation#

There are quite a few changes, theoretically, you can just pull out the reCAPTCHA section and modify it to work, but for the convenience of beginners, I will provide the complete example.

Login Page#

Overwrite the following content to /admin/login.php

<?php
include 'common.php';

if ($user->hasLogin()) {
    $response->redirect($options->adminUrl);
}
$rememberName = htmlspecialchars(\Typecho\Cookie::get('__typecho_remember_name', ''));
\Typecho\Cookie::delete('__typecho_remember_name');

$bodyClass = 'body-100';

include 'header.php';
?>
<div class="typecho-login-wrap">
    <div class="typecho-login">
        <h1><a href="http://typecho.org/" class="i-logo">Typecho</a></h1>
        <form action="<?php $options->loginAction(); ?>" method="post" name="login" role="form">
            <p>
                <label for="name" class="sr-only"><?php _e('Username'); ?></label>
                <input type="text" id="name" name="name" value="<?php echo $rememberName; ?>" placeholder="<?php _e('Username'); ?>" class="text-l w-100" autofocus />
            </p>
            <p>
                <label for="password" class="sr-only"><?php _e('Password'); ?></label>
                <input type="password" id="password" name="password" class="text-l w-100" placeholder="<?php _e('Password'); ?>" />
            </p>
            <div class="cf-turnstile" data-sitekey="your site key" data-callback="javascriptCallback" style="transform: scale(0.926); -webkit-transform: scale(0.926); transform-origin: 0 0; -webkit-transform-origin: 0 0;"></div>
                <button type="submit" class="btn btn-l w-100 primary"><?php _e('Login'); ?></button>
                <input type="hidden" name="referer" value="<?php echo htmlspecialchars($request->get('referer')); ?>" />
            </p>
            <p>
                <label for="remember">
                    <input<?php if(\Typecho\Cookie::get('__typecho_remember_remember')): ?> checked<?php endif; ?> type="checkbox" name="remember" class="checkbox" value="1" id="remember" /> <?php _e('Remember me'); ?>
                </label>
            </p>
        </form>

        <p class="more-link">
            <a href="<?php $options->siteUrl(); ?>"><?php _e('Return to homepage'); ?></a>
            <?php if($options->allowRegister): ?>
            &bull;
            <a href="<?php $options->registerUrl(); ?>"><?php _e('User Registration'); ?></a>
            <?php endif; ?>
        </p>
    </div>
</div>
<?php
include 'common-js.php';
?>
<script>
$(document).ready(function () {
    $('#name').focus();
});
</script>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<?php
include 'footer.php';
?>

Overwrite the following content to /var/Widget/Login.php

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
 * Login action
 *
 * @category typecho
 * @package Widget
 * @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
 * @license GNU General Public License 2.0
 * @version $Id$
 */

/**
 * Login component
 *
 * @category typecho
 * @package Widget
 * @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
 * @license GNU General Public License 2.0
 */

/**
 * CF CAPTCHA
 *
 * By BLxcwg666
 * https://blog.xcnya.cn
 * [email protected]
 */

class Paul_GCaptcha {
    public static $success, $failed;

    // Send verification information
    public static function send($post_data) {
        $postdata = http_build_query($post_data);
        $options = array(
            'http' => array(
                'method' => 'POST',
                'header' => 'Content-type:application/x-www-form-urlencoded',
                'content' => $postdata,
                'timeout' => 15 * 60 // Timeout
            )
        );
        $context = stream_context_create($options);
        $result = file_get_contents("https://challenges.cloudflare.com/turnstile/v0/siteverify", false, $context);
        return $result;
    }

    // Check verification status
    public static function check(){
        if($_POST["cf-turnstile-response"]){
            $data = array(
                'secret' => 'your secret',
                'response' => $_POST["cf-turnstile-response"] // Receive user submitted verification data
            );

            $result = self::send($data);
            $result = json_decode($result, true);
            $result = $result["success"];

            if($result == true){
                return true; // Verification successful
            }
            else{
                return false; // Verification failed
            }
        }
        else{
            return false; // User did not submit verification information
        }
    }
}

class Widget_Login extends Widget_Abstract_Users implements Widget_Interface_Do
{
    /**
     * Initialization function
     *
     * @access public
     * @return void
     */
    public function action()
    {
        // protect
        $this->security->protect();

        /** If already logged in */
        if ($this->user->hasLogin()) {
            /** Redirect directly */
            $this->response->redirect($this->options->index);
        }

        /** Initialize verification class */
        $validator = new Typecho_Validate();
        $validator->addRule('name', 'required', _t('Please enter username'));
        $validator->addRule('password', 'required', _t('Please enter password'));

        /** Intercept verification exceptions */
        if ($error = $validator->run($this->request->from('name', 'password'))) {
            Typecho_Cookie::set('__typecho_remember_name', $this->request->name);

            /** Set prompt information */
            $this->widget('Widget_Notice')->set($error);
            $this->response->goBack();
        }

        if(Paul_GCaptcha::check() == true){
            /** Start verifying user **/
            $valid = $this->user->login($this->request->name, $this->request->password,
            false, 1 == $this->request->remember ? $this->options->time + $this->options->timezone + 30*24*3600 : 0);

            /** Compare password */
            if (!$valid) {
                /** Prevent enumeration, sleep for 3 seconds */
                sleep(3);

                $this->pluginHandle()->loginFail($this->user, $this->request->name,
                $this->request->password, 1 == $this->request->remember);

                Typecho_Cookie::set('__typecho_remember_name', $this->request->name);
                $this->widget('Widget_Notice')->set(_t('Invalid username or password'), 'error');
                $this->response->goBack('?referer=' . urlencode($this->request->referer));
            }
            $this->pluginHandle()->loginSucceed($this->user, $this->request->name,
            $this->request->password, 1 == $this->request->remember);

            /** Redirect to verification address */
            if (NULL != $this->request->referer) {
                $this->response->redirect($this->request->referer);
            } else if (!$this->user->pass('contributor', true)) {
                /** Ordinary users are not allowed to directly redirect to the backend */
                $this->response->redirect($this->options->profileUrl);
            } else {
                $this->response->redirect($this->options->adminUrl);
            }
        }else{
            $this->widget('Widget_Notice')->set(_t('Verification failed'),'error');
            $this->response->goBack('?referer=' . urlencode($this->request->referer));
        }
    }
}

Replace the marked fields with your site key and secret

Registration Page#

Overwrite the following content to /admin/register.php

<?php
include 'common.php';

if ($user->hasLogin() || !$options->allowRegister) {
    $response->redirect($options->siteUrl);
}
$rememberName = htmlspecialchars(Typecho_Cookie::get('__typecho_remember_name'));
$rememberMail = htmlspecialchars(Typecho_Cookie::get('__typecho_remember_mail'));
Typecho_Cookie::delete('__typecho_remember_name');
Typecho_Cookie::delete('__typecho_remember_mail');

$bodyClass = 'body-100';

include 'header.php';
?>
<div class="typecho-login-wrap">
    <div class="typecho-login">
        <h1><a href="http://typecho.org/" class="i-logo">Typecho</a></h1>
        <form action="<?php $options->registerAction(); ?>" method="post" name="register" role="form">
            <p>
                <label for="name" class="sr-only"><?php _e('Username'); ?></label>
                <input type="text" id="name" name="name" placeholder="<?php _e('Username'); ?>" value="<?php echo $rememberName; ?>" class="text-l w-100" autofocus />
            </p>
            <p>
                <label for="mail" class="sr-only"><?php _e('Email'); ?></label>
                <input type="email" id="mail" name="mail" placeholder="<?php _e('Email'); ?>" value="<?php echo $rememberMail; ?>" class="text-l w-100" />
            </p>
            <div class="cf-turnstile" data-sitekey="your site key" data-callback="javascriptCallback" style="transform: scale(0.926); -webkit-transform: scale(0.926); transform-origin: 0 0; -webkit-transform-origin: 0 0;"></div>
            <p class="submit">
                <button type="submit" class="btn btn-l w-100 primary"><?php _e('Register'); ?></button>
            </p>
            <script src="https://challenges.cloudflare.com/turnstile/v0/api.js"></script>
            <p>
        </form>

        <p class="more-link">
            <a href="<?php $options->siteUrl(); ?>"><?php _e('Return to homepage'); ?></a>
            &bull;
            <a href="<?php $options->adminUrl('login.php'); ?>"><?php _e('User Login'); ?></a>
        </p>
    </div>
</div>
<?php
include 'common-js.php';
?>
<script>
$(document).ready(function () {
    $('#name').focus();
});
</script>
<?php
include 'footer.php';
?>

Overwrite the following content to /var/Widget/Register.php

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
 * Registration component
 *
 * @author qining
 * @category typecho
 * @package Widget
 */

/**
 * CF CAPTCHA
 *
 * By BLxcwg666
 * https://blog.xcnya.cn
 * [email protected]
 */

 class Paul_GCaptcha {
    public static $success, $failed;

    // Send verification information
    public static function send($post_data) {
        $postdata = http_build_query($post_data);
        $options = array(
            'http' => array(
                'method' => 'POST',
                'header' => 'Content-type:application/x-www-form-urlencoded',
                'content' => $postdata,
                'timeout' => 15 * 60 // Timeout
            )
        );
        $context = stream_context_create($options);
        $result = file_get_contents("https://challenges.cloudflare.com/turnstile/v0/siteverify", false, $context);
        return $result;
    }

    // Check verification status
    public static function check(){
        if($_POST["cf-turnstile-response"]){
            $data = array(
                'secret' => 'your secret',
                'response' => $_POST["cf-turnstile-response"] // Receive user submitted verification data
            );

            $result = self::send($data);
            $result = json_decode($result, true);
            $result = $result["success"];

            if($result == true){
                return true; // Verification successful
            }
            else{
                return false; // Verification failed
            }
        }
        else{
            return false; // User did not submit verification information
        }
    }
}

class Widget_Register extends Widget_Abstract_Users implements Widget_Interface_Do
{
    /**
     * Initialization function
     *
     * @access public
     * @return void
     */
    public function action()
    {
        // protect
        $this->security->protect();

        /** If already logged in */
        if ($this->user->hasLogin() || !$this->options->allowRegister) {
            /** Redirect directly */
            $this->response->redirect($this->options->index);
        }

        /** Initialize verification class */
        $validator = new Typecho_Validate();
        $validator->addRule('name', 'required', _t('Username is required'));
        $validator->addRule('name', 'minLength', _t('Username must be at least 2 characters'), 2);
        $validator->addRule('name', 'maxLength', _t('Username can be at most 32 characters'), 32);
        $validator->addRule('name', 'xssCheck', _t('Please do not use special characters in the username'));
        $validator->addRule('name', array($this, 'nameExists'), _t('Username already exists'));
        $validator->addRule('mail', 'required', _t('Email is required'));
        $validator->addRule('mail', array($this, 'mailExists'), _t('Email address already exists'));
        $validator->addRule('mail', 'email', _t('Email format is incorrect'));
        $validator->addRule('mail', 'maxLength', _t('Email can be at most 200 characters'), 200);

        /** If there is a password in the request */
        if (array_key_exists('password', $_REQUEST)) {
            $validator->addRule('password', 'required', _t('Password is required'));
            $validator->addRule('password', 'minLength', _t('To ensure account security, please enter at least six characters'), 6);
            $validator->addRule('password', 'maxLength', _t('For easy memorization, please do not exceed eighteen characters'), 18);
            $validator->addRule('confirm', 'confirm', _t('The two passwords do not match'), 'password');
        }

        /** Intercept verification exceptions */
        if ($error = $validator->run($this->request->from('name', 'password', 'mail', 'confirm'))) {
            Typecho_Cookie::set('__typecho_remember_name', $this->request->name);
            Typecho_Cookie::set('__typecho_remember_mail', $this->request->mail);

            /** Set prompt information */
            $this->widget('Widget_Notice')->set($error);
            $this->response->goBack();
        }

        if(Paul_GCaptcha::check() == true){
            $hasher = new PasswordHash(8, true);
        $generatedPassword = Typecho_Common::randString(7);

        $dataStruct = array(
            'name'      =>  $this->request->name,
            'mail'      =>  $this->request->mail,
            'screenName'=>  $this->request->name,
            'password'  =>  $hasher->HashPassword($generatedPassword),
            'created'   =>  $this->options->time,
            'group'     =>  'subscriber'
        );

        $dataStruct = $this->pluginHandle()->register($dataStruct);

        $insertId = $this->insert($dataStruct);
        $this->db->fetchRow($this->select()->where('uid = ?', $insertId)
        ->limit(1), array($this, 'push'));

        $this->pluginHandle()->finishRegister($this);

        $this->user->login($this->request->name, $generatedPassword);

        Typecho_Cookie::delete('__typecho_first_run');
        Typecho_Cookie::delete('__typecho_remember_name');
        Typecho_Cookie::delete('__typecho_remember_mail');

        $this->widget('Widget_Notice')->set(_t('User <strong>%s</strong> has successfully registered, password is <strong>%s</strong>', $this->screenName, $generatedPassword), 'success');
        $this->response->redirect($this->options->adminUrl);
        }else{
            $this->widget('Widget_Notice')->set(_t('Verification failed'),'error');
            $this->response->goBack('?referer=' . urlencode($this->request->referer));
        }

    }
}

Replace the marked fields with your site key and secret

Done! Open the website backend, Enjoy!

![5][5]

The colors here are based on the device theme; if you need a fixed color, please refer to the [Turnstile documentation][6] to add the theme tag yourself.

Aftermath#

Tip: If you, like me, are using the Handsome theme, to achieve front-end login, you also need to do the following steps (no need for other themes)::::

Open the backend, Appearance Settings → Developer Settings → Custom Output HTML Code at the end of body, enter the following content

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

Then in /usr/themes/handsome/component/headnav.php around line 369, add the following content on a new line (Handsome 9.0.2)

<div class="cf-turnstile" style="transform: scale(0.827); -webkit-transform: scale(0.827); transform-origin: 0 0; -webkit-transform-origin: 0 0;" data-sitekey="your site key"></div>

Replace the marked fields with your site key and secret

If user registration is enabled, you also need to add the following content on a new line around line 390 in the same file (Handsome 9.0.2)

<div class="cf-turnstile" style="transform: scale(0.827); -webkit-transform: scale(0.827); transform-origin: 0 0; -webkit-transform-origin: 0 0;" data-sitekey="your secret"></div>

Replace the marked fields with your site key and secret

If there are other versions with mismatched line numbers and you cannot modify them yourself, please send the PHP file to the site owner via email for assistance.

Great job! Log out of the website, refresh the homepage, Enjoy!

6

Looking Ahead#

Known issue: The Turnstile on the Handsome front-end login remains white in dark mode, and there is currently no solution. Suggestions are welcome in the comments.
By the way: This verification supports IP authentication, theoretically making it more secure, but the documentation examples are not comprehensive enough. If anyone is skilled, please adapt this feature in the comments.

This article is synchronized and updated to xLog by Mix Space
The original link is https://blog.nekorua.com/posts/coding/30.html


Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.