refactor: 重构认证模型和数据库结构

v3
veypi 1 week ago
parent 8b2a1aba3b
commit 178fa755d4

@ -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 {

@ -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
}
// 生成新的访问令牌

@ -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

@ -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是否在黑名单中

@ -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 {

@ -1,30 +0,0 @@
//
// Copyright (C) 2024 veypi <i@veypi.com>
// 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
}

@ -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 {

@ -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 {

@ -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 {

@ -118,7 +118,7 @@
<i class="fas" :class="collapsed ? 'fa-indent' : 'fa-outdent'"></i>
</div>
<div class="breadcrumb">
{{ currentRouteName }}
{{ getRouteName() }}
</div>
</div>
@ -165,7 +165,11 @@
{label: () => $t('nav.oauth'), icon: "<i class='fas fa-key'></i>", 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');
};
</script>
<script>
$router.onChange(() => {
const path = $router.current.path;
console.log(path)
const item = $data.menuItems.find(i => i.path === path);
$data.currentRouteName = item ? item.label : path;
});
</script>
</html>

Loading…
Cancel
Save