初步增加验证码

This commit is contained in:
xmdhs 2023-09-24 00:21:03 +08:00
parent 1fbe92f2d9
commit f0e9045e98
No known key found for this signature in database
GPG Key ID: E809D6D43DEFCC95
11 changed files with 144 additions and 4 deletions

View File

@ -23,4 +23,9 @@ type Config struct {
HomepageUrl string HomepageUrl string
RegisterUrl string RegisterUrl string
ServerName string ServerName string
Captcha struct {
Type string
SiteKey string
}
} }

View File

@ -12,13 +12,15 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.1", "@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@marsidev/react-turnstile": "^0.3.1",
"@mui/icons-material": "^5.14.9", "@mui/icons-material": "^5.14.9",
"@mui/material": "^5.14.10", "@mui/material": "^5.14.10",
"immer": "^10.0.2", "immer": "^10.0.2",
"jotai": "^2.4.2", "jotai": "^2.4.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.16.0" "react-router-dom": "^6.16.0",
"swr": "^2.2.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.6.2", "@types/node": "^20.6.2",

View File

@ -11,6 +11,9 @@ dependencies:
'@emotion/styled': '@emotion/styled':
specifier: ^11.11.0 specifier: ^11.11.0
version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.21)(react@18.2.0) version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.21)(react@18.2.0)
'@marsidev/react-turnstile':
specifier: ^0.3.1
version: 0.3.1(react-dom@18.2.0)(react@18.2.0)
'@mui/icons-material': '@mui/icons-material':
specifier: ^5.14.9 specifier: ^5.14.9
version: 5.14.9(@mui/material@5.14.10)(@types/react@18.2.21)(react@18.2.0) version: 5.14.9(@mui/material@5.14.10)(@types/react@18.2.21)(react@18.2.0)
@ -32,6 +35,9 @@ dependencies:
react-router-dom: react-router-dom:
specifier: ^6.16.0 specifier: ^6.16.0
version: 6.16.0(react-dom@18.2.0)(react@18.2.0) version: 6.16.0(react-dom@18.2.0)(react@18.2.0)
swr:
specifier: ^2.2.2
version: 2.2.2(react@18.2.0)
devDependencies: devDependencies:
'@types/node': '@types/node':
@ -527,6 +533,16 @@ packages:
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
dev: true dev: true
/@marsidev/react-turnstile@0.3.1(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-XnpIUqufuZp2VoC14fmYztB2/8lSABh8eFG6TWAR7Loipdy0p2lZXzuvUOTcQl69j1XKvWu3WChFCBWx/Cbd1A==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@mui/base@5.0.0-beta.16(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0): /@mui/base@5.0.0-beta.16(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-OYxhC81c9bO0wobGcM8rrY5bRwpCXAI21BL0P2wz/2vTv4ek7ALz9+U5M8wgdmtRNUhmCmAB4L2WRwFRf5Cd8Q==} resolution: {integrity: sha512-OYxhC81c9bO0wobGcM8rrY5bRwpCXAI21BL0P2wz/2vTv4ek7ALz9+U5M8wgdmtRNUhmCmAB4L2WRwFRf5Cd8Q==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@ -1139,6 +1155,10 @@ packages:
supports-color: 7.2.0 supports-color: 7.2.0
dev: true dev: true
/client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
dev: false
/clsx@2.0.0: /clsx@2.0.0:
resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -2035,6 +2055,16 @@ packages:
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: false dev: false
/swr@2.2.2(react@18.2.0):
resolution: {integrity: sha512-CbR41AoMD4TQBQw9ic3GTXspgfM9Y8Mdhb5Ob4uIKXhWqnRLItwA5fpGvB7SmSw3+zEjb0PdhiEumtUvYoQ+bQ==}
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0
dependencies:
client-only: 0.0.1
react: 18.2.0
use-sync-external-store: 1.2.0(react@18.2.0)
dev: false
/text-table@0.2.0: /text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
dev: true dev: true
@ -2084,6 +2114,14 @@ packages:
punycode: 2.3.0 punycode: 2.3.0
dev: true dev: true
/use-sync-external-store@1.2.0(react@18.2.0):
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.2.0
dev: false
/vite@4.4.9(@types/node@20.6.2): /vite@4.4.9(@types/node@20.6.2):
resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}

View File

@ -12,3 +12,10 @@ export interface Api<T> {
} }
export type ApiErr = Api<unknown> export type ApiErr = Api<unknown>
interface captcha {
type: string
siteKey: string
}
export type ApiCaptcha = Api<captcha>

View File

@ -0,0 +1,38 @@
import { Turnstile } from '@marsidev/react-turnstile'
import Button from '@mui/material/Button'
import { useRef, useState, memo } from 'react'
import type { TurnstileInstance } from '@marsidev/react-turnstile'
import useSWR from "swr";
import { ApiCaptcha } from '@/apis/model';
import Alert from '@mui/material/Alert';
import Skeleton from '@mui/material/Skeleton';
interface prop {
onSuccess: ((token: string) => void)
}
const TurnstileWidget = memo(({ onSuccess }: prop) => {
const ref = useRef<TurnstileInstance>(null)
const [key, setKey] = useState(1)
const { data, error, isLoading } = useSWR<ApiCaptcha>(import.meta.env.VITE_APIADDR + '/api/v1/captcha')
if (error) {
console.warn(error)
return <Alert severity="warning">{String(error)}</Alert>
}
if (isLoading) {
return <Skeleton variant="rectangular" width={210} height={118} />
}
if (data?.code != 0) {
console.warn(error)
return <Alert severity="warning">{String(data?.msg)}</Alert>
}
return (
<>
<Turnstile siteKey={data?.data.siteKey ?? ""} key={key} onSuccess={onSuccess} ref={ref} scriptOptions={{ async: true }} />
<Button onClick={() => setKey(key + 1)}></Button>
</>
)
})
export default TurnstileWidget

View File

@ -2,11 +2,16 @@ import React from 'react'
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import App from './App.tsx' import App from './App.tsx'
import CssBaseline from '@mui/material/CssBaseline'; import CssBaseline from '@mui/material/CssBaseline';
import { SWRConfig } from 'swr'
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>
<CssBaseline> <CssBaseline>
<SWRConfig value={{
fetcher: (resource, init) => fetch(resource, init).then(res => res.json())
}}>
<App /> <App />
</SWRConfig>
</CssBaseline> </CssBaseline>
</React.StrictMode> </React.StrictMode>
) )

View File

@ -16,6 +16,7 @@ import Alert from '@mui/material/Alert';
import Snackbar from '@mui/material/Snackbar'; import Snackbar from '@mui/material/Snackbar';
import Loading from '@/components/Loading' import Loading from '@/components/Loading'
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import TurnstileWidget from '@/components/TurnstileWidget';
export default function SignUp() { export default function SignUp() {
const [regErr, setRegErr] = useState(""); const [regErr, setRegErr] = useState("");
@ -119,6 +120,9 @@ export default function SignUp() {
autoComplete="new-password" autoComplete="new-password"
/> />
</Grid> </Grid>
<Grid item xs={12}>
<TurnstileWidget onSuccess={v => console.log(v)} />
</Grid>
</Grid> </Grid>
<Button <Button
type="submit" type="submit"

21
handle/captcha.go Normal file
View File

@ -0,0 +1,21 @@
package handle
import (
"encoding/json"
"net/http"
"github.com/julienschmidt/httprouter"
"github.com/xmdhs/authlib-skin/model"
)
func (h *Handel) GetCaptcha() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
ctx := r.Context()
c := h.webService.GetCaptcha(ctx)
m := model.API[model.Captcha]{
Code: 0,
Data: c,
}
json.NewEncoder(w).Encode(m)
}
}

View File

@ -23,3 +23,8 @@ type TokenClaims struct {
UID int `json:"uid"` UID int `json:"uid"`
jwt.RegisteredClaims jwt.RegisteredClaims
} }
type Captcha struct {
Type string `json:"type"`
SiteKey string `json:"siteKey"`
}

View File

@ -60,5 +60,6 @@ func newYggdrasil(r *httprouter.Router, handelY yggdrasil.Yggdrasil) error {
func newSkinApi(r *httprouter.Router, handel *handle.Handel) error { func newSkinApi(r *httprouter.Router, handel *handle.Handel) error {
r.PUT("/api/v1/user/reg", handel.Reg()) r.PUT("/api/v1/user/reg", handel.Reg())
r.GET("/api/v1/captcha", handel.GetCaptcha())
return nil return nil
} }

14
service/captcha.go Normal file
View File

@ -0,0 +1,14 @@
package service
import (
"context"
"github.com/xmdhs/authlib-skin/model"
)
func (w *WebService) GetCaptcha(ctx context.Context) model.Captcha {
return model.Captcha{
Type: w.config.Captcha.Type,
SiteKey: w.config.Captcha.SiteKey,
}
}