找回密码
This commit is contained in:
parent
8bdff06dca
commit
50e4be3ef6
@ -9,7 +9,9 @@ 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";
|
||||
import SendEmail from "@/views/SendEmail";
|
||||
import { sendForgotEmail, sendRegEmail } from "@/apis/apis";
|
||||
import Forgot from "@/views/Forgot";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{ path: "*", Component: Root },
|
||||
@ -24,7 +26,9 @@ function Root() {
|
||||
<Route path="/*" element={<p>404</p>} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<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 path="/profile" element={<Profile />} />
|
||||
|
@ -130,3 +130,27 @@ export async function sendRegEmail(email: string, captchaToken: string) {
|
||||
})
|
||||
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)
|
||||
}
|
120
frontend/src/views/Forgot.tsx
Normal file
120
frontend/src/views/Forgot.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -13,7 +13,7 @@ import Snackbar from '@mui/material/Snackbar';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import { useSetAtom } from 'jotai';
|
||||
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 Loading from '@/components/Loading'
|
||||
import CheckInput, { refType } from '@/components/CheckInput'
|
||||
@ -21,6 +21,7 @@ import useTitle from '@/hooks/useTitle';
|
||||
import CaptchaWidget from '@/components/CaptchaWidget';
|
||||
import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget'
|
||||
import { ApiErr } from '@/apis/error';
|
||||
import { useRequest } from 'ahooks';
|
||||
|
||||
|
||||
|
||||
@ -35,6 +36,16 @@ export default function SignIn() {
|
||||
const captchaRef = React.useRef<CaptchaWidgetRef>(null)
|
||||
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>) => {
|
||||
event.preventDefault();
|
||||
|
||||
@ -137,9 +148,9 @@ export default function SignIn() {
|
||||
</Button>
|
||||
<Grid container>
|
||||
<Grid item xs>
|
||||
{/* <Link href="#" variant="body2">
|
||||
{server.data?.NeedEmail && <Link component={RouterLink} to="/forgot_email" variant="body2">
|
||||
忘记密码?
|
||||
</Link> */}
|
||||
</Link>}
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Link component={RouterLink} to="/register" variant="body2">
|
||||
|
@ -12,7 +12,7 @@ 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 { getConfig } from '@/apis/apis';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import Snackbar from '@mui/material/Snackbar';
|
||||
import Alert from '@mui/material/Alert';
|
||||
@ -26,14 +26,14 @@ import { useNavigate } from "react-router-dom";
|
||||
import { ApiErr } from '@/apis/error';
|
||||
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 [domain, setDomain] = useState("");
|
||||
const [email, setEmail] = useState("")
|
||||
const captchaRef = useRef<CaptchaWidgetRef>(null)
|
||||
const [captchaToken, setCaptchaToken] = useState("");
|
||||
const [open, setOpen] = useState(false);
|
||||
useTitle("注册")
|
||||
useTitle(title)
|
||||
const navigate = useNavigate();
|
||||
const [helperText, setHelperText] = useState("")
|
||||
const [loading, setLoading] = useState(false);
|
||||
@ -73,7 +73,7 @@ export default function SignUpEmail() {
|
||||
return email
|
||||
})()
|
||||
|
||||
if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(sendEmail)){
|
||||
if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(sendEmail)) {
|
||||
setHelperText("邮箱格式错误")
|
||||
return
|
||||
}
|
||||
@ -82,7 +82,7 @@ export default function SignUpEmail() {
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
sendRegEmail(sendEmail, captchaToken).then(() => setOpen(true)).catch(e => {
|
||||
sendService(sendEmail, captchaToken).then(() => setOpen(true)).catch(e => {
|
||||
captchaRef.current?.reload()
|
||||
console.warn(e)
|
||||
if (e instanceof ApiErr) {
|
||||
@ -120,7 +120,7 @@ export default function SignUpEmail() {
|
||||
<LockOutlinedIcon />
|
||||
</Avatar>
|
||||
<Typography component="h1" variant="h5">
|
||||
输入邮箱
|
||||
{title}
|
||||
</Typography>
|
||||
<Box component="form" noValidate onSubmit={onSubmit} sx={{ mt: 3 }}>
|
||||
<Grid container spacing={2}>
|
||||
@ -135,7 +135,7 @@ export default function SignUpEmail() {
|
||||
onChange={emailonChange}
|
||||
/>
|
||||
{
|
||||
server.data?.AllowDomain.length != 0 &&
|
||||
server.data?.AllowDomain.length != 0 && !anyEmail &&
|
||||
<FormControl>
|
||||
<InputLabel>域名</InputLabel>
|
||||
<Select label="域名" value={domain} onChange={v => setDomain(v.target.value)}>
|
||||
@ -164,7 +164,7 @@ export default function SignUpEmail() {
|
||||
<Dialog open={open}>
|
||||
<DialogTitle>邮件已发送</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>请到收件箱(或垃圾箱)点击验证链接以继续完成注册。</Typography>
|
||||
<Typography>请到收件箱(或垃圾箱)点击验证链接以继续。</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>返回首页</Button>
|
@ -42,6 +42,7 @@ var errorHandlers = []errorHandler{
|
||||
{auth.ErrTokenInvalid, model.ErrAuth, 401, slog.LevelDebug},
|
||||
{email.ErrTokenInvalid, model.ErrAuth, 401, 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) {
|
||||
|
@ -2,6 +2,7 @@ package handle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"io"
|
||||
@ -10,6 +11,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/xmdhs/authlib-skin/config"
|
||||
"github.com/xmdhs/authlib-skin/handle/handelerror"
|
||||
"github.com/xmdhs/authlib-skin/model"
|
||||
"github.com/xmdhs/authlib-skin/service"
|
||||
@ -22,16 +24,18 @@ type UserHandel struct {
|
||||
userService *service.UserService
|
||||
logger *slog.Logger
|
||||
textureService *service.TextureService
|
||||
config config.Config
|
||||
}
|
||||
|
||||
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{
|
||||
handleError: handleError,
|
||||
validate: validate,
|
||||
userService: userService,
|
||||
logger: logger,
|
||||
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)
|
||||
return
|
||||
}
|
||||
err = h.userService.ChangePasswd(ctx, c, t)
|
||||
err = h.userService.ChangePasswd(ctx, c, t.UID, true)
|
||||
if err != nil {
|
||||
h.handleError.Service(ctx, w, err)
|
||||
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 {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
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
|
||||
}
|
||||
ip, err := utils.GetIP(r)
|
||||
if err != nil {
|
||||
h.handleError.Error(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug)
|
||||
c, ip, shouldReturn := h.sendMailParameter(ctx, r, w)
|
||||
if shouldReturn {
|
||||
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 {
|
||||
h.handleError.Service(ctx, w, err)
|
||||
return
|
||||
|
@ -89,6 +89,12 @@ type LoginRep struct {
|
||||
}
|
||||
|
||||
type SendRegEmail struct {
|
||||
Email string `json:"email"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
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"`
|
||||
}
|
||||
|
@ -81,7 +81,13 @@ 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(userHandel.NeedEnableEmail)
|
||||
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.Use(adminHandel.NeedAuth)
|
||||
|
@ -71,7 +71,7 @@ func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func()
|
||||
}
|
||||
userService := service.NewUserSerice(c, client, captchaService, authService, cache, emailService)
|
||||
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)
|
||||
adminHandel := handle.NewAdminHandel(handleError, adminService, validate)
|
||||
httpHandler := route.NewRoute(yggdrasil3, handel, c, handler, userHandel, adminHandel)
|
||||
|
@ -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>`))
|
||||
|
||||
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)
|
||||
sendB, err := e.cache.Get(sendKey)
|
||||
if err != nil {
|
||||
@ -125,7 +125,7 @@ func (e EmailService) SendVerifyUrl(ctx context.Context, email string, interval
|
||||
u := url.URL{
|
||||
Host: host,
|
||||
Scheme: "http",
|
||||
Path: "/register",
|
||||
Path: path,
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
@ -140,7 +140,7 @@ func (e EmailService) SendVerifyUrl(ctx context.Context, email string, interval
|
||||
|
||||
body := bytes.NewBuffer(nil)
|
||||
err = emailTemplate.Execute(body, map[string]any{
|
||||
"msg": "点击下方链接验证你的邮箱,1 天内有效",
|
||||
"msg": msg,
|
||||
"url": u.String(),
|
||||
})
|
||||
if err != nil {
|
||||
@ -167,18 +167,21 @@ func (e EmailService) VerifyJwt(email, jwtStr string) error {
|
||||
return fmt.Errorf("VerifyJwt: %w", err)
|
||||
}
|
||||
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 nil
|
||||
}
|
||||
|
||||
const issuer = "authlib-skin email verification"
|
||||
|
||||
func newJwtToken(jwtKey *rsa.PrivateKey, email string) (string, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * 24 * time.Hour)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
Subject: email,
|
||||
Issuer: "authlib-skin email verification",
|
||||
Issuer: issuer,
|
||||
})
|
||||
jwts, err := token.SignedString(jwtKey)
|
||||
if err != nil {
|
||||
|
@ -27,6 +27,7 @@ var (
|
||||
ErrRegLimit = errors.New("超过注册 ip 限制")
|
||||
ErrPassWord = errors.New("错误的密码或用户名")
|
||||
ErrChangeName = errors.New("离线模式 uuid 不允许修改用户名")
|
||||
ErrUsername = errors.New("邮箱不存在")
|
||||
)
|
||||
|
||||
type UserService struct {
|
||||
@ -181,14 +182,16 @@ func (w *UserService) Info(ctx context.Context, t *model.TokenClaims) (model.Use
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *UserService) ChangePasswd(ctx context.Context, p model.ChangePasswd, t *model.TokenClaims) error {
|
||||
u, err := w.client.User.Query().Where(user.IDEQ(t.UID)).WithToken().First(ctx)
|
||||
func (w *UserService) ChangePasswd(ctx context.Context, p model.ChangePasswd, uid int, validOldPass bool) error {
|
||||
u, err := w.client.User.Query().Where(user.IDEQ(uid)).WithToken().First(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ChangePasswd: %w", err)
|
||||
}
|
||||
err = validatePass(ctx, u, p.Old)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ChangePasswd: %w", err)
|
||||
if validOldPass {
|
||||
err := validatePass(ctx, u, p.Old)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ChangePasswd: %w", err)
|
||||
}
|
||||
}
|
||||
pass, salt := utils.Argon2ID(p.New)
|
||||
if u.Edges.Token != nil {
|
||||
@ -197,7 +200,7 @@ func (w *UserService) ChangePasswd(ctx context.Context, p model.ChangePasswd, t
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
err = w.emailService.SendVerifyUrl(ctx, email, 60, host)
|
||||
err = w.emailService.SendVerifyUrl(ctx, email, 60, host, "验证你的邮箱以完成注册", "点击下方链接完成注册,1 天内有效", "/register")
|
||||
if err != nil {
|
||||
return fmt.Errorf("SendRegEmail: %w", err)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user