修改用户名
This commit is contained in:
parent
c7f07491ad
commit
eaf9500907
@ -1,4 +1,4 @@
|
||||
import { Routes, Route, createBrowserRouter, RouterProvider, useNavigate, Outlet } from "react-router-dom";
|
||||
import { Routes, Route, createBrowserRouter, RouterProvider, useNavigate, Outlet, Navigate } from "react-router-dom";
|
||||
import { ScrollRestoration } from "react-router-dom";
|
||||
import Login from '@/views/Login'
|
||||
import Register from '@/views/Register'
|
||||
@ -69,12 +69,10 @@ function NeedLogin({ children, needAdmin = false }: { children: JSX.Element, nee
|
||||
},
|
||||
})
|
||||
if (t == "") {
|
||||
navigate("/login")
|
||||
return <></>
|
||||
return <Navigate to="/login" />
|
||||
}
|
||||
if (!loading && data && needAdmin && !data.is_admin) {
|
||||
navigate("/login")
|
||||
return <></>
|
||||
return <Navigate to="/login" />
|
||||
}
|
||||
return <> {children}</>
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import type { tokenData, ApiUser, ApiServerInfo, YggProfile } from '@/apis/model'
|
||||
import type { tokenData, ApiUser, ApiServerInfo, YggProfile, ApiConfig } from '@/apis/model'
|
||||
import { apiGet } from '@/apis/utils'
|
||||
|
||||
export async function login(username: string, password: string) {
|
||||
@ -84,4 +84,22 @@ export async function changePasswd(old: string, newpa: string, token: string) {
|
||||
}
|
||||
})
|
||||
return await apiGet<unknown>(r)
|
||||
}
|
||||
|
||||
export async function getConfig() {
|
||||
const r = await fetch(import.meta.env.VITE_APIADDR + "/api/v1/config")
|
||||
return await apiGet<ApiConfig>(r)
|
||||
}
|
||||
|
||||
export async function changeName(name: string, token: string) {
|
||||
const r = await fetch(import.meta.env.VITE_APIADDR + "/api/v1/user/name", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
"name": name,
|
||||
}),
|
||||
headers: {
|
||||
"Authorization": "Bearer " + token
|
||||
}
|
||||
})
|
||||
return await apiGet<unknown>(r)
|
||||
}
|
@ -12,14 +12,12 @@ export interface Api<T> {
|
||||
data: T
|
||||
}
|
||||
|
||||
export type ApiErr = Api<unknown>
|
||||
|
||||
interface captcha {
|
||||
type: string
|
||||
siteKey: string
|
||||
}
|
||||
|
||||
export type ApiCaptcha = Api<captcha>
|
||||
|
||||
export interface ApiUser {
|
||||
uid: string
|
||||
@ -39,4 +37,9 @@ export interface YggProfile {
|
||||
name: string
|
||||
value: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface ApiConfig {
|
||||
captcha: captcha
|
||||
AllowChangeName: boolean
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
import { Turnstile } from '@marsidev/react-turnstile'
|
||||
import Button from '@mui/material/Button'
|
||||
import { useRef, useState, memo, forwardRef, useImperativeHandle } from 'react'
|
||||
import { useRef, useState, memo, forwardRef, useImperativeHandle, useEffect } from 'react'
|
||||
import type { TurnstileInstance } from '@marsidev/react-turnstile'
|
||||
import { ApiCaptcha } from '@/apis/model';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Skeleton from '@mui/material/Skeleton';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { getConfig } from '@/apis/apis';
|
||||
|
||||
interface prop {
|
||||
onSuccess: ((token: string) => void)
|
||||
@ -19,7 +19,9 @@ export type refType = {
|
||||
const CaptchaWidget = forwardRef<refType, prop>(({ onSuccess }, ref) => {
|
||||
const Turnstileref = useRef<TurnstileInstance>(null)
|
||||
const [key, setKey] = useState(1)
|
||||
const { data, error, loading } = useRequest(() => fetch(import.meta.env.VITE_APIADDR + '/api/v1/captcha').then(v => v.json() as Promise<ApiCaptcha>), {
|
||||
const { data, error, loading } = useRequest(getConfig, {
|
||||
cacheKey: "/api/v1/config",
|
||||
staleTime: 600000,
|
||||
loadingDelay: 200
|
||||
})
|
||||
|
||||
@ -30,6 +32,12 @@ const CaptchaWidget = forwardRef<refType, prop>(({ onSuccess }, ref) => {
|
||||
}
|
||||
}
|
||||
})
|
||||
useEffect(() => {
|
||||
if (data?.captcha?.type != "turnstile") {
|
||||
onSuccess("ok")
|
||||
return
|
||||
}
|
||||
}, [data?.captcha?.type, onSuccess])
|
||||
|
||||
|
||||
if (error) {
|
||||
@ -39,18 +47,10 @@ const CaptchaWidget = forwardRef<refType, prop>(({ onSuccess }, ref) => {
|
||||
if (loading) {
|
||||
return <Skeleton variant="rectangular" width={300} height={65} />
|
||||
}
|
||||
if (data?.code != 0) {
|
||||
console.warn(error)
|
||||
return <Alert severity="warning">{String(data?.msg)}</Alert>
|
||||
}
|
||||
if (data.data.type != "turnstile") {
|
||||
onSuccess("ok")
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Turnstile siteKey={data?.data.siteKey ?? ""} key={key} onSuccess={onSuccess} ref={Turnstileref} scriptOptions={{ async: true }} />
|
||||
<Turnstile siteKey={data?.captcha?.siteKey ?? ""} key={key} onSuccess={onSuccess} ref={Turnstileref} scriptOptions={{ async: true }} />
|
||||
<Button onClick={() => setKey(key + 1)}>刷新验证码</Button>
|
||||
</>
|
||||
)
|
||||
|
@ -19,6 +19,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import CaptchaWidget from '@/components/CaptchaWidget';
|
||||
import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget'
|
||||
import useTitle from '@/hooks/useTitle';
|
||||
import { ApiErr } from '@/apis/error';
|
||||
|
||||
export default function SignUp() {
|
||||
const [regErr, setRegErr] = useState("");
|
||||
@ -51,7 +52,23 @@ export default function SignUp() {
|
||||
setLoading(true)
|
||||
register(d.email ?? "", d.username ?? "", d.password ?? "", captchaToken).
|
||||
then(() => navigate("/login")).
|
||||
catch(v => [setRegErr(String(v)), console.warn(v), captchaRef.current?.reload()]).
|
||||
catch(v => {
|
||||
captchaRef.current?.reload()
|
||||
console.warn(v)
|
||||
|
||||
if (v instanceof ApiErr) {
|
||||
switch (v.code) {
|
||||
case 3:
|
||||
setRegErr("邮箱已存在")
|
||||
break
|
||||
case 7:
|
||||
setRegErr("用户名已存在")
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
setRegErr(String(v))
|
||||
}).
|
||||
finally(() => setLoading(false))
|
||||
};
|
||||
|
||||
|
@ -5,15 +5,47 @@ import CardHeader from "@mui/material/CardHeader";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import { useEffect, useState } from "react";
|
||||
import { produce } from 'immer'
|
||||
import { changePasswd } from "@/apis/apis";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import { changeName, changePasswd, getConfig } from "@/apis/apis";
|
||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||
import { LayoutAlertErr, token, user } from "@/store/store";
|
||||
import Loading from "@/components/Loading";
|
||||
import { ApiErr } from "@/apis/error";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import useTitle from "@/hooks/useTitle";
|
||||
import Box from "@mui/material/Box";
|
||||
import { useRequest } from "ahooks";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
|
||||
export default function Security() {
|
||||
useTitle("安全设置")
|
||||
const setLayoutErr = useSetAtom(LayoutAlertErr)
|
||||
|
||||
const { data } = useRequest(getConfig, {
|
||||
cacheKey: "/api/v1/config",
|
||||
staleTime: 600000,
|
||||
onError: e => {
|
||||
setLayoutErr(String(e))
|
||||
}
|
||||
})
|
||||
|
||||
return (<>
|
||||
<Box sx={{
|
||||
display: "grid", gap: "1em",
|
||||
gridTemplateColumns: {
|
||||
lg: "1fr 1fr"
|
||||
}
|
||||
}}>
|
||||
<ChangePasswd />
|
||||
{data?.AllowChangeName && <ChangeName />}
|
||||
</Box>
|
||||
</>)
|
||||
}
|
||||
|
||||
function ChangePasswd() {
|
||||
const [pass, setPass] = useState({
|
||||
old: "",
|
||||
pass1: "",
|
||||
@ -26,7 +58,6 @@ export default function Security() {
|
||||
const setLayoutErr = useSetAtom(LayoutAlertErr)
|
||||
const setUser = useSetAtom(user)
|
||||
const navigate = useNavigate();
|
||||
useTitle("安全设置")
|
||||
|
||||
useEffect(() => {
|
||||
if (pass.pass1 != pass.pass2 && pass.pass2 != "") {
|
||||
@ -91,4 +122,78 @@ export default function Security() {
|
||||
{load && <Loading />}
|
||||
|
||||
</>)
|
||||
}
|
||||
|
||||
function ChangeName() {
|
||||
const [err, setErr] = useState("")
|
||||
const [name, setName] = useState("")
|
||||
const [open, setOpen] = useState(false)
|
||||
const [load, setLoad] = useState(false)
|
||||
const nowToken = useAtomValue(token)
|
||||
const setUser = useSetAtom(user)
|
||||
|
||||
const handelClick = () => {
|
||||
if (name == "") return
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (load) return
|
||||
setLoad(true)
|
||||
changeName(name, nowToken).then(() => {
|
||||
setName("")
|
||||
setUser(v => { return { name: name, uuid: v.uuid } })
|
||||
}).catch(e => {
|
||||
if (e instanceof ApiErr && e.code == 7) {
|
||||
setErr("用户名已存在")
|
||||
return
|
||||
}
|
||||
setErr(String(e))
|
||||
console.warn(e)
|
||||
}).finally(() => [setLoad(false), setOpen(false)])
|
||||
}
|
||||
|
||||
return (<>
|
||||
<Card sx={{ height: "min-content" }}>
|
||||
<CardHeader title="更改用户名" />
|
||||
<CardContent>
|
||||
<TextField
|
||||
margin='dense'
|
||||
fullWidth
|
||||
label="新用户名"
|
||||
type='text'
|
||||
required
|
||||
error={err != ""}
|
||||
helperText={err}
|
||||
onChange={v => setName(v.target.value)}
|
||||
autoComplete="username"
|
||||
/>
|
||||
<Button sx={{ marginTop: "1em" }} onClick={handelClick} variant='contained'>提交</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<DialogTitle>
|
||||
确认修改后的用户名
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
{`用户名改为`} {<code> {name} </code>} {`?`}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>取消</Button>
|
||||
<Button onClick={handleSubmit} autoFocus>
|
||||
好
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
{load && <Loading />}
|
||||
</>)
|
||||
}
|
@ -8,11 +8,11 @@ import (
|
||||
"github.com/xmdhs/authlib-skin/model"
|
||||
)
|
||||
|
||||
func (h *Handel) GetCaptcha() httprouter.Handle {
|
||||
func (h *Handel) GetConfig() 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]{
|
||||
c := h.webService.GetConfig(ctx)
|
||||
m := model.API[model.Config]{
|
||||
Code: 0,
|
||||
Data: c,
|
||||
}
|
@ -38,6 +38,10 @@ func (h *Handel) Reg() httprouter.Handle {
|
||||
h.handleError(ctx, w, err.Error(), model.ErrExistUser, 400, slog.LevelDebug)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrExitsName) {
|
||||
h.handleError(ctx, w, err.Error(), model.ErrExitsName, 400, slog.LevelDebug)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrRegLimit) {
|
||||
h.handleError(ctx, w, err.Error(), model.ErrRegLimit, 400, slog.LevelDebug)
|
||||
return
|
||||
@ -103,3 +107,30 @@ func (h *Handel) ChangePasswd() httprouter.Handle {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handel) ChangeName() httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
ctx := r.Context()
|
||||
token := h.getTokenbyAuthorization(ctx, w, r)
|
||||
if token == "" {
|
||||
return
|
||||
}
|
||||
c, err := utils.DeCodeBody[model.ChangeName](r.Body, h.validate)
|
||||
if err != nil {
|
||||
h.handleError(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug)
|
||||
return
|
||||
}
|
||||
err = h.webService.ChangeName(ctx, c.Name, token)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrExitsName) {
|
||||
h.handleError(ctx, w, err.Error(), model.ErrExitsName, 400, slog.LevelDebug)
|
||||
return
|
||||
}
|
||||
h.handleError(ctx, w, err.Error(), model.ErrService, 500, slog.LevelWarn)
|
||||
return
|
||||
}
|
||||
encodeJson(w, model.API[any]{
|
||||
Code: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -11,4 +11,5 @@ const (
|
||||
ErrRegLimit
|
||||
ErrAuth
|
||||
ErrPassWord
|
||||
ErrExitsName
|
||||
)
|
||||
|
@ -51,3 +51,12 @@ type UserList struct {
|
||||
Email string `json:"email"`
|
||||
RegIp string `json:"reg_ip"`
|
||||
}
|
||||
|
||||
type ChangeName struct {
|
||||
Name string `json:"name" validate:"required,min=3,max=16"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Captcha Captcha `json:"captcha"`
|
||||
AllowChangeName bool
|
||||
}
|
||||
|
@ -53,9 +53,11 @@ func newYggdrasil(r *httprouter.Router, handelY yggdrasil.Yggdrasil) error {
|
||||
|
||||
func newSkinApi(r *httprouter.Router, handel *handle.Handel) error {
|
||||
r.PUT("/api/v1/user/reg", handel.Reg())
|
||||
r.GET("/api/v1/captcha", handel.GetCaptcha())
|
||||
r.GET("/api/v1/config", handel.GetConfig())
|
||||
r.GET("/api/v1/user", handel.UserInfo())
|
||||
r.POST("/api/v1/user/password", handel.ChangePasswd())
|
||||
r.POST("/api/v1/user/name", handel.ChangeName())
|
||||
|
||||
r.GET("/api/v1/admin/users", handel.NeedAdmin(handel.ListUser()))
|
||||
return nil
|
||||
}
|
||||
|
@ -11,10 +11,13 @@ import (
|
||||
"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,
|
||||
func (w *WebService) GetConfig(ctx context.Context) model.Config {
|
||||
return model.Config{
|
||||
Captcha: model.Captcha{
|
||||
Type: w.config.Captcha.Type,
|
||||
SiteKey: w.config.Captcha.SiteKey,
|
||||
},
|
||||
AllowChangeName: !w.config.OfflineUUID,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,10 +19,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrExistUser = errors.New("邮箱已存在")
|
||||
ErrExitsName = errors.New("用户名已存在")
|
||||
ErrRegLimit = errors.New("超过注册 ip 限制")
|
||||
ErrPassWord = errors.New("错误的密码")
|
||||
ErrExistUser = errors.New("邮箱已存在")
|
||||
ErrExitsName = errors.New("用户名已存在")
|
||||
ErrRegLimit = errors.New("超过注册 ip 限制")
|
||||
ErrPassWord = errors.New("错误的密码")
|
||||
ErrChangeName = errors.New("离线模式 uuid 不允许修改用户名")
|
||||
)
|
||||
|
||||
func (w *WebService) Reg(ctx context.Context, u model.User, ipPrefix, ip string) error {
|
||||
@ -138,3 +139,33 @@ func (w *WebService) ChangePasswd(ctx context.Context, p model.ChangePasswd, tok
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WebService) changeName(ctx context.Context, newName string, uid int) error {
|
||||
if w.config.OfflineUUID {
|
||||
return fmt.Errorf("changeName: %w", ErrChangeName)
|
||||
}
|
||||
c, err := w.client.UserProfile.Query().Where(userprofile.Name(newName)).Count(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("changeName: %w", err)
|
||||
}
|
||||
if c != 0 {
|
||||
return fmt.Errorf("changeName: %w", ErrExitsName)
|
||||
}
|
||||
err = w.client.UserProfile.Update().Where(userprofile.HasUserWith(user.ID(uid))).SetName(newName).Exec(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("changeName: %w", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *WebService) ChangeName(ctx context.Context, newName string, token string) error {
|
||||
t, err := utilsService.Auth(ctx, yggdrasil.ValidateToken{AccessToken: token}, w.client, w.cache, &w.prikey.PublicKey, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ChangeName: %w", err)
|
||||
}
|
||||
err = w.changeName(ctx, newName, t.UID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ChangeName: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user