Live video on the AT Protocol
at natb/command-errors 114 lines 3.5 kB view raw
1package moderation 2 3import ( 4 "context" 5 "fmt" 6 "time" 7 8 "stream.place/streamplace/pkg/streamplace" 9) 10 11type delegationGetter interface { 12 GetModerationDelegations(ctx context.Context, streamerDID, moderatorDID string) ([]*streamplace.ModerationDefs_PermissionView, error) 13} 14 15// Permission scope constants 16const ( 17 PermissionBan = "ban" 18 PermissionHide = "hide" 19 PermissionLivestreamManage = "livestream.manage" 20) 21 22// ActionPermissions maps moderation actions to required permissions 23var ActionPermissions = map[string]string{ 24 "createBlock": PermissionBan, 25 "deleteBlock": PermissionBan, 26 "createGate": PermissionHide, 27 "deleteGate": PermissionHide, 28 "updateLivestream": PermissionLivestreamManage, 29} 30 31// PermissionChecker validates moderation permissions 32type PermissionChecker struct { 33 model delegationGetter 34} 35 36// NewPermissionChecker creates a new permission checker 37func NewPermissionChecker(m delegationGetter) *PermissionChecker { 38 return &PermissionChecker{model: m} 39} 40 41// CheckPermission validates that a moderator has permission to perform an action 42// Returns an error if the moderator lacks the required permission 43func (pc *PermissionChecker) CheckPermission(ctx context.Context, moderatorDID, streamerDID, action string) error { 44 // Get required permission for this action (validate action first) 45 requiredPermission, ok := ActionPermissions[action] 46 if !ok { 47 return fmt.Errorf("unknown action: %s", action) 48 } 49 50 // Streamers always have permission for their own content 51 if moderatorDID == streamerDID { 52 return nil 53 } 54 55 // Check if moderator has the required permission 56 hasPermission, err := pc.HasPermission(ctx, moderatorDID, streamerDID, requiredPermission) 57 if err != nil { 58 return fmt.Errorf("failed to check permissions: %w", err) 59 } 60 61 if !hasPermission { 62 return fmt.Errorf("moderator %s does not have permission '%s' for streamer %s", moderatorDID, requiredPermission, streamerDID) 63 } 64 65 return nil 66} 67 68// HasPermission checks if a moderator has a specific permission for a streamer. 69// It merges permissions from ALL delegation records for the moderator. 70func (pc *PermissionChecker) HasPermission(ctx context.Context, moderatorDID, streamerDID, permission string) (bool, error) { 71 // Streamers always have all permissions for their own content 72 if moderatorDID == streamerDID { 73 return true, nil 74 } 75 76 // Look up ALL delegation records for this moderator 77 delegations, err := pc.model.GetModerationDelegations(ctx, streamerDID, moderatorDID) 78 if err != nil { 79 return false, fmt.Errorf("failed to get moderation delegations: %w", err) 80 } 81 82 if len(delegations) == 0 { 83 return false, nil 84 } 85 86 // Check all delegation records and merge their permissions 87 for _, delegationView := range delegations { 88 // Extract the actual permission record from the view 89 permRecord, ok := delegationView.Record.Val.(*streamplace.ModerationPermission) 90 if !ok { 91 return false, fmt.Errorf("failed to cast record to ModerationPermission") 92 } 93 94 // Skip expired delegations 95 if permRecord.ExpirationTime != nil { 96 expirationTime, err := time.Parse(time.RFC3339, *permRecord.ExpirationTime) 97 if err != nil { 98 return false, fmt.Errorf("failed to parse expiration time: %w", err) 99 } 100 if time.Now().After(expirationTime) { 101 continue 102 } 103 } 104 105 // Check if this delegation has the required permission 106 for _, p := range permRecord.Permissions { 107 if p == permission { 108 return true, nil 109 } 110 } 111 } 112 113 return false, nil 114}