@ -16,8 +16,10 @@ import (
"github.com/veypi/vbase/cfg"
"github.com/veypi/vbase/cfg"
"github.com/veypi/vbase/libs/cache"
"github.com/veypi/vbase/libs/cache"
"github.com/veypi/vbase/libs/jwt"
"github.com/veypi/vbase/models"
"github.com/veypi/vbase/models"
"github.com/veypi/vigo"
"github.com/veypi/vigo"
vigoauth "github.com/veypi/vigo/contrib/auth"
"github.com/veypi/vigo/contrib/event"
"github.com/veypi/vigo/contrib/event"
"gorm.io/gorm"
"gorm.io/gorm"
)
)
@ -36,79 +38,40 @@ const (
RoleCodeUser = "user"
RoleCodeUser = "user"
)
)
// ========== 辅助函数 ==========
// ========== Token 提取 ==========
func GetUserID ( x * vigo . X ) string {
if userID , ok := x . Get ( CtxKeyUserID ) . ( string ) ; ok {
return userID
}
return ""
}
func GetOrgID ( x * vigo . X ) string {
// extractToken 从 Header 或 Query 中提取 JWT token
if orgID , ok := x . Get ( CtxKeyOrgID ) . ( string ) ; ok {
func extractToken ( x * vigo . X ) string {
return orgID
auth := x . Request . Header . Get ( "Authorization" )
if auth != "" {
if len ( auth ) > 7 && strings . HasPrefix ( auth , "Bearer " ) {
return auth [ 7 : ]
}
}
return ""
}
return x . Request . URL . Query ( ) . Get ( "access_token" )
}
}
func GetOrgRoles ( x * vigo . X ) [ ] string {
// getOrgID 从请求中提取组织ID (Header/Query/Path)
if roles , ok := x . Get ( CtxKeyOrgRoles ) . ( [ ] string ) ; ok {
func getOrgID ( x * vigo . X ) string {
return roles
orgID := x . Request . Header . Get ( "X-Org-ID" )
if orgID == "" {
orgID = x . Request . URL . Query ( ) . Get ( "org_id" )
}
}
return nil
if orgID == "" {
orgID = x . PathParams . Get ( "org_id" )
}
return orgID
}
}
// Auth 权限管理接口
// Auth 权限管理接口 (继承 Vigo auth.Auth 并扩展 LoadOrg)
// 注意: appAuth 同时实现了此接口和 vigoauth.Auth 接口
type Auth interface {
type Auth interface {
UserID ( x * vigo . X ) string
vigoauth . Auth
OrgID ( x * vigo . X ) string
// 加载组织信息 (中间件/手动调用)
// 加载组织信息 (中间件/手动调用)
LoadOrg ( x * vigo . X ) error
LoadOrg ( x * vigo . X ) error
// ========== 中间件生成 ==========
// 检查权限 (兼容旧接口)
// 基础权限检查
Perm ( permissionID string ) func ( * vigo . X ) error
// 特定资源权限检查 (自动从 Path/Query 获取资源ID)
PermOnResource ( permissionID , resourceKey string ) func ( * vigo . X ) error
// 满足任一权限
PermAny ( permissionIDs ... string ) func ( * vigo . X ) error
// 满足所有权限
PermAll ( permissionIDs ... string ) func ( * vigo . X ) error
// ========== 角色管理 ==========
// 添加角色定义
// policies 格式: "resource:action",例如 "user:read", "*:*"
AddRole ( roleCode , roleName string , policies ... string ) error
// ========== 权限管理 ==========
// 授予角色
GrantRole ( ctx context . Context , userID , orgID , roleCode string ) error
// 撤销角色
RevokeRole ( ctx context . Context , userID , orgID , roleCode string ) error
// 授予特定资源权限
GrantResourcePerm ( ctx context . Context , userID , orgID , permissionID , resourceID string ) error
// 撤销特定资源权限
RevokeResourcePerm ( ctx context . Context , userID , orgID , permissionID , resourceID string ) error
// 撤销用户所有权限
RevokeAll ( ctx context . Context , userID , orgID string ) error
// ========== 权限查询 ==========
// 检查权限
CheckPermission ( ctx context . Context , userID , orgID , permissionID , resourceID string ) bool
CheckPermission ( ctx context . Context , userID , orgID , permissionID , resourceID string ) bool
CheckPerm ( ctx context . Context , userID , orgID , permissionID , resourceID string ) bool
// 列出用户权限
ListUserPermissions ( ctx context . Context , userID , orgID string ) ( [ ] models . UserPermissionResult , error )
// 列出资源授权用户
ListResourceUsers ( ctx context . Context , orgID , permissionID , resourceID string ) ( [ ] models . ResourceUser , error )
}
}
// 全局 Auth 工厂
// 全局 Auth 工厂
@ -262,6 +225,64 @@ func (a *appAuth) AddRole(roleCode, roleName string, policies ...string) error {
return nil
return nil
}
}
// GetRole 获取角色定义
func ( a * appAuth ) GetRole ( roleCode string ) ( * vigoauth . Role , error ) {
roleDef , exists := a . roleDefs [ roleCode ]
if ! exists {
return nil , fmt . Errorf ( "role not found: %s" , roleCode )
}
// 从数据库获取完整角色信息
var role models . Role
err := cfg . DB ( ) . Where ( "code = ? AND org_id IS NULL" , roleCode ) . First ( & role ) . Error
if err != nil {
if errors . Is ( err , gorm . ErrRecordNotFound ) {
return nil , fmt . Errorf ( "role not found: %s" , roleCode )
}
return nil , err
}
// 转换策略为 Vigo 格式
policies := make ( [ ] string , 0 )
if rolePolicies , ok := a . policies [ roleCode ] ; ok {
for _ , p := range rolePolicies {
policies = append ( policies , fmt . Sprintf ( "%s:%s" , p [ 0 ] , p [ 1 ] ) )
}
}
return & vigoauth . Role {
Code : roleDef . code ,
Name : roleDef . name ,
Policies : policies ,
Description : roleDef . description ,
} , nil
}
// ListRoles 列出所有角色定义
func ( a * appAuth ) ListRoles ( ) ( [ ] * vigoauth . Role , error ) {
result := make ( [ ] * vigoauth . Role , 0 )
for code , roleDef := range a . roleDefs {
if code == "_app_info" {
continue
}
policies := make ( [ ] string , 0 )
if rolePolicies , ok := a . policies [ code ] ; ok {
for _ , p := range rolePolicies {
policies = append ( policies , fmt . Sprintf ( "%s:%s" , p [ 0 ] , p [ 1 ] ) )
}
}
result = append ( result , & vigoauth . Role {
Code : roleDef . code ,
Name : roleDef . name ,
Policies : policies ,
Description : roleDef . description ,
} )
}
return result , nil
}
// init 初始化应用的权限配置
// init 初始化应用的权限配置
func ( a * appAuth ) init ( ) error {
func ( a * appAuth ) init ( ) error {
// 1. 同步权限定义到数据库
// 1. 同步权限定义到数据库
@ -427,29 +448,59 @@ func (a *appAuth) initRole(roleCode string) error {
// ========== 中间件实现 ==========
// ========== 中间件实现 ==========
// PermLogin JWT 认证中间件
// 解析 token、验证黑名单、设置 CtxKeyUserID
func ( a * appAuth ) PermLogin ( x * vigo . X ) error {
// 1. 提取 token
tokenString := extractToken ( x )
if tokenString == "" {
return vigo . ErrUnauthorized . WithString ( "missing token" )
}
// 2. 解析 token
claims , err := jwt . ParseToken ( tokenString )
if err != nil {
if err == jwt . ErrExpiredToken {
return vigo . ErrTokenExpired
}
return vigo . ErrTokenInvalid
}
// 3. 检查 token 黑名单
if cache . IsEnabled ( ) {
blacklisted , _ := cache . IsTokenBlacklisted ( claims . ID )
if blacklisted {
return vigo . ErrUnauthorized . WithString ( "token has been revoked" )
}
}
// 4. 设置用户ID到上下文
x . Set ( CtxKeyUserID , claims . UserID )
return nil
}
func ( a * appAuth ) UserID ( x * vigo . X ) string {
func ( a * appAuth ) UserID ( x * vigo . X ) string {
return GetUserID ( x )
if userID , ok := x . Get ( CtxKeyUserID ) . ( string ) ; ok {
return userID
}
return ""
}
}
func ( a * appAuth ) OrgID ( x * vigo . X ) string {
func ( a * appAuth ) OrgID ( x * vigo . X ) string {
return GetOrgID ( x )
if orgID , ok := x . Get ( CtxKeyOrgID ) . ( string ) ; ok {
return orgID
}
return ""
}
}
// LoadOrg 加载组织信息
func ( a * appAuth ) LoadOrg ( x * vigo . X ) error {
func ( a * appAuth ) LoadOrg ( x * vigo . X ) error {
orgID := x . Request . Header . Get ( "X-Org-ID" )
orgID := getOrgID ( x )
if orgID == "" {
if orgID == "" {
orgID = x . Request . URL . Query ( ) . Get ( "org_id" )
}
if orgID == "" {
orgID = x . PathParams . Get ( "org_id" )
}
if orgID == "" {
// 没有指定组织
return vigo . ErrInvalidArg . WithString ( "missing org_id" )
return vigo . ErrInvalidArg . WithString ( "missing org_id" )
}
}
userID := GetUserID ( x )
userID := a . UserID ( x )
if userID == "" {
if userID == "" {
return vigo . ErrUnauthorized
return vigo . ErrUnauthorized
}
}
@ -478,13 +529,12 @@ func (a *appAuth) LoadOrg(x *vigo.X) error {
func ( a * appAuth ) Perm ( permissionID string ) func ( * vigo . X ) error {
func ( a * appAuth ) Perm ( permissionID string ) func ( * vigo . X ) error {
validatePermissionID ( permissionID )
validatePermissionID ( permissionID )
return func ( x * vigo . X ) error {
return func ( x * vigo . X ) error {
userID := Get UserID( x )
userID := a . UserID( x )
if userID == "" {
if userID == "" {
return vigo . ErrUnauthorized
return vigo . ErrUnauthorized
}
}
orgID := GetOrgID ( x )
if err := a . checkPermission ( x . Context ( ) , userID , "" , permissionID , "" ) ; err != nil {
if err := a . checkPermission ( x . Context ( ) , userID , orgID , permissionID , "" ) ; err != nil {
return err
return err
}
}
return nil
return nil
@ -504,12 +554,12 @@ func (a *appAuth) Perm(permissionID string) func(*vigo.X) error {
func ( a * appAuth ) PermOnResource ( permissionID , resourceKey string ) func ( * vigo . X ) error {
func ( a * appAuth ) PermOnResource ( permissionID , resourceKey string ) func ( * vigo . X ) error {
validatePermissionID ( permissionID )
validatePermissionID ( permissionID )
return func ( x * vigo . X ) error {
return func ( x * vigo . X ) error {
userID := Get UserID( x )
userID := a . UserID( x )
if userID == "" {
if userID == "" {
return vigo . ErrUnauthorized
return vigo . ErrUnauthorized
}
}
orgID := Get OrgID( x )
orgID := a . OrgID( x )
// 尝试从 PathParams 获取
// 尝试从 PathParams 获取
resourceID := x . PathParams . Get ( resourceKey )
resourceID := x . PathParams . Get ( resourceKey )
@ -542,11 +592,11 @@ func (a *appAuth) PermAny(permissionIDs ...string) func(*vigo.X) error {
validatePermissionID ( pid )
validatePermissionID ( pid )
}
}
return func ( x * vigo . X ) error {
return func ( x * vigo . X ) error {
userID := Get UserID( x )
userID := a . UserID( x )
if userID == "" {
if userID == "" {
return vigo . ErrUnauthorized
return vigo . ErrUnauthorized
}
}
orgID := Get OrgID( x )
orgID := a . OrgID( x )
for _ , pid := range permissionIDs {
for _ , pid := range permissionIDs {
if err := a . checkPermission ( x . Context ( ) , userID , orgID , pid , "" ) ; err == nil {
if err := a . checkPermission ( x . Context ( ) , userID , orgID , pid , "" ) ; err == nil {
@ -562,11 +612,11 @@ func (a *appAuth) PermAll(permissionIDs ...string) func(*vigo.X) error {
validatePermissionID ( pid )
validatePermissionID ( pid )
}
}
return func ( x * vigo . X ) error {
return func ( x * vigo . X ) error {
userID := Get UserID( x )
userID := a . UserID( x )
if userID == "" {
if userID == "" {
return vigo . ErrUnauthorized
return vigo . ErrUnauthorized
}
}
orgID := Get OrgID( x )
orgID := a . OrgID( x )
for _ , pid := range permissionIDs {
for _ , pid := range permissionIDs {
if err := a . checkPermission ( x . Context ( ) , userID , orgID , pid , "" ) ; err != nil {
if err := a . checkPermission ( x . Context ( ) , userID , orgID , pid , "" ) ; err != nil {
@ -634,6 +684,49 @@ func (a *appAuth) GrantRole(ctx context.Context, userID, orgID, roleCode string)
return nil
return nil
}
}
// GrantRoles 批量授予角色
func ( a * appAuth ) GrantRoles ( ctx context . Context , userID , orgID string , roleCodes ... string ) error {
for _ , roleCode := range roleCodes {
if err := a . GrantRole ( ctx , userID , orgID , roleCode ) ; err != nil {
return err
}
}
return nil
}
// ListUserRoles 查询用户的角色列表
func ( a * appAuth ) ListUserRoles ( ctx context . Context , userID , orgID string ) ( [ ] string , error ) {
var roleIDs [ ] string
query := cfg . DB ( ) . Model ( & models . UserRole { } ) .
Where ( "user_id = ?" , userID )
if orgID != "" {
query = query . Where ( "org_id = ? OR org_id IS NULL" , orgID )
} else {
query = query . Where ( "org_id IS NULL" )
}
if err := query . Pluck ( "role_id" , & roleIDs ) . Error ; err != nil {
return nil , err
}
if len ( roleIDs ) == 0 {
return [ ] string { } , nil
}
// 获取角色代码
var roles [ ] models . Role
if err := cfg . DB ( ) . Where ( "id IN ?" , roleIDs ) . Pluck ( "code" , & roles ) . Error ; err != nil {
return nil , err
}
codes := make ( [ ] string , 0 , len ( roles ) )
for _ , role := range roles {
codes = append ( codes , role . Code )
}
return codes , nil
}
func ( a * appAuth ) RevokeRole ( ctx context . Context , userID , orgID , roleCode string ) error {
func ( a * appAuth ) RevokeRole ( ctx context . Context , userID , orgID , roleCode string ) error {
var role models . Role
var role models . Role
// 优先查找组织特定角色
// 优先查找组织特定角色
@ -669,6 +762,16 @@ func (a *appAuth) RevokeRole(ctx context.Context, userID, orgID, roleCode string
return nil
return nil
}
}
// RevokeRoles 批量撤销角色
func ( a * appAuth ) RevokeRoles ( ctx context . Context , userID , orgID string , roleCodes ... string ) error {
for _ , roleCode := range roleCodes {
if err := a . RevokeRole ( ctx , userID , orgID , roleCode ) ; err != nil {
return err
}
}
return nil
}
func ( a * appAuth ) GrantResourcePerm ( ctx context . Context , userID , orgID , permissionID , resourceID string ) error {
func ( a * appAuth ) GrantResourcePerm ( ctx context . Context , userID , orgID , permissionID , resourceID string ) error {
if strings . Count ( permissionID , ":" ) == 1 {
if strings . Count ( permissionID , ":" ) == 1 {
permissionID = fmt . Sprintf ( "%s:%s" , a . scope , permissionID )
permissionID = fmt . Sprintf ( "%s:%s" , a . scope , permissionID )
@ -855,8 +958,8 @@ func (a *appAuth) checkPermissionDB(ctx context.Context, userID, orgID, permissi
return userPermCount > 0 , nil
return userPermCount > 0 , nil
}
}
func ( a * appAuth ) ListUserPermissions ( ctx context . Context , userID , orgID string ) ( [ ] models . UserPermissionResult , error ) {
func ( a * appAuth ) ListUserPermissions ( ctx context . Context , userID , orgID string ) ( [ ] * vigoauth . UserPermission , error ) {
result := make ( [ ] models . UserPermissionResult , 0 )
result := make ( [ ] * vigoauth . UserPermission , 0 )
// 1. 获取用户角色对应的权限
// 1. 获取用户角色对应的权限
var roleIDs [ ] string
var roleIDs [ ] string
@ -876,8 +979,13 @@ func (a *appAuth) ListUserPermissions(ctx context.Context, userID, orgID string)
}
}
for _ , permID := range permIDs {
for _ , permID := range permIDs {
result = append ( result , models . UserPermissionResult {
parts := strings . Split ( permID , ":" )
PermissionID : permID ,
resource := "*"
if len ( parts ) >= 2 {
resource = parts [ len ( parts ) - 2 ]
}
result = append ( result , & vigoauth . UserPermission {
Resource : resource ,
ResourceID : "*" ,
ResourceID : "*" ,
Actions : [ ] string { "*" } ,
Actions : [ ] string { "*" } ,
} )
} )
@ -893,8 +1001,13 @@ func (a *appAuth) ListUserPermissions(ctx context.Context, userID, orgID string)
}
}
for _ , up := range userPerms {
for _ , up := range userPerms {
result = append ( result , models . UserPermissionResult {
parts := strings . Split ( up . PermissionID , ":" )
PermissionID : up . PermissionID ,
resource := "*"
if len ( parts ) >= 2 {
resource = parts [ len ( parts ) - 2 ]
}
result = append ( result , & vigoauth . UserPermission {
Resource : resource ,
ResourceID : up . ResourceID ,
ResourceID : up . ResourceID ,
Actions : [ ] string { "*" } ,
Actions : [ ] string { "*" } ,
} )
} )
@ -903,8 +1016,8 @@ func (a *appAuth) ListUserPermissions(ctx context.Context, userID, orgID string)
return result , nil
return result , nil
}
}
func ( a * appAuth ) ListResourceUsers ( ctx context . Context , orgID , permissionID , resourceID string ) ( [ ] models . ResourceUser , error ) {
func ( a * appAuth ) ListResourceUsers ( ctx context . Context , orgID , permissionID , resourceID string ) ( [ ] * vigoauth . ResourceUser , error ) {
result := make ( [ ] models . ResourceUser , 0 )
result := make ( [ ] * vigoauth . ResourceUser , 0 )
// 查询有该资源权限的用户
// 查询有该资源权限的用户
var userPerms [ ] models . UserPermission
var userPerms [ ] models . UserPermission
@ -923,7 +1036,7 @@ func (a *appAuth) ListResourceUsers(ctx context.Context, orgID, permissionID, re
}
}
for userID , actions := range userMap {
for userID , actions := range userMap {
result = append ( result , models . ResourceUser {
result = append ( result , & vigoauth . ResourceUser {
UserID : userID ,
UserID : userID ,
Actions : actions ,
Actions : actions ,
} )
} )