233 lines
6.7 KiB
Go
233 lines
6.7 KiB
Go
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
|
||
}
|