Preface#
Following the last episode, we added reCAPTCHA in the Typecho backend.
Now we will add Cloudflare Turnstile in the Typecho backend.
Getting Started#
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 in the domain field, and select the hosted option for the widget mode.
![3][3]
Then remember these two strings, as you will need them later.
![4][4]
Installation#
There are quite a few changes to make. Theoretically, you could just modify the reCAPTCHA section to make it work, but for the convenience of beginners, I will provide the complete example.
Login Page#
Replace the following content in /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): ?>
•
<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';
?>
Replace the following content in /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-key',
'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 validation class */
$validator = new Typecho_Validate();
$validator->addRule('name', 'required', _t('Please enter username'));
$validator->addRule('password', 'required', _t('Please enter password'));
/** Intercept validation 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 validating 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 redirect directly 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 following content in /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>
•
<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';
?>
Replace the following content in /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-key',
'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 validation 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 long'), 2);
$validator->addRule('name', 'maxLength', _t('Username can be up to 32 characters long'), 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('Invalid email format'));
$validator->addRule('mail', 'maxLength', _t('Email can be up to 200 characters long'), 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('For account security, please enter at least six characters for the password'), 6);
$validator->addRule('password', 'maxLength', _t('For easy memory, please do not exceed eighteen characters for the password'), 18);
$validator->addRule('confirm', 'confirm', _t('The two passwords do not match'), 'password');
}
/** Intercept validation 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 key.
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.
Afterword#
Note: If you are using the Handsome theme like me, to implement front-end login, you need to do the following steps (no need for this step if you are not using the Handsome theme):::
Open the backend, Appearance Settings → Developer Settings → Custom Output HTML Code at the end of body and 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 key.
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-key"></div>
Replace the marked fields with your site key and secret key.
If there are other versions with mismatched line numbers and you cannot modify them yourself, please kindly send the PHP file to the site owner via email for assistance.
Great job! Log out of the website, refresh the homepage, Enjoy!
Looking Ahead#
Known issues: 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 section.
By the way: This verification supports IP authentication, which is theoretically more secure, but the examples in the documentation are not comprehensive enough. If any experts are available, please adapt this feature in the comments section.
This article is synchronized and updated by Mix Space to xLog. The original link is https://blog.nekorua.com/posts/coding/30.html