Skip to content

Commit

Permalink
feat: Turnstile support is done
Browse files Browse the repository at this point in the history
  • Loading branch information
songquanpeng committed Nov 19, 2022
1 parent d9288c6 commit 7c217c6
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
+ [x] Mobile friendly UI.
+ [x] Token based authorization.
+ [x] Use GitHub Actions to build releases & Docker images.
+ [ ] Turnstile CAPTCHA
+ [x] Cloudflare Turnstile user validation.

## Usage
1. Download built binaries from [GitHub Releases](https://github.com/songquanpeng/gin-template/releases/latest) or build from source:
Expand Down
22 changes: 19 additions & 3 deletions middleware/turnstile-check.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package middleware
import (
"encoding/json"
"gin-template/common"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"net/http"
"net/url"
Expand All @@ -15,11 +16,17 @@ type turnstileCheckResponse struct {
func TurnstileCheck() gin.HandlerFunc {
return func(c *gin.Context) {
if common.TurnstileCheckEnabled {
response := c.GetHeader("X-Turnstile-Response")
session := sessions.Default(c)
turnstileChecked := session.Get("turnstile")
if turnstileChecked != nil {
c.Next()
return
}
response := c.Query("turnstile")
if response == "" {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "X-Turnstile-Response 为空",
"message": "Turnstile Token 为空",
})
c.Abort()
return
Expand Down Expand Up @@ -53,11 +60,20 @@ func TurnstileCheck() gin.HandlerFunc {
if !res.Success {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "Turnstile 校验失败",
"message": "Turnstile 校验失败,请刷新重试!",
})
c.Abort()
return
}
session.Set("turnstile", true)
err = session.Save()
if err != nil {
c.JSON(http.StatusOK, gin.H{
"message": "无法保存会话信息,请重试",
"success": false,
})
return
}
}
c.Next()
}
Expand Down
2 changes: 1 addition & 1 deletion router/api-router.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func SetApiRouter(router *gin.Engine) {

userRoute := apiRouter.Group("/user")
{
userRoute.POST("/register", middleware.CriticalRateLimit(), controller.Register)
userRoute.POST("/register", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.Register)
userRoute.POST("/login", middleware.CriticalRateLimit(), controller.Login)
userRoute.GET("/logout", controller.Logout)

Expand Down
1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"react-toastify": "^9.0.8",
"react-turnstile": "^1.0.5",
"semantic-ui-css": "^2.5.0",
"semantic-ui-react": "^2.1.3"
},
Expand Down
57 changes: 44 additions & 13 deletions web/src/components/PasswordResetForm.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Button, Form, Grid, Header, Image, Segment } from 'semantic-ui-react';
import { API, showError, showSuccess } from '../helpers';
import { API, showError, showInfo, showSuccess } from '../helpers';
import Turnstile from 'react-turnstile';

const PasswordResetForm = () => {
const [inputs, setInputs] = useState({
Expand All @@ -9,6 +10,20 @@ const PasswordResetForm = () => {
const { email } = inputs;

const [loading, setLoading] = useState(false);
const [turnstileEnabled, setTurnstileEnabled] = useState(false);
const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
const [turnstileToken, setTurnstileToken] = useState('');

useEffect(() => {
let status = localStorage.getItem('status');
if (status) {
status = JSON.parse(status);
if (status.turnstile_check) {
setTurnstileEnabled(true);
setTurnstileSiteKey(status.turnstile_site_key);
}
}
}, []);

function handleChange(e) {
const { name, value } = e.target;
Expand All @@ -17,8 +32,14 @@ const PasswordResetForm = () => {

async function handleSubmit(e) {
if (!email) return;
if (turnstileEnabled && turnstileToken === '') {
showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
return;
}
setLoading(true);
const res = await API.get(`/api/reset_password?email=${email}`);
const res = await API.get(
`/api/reset_password?email=${email}&turnstile=${turnstileToken}`
);
const { success, message } = res.data;
if (success) {
showSuccess('重置邮件发送成功,请检查邮箱!');
Expand All @@ -30,26 +51,36 @@ const PasswordResetForm = () => {
}

return (
<Grid textAlign="center" style={{ marginTop: '48px' }}>
<Grid textAlign='center' style={{ marginTop: '48px' }}>
<Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" color="teal" textAlign="center">
<Image src="/logo.png" /> 密码重置
<Header as='h2' color='teal' textAlign='center'>
<Image src='/logo.png' /> 密码重置
</Header>
<Form size="large">
<Form size='large'>
<Segment>
<Form.Input
fluid
icon="mail"
iconPosition="left"
placeholder="邮箱地址"
name="email"
icon='mail'
iconPosition='left'
placeholder='邮箱地址'
name='email'
value={email}
onChange={handleChange}
/>
{turnstileEnabled ? (
<Turnstile
sitekey={turnstileSiteKey}
onVerify={(token) => {
setTurnstileToken(token);
}}
/>
) : (
<></>
)}
<Button
color="teal"
color='teal'
fluid
size="large"
size='large'
onClick={handleSubmit}
loading={loading}
>
Expand Down
30 changes: 27 additions & 3 deletions web/src/components/PersonalSetting.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useEffect, useState } from 'react';
import { Button, Divider, Form, Header, Image, Modal } from 'semantic-ui-react';
import { Link } from 'react-router-dom';
import { API, copy, showError, showSuccess } from '../helpers';
import { API, copy, showError, showInfo, showSuccess } from '../helpers';
import Turnstile from 'react-turnstile';

const PersonalSetting = () => {
const [inputs, setInputs] = useState({
Expand All @@ -12,12 +13,19 @@ const PersonalSetting = () => {
const [status, setStatus] = useState({});
const [showWeChatBindModal, setShowWeChatBindModal] = useState(false);
const [showEmailBindModal, setShowEmailBindModal] = useState(false);
const [turnstileEnabled, setTurnstileEnabled] = useState(false);
const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
const [turnstileToken, setTurnstileToken] = useState('');

useEffect(() => {
let status = localStorage.getItem('status');
if (status) {
status = JSON.parse(status);
setStatus(status);
if (status.turnstile_check) {
setTurnstileEnabled(true);
setTurnstileSiteKey(status.turnstile_site_key);
}
}
}, []);

Expand Down Expand Up @@ -58,10 +66,16 @@ const PersonalSetting = () => {

const sendVerificationCode = async () => {
if (inputs.email === '') return;
const res = await API.get(`/api/verification?email=${inputs.email}`);
if (turnstileEnabled && turnstileToken === '') {
showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
return;
}
const res = await API.get(
`/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`
);
const { success, message } = res.data;
if (success) {
showSuccess('验证码发送成功,请检查你的邮箱!');
showSuccess('验证码发送成功,请检查邮箱!');
} else {
showError(message);
}
Expand Down Expand Up @@ -161,6 +175,16 @@ const PersonalSetting = () => {
value={inputs.email_verification_code}
onChange={handleInputChange}
/>
{turnstileEnabled ? (
<Turnstile
sitekey={turnstileSiteKey}
onVerify={(token) => {
setTurnstileToken(token);
}}
/>
) : (
<></>
)}
<Button color='teal' fluid size='large' onClick={bindEmail}>
绑定
</Button>
Expand Down
36 changes: 33 additions & 3 deletions web/src/components/RegisterForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from 'semantic-ui-react';
import { Link, useNavigate } from 'react-router-dom';
import { API, showError, showInfo, showSuccess } from '../helpers';
import Turnstile from 'react-turnstile';

const RegisterForm = () => {
const [inputs, setInputs] = useState({
Expand All @@ -20,14 +21,20 @@ const RegisterForm = () => {
verification_code: '',
});
const { username, password, password2 } = inputs;

const [showEmailVerification, setShowEmailVerification] = useState(false);
const [turnstileEnabled, setTurnstileEnabled] = useState(false);
const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
const [turnstileToken, setTurnstileToken] = useState('');

useEffect(() => {
let status = localStorage.getItem('status');
if (status) {
status = JSON.parse(status);
setShowEmailVerification(status.email_verification);
if (status.turnstile_check) {
setTurnstileEnabled(true);
setTurnstileSiteKey(status.turnstile_site_key);
}
}
});

Expand All @@ -49,7 +56,14 @@ const RegisterForm = () => {
return;
}
if (username && password) {
const res = await API.post('/api/user/register', inputs);
if (turnstileEnabled && turnstileToken === '') {
showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
return;
}
const res = await API.post(
`/api/user/register?turnstile=${turnstileToken}`,
inputs
);
const { success, message } = res.data;
if (success) {
navigate('/login');
Expand All @@ -62,7 +76,13 @@ const RegisterForm = () => {

const sendVerificationCode = async () => {
if (inputs.email === '') return;
const res = await API.get(`/api/verification?email=${inputs.email}`);
if (turnstileEnabled && turnstileToken === '') {
showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
return;
}
const res = await API.get(
`/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`
);
const { success, message } = res.data;
if (success) {
showSuccess('验证码发送成功,请检查你的邮箱!');
Expand Down Expand Up @@ -131,6 +151,16 @@ const RegisterForm = () => {
) : (
<></>
)}
{turnstileEnabled ? (
<Turnstile
sitekey={turnstileSiteKey}
onVerify={(token) => {
setTurnstileToken(token);
}}
/>
) : (
<></>
)}
<Button color='teal' fluid size='large' onClick={handleSubmit}>
注册
</Button>
Expand Down

0 comments on commit 7c217c6

Please sign in to comment.