Files
backend/internal/service/role_service.go
lafay eb931bf1c6
All checks were successful
Build Backend / build (push) Successful in 2m19s
Build Backend / build-docker (push) Successful in 46s
feat(auth): implement session-based token management with revocation support
Add Session model, SessionService, and SessionRepository to track user login
sessions and enable token revocation on auth-critical events.

- Introduce explicit TokenType (access/refresh) in JWT claims to prevent
  refresh token misuse via access token endpoints
- Add SessionID field to JWT claims, enabling stateless JWT validation
  against revoked sessions
- Replace legacy Auth middleware with RequireAuth/OptionalAuth pipeline
  that validates token type, account status, and session validity
- Implement session revocation on password change, reset, user ban/inactive,
  and explicit logout
- Add Principal cache with active invalidation for banned/role-changed users
- Fix IDOR vulnerability: GetMessagesByCursor now validates currentUserID
  is conversation participant via GetParticipantStrict
- Add group member visibility checks: announcements, group info, member list
  now require group membership
- Simplify Casbin policy: remove g grouping, use r.sub == p.sub matcher
  with globMatch; user_roles table is single source of truth for roles
- Add migration logic to clean legacy casbin g rules and migrate old p rules
  from path-style to admin/<domain> resource naming
2026-07-05 18:28:08 +08:00

276 lines
7.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"context"
apperrors "with_you/internal/errors"
"with_you/internal/model"
"with_you/internal/repository"
)
// RoleService 角色管理服务接口
type RoleService interface {
// GetUserRoles 获取用户的所有角色
GetUserRoles(ctx context.Context, userID string) ([]model.Role, error)
// AssignRole 为用户分配角色
AssignRole(ctx context.Context, operatorID, targetUserID, role string) error
// RemoveRole 移除用户的角色
RemoveRole(ctx context.Context, operatorID, targetUserID, role string) error
// GetAllRoles 获取所有角色定义
GetAllRoles(ctx context.Context) ([]model.Role, error)
// GetRole 获取角色详情
GetRole(ctx context.Context, name string) (*model.Role, error)
// GetRoleDetail 获取角色详情(包含用户数量和权限)
GetRoleDetail(ctx context.Context, name string) (*RoleDetail, error)
// GetRolePermissions 获取角色的权限列表
GetRolePermissions(ctx context.Context, name string) ([]Permission, error)
// GetAllPermissions 获取系统中所有权限定义
GetAllPermissions(ctx context.Context) ([]Permission, error)
// UpdateRolePermissions 更新角色权限
UpdateRolePermissions(ctx context.Context, roleName string, permissions []Permission) error
}
// Permission 权限定义
type Permission struct {
Resource string `json:"resource"`
Action string `json:"action"`
}
// RoleDetail 角色详情
type RoleDetail struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
ParentRole *string `json:"parent_role"`
Priority int `json:"priority"`
UserCount int64 `json:"user_count"`
Permissions []Permission `json:"permissions"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type roleService struct {
roleRepo repository.RoleRepository
casbinSvc CasbinService
}
// NewRoleService 创建角色管理服务
func NewRoleService(
roleRepo repository.RoleRepository,
casbinSvc CasbinService,
) RoleService {
return &roleService{
roleRepo: roleRepo,
casbinSvc: casbinSvc,
}
}
func (s *roleService) GetUserRoles(ctx context.Context, userID string) ([]model.Role, error) {
roleNames, err := s.casbinSvc.GetRolesForUser(ctx, userID)
if err != nil {
return nil, err
}
var roles []model.Role
for _, name := range roleNames {
role, err := s.roleRepo.GetRole(ctx, name)
if err != nil {
continue // 忽略找不到的角色
}
roles = append(roles, *role)
}
return roles, nil
}
func (s *roleService) AssignRole(ctx context.Context, operatorID, targetUserID, role string) error {
// 不能修改自己的角色
if operatorID == targetUserID {
return apperrors.ErrCannotModifyOwnRole
}
// 纵深防御:分配 super_admin 仅 super_admin 自身可操作。
// 路由层 Authorize 已通过 admin.users.roles 限制 super_admin 能拿到此动作,
// 这里再独立校验 operator 角色,防止路由策略错配或服务被其他路径调用造成提权。
if role == model.RoleSuperAdmin {
roles, err := s.casbinSvc.GetRolesForUser(ctx, operatorID)
if err != nil {
return apperrors.ErrCasbinInternal
}
if !containsRole(roles, model.RoleSuperAdmin) {
return apperrors.ErrCannotModifySuperAdmin
}
}
// 检查目标用户是否已有该角色
hasRole, err := s.casbinSvc.HasRoleForUser(ctx, targetUserID, role)
if err != nil {
return err
}
if hasRole {
return apperrors.ErrRoleAlreadyAssigned
}
// 检查角色是否存在
_, err = s.roleRepo.GetRole(ctx, role)
if err != nil {
return apperrors.ErrRoleNotFound
}
// 添加角色
return s.casbinSvc.AddRoleForUser(ctx, targetUserID, role)
}
func (s *roleService) RemoveRole(ctx context.Context, operatorID, targetUserID, role string) error {
// 不能修改自己的角色
if operatorID == targetUserID {
return apperrors.ErrCannotModifyOwnRole
}
// 不能修改超级管理员角色
if role == model.RoleSuperAdmin {
return apperrors.ErrCannotModifySuperAdmin
}
// 纵深防御:移除其他用户的 super_admin 角色同样要求 operator 是 super_admin。
// role==super_admin 已被上面拦截,这里针对未来扩展:如果允许移除其他高优先级角色再加规则。)
// 检查用户是否有该角色
hasRole, err := s.casbinSvc.HasRoleForUser(ctx, targetUserID, role)
if err != nil {
return err
}
if !hasRole {
return apperrors.ErrRoleNotAssigned
}
return s.casbinSvc.DeleteRoleForUser(ctx, targetUserID, role)
}
// containsRole 检查角色集合中是否包含指定角色。
func containsRole(roles []string, role string) bool {
for _, r := range roles {
if r == role {
return true
}
}
return false
}
func (s *roleService) GetAllRoles(ctx context.Context) ([]model.Role, error) {
return s.roleRepo.GetAllRoles(ctx)
}
func (s *roleService) GetRole(ctx context.Context, name string) (*model.Role, error) {
return s.roleRepo.GetRole(ctx, name)
}
func (s *roleService) GetRoleDetail(ctx context.Context, name string) (*RoleDetail, error) {
role, err := s.roleRepo.GetRole(ctx, name)
if err != nil {
return nil, apperrors.ErrRoleNotFound
}
// 获取用户数量
userCount, err := s.roleRepo.GetRoleUserCount(ctx, name)
if err != nil {
userCount = 0
}
// 获取权限
permissions, err := s.casbinSvc.GetPermissionsForRole(ctx, name)
if err != nil {
return nil, err
}
perms := make([]Permission, 0)
for _, p := range permissions {
if len(p) >= 2 {
perms = append(perms, Permission{
Resource: p[0],
Action: p[1],
})
}
}
return &RoleDetail{
Name: role.Name,
DisplayName: role.DisplayName,
Description: role.Description,
ParentRole: role.ParentRole,
Priority: role.Priority,
UserCount: userCount,
Permissions: perms,
CreatedAt: role.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: role.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
}, nil
}
func (s *roleService) GetRolePermissions(ctx context.Context, name string) ([]Permission, error) {
// 检查角色是否存在
_, err := s.roleRepo.GetRole(ctx, name)
if err != nil {
return nil, apperrors.ErrRoleNotFound
}
permissions, err := s.casbinSvc.GetPermissionsForRole(ctx, name)
if err != nil {
return nil, err
}
// 初始化为空数组,避免 JSON 序列化为 null
perms := make([]Permission, 0)
for _, p := range permissions {
if len(p) >= 2 {
perms = append(perms, Permission{
Resource: p[0],
Action: p[1],
})
}
}
return perms, nil
}
func (s *roleService) GetAllPermissions(ctx context.Context) ([]Permission, error) {
permissions := s.casbinSvc.GetAllPermissions(ctx)
perms := make([]Permission, 0)
for _, p := range permissions {
if len(p) >= 3 {
// p[0] 是角色名p[1] 是资源p[2] 是操作
perms = append(perms, Permission{
Resource: p[1],
Action: p[2],
})
}
}
return perms, nil
}
func (s *roleService) UpdateRolePermissions(ctx context.Context, roleName string, permissions []Permission) error {
// 检查角色是否存在
_, err := s.roleRepo.GetRole(ctx, roleName)
if err != nil {
return apperrors.ErrRoleNotFound
}
// 不能修改超级管理员权限
if roleName == model.RoleSuperAdmin {
return apperrors.ErrCannotModifySuperAdmin
}
// 转换权限格式
var perms [][]string
for _, p := range permissions {
perms = append(perms, []string{p.Resource, p.Action})
}
return s.casbinSvc.UpdatePermissionsForRole(ctx, roleName, perms)
}