From c5889624851532ec4aca02c6bef4e279fb2089a1 Mon Sep 17 00:00:00 2001 From: veypi Date: Wed, 18 Feb 2026 04:22:59 +0800 Subject: [PATCH] fix: Add input validation and OAuth client access control - Add username validation (required, 3-50 chars, alphanumeric + underscore) - Add password validation (required, minimum 8 characters) - Add email format validation using regex - Add owner check in OAuth client update and delete operations - Allow admin users with wildcard permission to access all organizations --- api/auth/register.go | 31 +++++++++++++++++++++++++++++++ api/oauth/client.go | 32 ++++++++++++++++++++++++++++++++ auth/auth.go | 7 +++++++ 3 files changed, 70 insertions(+) diff --git a/api/auth/register.go b/api/auth/register.go index 44c3825..090bad8 100644 --- a/api/auth/register.go +++ b/api/auth/register.go @@ -7,6 +7,9 @@ package auth import ( + "regexp" + "strings" + baseauth "github.com/veypi/vbase/auth" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/libs/crypto" @@ -15,6 +18,9 @@ import ( "github.com/veypi/vigo" ) +// Email 正则表达式 +var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`) + // RegisterRequest 注册请求 type RegisterRequest struct { Username string `json:"username" src:"json" desc:"用户名"` @@ -26,6 +32,31 @@ type RegisterRequest struct { // register 用户注册 func register(x *vigo.X, req *RegisterRequest) (*AuthResponse, error) { + // 验证用户名 + if strings.TrimSpace(req.Username) == "" { + return nil, vigo.ErrInvalidArg.WithString("username is required") + } + if len(req.Username) < 3 || len(req.Username) > 50 { + return nil, vigo.ErrInvalidArg.WithString("username must be between 3 and 50 characters") + } + // 用户名只能包含字母、数字和下划线 + if !regexp.MustCompile(`^[a-zA-Z0-9_]+$`).MatchString(req.Username) { + return nil, vigo.ErrInvalidArg.WithString("username can only contain letters, numbers and underscores") + } + + // 验证密码 + if strings.TrimSpace(req.Password) == "" { + return nil, vigo.ErrInvalidArg.WithString("password is required") + } + if len(req.Password) < 8 { + return nil, vigo.ErrInvalidArg.WithString("password must be at least 8 characters") + } + + // 验证邮箱格式 + if req.Email != "" && !emailRegex.MatchString(req.Email) { + return nil, vigo.ErrInvalidArg.WithString("invalid email format") + } + // 检查注册配置 requireEmail, _ := models.GetSettingBool(models.SettingAuthRegRequireEmail) requirePhone, _ := models.GetSettingBool(models.SettingAuthRegRequirePhone) diff --git a/api/oauth/client.go b/api/oauth/client.go index 053ef52..83886d2 100644 --- a/api/oauth/client.go +++ b/api/oauth/client.go @@ -122,6 +122,22 @@ func updateClient(x *vigo.X, req *UpdateClientRequest) (*models.OAuthClient, err return nil, vigo.ErrNotFound } + // 检查权限:只有所有者或管理员可以修改 + currentUserID := auth.GetUserID(x) + if currentUserID == "" { + return nil, vigo.ErrUnauthorized + } + + // 检查是否是所有者 + isOwner := client.OwnerID == currentUserID + + // 检查是否是管理员(拥有 *:* 权限) + isAdmin := auth.VBaseAuth.CheckPerm(x.Context(), currentUserID, "", "vb:*:*", "") + + if !isOwner && !isAdmin { + return nil, vigo.ErrForbidden.WithString("not the owner of this client") + } + updates := make(map[string]any) if req.Name != nil { updates["name"] = *req.Name @@ -153,6 +169,22 @@ func deleteClient(x *vigo.X, req *DeleteClientRequest) error { return vigo.ErrNotFound } + // 检查权限:只有所有者或管理员可以删除 + currentUserID := auth.GetUserID(x) + if currentUserID == "" { + return vigo.ErrUnauthorized + } + + // 检查是否是所有者 + isOwner := client.OwnerID == currentUserID + + // 检查是否是管理员(拥有 *:* 权限) + isAdmin := auth.VBaseAuth.CheckPerm(x.Context(), currentUserID, "", "vb:*:*", "") + + if !isOwner && !isAdmin { + return vigo.ErrForbidden.WithString("not the owner of this client") + } + if err := cfg.DB().Delete(&client).Error; err != nil { return vigo.ErrInternalServer.WithError(err) } diff --git a/auth/auth.go b/auth/auth.go index 820efb1..e1840c0 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -454,6 +454,13 @@ func (a *appAuth) LoadOrg(x *vigo.X) error { return vigo.ErrUnauthorized } + // 检查是否是管理员(拥有 *:* 权限),管理员可以访问所有组织 + isAdmin := a.CheckPerm(x.Context(), userID, "", "vb:*:*", "") + if isAdmin { + x.Set(CtxKeyOrgID, orgID) + return nil + } + // 检查用户是否为组织成员 var member models.OrgMember err := cfg.DB().Where("user_id = ? AND org_id = ? AND status = ?", userID, orgID, models.MemberStatusActive).First(&member).Error