package auth import ( "fmt" "os" "time" ) type Manager struct { Store *UserStore JWT *JWTManager Enforcer *EnforcerWrapper } func NewManager(jwtSecret string, jwtTTL time.Duration, modelPath, policyPath, userFile string) (*Manager, error) { store, err := NewUserStore(userFile) if err != nil { return nil, fmt.Errorf("init user store: %w", err) } if bu, bp := os.Getenv("BOOTSTRAP_ADMIN_USER"), os.Getenv("BOOTSTRAP_ADMIN_PASS"); bu != "" && bp != "" { if len(store.List()) == 0 { if err := store.BootstrapAdmin(bu, bp); err != nil { return nil, fmt.Errorf("bootstrap admin: %w", err) } // 若配置为 root 引导,则提升首个用户为 root(对标 SyncTV Root 超管) if br := os.Getenv("BOOTSTRAP_ADMIN_ROLE"); br == RoleRoot { _ = store.UpdateRole(bu, RoleRoot) } } } jwtMgr := NewJWTManager(jwtSecret, jwtTTL) enc, err := NewEnforcer(modelPath, policyPath, store) if err != nil { return nil, fmt.Errorf("init casbin: %w", err) } m := &Manager{Store: store, JWT: jwtMgr, Enforcer: enc} return m, nil } func NewManagerWithStore(store *UserStore, jwtSecret string, ttl time.Duration, enforcer *EnforcerWrapper) *Manager { return &Manager{ Store: store, JWT: NewJWTManager(jwtSecret, ttl), Enforcer: enforcer, } } func (m *Manager) Login(username, password string) (string, *User, error) { u, ok := m.Store.Verify(username, password) if !ok { return "", nil, fmt.Errorf("invalid credentials") } if u.Status == StatusBanned { return "", nil, fmt.Errorf("user is banned") } _ = m.Enforcer.AddUserRole(u.Username, u.Role) token, err := m.JWT.Sign(u.Username, u.Role) if err != nil { return "", nil, err } ret := &User{Username: u.Username, Role: u.Role, Status: u.Status, CreatedAt: u.CreatedAt} return token, ret, nil } func (m *Manager) LoginWithRefresh(username, password string) (access, refresh string, user *User, err error) { u, ok := m.Store.Verify(username, password) if !ok { return "", "", nil, fmt.Errorf("invalid credentials") } if u.Status == StatusBanned { return "", "", nil, fmt.Errorf("user is banned") } _ = m.Enforcer.AddUserRole(u.Username, u.Role) access, refresh, err = m.JWT.IssuePair(u.Username, u.Role) if err != nil { return "", "", nil, err } ret := &User{Username: u.Username, Role: u.Role, Status: u.Status, CreatedAt: u.CreatedAt} return access, refresh, ret, nil } func (m *Manager) Register(username, password, role string) (string, *User, error) { if role == "" { role = RoleViewer } role = NormalizeRole(role) // 非 root/admin 不可自行注册高权限角色 if role == RoleRoot { return "", nil, fmt.Errorf("cannot register as root") } u, err := m.Store.Create(username, password, role) if err != nil { return "", nil, err } _ = m.Enforcer.AddUserRole(u.Username, u.Role) token, err := m.JWT.Sign(u.Username, u.Role) if err != nil { return "", nil, err } return token, u, nil } func (m *Manager) VerifyToken(token string) (*Claims, *User, error) { claims, err := m.JWT.Verify(token) if err != nil { return nil, nil, err } if u, ok := m.Store.Get(claims.Username); ok { if u.Status == StatusBanned { return nil, nil, fmt.Errorf("user is banned") } claims.Role = u.Role return claims, &User{Username: u.Username, Role: u.Role, Status: u.Status, CreatedAt: u.CreatedAt}, nil } return claims, &User{Username: claims.Username, Role: claims.Role, Status: StatusActive}, nil } func (m *Manager) RefreshAccess(refreshToken string) (string, *Claims, error) { claims, err := m.JWT.VerifyRefresh(refreshToken) if err != nil { return "", nil, fmt.Errorf("invalid refresh token: %w", err) } if u, ok := m.Store.Get(claims.Username); ok { if u.Status == StatusBanned { return "", nil, fmt.Errorf("user is banned") } claims.Role = u.Role } newAccess, err := m.JWT.Sign(claims.Username, claims.Role) if err != nil { return "", nil, err } newClaims, err := m.JWT.Verify(newAccess) if err != nil { return "", nil, err } return newAccess, newClaims, nil } func (m *Manager) Check(username, role, obj, act string) (bool, error) { if role == "" { if u, ok := m.Store.Get(username); ok { if u.Status == StatusBanned { return false, nil } role = u.Role } else { role = RoleGuest } } else { if u, ok := m.Store.Get(username); ok && u.Status == StatusBanned { return false, nil } } sub := username if sub == "" { sub = role } return m.Enforcer.Enforce(sub, obj, act) } func (m *Manager) UpdateUserRole(targetUser, newRole string) error { return m.UpdateUserRoleAs("", targetUser, newRole) } func (m *Manager) UpdateUserRoleAs(actor, targetUser, newRole string) error { newRole = NormalizeRole(newRole) if newRole == RoleRoot && actor != targetUser { // 仅 root 可授予 root,对标 SyncTV can_manage(Root, _) if actorRole := m.getRole(actor); actorRole != RoleRoot { return fmt.Errorf("only root can grant root role") } } if !isValidRole(newRole) { return fmt.Errorf("invalid role %q", newRole) } actorRole := m.getRole(actor) targetRole := m.getRole(targetUser) if actor != "" && !CanManage(actorRole, newRole) && actorRole != RoleRoot { // Admin 只能管理 viewer/publisher,不能提权为 admin/root if targetRole == newRole { return fmt.Errorf("permission denied: %s cannot manage %s", actorRole, newRole) } if !CanManage(actorRole, targetRole) { return fmt.Errorf("permission denied: %s cannot manage %s", actorRole, targetRole) } if newRole == RoleAdmin || newRole == RoleRoot { return fmt.Errorf("permission denied: %s cannot grant %s", actorRole, newRole) } } if err := m.Store.UpdateRole(targetUser, newRole); err != nil { return err } return m.Enforcer.AddUserRole(targetUser, newRole) } func (m *Manager) UpdateUserRoleSimple(targetUser, newRole string) error { return m.UpdateUserRoleAs("", targetUser, newRole) } func (m *Manager) BanUser(actor, target string) error { actorRole := m.getRole(actor) if actorRole != RoleRoot && actorRole != RoleAdmin { return fmt.Errorf("permission denied: ban requires admin") } targetRole := m.getRole(target) if targetRole == RoleRoot { return fmt.Errorf("cannot ban root") } if actorRole == RoleAdmin && targetRole == RoleAdmin && actor != target { return fmt.Errorf("admin cannot ban another admin") } return m.Store.BanUser(target) } func (m *Manager) UnbanUser(actor, target string) error { actorRole := m.getRole(actor) if actorRole != RoleRoot && actorRole != RoleAdmin { return fmt.Errorf("permission denied: unban requires admin") } return m.Store.UnbanUser(target) } func (m *Manager) getRole(username string) string { if username == "" { return RoleGuest } if u, ok := m.Store.Get(username); ok { return u.Role } return RoleGuest }