live-sfu-demo/internal/auth/auth.go

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