init push
This commit is contained in:
302
backend/handler/v1/user.go
Normal file
302
backend/handler/v1/user.go
Normal file
@@ -0,0 +1,302 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
v1 "github.com/chaitin/panda-wiki/api/user/v1"
|
||||
"github.com/chaitin/panda-wiki/config"
|
||||
"github.com/chaitin/panda-wiki/consts"
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/handler"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
"github.com/chaitin/panda-wiki/middleware"
|
||||
"github.com/chaitin/panda-wiki/pkg/ratelimit"
|
||||
"github.com/chaitin/panda-wiki/store/cache"
|
||||
"github.com/chaitin/panda-wiki/usecase"
|
||||
)
|
||||
|
||||
type UserHandler struct {
|
||||
*handler.BaseHandler
|
||||
usecase *usecase.UserUsecase
|
||||
logger *log.Logger
|
||||
config *config.Config
|
||||
auth middleware.AuthMiddleware
|
||||
rateLimiter *ratelimit.RateLimiter
|
||||
}
|
||||
|
||||
func NewUserHandler(e *echo.Echo, baseHandler *handler.BaseHandler, logger *log.Logger, usecase *usecase.UserUsecase, auth middleware.AuthMiddleware, config *config.Config, cache *cache.Cache) *UserHandler {
|
||||
handlerLogger := logger.WithModule("handler.v1.user")
|
||||
h := &UserHandler{
|
||||
BaseHandler: baseHandler,
|
||||
logger: handlerLogger,
|
||||
usecase: usecase,
|
||||
auth: auth,
|
||||
config: config,
|
||||
rateLimiter: ratelimit.NewRateLimiter(handlerLogger, cache),
|
||||
}
|
||||
group := e.Group("/api/v1/user")
|
||||
group.POST("/login", h.Login)
|
||||
|
||||
group.GET("", h.GetUserInfo, h.auth.Authorize)
|
||||
group.GET("/list", h.ListUsers, h.auth.Authorize)
|
||||
group.POST("/create", h.CreateUser, h.auth.Authorize, h.auth.ValidateUserRole(consts.UserRoleAdmin))
|
||||
group.PUT("/reset_password", h.ResetPassword, h.auth.Authorize, h.auth.ValidateUserRole(consts.UserRoleAdmin))
|
||||
group.DELETE("/delete", h.DeleteUser, h.auth.Authorize, h.auth.ValidateUserRole(consts.UserRoleAdmin))
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// CreateUser
|
||||
//
|
||||
// @Summary CreateUser
|
||||
// @Description CreateUser
|
||||
// @Tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body v1.CreateUserReq true "CreateUser Request"
|
||||
// @Success 200 {object} domain.Response{data=v1.CreateUserResp}
|
||||
// @Router /api/v1/user/create [post]
|
||||
func (h *UserHandler) CreateUser(c echo.Context) error {
|
||||
var req v1.CreateUserReq
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return h.NewResponseWithError(c, "invalid request", err)
|
||||
}
|
||||
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return h.NewResponseWithError(c, "invalid request", err)
|
||||
}
|
||||
|
||||
uid := uuid.New().String()
|
||||
err := h.usecase.CreateUser(c.Request().Context(), &domain.User{
|
||||
ID: uid,
|
||||
Account: req.Account,
|
||||
Password: req.Password,
|
||||
Role: req.Role,
|
||||
}, consts.GetLicenseEdition(c))
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to create user", err)
|
||||
}
|
||||
|
||||
return h.NewResponseWithData(c, v1.CreateUserResp{ID: uid})
|
||||
}
|
||||
|
||||
// Login
|
||||
//
|
||||
// @Summary Login
|
||||
// @Description Login
|
||||
// @Tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body v1.LoginReq true "Login Request"
|
||||
// @Success 200 {object} v1.LoginResp
|
||||
// @Router /api/v1/user/login [post]
|
||||
func (h *UserHandler) Login(c echo.Context) error {
|
||||
var req v1.LoginReq
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return h.NewResponseWithError(c, "invalid request", err)
|
||||
}
|
||||
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return h.NewResponseWithError(c, "invalid request", err)
|
||||
}
|
||||
|
||||
ctx := c.Request().Context()
|
||||
ip := c.RealIP()
|
||||
locked, remaining := h.rateLimiter.CheckIPLocked(ctx, ip)
|
||||
if locked {
|
||||
h.logger.Warn("IP is locked", "ip", ip, "remaining", remaining)
|
||||
return h.NewResponseWithError(c, fmt.Sprintf("账号已被锁定,请 %s 后重试", remaining.String()), nil)
|
||||
}
|
||||
|
||||
token, err := h.usecase.VerifyUserAndGenerateToken(ctx, req)
|
||||
if err != nil {
|
||||
h.rateLimiter.LockAttempt(ctx, ip)
|
||||
return h.NewResponseWithError(c, "用户名或密码错误", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := h.rateLimiter.ResetLoginAttempts(context.Background(), ip); err != nil {
|
||||
h.logger.Error("failed to reset login attempts", "error", err, "ip", ip)
|
||||
}
|
||||
}()
|
||||
|
||||
return h.NewResponseWithData(c, v1.LoginResp{Token: token})
|
||||
}
|
||||
|
||||
// GetUserInfo
|
||||
//
|
||||
// @Summary GetUser
|
||||
// @Description GetUser
|
||||
// @Tags user
|
||||
// @Accept json
|
||||
// @Success 200 {object} v1.UserInfoResp
|
||||
// @Router /api/v1/user [get]
|
||||
func (h *UserHandler) GetUserInfo(c echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
authInfo := domain.GetAuthInfoFromCtx(ctx)
|
||||
if authInfo == nil {
|
||||
return h.NewResponseWithError(c, "authInfo not found in context", nil)
|
||||
}
|
||||
|
||||
user, err := h.usecase.GetUser(c.Request().Context(), authInfo.UserId)
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to get user", err)
|
||||
}
|
||||
|
||||
userInfo := &v1.UserInfoResp{
|
||||
ID: user.ID,
|
||||
Account: user.Account,
|
||||
Role: user.Role,
|
||||
IsToken: authInfo.IsToken,
|
||||
LastAccess: &user.LastAccess,
|
||||
CreatedAt: user.CreatedAt,
|
||||
}
|
||||
|
||||
return h.NewResponseWithData(c, userInfo)
|
||||
}
|
||||
|
||||
// ListUsers
|
||||
//
|
||||
// @Summary ListUsers
|
||||
// @Description ListUsers
|
||||
// @Tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} domain.PWResponse{data=v1.UserListResp}
|
||||
// @Router /api/v1/user/list [get]
|
||||
func (h *UserHandler) ListUsers(c echo.Context) error {
|
||||
var req v1.UserListReq
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return h.NewResponseWithError(c, "invalid request", err)
|
||||
}
|
||||
|
||||
users, err := h.usecase.ListUsers(c.Request().Context())
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to list users", err)
|
||||
}
|
||||
return h.NewResponseWithData(c, users)
|
||||
}
|
||||
|
||||
// ResetPassword
|
||||
//
|
||||
// @Summary ResetPassword
|
||||
// @Description ResetPassword
|
||||
// @Tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body v1.ResetPasswordReq true "ResetPassword Request"
|
||||
// @Success 200 {object} domain.Response
|
||||
// @Router /api/v1/user/reset_password [put]
|
||||
func (h *UserHandler) ResetPassword(c echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
var req v1.ResetPasswordReq
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return h.NewResponseWithError(c, "invalid request", err)
|
||||
}
|
||||
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return h.NewResponseWithError(c, "invalid request", err)
|
||||
}
|
||||
|
||||
authInfo := domain.GetAuthInfoFromCtx(ctx)
|
||||
if authInfo == nil {
|
||||
return h.NewResponseWithError(c, "authInfo not found in context", nil)
|
||||
}
|
||||
|
||||
if authInfo.IsToken {
|
||||
return h.NewResponseWithError(c, "this api not support token call", nil)
|
||||
}
|
||||
|
||||
user, err := h.usecase.GetUser(ctx, authInfo.UserId)
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to get user", err)
|
||||
}
|
||||
|
||||
if user.Account == "admin" {
|
||||
// admin 改不了自己的密码
|
||||
if authInfo.UserId == req.ID {
|
||||
return h.NewResponseWithError(c, "请修改安装目录下 .env 文件中的 ADMIN_PASSWORD,并重启 panda-wiki-api 容器使更改生效。", nil)
|
||||
}
|
||||
} else {
|
||||
targetUser, err := h.usecase.GetUser(ctx, req.ID)
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to get target user", err)
|
||||
}
|
||||
|
||||
// 超级管理员不能改其他超级管理员密码
|
||||
if targetUser.Role == consts.UserRoleAdmin && targetUser.ID != authInfo.UserId {
|
||||
return h.NewResponseWithError(c, "无法修改其他超级管理员密码", nil)
|
||||
}
|
||||
}
|
||||
|
||||
err = h.usecase.ResetPassword(c.Request().Context(), &req)
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to reset password", err)
|
||||
}
|
||||
|
||||
return h.NewResponseWithData(c, nil)
|
||||
}
|
||||
|
||||
// DeleteUser
|
||||
//
|
||||
// @Summary DeleteUser
|
||||
// @Description DeleteUser
|
||||
// @Tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param params query v1.DeleteUserReq true "DeleteUser Request"
|
||||
// @Success 200 {object} domain.Response
|
||||
// @Router /api/v1/user/delete [delete]
|
||||
func (h *UserHandler) DeleteUser(c echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
|
||||
var req v1.DeleteUserReq
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return h.NewResponseWithError(c, "invalid request", err)
|
||||
}
|
||||
|
||||
authInfo := domain.GetAuthInfoFromCtx(ctx)
|
||||
if authInfo == nil {
|
||||
return h.NewResponseWithError(c, "authInfo not found in context", nil)
|
||||
}
|
||||
|
||||
if authInfo.IsToken {
|
||||
return h.NewResponseWithError(c, "this api not support token call", nil)
|
||||
}
|
||||
|
||||
if authInfo.UserId == req.UserID {
|
||||
return h.NewResponseWithError(c, "cannot delete yourself", nil)
|
||||
}
|
||||
|
||||
user, err := h.usecase.GetUser(ctx, authInfo.UserId)
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to get user", err)
|
||||
}
|
||||
|
||||
targetUser, err := h.usecase.GetUser(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to get target user", err)
|
||||
}
|
||||
|
||||
if targetUser.Account == "admin" {
|
||||
return h.NewResponseWithError(c, "cannot delete admin user", nil)
|
||||
}
|
||||
|
||||
// 非admin账号的管理员只能删除普通用户的账户
|
||||
if user.Account != "admin" {
|
||||
if targetUser.Role != consts.UserRoleUser {
|
||||
return h.NewResponseWithError(c, "cannot delete other admin users", nil)
|
||||
}
|
||||
}
|
||||
|
||||
err = h.usecase.DeleteUser(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to delete user", err)
|
||||
}
|
||||
|
||||
return h.NewResponseWithData(c, nil)
|
||||
}
|
||||
Reference in New Issue
Block a user