找回密码

This commit is contained in:
xmdhs 2023-11-25 00:57:02 +08:00
parent 8bdff06dca
commit 50e4be3ef6
No known key found for this signature in database
GPG Key ID: E809D6D43DEFCC95
12 changed files with 314 additions and 40 deletions

View File

@ -9,7 +9,9 @@ import Layout from '@/views/Layout'
import UserAdmin from "@/views/admin/UserAdmin"; import UserAdmin from "@/views/admin/UserAdmin";
import NeedLogin from "@/components/NeedLogin"; import NeedLogin from "@/components/NeedLogin";
import Index from "@/views/Index"; import Index from "@/views/Index";
import SignUpEmail from "@/views/SignUpEmail"; import SendEmail from "@/views/SendEmail";
import { sendForgotEmail, sendRegEmail } from "@/apis/apis";
import Forgot from "@/views/Forgot";
const router = createBrowserRouter([ const router = createBrowserRouter([
{ path: "*", Component: Root }, { path: "*", Component: Root },
@ -24,7 +26,9 @@ function Root() {
<Route path="/*" element={<p>404</p>} /> <Route path="/*" element={<p>404</p>} />
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} /> <Route path="/register" element={<Register />} />
<Route path="/register_email" element={<SignUpEmail />} /> <Route path="/register_email" element={<SendEmail title="注册" sendService={sendRegEmail} />} />
<Route path="/forgot_email" element={<SendEmail title="找回密码" anyEmail sendService={sendForgotEmail} />} />
<Route path="/forgot" element={<Forgot />} />
<Route element={<NeedLogin><Outlet /></NeedLogin>}> <Route element={<NeedLogin><Outlet /></NeedLogin>}>
<Route path="/profile" element={<Profile />} /> <Route path="/profile" element={<Profile />} />

View File

@ -130,3 +130,27 @@ export async function sendRegEmail(email: string, captchaToken: string) {
}) })
return await apiGet<unknown>(r) return await apiGet<unknown>(r)
} }
export async function sendForgotEmail(email: string, captchaToken: string) {
const r = await fetch(root() + "/api/v1/user/forgot_email", {
method: "POST",
body: JSON.stringify({
"email": email,
"captchaToken": captchaToken
})
})
return await apiGet<unknown>(r)
}
export async function forgotPassWord(email: string, emailJwt: string, password: string) {
const r = await fetch(root() + "/api/v1/user/forgot", {
method: "POST",
body: JSON.stringify({
"email": email,
"emailJwt": emailJwt,
"passWord": password,
})
})
return await apiGet<unknown>(r)
}

View File

@ -0,0 +1,120 @@
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 TextField from '@mui/material/TextField';
import { useTitle } from 'ahooks';
import { useEffect, useState } from 'react';
import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
import Loading from '@/components/Loading';
import { produce } from 'immer';
import { forgotPassWord } from '@/apis/apis';
import { useNavigate } from 'react-router-dom';
export default function Forgot() {
const [err, setErr] = useState("")
useTitle("找回密码")
const [passerr, setPasserr] = useState("")
const [pass, setPass] = useState({
pass1: "",
pass2: "",
})
const [load, setLoad] = useState(false)
const [email, setEmail] = useState("")
const [code, setCode] = useState("")
const navigate = useNavigate();
useEffect(() => {
if (pass.pass1 != pass.pass2 && pass.pass2 != "") {
setPasserr("密码不相等")
return
}
setPasserr("")
}, [pass.pass1, pass.pass2])
const u = new URL(location.href)
useEffect(() => {
setEmail(u.searchParams.get("email") ?? "")
setCode(u.searchParams.get("code") ?? "")
}, [u.searchParams])
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
setLoad(true)
forgotPassWord(email, code, pass.pass1).then(() => {
navigate("/")
}).catch(e => {
setErr(String(e))
}).finally(() => { setLoad(false) })
}
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
</Typography>
<Box component="form" noValidate onSubmit={onSubmit} sx={{ mt: 3 }}>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
margin='dense'
fullWidth
label="新密码"
type="password"
required
onChange={p => setPass(produce(v => { v.pass1 = p.target.value }))}
autoComplete="new-password"
/>
</Grid>
<Grid item xs={12}>
<TextField
margin='dense'
fullWidth
label="确认新密码"
type="password"
required
error={passerr != ""}
helperText={passerr}
onChange={p => setPass(produce(v => { v.pass2 = p.target.value }))}
autoComplete="new-password"
/>
</Grid>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
</Button>
</Box>
</Box>
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={err !== ""}>
<Alert onClose={() => setErr("")} severity="error">{err}</Alert>
</Snackbar>
{load && <Loading />}
</Container>
)
}

View File

@ -13,7 +13,7 @@ import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert'; import Alert from '@mui/material/Alert';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { token, user } from '@/store/store' import { token, user } from '@/store/store'
import { login } from '@/apis/apis' import { getConfig, login } from '@/apis/apis'
import { Link as RouterLink, useNavigate } from "react-router-dom"; import { Link as RouterLink, useNavigate } from "react-router-dom";
import Loading from '@/components/Loading' import Loading from '@/components/Loading'
import CheckInput, { refType } from '@/components/CheckInput' import CheckInput, { refType } from '@/components/CheckInput'
@ -21,6 +21,7 @@ import useTitle from '@/hooks/useTitle';
import CaptchaWidget from '@/components/CaptchaWidget'; import CaptchaWidget from '@/components/CaptchaWidget';
import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget' import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget'
import { ApiErr } from '@/apis/error'; import { ApiErr } from '@/apis/error';
import { useRequest } from 'ahooks';
@ -35,6 +36,16 @@ export default function SignIn() {
const captchaRef = React.useRef<CaptchaWidgetRef>(null) const captchaRef = React.useRef<CaptchaWidgetRef>(null)
const [captchaToken, setCaptchaToken] = useState(""); const [captchaToken, setCaptchaToken] = useState("");
const server = useRequest(getConfig, {
cacheKey: "/api/v1/config",
staleTime: 60000,
onError: e => {
console.warn(e)
setErr(String(e))
}
})
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
@ -137,9 +148,9 @@ export default function SignIn() {
</Button> </Button>
<Grid container> <Grid container>
<Grid item xs> <Grid item xs>
{/* <Link href="#" variant="body2"> {server.data?.NeedEmail && <Link component={RouterLink} to="/forgot_email" variant="body2">
</Link> */} </Link>}
</Grid> </Grid>
<Grid item> <Grid item>
<Link component={RouterLink} to="/register" variant="body2"> <Link component={RouterLink} to="/register" variant="body2">

View File

@ -12,7 +12,7 @@ import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import TextField from '@mui/material/TextField'; import TextField from '@mui/material/TextField';
import { useRequest, useTitle } from 'ahooks'; import { useRequest, useTitle } from 'ahooks';
import { getConfig, sendRegEmail } from '@/apis/apis'; import { getConfig } from '@/apis/apis';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import Snackbar from '@mui/material/Snackbar'; import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert'; import Alert from '@mui/material/Alert';
@ -26,14 +26,14 @@ import { useNavigate } from "react-router-dom";
import { ApiErr } from '@/apis/error'; import { ApiErr } from '@/apis/error';
import Loading from '@/components/Loading'; import Loading from '@/components/Loading';
export default function SignUpEmail() { export default function SendEmail({ title, anyEmail = false, sendService }: { title: string, anyEmail?: boolean, sendService: (email: string, captchaToken: string) => Promise<unknown> }) {
const [err, setErr] = useState(""); const [err, setErr] = useState("");
const [domain, setDomain] = useState(""); const [domain, setDomain] = useState("");
const [email, setEmail] = useState("") const [email, setEmail] = useState("")
const captchaRef = useRef<CaptchaWidgetRef>(null) const captchaRef = useRef<CaptchaWidgetRef>(null)
const [captchaToken, setCaptchaToken] = useState(""); const [captchaToken, setCaptchaToken] = useState("");
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
useTitle("注册") useTitle(title)
const navigate = useNavigate(); const navigate = useNavigate();
const [helperText, setHelperText] = useState("") const [helperText, setHelperText] = useState("")
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -82,7 +82,7 @@ export default function SignUpEmail() {
return return
} }
setLoading(true) setLoading(true)
sendRegEmail(sendEmail, captchaToken).then(() => setOpen(true)).catch(e => { sendService(sendEmail, captchaToken).then(() => setOpen(true)).catch(e => {
captchaRef.current?.reload() captchaRef.current?.reload()
console.warn(e) console.warn(e)
if (e instanceof ApiErr) { if (e instanceof ApiErr) {
@ -120,7 +120,7 @@ export default function SignUpEmail() {
<LockOutlinedIcon /> <LockOutlinedIcon />
</Avatar> </Avatar>
<Typography component="h1" variant="h5"> <Typography component="h1" variant="h5">
{title}
</Typography> </Typography>
<Box component="form" noValidate onSubmit={onSubmit} sx={{ mt: 3 }}> <Box component="form" noValidate onSubmit={onSubmit} sx={{ mt: 3 }}>
<Grid container spacing={2}> <Grid container spacing={2}>
@ -135,7 +135,7 @@ export default function SignUpEmail() {
onChange={emailonChange} onChange={emailonChange}
/> />
{ {
server.data?.AllowDomain.length != 0 && server.data?.AllowDomain.length != 0 && !anyEmail &&
<FormControl> <FormControl>
<InputLabel></InputLabel> <InputLabel></InputLabel>
<Select label="域名" value={domain} onChange={v => setDomain(v.target.value)}> <Select label="域名" value={domain} onChange={v => setDomain(v.target.value)}>
@ -164,7 +164,7 @@ export default function SignUpEmail() {
<Dialog open={open}> <Dialog open={open}>
<DialogTitle></DialogTitle> <DialogTitle></DialogTitle>
<DialogContent> <DialogContent>
<Typography></Typography> <Typography></Typography>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={handleClose}></Button> <Button onClick={handleClose}></Button>

View File

@ -42,6 +42,7 @@ var errorHandlers = []errorHandler{
{auth.ErrTokenInvalid, model.ErrAuth, 401, slog.LevelDebug}, {auth.ErrTokenInvalid, model.ErrAuth, 401, slog.LevelDebug},
{email.ErrTokenInvalid, model.ErrAuth, 401, slog.LevelDebug}, {email.ErrTokenInvalid, model.ErrAuth, 401, slog.LevelDebug},
{email.ErrSendLimit, model.ErrEmailSend, 403, slog.LevelDebug}, {email.ErrSendLimit, model.ErrEmailSend, 403, slog.LevelDebug},
{service.ErrUsername, model.ErrPassWord, 401, slog.LevelInfo},
} }
func (h *HandleError) Service(ctx context.Context, w http.ResponseWriter, err error) { func (h *HandleError) Service(ctx context.Context, w http.ResponseWriter, err error) {

View File

@ -2,6 +2,7 @@ package handle
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"image/png" "image/png"
"io" "io"
@ -10,6 +11,7 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/xmdhs/authlib-skin/config"
"github.com/xmdhs/authlib-skin/handle/handelerror" "github.com/xmdhs/authlib-skin/handle/handelerror"
"github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/service" "github.com/xmdhs/authlib-skin/service"
@ -22,16 +24,18 @@ type UserHandel struct {
userService *service.UserService userService *service.UserService
logger *slog.Logger logger *slog.Logger
textureService *service.TextureService textureService *service.TextureService
config config.Config
} }
func NewUserHandel(handleError *handelerror.HandleError, validate *validator.Validate, func NewUserHandel(handleError *handelerror.HandleError, validate *validator.Validate,
userService *service.UserService, logger *slog.Logger, textureService *service.TextureService) *UserHandel { userService *service.UserService, logger *slog.Logger, textureService *service.TextureService, config config.Config) *UserHandel {
return &UserHandel{ return &UserHandel{
handleError: handleError, handleError: handleError,
validate: validate, validate: validate,
userService: userService, userService: userService,
logger: logger, logger: logger,
textureService: textureService, textureService: textureService,
config: config,
} }
} }
@ -120,7 +124,7 @@ func (h *UserHandel) ChangePasswd() http.HandlerFunc {
h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug) h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug)
return return
} }
err = h.userService.ChangePasswd(ctx, c, t) err = h.userService.ChangePasswd(ctx, c, t.UID, true)
if err != nil { if err != nil {
h.handleError.Service(ctx, w, err) h.handleError.Service(ctx, w, err)
return return
@ -213,21 +217,77 @@ func (h *UserHandel) PutTexture() http.HandlerFunc {
} }
} }
func (h *UserHandel) NeedEnableEmail(handle http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if !h.config.Email.Enable {
h.handleError.Error(ctx, w, "未开启邮件功能", model.ErrUnknown, 403, slog.LevelInfo)
}
handle.ServeHTTP(w, r)
})
}
func (h *UserHandel) SendRegEmail() http.HandlerFunc { func (h *UserHandel) SendRegEmail() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
c, err := utils.DeCodeBody[model.SendRegEmail](r.Body, h.validate) c, ip, shouldReturn := h.sendMailParameter(ctx, r, w)
if err != nil { if shouldReturn {
h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug)
return
}
ip, err := utils.GetIP(r)
if err != nil {
h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug)
return return
} }
err = h.userService.SendRegEmail(ctx, c.Email, c.CaptchaToken, r.Host, ip) err := h.userService.SendRegEmail(ctx, c.Email, c.CaptchaToken, r.Host, ip)
if err != nil {
h.handleError.Service(ctx, w, err)
return
}
encodeJson(w, model.API[any]{
Code: 0,
})
}
}
func (h *UserHandel) SendForgotEmail() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
c, ip, shouldReturn := h.sendMailParameter(ctx, r, w)
if shouldReturn {
return
}
err := h.userService.SendChangePasswordEmail(ctx, c.Email, c.CaptchaToken, r.Host, ip)
if err != nil {
h.handleError.Service(ctx, w, err)
return
}
encodeJson(w, model.API[any]{
Code: 0,
})
}
}
func (h *UserHandel) sendMailParameter(ctx context.Context, r *http.Request, w http.ResponseWriter) (model.SendRegEmail, string, bool) {
c, err := utils.DeCodeBody[model.SendRegEmail](r.Body, h.validate)
if err != nil {
h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug)
return model.SendRegEmail{}, "", true
}
ip, err := utils.GetIP(r)
if err != nil {
h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug)
return model.SendRegEmail{}, "", true
}
return c, ip, false
}
func (h *UserHandel) ForgotPassword() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
c, err := utils.DeCodeBody[model.ForgotPassword](r.Body, h.validate)
if err != nil {
h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug)
return
}
err = h.userService.ForgotPassword(ctx, c.Email, c.PassWord, c.EmailJwt)
if err != nil { if err != nil {
h.handleError.Service(ctx, w, err) h.handleError.Service(ctx, w, err)
return return

View File

@ -89,6 +89,12 @@ type LoginRep struct {
} }
type SendRegEmail struct { type SendRegEmail struct {
Email string `json:"email"` Email string `json:"email" validate:"required,email"`
CaptchaToken string `json:"captchaToken"` CaptchaToken string `json:"captchaToken"`
} }
type ForgotPassword struct {
Email string `json:"email" validate:"required,email"`
EmailJwt string `json:"emailJwt" validate:"required"`
PassWord string `json:"passWord" validate:"required"`
}

View File

@ -81,7 +81,13 @@ func newSkinApi(handel *handle.Handel, userHandel *handle.UserHandel, adminHande
r.Post("/user/reg", userHandel.Reg()) r.Post("/user/reg", userHandel.Reg())
r.Post("/user/login", userHandel.Login()) r.Post("/user/login", userHandel.Login())
r.Get("/config", handel.GetConfig()) r.Get("/config", handel.GetConfig())
r.Group(func(r chi.Router) {
r.Use(userHandel.NeedEnableEmail)
r.Post("/user/reg_email", userHandel.SendRegEmail()) r.Post("/user/reg_email", userHandel.SendRegEmail())
r.Post("/user/forgot_email", userHandel.SendForgotEmail())
r.Post("/user/forgot", userHandel.ForgotPassword())
})
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
r.Use(adminHandel.NeedAuth) r.Use(adminHandel.NeedAuth)

View File

@ -71,7 +71,7 @@ func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func()
} }
userService := service.NewUserSerice(c, client, captchaService, authService, cache, emailService) userService := service.NewUserSerice(c, client, captchaService, authService, cache, emailService)
textureService := service.NewTextureService(client, c, cache) textureService := service.NewTextureService(client, c, cache)
userHandel := handle.NewUserHandel(handleError, validate, userService, logger, textureService) userHandel := handle.NewUserHandel(handleError, validate, userService, logger, textureService, c)
adminService := service.NewAdminService(authService, client, c, cache) adminService := service.NewAdminService(authService, client, c, cache)
adminHandel := handle.NewAdminHandel(handleError, adminService, validate) adminHandel := handle.NewAdminHandel(handleError, adminService, validate)
httpHandler := route.NewRoute(yggdrasil3, handel, c, handler, userHandel, adminHandel) httpHandler := route.NewRoute(yggdrasil3, handel, c, handler, userHandel, adminHandel)

View File

@ -99,7 +99,7 @@ func (e EmailService) SendEmail(ctx context.Context, to string, subject, body st
var emailTemplate = lo.Must(template.New("email").Parse(`<p>{{ .msg }}</p><a href="{{.url}}">{{ .url }}</a>`)) var emailTemplate = lo.Must(template.New("email").Parse(`<p>{{ .msg }}</p><a href="{{.url}}">{{ .url }}</a>`))
func (e EmailService) SendVerifyUrl(ctx context.Context, email string, interval int, host string) error { func (e EmailService) SendVerifyUrl(ctx context.Context, email string, interval int, host string, subject, msg, path string) error {
sendKey := []byte("SendEmail" + email) sendKey := []byte("SendEmail" + email)
sendB, err := e.cache.Get(sendKey) sendB, err := e.cache.Get(sendKey)
if err != nil { if err != nil {
@ -125,7 +125,7 @@ func (e EmailService) SendVerifyUrl(ctx context.Context, email string, interval
u := url.URL{ u := url.URL{
Host: host, Host: host,
Scheme: "http", Scheme: "http",
Path: "/register", Path: path,
} }
u.RawQuery = q.Encode() u.RawQuery = q.Encode()
@ -140,7 +140,7 @@ func (e EmailService) SendVerifyUrl(ctx context.Context, email string, interval
body := bytes.NewBuffer(nil) body := bytes.NewBuffer(nil)
err = emailTemplate.Execute(body, map[string]any{ err = emailTemplate.Execute(body, map[string]any{
"msg": "点击下方链接验证你的邮箱1 天内有效", "msg": msg,
"url": u.String(), "url": u.String(),
}) })
if err != nil { if err != nil {
@ -167,18 +167,21 @@ func (e EmailService) VerifyJwt(email, jwtStr string) error {
return fmt.Errorf("VerifyJwt: %w", err) return fmt.Errorf("VerifyJwt: %w", err)
} }
sub, _ := token.Claims.GetSubject() sub, _ := token.Claims.GetSubject()
if !token.Valid || sub != email { iss, _ := token.Claims.GetIssuer()
if !token.Valid || sub != email || iss != issuer {
return fmt.Errorf("VerifyJwt: %w", ErrTokenInvalid) return fmt.Errorf("VerifyJwt: %w", ErrTokenInvalid)
} }
return nil return nil
} }
const issuer = "authlib-skin email verification"
func newJwtToken(jwtKey *rsa.PrivateKey, email string) (string, error) { func newJwtToken(jwtKey *rsa.PrivateKey, email string) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.RegisteredClaims{ token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * 24 * time.Hour)), ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * 24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()), IssuedAt: jwt.NewNumericDate(time.Now()),
Subject: email, Subject: email,
Issuer: "authlib-skin email verification", Issuer: issuer,
}) })
jwts, err := token.SignedString(jwtKey) jwts, err := token.SignedString(jwtKey)
if err != nil { if err != nil {

View File

@ -27,6 +27,7 @@ var (
ErrRegLimit = errors.New("超过注册 ip 限制") ErrRegLimit = errors.New("超过注册 ip 限制")
ErrPassWord = errors.New("错误的密码或用户名") ErrPassWord = errors.New("错误的密码或用户名")
ErrChangeName = errors.New("离线模式 uuid 不允许修改用户名") ErrChangeName = errors.New("离线模式 uuid 不允许修改用户名")
ErrUsername = errors.New("邮箱不存在")
) )
type UserService struct { type UserService struct {
@ -181,15 +182,17 @@ func (w *UserService) Info(ctx context.Context, t *model.TokenClaims) (model.Use
}, nil }, nil
} }
func (w *UserService) ChangePasswd(ctx context.Context, p model.ChangePasswd, t *model.TokenClaims) error { func (w *UserService) ChangePasswd(ctx context.Context, p model.ChangePasswd, uid int, validOldPass bool) error {
u, err := w.client.User.Query().Where(user.IDEQ(t.UID)).WithToken().First(ctx) u, err := w.client.User.Query().Where(user.IDEQ(uid)).WithToken().First(ctx)
if err != nil { if err != nil {
return fmt.Errorf("ChangePasswd: %w", err) return fmt.Errorf("ChangePasswd: %w", err)
} }
err = validatePass(ctx, u, p.Old) if validOldPass {
err := validatePass(ctx, u, p.Old)
if err != nil { if err != nil {
return fmt.Errorf("ChangePasswd: %w", err) return fmt.Errorf("ChangePasswd: %w", err)
} }
}
pass, salt := utils.Argon2ID(p.New) pass, salt := utils.Argon2ID(p.New)
if u.Edges.Token != nil { if u.Edges.Token != nil {
err := w.client.UserToken.UpdateOne(u.Edges.Token).AddTokenID(1).Exec(ctx) err := w.client.UserToken.UpdateOne(u.Edges.Token).AddTokenID(1).Exec(ctx)
@ -197,7 +200,7 @@ func (w *UserService) ChangePasswd(ctx context.Context, p model.ChangePasswd, t
return fmt.Errorf("ChangePasswd: %w", err) return fmt.Errorf("ChangePasswd: %w", err)
} }
} }
err = w.cache.Del([]byte("auth" + strconv.Itoa(t.UID))) err = w.cache.Del([]byte("auth" + strconv.Itoa(uid)))
if err != nil { if err != nil {
return fmt.Errorf("ChangePasswd: %w", err) return fmt.Errorf("ChangePasswd: %w", err)
} }
@ -256,9 +259,45 @@ func (w *UserService) SendRegEmail(ctx context.Context, email, CaptchaToken, hos
return fmt.Errorf("SendRegEmail: %w", err) return fmt.Errorf("SendRegEmail: %w", err)
} }
err = w.emailService.SendVerifyUrl(ctx, email, 60, host) err = w.emailService.SendVerifyUrl(ctx, email, 60, host, "验证你的邮箱以完成注册", "点击下方链接完成注册1 天内有效", "/register")
if err != nil { if err != nil {
return fmt.Errorf("SendRegEmail: %w", err) return fmt.Errorf("SendRegEmail: %w", err)
} }
return nil return nil
} }
func (w *UserService) SendChangePasswordEmail(ctx context.Context, email, CaptchaToken, host, ip string) error {
err := w.captchaService.VerifyCaptcha(ctx, CaptchaToken, ip)
if err != nil {
return fmt.Errorf("SendChangePasswordEmail: %w", err)
}
c, err := w.client.User.Query().Where(user.Email(email)).Count(ctx)
if err != nil {
return fmt.Errorf("SendChangePasswordEmail: %w", err)
}
if c == 0 {
return fmt.Errorf("SendChangePasswordEmail: %w", ErrUsername)
}
err = w.emailService.SendVerifyUrl(ctx, email, 60, host, "找回密码邮箱验证", "点击下方链接更改你的密码1 天内有效", "/forgot")
if err != nil {
return fmt.Errorf("SendChangePasswordEmail: %w", err)
}
return nil
}
func (w *UserService) ForgotPassword(ctx context.Context, email, passWord, emailJwt string) error {
err := w.emailService.VerifyJwt(email, emailJwt)
if err != nil {
return fmt.Errorf("ForgotPassword: %w", err)
}
u, err := w.client.User.Query().Where(user.Email(email)).First(ctx)
if err != nil {
return fmt.Errorf("ForgotPassword: %w", err)
}
err = w.ChangePasswd(ctx, model.ChangePasswd{New: passWord}, u.ID, false)
if err != nil {
return fmt.Errorf("ForgotPassword: %w", err)
}
return nil
}