diff --git a/api/auth/login.go b/api/auth/login.go index 8cbeacf..84c435f 100644 --- a/api/auth/login.go +++ b/api/auth/login.go @@ -139,19 +139,19 @@ func refresh(x *vigo.X, req *RefreshRequest) (*AuthResponse, error) { claims, err := jwt.ParseToken(req.RefreshToken) if err != nil { if err == jwt.ErrExpiredToken { - return nil, vigo.ErrUnauthorized.WithString("refresh token expired") + return nil, vigo.ErrTokenExpired } - return nil, vigo.ErrUnauthorized.WithString("invalid refresh token") + return nil, vigo.ErrTokenInvalid } if !jwt.IsRefreshToken(claims) { - return nil, vigo.ErrUnauthorized.WithString("invalid token type") + return nil, vigo.ErrTokenInvalid } // 查找用户 var user models.User if err := cfg.DB().First(&user, "id = ?", claims.UserID).Error; err != nil { - return nil, vigo.ErrUnauthorized.WithString("user not found") + return nil, vigo.ErrTokenInvalid } if user.Status != models.UserStatusActive { diff --git a/api/oauth/token.go b/api/oauth/token.go index 25a4098..0c2dc6f 100644 --- a/api/oauth/token.go +++ b/api/oauth/token.go @@ -91,11 +91,11 @@ func handleRefreshToken(req *TokenRequest) (*TokenResponse, error) { // 查找刷新令牌 var token models.OAuthToken if err := cfg.DB().First(&token, "refresh_token = ?", req.RefreshToken).Error; err != nil { - return nil, vigo.ErrUnauthorized.WithString("invalid refresh token") + return nil, vigo.ErrTokenInvalid } if token.Revoked { - return nil, vigo.ErrUnauthorized.WithString("token has been revoked") + return nil, vigo.ErrTokenInvalid } // 生成新的访问令牌 diff --git a/auth/auth.go b/auth/auth.go index b6b9335..ec0c1b8 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -111,9 +111,7 @@ func (f *authFactory) New(appKey string, config models.AppConfig) Auth { return auth } -var ( - validResourceRegex = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_-]*$`) -) +var validResourceRegex = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_-]*$`) func validatePermissionID(permissionID string) { if permissionID == "*:*" { @@ -598,8 +596,6 @@ func (a *appAuth) CheckPermission(ctx context.Context, userID, orgID, permission } func (a *appAuth) checkPermissionDB(ctx context.Context, userID, orgID, permissionID, resourceID string) (bool, error) { - fmt.Printf("[DEBUG] CheckPermission: userID=%s, orgID=%s, permID=%s, resID=%s\n", userID, orgID, permissionID, resourceID) - // 1. 检查用户是否有该权限的角色(包括当前组织角色和系统全局角色) var roleIDs []string roleQuery := cfg.DB().Model(&models.UserRole{}). @@ -612,10 +608,8 @@ func (a *appAuth) checkPermissionDB(ctx context.Context, userID, orgID, permissi } if err := roleQuery.Pluck("role_id", &roleIDs).Error; err != nil { - fmt.Printf("[DEBUG] CheckPermission: failed to get roles: %v\n", err) return false, err } - fmt.Printf("[DEBUG] CheckPermission: roleIDs=%v\n", roleIDs) if len(roleIDs) > 0 { // 构造可能的通配符权限ID diff --git a/auth/middleware.go b/auth/middleware.go index 69f39be..49a3b71 100644 --- a/auth/middleware.go +++ b/auth/middleware.go @@ -29,9 +29,9 @@ func AuthMiddleware() func(*vigo.X) error { claims, err := jwt.ParseToken(tokenString) if err != nil { if err == jwt.ErrExpiredToken { - return vigo.ErrUnauthorized.WithString("token expired") + return vigo.ErrTokenExpired } - return vigo.ErrUnauthorized.WithString("invalid token") + return vigo.ErrTokenInvalid } // 检查token是否在黑名单中 diff --git a/models/auth.go b/models/auth.go index cd2ee8a..8ba7b65 100644 --- a/models/auth.go +++ b/models/auth.go @@ -9,7 +9,7 @@ package models import ( "time" - "gorm.io/gorm" + "github.com/veypi/vigo" ) // 角色代码常量 @@ -22,14 +22,13 @@ const ( // Permission 权限定义表(权限字典) // ID 格式: app:resource:action (例如: crm:customer:read) type Permission struct { - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` - ID string `json:"id" gorm:"primaryKey;size:100" desc:"权限ID,格式: app:resource:action"` - AppKey string `json:"app_key" gorm:"index;size:50" desc:"应用标识"` - Resource string `json:"resource" gorm:"index;size:50" desc:"资源类型"` - Action string `json:"action" gorm:"index;size:50" desc:"操作类型"` - Description string `json:"description" desc:"权限描述"` + ID string `json:"id" gorm:"primaryKey;size:100" desc:"权限ID,格式: app:resource:action"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + AppKey string `json:"app_key" gorm:"index;size:50" desc:"应用标识"` + Resource string `json:"resource" gorm:"index;size:50" desc:"资源类型"` + Action string `json:"action" gorm:"index;size:50" desc:"操作类型"` + Description string `json:"description" desc:"权限描述"` } func (Permission) TableName() string { @@ -38,13 +37,16 @@ func (Permission) TableName() string { // Role 角色表(不关联 app,可跨应用) type Role struct { - Base + vigo.Model OrgID string `json:"org_id" gorm:"index;size:36" desc:"组织ID,空=系统预设"` Code string `json:"code" gorm:"index;size:50" desc:"角色代码"` Name string `json:"name" desc:"角色名称"` Description string `json:"description" desc:"角色描述"` IsSystem bool `json:"is_system" desc:"是否系统预设角色"` Status int `json:"status" gorm:"default:1" desc:"状态: 1=启用, 0=禁用"` + + // 外键关联 + Org *Org `json:"org,omitempty" gorm:"foreignKey:OrgID;references:ID"` } func (Role) TableName() string { @@ -53,10 +55,14 @@ func (Role) TableName() string { // RolePermission 角色权限关联表 type RolePermission struct { - Base + vigo.Model RoleID string `json:"role_id" gorm:"index;size:36" desc:"角色ID"` PermissionID string `json:"permission_id" gorm:"index;size:100" desc:"权限ID"` Condition string `json:"condition" gorm:"size:20;default:'none'" desc:"权限条件: none/owner/admin"` + + // 外键关联 + Role Role `json:"role,omitempty" gorm:"foreignKey:RoleID;references:ID"` + Permission Permission `json:"permission,omitempty" gorm:"foreignKey:PermissionID;references:ID"` } func (RolePermission) TableName() string { @@ -65,11 +71,16 @@ func (RolePermission) TableName() string { // UserRole 用户角色关联表 type UserRole struct { - Base + vigo.Model UserID string `json:"user_id" gorm:"index;size:36" desc:"用户ID"` OrgID string `json:"org_id" gorm:"index;size:36" desc:"组织ID"` RoleID string `json:"role_id" gorm:"index;size:36" desc:"角色ID"` ExpireAt *time.Time `json:"expire_at" desc:"过期时间(可选)"` + + // 外键关联 + User User `json:"user,omitempty" gorm:"foreignKey:UserID;references:ID"` + Org *Org `json:"org,omitempty" gorm:"foreignKey:OrgID;references:ID"` + Role Role `json:"role,omitempty" gorm:"foreignKey:RoleID;references:ID"` } func (UserRole) TableName() string { @@ -78,13 +89,18 @@ func (UserRole) TableName() string { // UserPermission 用户特定资源权限表(数据级权限) type UserPermission struct { - Base + vigo.Model UserID string `json:"user_id" gorm:"index;size:36" desc:"用户ID"` OrgID string `json:"org_id" gorm:"index;size:36" desc:"组织ID"` PermissionID string `json:"permission_id" gorm:"index;size:100" desc:"权限ID"` ResourceID string `json:"resource_id" gorm:"index;size:100" desc:"具体资源ID,* 表示所有"` ExpireAt *time.Time `json:"expire_at" desc:"过期时间(可选)"` GrantedBy string `json:"granted_by" gorm:"size:36" desc:"授权人ID"` + + // 外键关联 + User User `json:"user,omitempty" gorm:"foreignKey:UserID;references:ID"` + Org *Org `json:"org,omitempty" gorm:"foreignKey:OrgID;references:ID"` + Permission Permission `json:"permission,omitempty" gorm:"foreignKey:PermissionID;references:ID"` } func (UserPermission) TableName() string { diff --git a/models/base.go b/models/base.go deleted file mode 100644 index a603930..0000000 --- a/models/base.go +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (C) 2024 veypi -// 2025-03-04 16:08:06 -// Distributed under terms of the MIT license. -// - -package models - -import ( - "time" - - "github.com/google/uuid" - "gorm.io/gorm" -) - -// Base 基础模型 -type Base struct { - ID string `json:"id" gorm:"primaryKey;type:varchar(36)"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` -} - -// BeforeCreate 自动生成UUID -func (b *Base) BeforeCreate(tx *gorm.DB) error { - if b.ID == "" { - b.ID = uuid.New().String() - } - return nil -} diff --git a/models/oauth.go b/models/oauth.go index 7bd1a5a..a3ca2a9 100644 --- a/models/oauth.go +++ b/models/oauth.go @@ -8,20 +8,26 @@ package models import ( "time" + + "github.com/veypi/vigo" ) // OAuthClient OAuth2.0 客户端 type OAuthClient struct { - Base - ClientID string `json:"client_id" gorm:"uniqueIndex;size:100;not null"` - ClientSecret string `json:"-" gorm:"size:255;not null"` - Name string `json:"name" gorm:"size:100;not null"` - Description string `json:"description" gorm:"size:500"` - RedirectURIs string `json:"redirect_uris" gorm:"type:text"` // JSON数组 + vigo.Model + ClientID string `json:"client_id" gorm:"uniqueIndex;size:100;not null"` + ClientSecret string `json:"-" gorm:"size:255;not null"` + Name string `json:"name" gorm:"size:100;not null"` + Description string `json:"description" gorm:"size:500"` + RedirectURIs string `json:"redirect_uris" gorm:"type:text"` // JSON数组 AllowedScopes string `json:"allowed_scopes" gorm:"size:500"` // 空格分隔 - OwnerID string `json:"owner_id" gorm:"not null"` - OrgID string `json:"org_id" gorm:"index"` - Status int `json:"status" gorm:"default:1"` + OwnerID string `json:"owner_id" gorm:"not null"` + OrgID string `json:"org_id" gorm:"index"` + Status int `json:"status" gorm:"default:1"` + + // 外键关联 + Owner User `json:"owner,omitempty" gorm:"foreignKey:OwnerID;references:ID"` + Org *Org `json:"org,omitempty" gorm:"foreignKey:OrgID;references:ID"` } func (OAuthClient) TableName() string { @@ -30,17 +36,22 @@ func (OAuthClient) TableName() string { // OAuthAuthorizationCode OAuth2.0 授权码 type OAuthAuthorizationCode struct { - Base - Code string `json:"code" gorm:"uniqueIndex;size:100;not null"` - ClientID string `json:"client_id" gorm:"index;not null"` - UserID string `json:"user_id" gorm:"index;not null"` - OrgID string `json:"org_id" gorm:"index"` - RedirectURI string `json:"redirect_uri" gorm:"size:500"` - Scope string `json:"scope" gorm:"size:200"` + vigo.Model + Code string `json:"code" gorm:"uniqueIndex;size:100;not null"` + ClientID string `json:"client_id" gorm:"index;not null"` + UserID string `json:"user_id" gorm:"index;not null"` + OrgID string `json:"org_id" gorm:"index"` + RedirectURI string `json:"redirect_uri" gorm:"size:500"` + Scope string `json:"scope" gorm:"size:200"` CodeChallenge string `json:"-" gorm:"size:128"` CodeChallengeMethod string `json:"-" gorm:"size:10"` - ExpiresAt time.Time `json:"expires_at"` - Used bool `json:"used" gorm:"default:false"` + ExpiresAt time.Time `json:"expires_at"` + Used bool `json:"used" gorm:"default:false"` + + // 外键关联 + Client OAuthClient `json:"client,omitempty" gorm:"foreignKey:ClientID;references:ID"` + User User `json:"user,omitempty" gorm:"foreignKey:UserID;references:ID"` + Org *Org `json:"org,omitempty" gorm:"foreignKey:OrgID;references:ID"` } func (OAuthAuthorizationCode) TableName() string { @@ -49,7 +60,7 @@ func (OAuthAuthorizationCode) TableName() string { // OAuthToken OAuth2.0 令牌 type OAuthToken struct { - Base + vigo.Model ClientID string `json:"client_id" gorm:"index;not null"` UserID string `json:"user_id" gorm:"index;not null"` OrgID string `json:"org_id" gorm:"index"` @@ -59,6 +70,11 @@ type OAuthToken struct { Scope string `json:"scope" gorm:"size:200"` ExpiresAt time.Time `json:"expires_at"` Revoked bool `json:"revoked" gorm:"default:false"` + + // 外键关联 + Client OAuthClient `json:"client,omitempty" gorm:"foreignKey:ClientID;references:ID"` + User User `json:"user,omitempty" gorm:"foreignKey:UserID;references:ID"` + Org *Org `json:"org,omitempty" gorm:"foreignKey:OrgID;references:ID"` } func (OAuthToken) TableName() string { diff --git a/models/org.go b/models/org.go index fb53222..36fa671 100644 --- a/models/org.go +++ b/models/org.go @@ -6,9 +6,13 @@ package models +import ( + "github.com/veypi/vigo" +) + // Org 组织/租户 type Org struct { - Base + vigo.Model Name string `json:"name" gorm:"size:50;not null"` Code string `json:"code" gorm:"uniqueIndex;size:30;not null"` OwnerID string `json:"owner_id" gorm:"not null"` @@ -21,6 +25,11 @@ type Org struct { Settings string `json:"-" gorm:"type:text"` // JSON配置 Status int `json:"status" gorm:"default:1"` MaxMembers int `json:"max_members" gorm:"default:100"` + + // 外键关联 + Owner User `json:"owner,omitempty" gorm:"foreignKey:OwnerID;references:ID"` + Parent *Org `json:"parent,omitempty" gorm:"foreignKey:ParentID;references:ID"` + Leader *User `json:"leader,omitempty" gorm:"foreignKey:LeaderID;references:ID"` } func (Org) TableName() string { @@ -29,7 +38,7 @@ func (Org) TableName() string { // OrgMember 组织成员关系 type OrgMember struct { - Base + vigo.Model OrgID string `json:"org_id" gorm:"type:varchar(36);uniqueIndex:idx_org_user;not null"` UserID string `json:"user_id" gorm:"type:varchar(36);uniqueIndex:idx_org_user;not null"` RoleIDs string `json:"role_ids" gorm:"size:200"` // 逗号分隔 @@ -37,6 +46,10 @@ type OrgMember struct { Department string `json:"department" gorm:"size:50"` JoinedAt string `json:"joined_at"` Status int `json:"status" gorm:"default:1"` // 0:待审核 1:正常 2:禁用 + + // 外键关联 + Org Org `json:"org,omitempty" gorm:"foreignKey:OrgID;references:ID"` + User User `json:"user,omitempty" gorm:"foreignKey:UserID;references:ID"` } func (OrgMember) TableName() string { diff --git a/models/user.go b/models/user.go index 17f6f67..340f6f1 100644 --- a/models/user.go +++ b/models/user.go @@ -8,11 +8,13 @@ package models import ( "time" + + "github.com/veypi/vigo" ) // User 全局用户表 type User struct { - Base + vigo.Model Username string `json:"username" gorm:"uniqueIndex;size:50;not null"` Password string `json:"-" gorm:"size:255"` // bcrypt hash Nickname string `json:"nickname" gorm:"size:50"` @@ -32,7 +34,7 @@ func (User) TableName() string { // Identity 第三方身份绑定 type Identity struct { - Base + vigo.Model UserID string `json:"user_id" gorm:"index;not null"` Provider string `json:"provider" gorm:"size:20;not null"` // google/github/wechat/ldap ProviderUID string `json:"provider_uid" gorm:"index;size:100;not null"` // 第三方唯一ID @@ -42,6 +44,9 @@ type Identity struct { AccessToken string `json:"-" gorm:"size:500"` RefreshToken string `json:"-" gorm:"size:500"` ExpiresAt *time.Time `json:"-"` + + // 外键关联 + User User `json:"user,omitempty" gorm:"foreignKey:UserID;references:ID"` } func (Identity) TableName() string { @@ -50,7 +55,7 @@ func (Identity) TableName() string { // Session 登录会话 type Session struct { - Base + vigo.Model UserID string `json:"user_id" gorm:"index;not null"` TokenID string `json:"token_id" gorm:"uniqueIndex;size:36"` // JWT jti Type string `json:"type" gorm:"size:20;not null"` // access/refresh @@ -59,6 +64,9 @@ type Session struct { ExpiresAt time.Time `json:"expires_at"` Revoked bool `json:"revoked" gorm:"default:false"` RevokedAt *time.Time `json:"revoked_at"` + + // 外键关联 + User User `json:"user,omitempty" gorm:"foreignKey:UserID;references:ID"` } func (Session) TableName() string { diff --git a/ui/layout/default.html b/ui/layout/default.html index 8803cc6..5679c2c 100644 --- a/ui/layout/default.html +++ b/ui/layout/default.html @@ -118,7 +118,7 @@ @@ -165,7 +165,11 @@ {label: () => $t('nav.oauth'), icon: "", path: "/oauth/apps"}, ]; - currentRouteName = ""; + getRouteName = () => { + const path = $router.current.path; + const item = menuItems.find(i => i.path === path); + return item ? item.label() : path; + }; toggleCollapse = () => { collapsed = !collapsed; @@ -185,13 +189,5 @@ $router.push('/org'); }; -