diff --git a/frontend/src/Route.tsx b/frontend/src/Route.tsx index ef46d14..0d5597a 100644 --- a/frontend/src/Route.tsx +++ b/frontend/src/Route.tsx @@ -9,6 +9,7 @@ import Layout from '@/views/Layout' import UserAdmin from "@/views/admin/UserAdmin"; import NeedLogin from "@/components/NeedLogin"; import Index from "@/views/Index"; +import SignUpEmail from "@/views/SignUpEmail"; const router = createBrowserRouter([ { path: "*", Component: Root }, @@ -23,6 +24,7 @@ function Root() { 404

} /> } /> } /> + } /> }> } /> diff --git a/frontend/src/apis/apis.ts b/frontend/src/apis/apis.ts index b191fb8..76be4f4 100644 --- a/frontend/src/apis/apis.ts +++ b/frontend/src/apis/apis.ts @@ -117,4 +117,15 @@ export async function editUser(u: EditUser, token: string, uid: string) { body: JSON.stringify(u) }) return await apiGet(r) +} + +export async function sendRegEmail(email: string, captchaToken: string) { + const r = await fetch(root() + "/api/v1/user/reg_email", { + method: "POST", + body: JSON.stringify({ + "email": email, + "captchaToken": captchaToken + }) + }) + return await apiGet(r) } \ No newline at end of file diff --git a/frontend/src/apis/model.ts b/frontend/src/apis/model.ts index cb930d6..dffb170 100644 --- a/frontend/src/apis/model.ts +++ b/frontend/src/apis/model.ts @@ -40,6 +40,8 @@ export interface ApiConfig { captcha: captcha AllowChangeName: boolean serverName: string + NeedEmail: boolean + AllowDomain: string[] } export interface UserInfo { diff --git a/frontend/src/views/Login.tsx b/frontend/src/views/Login.tsx index 4dc2751..a4fe5f0 100644 --- a/frontend/src/views/Login.tsx +++ b/frontend/src/views/Login.tsx @@ -64,6 +64,9 @@ export default function SignIn() { console.warn(v) if (v instanceof ApiErr) { switch (v.code) { + case 10: + setErr("验证码错误") + return case 6: setErr("密码或用户名错误") return diff --git a/frontend/src/views/Register.tsx b/frontend/src/views/Register.tsx index dfd1c7e..79f2093 100644 --- a/frontend/src/views/Register.tsx +++ b/frontend/src/views/Register.tsx @@ -69,6 +69,9 @@ export default function SignUp() { console.warn(v) if (v instanceof ApiErr) { switch (v.code) { + case 10: + setRegErr("验证码错误") + return case 3: setRegErr("邮箱已存在") return diff --git a/frontend/src/views/SignUpEmail.tsx b/frontend/src/views/SignUpEmail.tsx new file mode 100644 index 0000000..203ff79 --- /dev/null +++ b/frontend/src/views/SignUpEmail.tsx @@ -0,0 +1,176 @@ +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import CssBaseline from '@mui/material/CssBaseline'; +import Grid from '@mui/material/Grid'; +import Box from '@mui/material/Box'; +import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; +import Select from '@mui/material/Select'; +import MenuItem from '@mui/material/MenuItem'; +import TextField from '@mui/material/TextField'; +import { useRequest, useTitle } from 'ahooks'; +import { getConfig, sendRegEmail } from '@/apis/apis'; +import { useEffect, useRef, useState } from 'react'; +import Snackbar from '@mui/material/Snackbar'; +import Alert from '@mui/material/Alert'; +import CaptchaWidget from '@/components/CaptchaWidget'; +import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget' +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import { useNavigate } from "react-router-dom"; +import { ApiErr } from '@/apis/error'; +import Loading from '@/components/Loading'; + +export default function SignUpEmail() { + const [err, setErr] = useState(""); + const [domain, setDomain] = useState(""); + const [email, setEmail] = useState("") + const captchaRef = useRef(null) + const [captchaToken, setCaptchaToken] = useState(""); + const [open, setOpen] = useState(false); + useTitle("注册") + const navigate = useNavigate(); + const [helperText, setHelperText] = useState("") + const [loading, setLoading] = useState(false); + + const server = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 60000, + onError: e => { + console.warn(e) + setErr(String(e)) + } + }) + + useEffect(() => { + if (server.data?.AllowDomain.length != 0) { + setDomain(server.data?.AllowDomain[0] ?? "") + } + }, [server.data?.AllowDomain]) + + const emailonChange = (e: React.ChangeEvent) => { + setEmail(e.target.value) + if (e.target.value == "") { + setHelperText("邮箱不得为空") + } + setHelperText("") + } + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (email == "") { + setHelperText("邮箱不得为空") + } + const sendEmail = (() => { + if (domain != "") { + return `${email}@${domain}` + } + return email + })() + + if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(sendEmail)){ + setHelperText("邮箱格式错误") + return + } + + if (server.data?.captcha.type != "" && captchaToken == "") { + return + } + setLoading(true) + sendRegEmail(sendEmail, captchaToken).then(() => setOpen(true)).catch(e => { + captchaRef.current?.reload() + console.warn(e) + if (e instanceof ApiErr) { + switch (e.code) { + case 10: + setErr("验证码错误") + return + case 11: + setErr("暂时无法对此邮箱发送邮件") + return + } + } + setErr(String(e)) + }).finally(() => setLoading(false)) + + } + + const handleClose = () => { + navigate("/") + } + + + return ( + + + + + + + + 输入邮箱 + + + + + + { + server.data?.AllowDomain.length != 0 && + + 域名 + + + } + + + + + + + + + + setErr("")} severity="error">{err} + + + 邮件已发送 + + 请到收件箱(或垃圾箱)点击验证链接以继续完成注册。 + + + + + + {loading && } + + ) +} \ No newline at end of file diff --git a/model/model.go b/model/model.go index 58cdce2..342c3d2 100644 --- a/model/model.go +++ b/model/model.go @@ -63,6 +63,8 @@ type Config struct { Captcha Captcha `json:"captcha"` AllowChangeName bool ServerName string `json:"serverName"` + NeedEmail bool + AllowDomain []string } type EditUser struct { diff --git a/server/route/route.go b/server/route/route.go index adf35ad..39267d5 100644 --- a/server/route/route.go +++ b/server/route/route.go @@ -81,6 +81,7 @@ func newSkinApi(handel *handle.Handel, userHandel *handle.UserHandel, adminHande r.Post("/user/reg", userHandel.Reg()) r.Post("/user/login", userHandel.Login()) r.Get("/config", handel.GetConfig()) + r.Post("/user/reg_email", userHandel.SendRegEmail()) r.Group(func(r chi.Router) { r.Use(adminHandel.NeedAuth) diff --git a/server/wire_gen.go b/server/wire_gen.go index 1d55ab1..c7d6992 100644 --- a/server/wire_gen.go +++ b/server/wire_gen.go @@ -63,9 +63,15 @@ func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func() handleError := handelerror.NewHandleError(logger) httpClient := ProvideHttpClient() captchaService := captcha.NewCaptchaService(c, httpClient) - userSerice := service.NewUserSerice(c, client, captchaService, authService, cache) + emailService, err := email.NewEmail(privateKey, c, cache) + if err != nil { + cleanup2() + cleanup() + return nil, nil, err + } + userService := service.NewUserSerice(c, client, captchaService, authService, cache, emailService) textureService := service.NewTextureService(client, c, cache) - userHandel := handle.NewUserHandel(handleError, validate, userSerice, logger, textureService) + userHandel := handle.NewUserHandel(handleError, validate, userService, logger, textureService) adminService := service.NewAdminService(authService, client, c, cache) adminHandel := handle.NewAdminHandel(handleError, adminService, validate) httpHandler := route.NewRoute(yggdrasil3, handel, c, handler, userHandel, adminHandel) diff --git a/service/email/email.go b/service/email/email.go index 1fb2e27..10d600d 100644 --- a/service/email/email.go +++ b/service/email/email.go @@ -52,16 +52,23 @@ func NewEmail(pri *rsa.PrivateKey, c config.Config, cache cache.Cache) (*EmailSe }, nil } -func (e EmailService) getRandEmailUser() EmailConfig { +func (e EmailService) getRandEmailUser() (EmailConfig, error) { + if len(e.emailConfig) == 0 { + return EmailConfig{}, fmt.Errorf("没有可用的邮箱账号") + } + i := rand.Intn(len(e.emailConfig)) - return e.emailConfig[i] + return e.emailConfig[i], nil } func (e EmailService) SendEmail(ctx context.Context, to string, subject, body string) error { - u := e.getRandEmailUser() + u, err := e.getRandEmailUser() + if err != nil { + return fmt.Errorf("SendRegVerify: %w", err) + } m := mail.NewMsg() - err := m.From(u.Name) + err = m.From(u.Name) if err != nil { return fmt.Errorf("SendRegVerify: %w", err) } @@ -114,7 +121,7 @@ func (e EmailService) SendVerifyUrl(ctx context.Context, email string, interval u := url.URL{ Host: host, Scheme: "http", - Path: "/test?" + code, + Path: "/register?code=" + url.QueryEscape(code), } if e.config.WebBaseUrl != "" { diff --git a/service/user.go b/service/user.go index 423721b..8122787 100644 --- a/service/user.go +++ b/service/user.go @@ -258,7 +258,7 @@ func (w *UserService) SendRegEmail(ctx context.Context, email, CaptchaToken, hos err = w.emailService.SendVerifyUrl(ctx, email, 60, host) if err != nil { - return fmt.Errorf("SendRegEmail: %w", ErrNotAllowDomain) + return fmt.Errorf("SendRegEmail: %w", err) } return nil } diff --git a/service/web.go b/service/web.go index 2b08f4b..cfdaf29 100644 --- a/service/web.go +++ b/service/web.go @@ -35,5 +35,7 @@ func (w *WebService) GetConfig(ctx context.Context) model.Config { }, ServerName: w.config.ServerName, AllowChangeName: !w.config.OfflineUUID, + NeedEmail: w.config.Email.Enable, + AllowDomain: w.config.Email.AllowDomain, } }