初步增加验证码
This commit is contained in:
parent
1fbe92f2d9
commit
f0e9045e98
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
38
frontend/pnpm-lock.yaml
generated
38
frontend/pnpm-lock.yaml
generated
@ -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}
|
||||||
|
@ -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>
|
38
frontend/src/components/TurnstileWidget.tsx
Normal file
38
frontend/src/components/TurnstileWidget.tsx
Normal 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
|
@ -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>
|
||||||
)
|
)
|
||||||
|
@ -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
21
handle/captcha.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -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"`
|
||||||
|
}
|
||||||
|
@ -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
14
service/captcha.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user