feat: complete pet SOP governance workflow
This commit is contained in:
@@ -3,6 +3,7 @@ package auth
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
@@ -19,11 +20,12 @@ import (
|
||||
var ErrInvalidCredentials = errors.New("invalid username or password")
|
||||
|
||||
type Principal struct {
|
||||
UserID uint64 `json:"user_id"`
|
||||
TenantID uint64 `json:"tenant_id"`
|
||||
RoleCode string `json:"role_code"`
|
||||
Username string `json:"username"`
|
||||
DisplayName string `json:"display_name"`
|
||||
UserID uint64 `json:"user_id"`
|
||||
TenantID uint64 `json:"tenant_id"`
|
||||
RoleCode string `json:"role_code"`
|
||||
Permissions []string `json:"permissions"`
|
||||
Username string `json:"username"`
|
||||
DisplayName string `json:"display_name"`
|
||||
}
|
||||
|
||||
type Claims struct {
|
||||
@@ -79,7 +81,7 @@ func (s *Service) Refresh(rawToken string) (TokenPair, error) {
|
||||
if err := s.db.First(&user, stored.UserID).Error; err != nil {
|
||||
return TokenPair{}, err
|
||||
}
|
||||
principal, err := s.principalForUser(user)
|
||||
principal, err := s.principalForTenant(user, stored.TenantID)
|
||||
if err != nil {
|
||||
return TokenPair{}, err
|
||||
}
|
||||
@@ -108,6 +110,18 @@ func (s *Service) ParseAccessToken(rawToken string) (Principal, error) {
|
||||
return Principal{UserID: userID, TenantID: claims.TenantID, RoleCode: claims.RoleCode, Username: claims.Username, DisplayName: claims.DisplayName}, nil
|
||||
}
|
||||
|
||||
func (s *Service) RefreshPrincipal(principal Principal) (Principal, error) {
|
||||
var user model.User
|
||||
if err := s.db.Where("id = ? AND status = ?", principal.UserID, "active").First(&user).Error; err != nil {
|
||||
return Principal{}, ErrInvalidCredentials
|
||||
}
|
||||
current, err := s.principalForTenant(user, principal.TenantID)
|
||||
if err != nil {
|
||||
return Principal{}, ErrInvalidCredentials
|
||||
}
|
||||
return current, nil
|
||||
}
|
||||
|
||||
func (s *Service) issueTokenPair(principal Principal) (TokenPair, error) {
|
||||
now := time.Now()
|
||||
expiresAt := now.Add(s.cfg.AccessTokenTTL)
|
||||
@@ -128,16 +142,29 @@ func (s *Service) issueTokenPair(principal Principal) (TokenPair, error) {
|
||||
}
|
||||
|
||||
func (s *Service) principalForUser(user model.User) (Principal, error) {
|
||||
return s.principalForTenant(user, 0)
|
||||
}
|
||||
|
||||
func (s *Service) principalForTenant(user model.User, tenantID uint64) (Principal, error) {
|
||||
type row struct {
|
||||
TenantID uint64
|
||||
RoleCode string
|
||||
TenantID uint64
|
||||
RoleCode string
|
||||
Permissions datatypes.JSON
|
||||
}
|
||||
var membership row
|
||||
err := s.db.Table("tenant_members tm").Select("tm.tenant_id, r.code AS role_code").Joins("JOIN roles r ON r.id = tm.role_id").Where("tm.user_id = ? AND tm.status = ?", user.ID, "active").First(&membership).Error
|
||||
query := s.db.Table("tenant_members tm").Select("tm.tenant_id, r.code AS role_code, r.permissions").Joins("JOIN roles r ON r.id = tm.role_id").Where("tm.user_id = ? AND tm.status = ?", user.ID, "active")
|
||||
if tenantID != 0 {
|
||||
query = query.Where("tm.tenant_id = ?", tenantID)
|
||||
}
|
||||
err := query.Order("tm.id").First(&membership).Error
|
||||
if err != nil {
|
||||
return Principal{}, err
|
||||
}
|
||||
return Principal{UserID: user.ID, TenantID: membership.TenantID, RoleCode: membership.RoleCode, Username: user.Username, DisplayName: user.DisplayName}, nil
|
||||
var permissions []string
|
||||
if err := json.Unmarshal(membership.Permissions, &permissions); err != nil {
|
||||
return Principal{}, fmt.Errorf("decode role permissions: %w", err)
|
||||
}
|
||||
return Principal{UserID: user.ID, TenantID: membership.TenantID, RoleCode: membership.RoleCode, Permissions: permissions, Username: user.Username, DisplayName: user.DisplayName}, nil
|
||||
}
|
||||
|
||||
func Seed(db *gorm.DB, cfg config.SeedConfig) error {
|
||||
@@ -149,10 +176,23 @@ func Seed(db *gorm.DB, cfg config.SeedConfig) error {
|
||||
if err := tx.Where("slug = ?", "default").FirstOrCreate(&tenant, model.Tenant{Name: "默认企业", Slug: "default", Status: "active"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
permissions := datatypes.JSON([]byte(`["*"]`))
|
||||
var role model.Role
|
||||
if err := tx.Where("tenant_id = ? AND code = ?", tenant.ID, "admin").FirstOrCreate(&role, model.Role{TenantID: tenant.ID, Name: "管理员", Code: "admin", Permissions: permissions}).Error; err != nil {
|
||||
return err
|
||||
roleDefinitions := []struct {
|
||||
Name, Code string
|
||||
Permissions []string
|
||||
}{
|
||||
{Name: "管理员", Code: "admin", Permissions: []string{"*"}},
|
||||
{Name: "SOP 编辑者", Code: "editor", Permissions: []string{"dashboard.view", "scenario.view", "scenario.team_view", "scenario.edit", "sop.view", "sop.edit", "sop.submit_review", "knowledge.view", "knowledge.edit", "runs.view_all"}},
|
||||
{Name: "审核者", Code: "reviewer", Permissions: []string{"dashboard.view", "scenario.view", "scenario.team_view", "sop.view", "sop.review", "sop.publish", "knowledge.view", "runs.view_all"}},
|
||||
{Name: "一线执行者", Code: "operator", Permissions: []string{"dashboard.view", "scenario.view", "sop.execute", "knowledge.view", "runs.view_own", "runs.feedback"}},
|
||||
}
|
||||
roles := make(map[string]model.Role, len(roleDefinitions))
|
||||
for _, definition := range roleDefinitions {
|
||||
permissions, _ := json.Marshal(definition.Permissions)
|
||||
var role model.Role
|
||||
if err := tx.Where("tenant_id = ? AND code = ?", tenant.ID, definition.Code).Assign(model.Role{Name: definition.Name, Permissions: datatypes.JSON(permissions)}).FirstOrCreate(&role, model.Role{TenantID: tenant.ID, Code: definition.Code}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
roles[definition.Code] = role
|
||||
}
|
||||
var user model.User
|
||||
err := tx.Where("username = ?", cfg.AdminUsername).First(&user).Error
|
||||
@@ -169,7 +209,7 @@ func Seed(db *gorm.DB, cfg config.SeedConfig) error {
|
||||
return err
|
||||
}
|
||||
var member model.TenantMember
|
||||
return tx.Where("tenant_id = ? AND user_id = ?", tenant.ID, user.ID).FirstOrCreate(&member, model.TenantMember{TenantID: tenant.ID, UserID: user.ID, RoleID: role.ID, Status: "active"}).Error
|
||||
return tx.Where("tenant_id = ? AND user_id = ?", tenant.ID, user.ID).FirstOrCreate(&member, model.TenantMember{TenantID: tenant.ID, UserID: user.ID, RoleID: roles["admin"].ID, Status: "active"}).Error
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user