Commands: /ban and /op
This commit is contained in:
parent
e0dcc33519
commit
974ee25b48
12
README.md
12
README.md
|
@ -34,14 +34,14 @@ It is very much in the style of the old-school Flash based webcam chat rooms of
|
|||
* WebRTC means peer-to-peer video streaming so cheap on hosting costs!
|
||||
* Simple integration with your existing userbase via signed JWT tokens.
|
||||
* User configurable sound effects to be notified of DMs or users entering/exiting the room.
|
||||
|
||||
Some important features still lacking:
|
||||
|
||||
* Operator commands
|
||||
* [x] /kick users
|
||||
* [x] /nsfw to mark someone's camera
|
||||
* [ ] /ban users
|
||||
* [ ] /op users (give temporary mod control)
|
||||
* [x] /ban users (and /unban, /bans to list)
|
||||
* [x] /nsfw to tag a user's camera as explicit
|
||||
* [x] /shutdown to gracefully reboot the server
|
||||
* [x] /kickall to kick EVERYBODY off the server (e.g., for mandatory front-end reload for new features)
|
||||
* [x] /op and /deop users (give temporary mod control)
|
||||
* [x] /help to get in-chat help for moderator commands
|
||||
|
||||
# Configuration
|
||||
|
||||
|
|
|
@ -1,28 +1,73 @@
|
|||
package barertc
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
/* Functions to handle banned users */
|
||||
|
||||
/*
|
||||
BanList holds (in memory) knowledge of currently banned users.
|
||||
|
||||
All bans are reset if the chat server is rebooted. Otherwise each ban
|
||||
comes with a duration - default is 24 hours by the operator can specify
|
||||
a duration with a ban. If the server is not rebooted, bans will be lifted
|
||||
after they expire.
|
||||
|
||||
Bans are against usernames and will also block a JWT token from
|
||||
authenticating if they are currently banned.
|
||||
*/
|
||||
type BanList struct {
|
||||
Active []Ban
|
||||
}
|
||||
|
||||
// Ban is an entry on the ban list.
|
||||
type Ban struct {
|
||||
Username string
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
//
|
||||
// Global storage for banned users in memory.
|
||||
var (
|
||||
banList = map[string]Ban{}
|
||||
banListMu sync.RWMutex
|
||||
)
|
||||
|
||||
// BanUser adds a user to the ban list.
|
||||
func BanUser(username string, duration time.Duration) {
|
||||
banListMu.Lock()
|
||||
defer banListMu.Unlock()
|
||||
banList[username] = Ban{
|
||||
Username: username,
|
||||
ExpiresAt: time.Now().Add(duration),
|
||||
}
|
||||
}
|
||||
|
||||
// UnbanUser lifts the ban of a user early.
|
||||
func UnbanUser(username string) bool {
|
||||
banListMu.RLock()
|
||||
defer banListMu.RUnlock()
|
||||
_, ok := banList[username]
|
||||
if ok {
|
||||
delete(banList, username)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// StringifyBannedUsers returns a stringified list of all the current banned users.
|
||||
func StringifyBannedUsers() string {
|
||||
var lines = []string{}
|
||||
banListMu.RLock()
|
||||
defer banListMu.RUnlock()
|
||||
for username, ban := range banList {
|
||||
lines = append(lines, fmt.Sprintf(
|
||||
"* `%s` banned until %s",
|
||||
username,
|
||||
ban.ExpiresAt.Format(time.RFC3339),
|
||||
))
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// IsBanned returns whether the username is currently banned.
|
||||
func IsBanned(username string) bool {
|
||||
banListMu.Lock()
|
||||
defer banListMu.Unlock()
|
||||
ban, ok := banList[username]
|
||||
if ok {
|
||||
// Has the ban expired?
|
||||
if time.Now().After(ban.ExpiresAt) {
|
||||
delete(banList, username)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
|
126
pkg/commands.go
126
pkg/commands.go
|
@ -7,7 +7,9 @@ import (
|
|||
"time"
|
||||
|
||||
"git.kirsle.net/apps/barertc/pkg/config"
|
||||
ourjwt "git.kirsle.net/apps/barertc/pkg/jwt"
|
||||
"git.kirsle.net/apps/barertc/pkg/log"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/mattn/go-shellwords"
|
||||
)
|
||||
|
||||
|
@ -35,6 +37,12 @@ func (s *Server) ProcessCommand(sub *Subscriber, msg Message) bool {
|
|||
case "/ban":
|
||||
s.BanCommand(words, sub)
|
||||
return true
|
||||
case "/unban":
|
||||
s.UnbanCommand(words, sub)
|
||||
return true
|
||||
case "/bans":
|
||||
s.BansCommand(words, sub)
|
||||
return true
|
||||
case "/nsfw":
|
||||
if len(words) == 1 {
|
||||
sub.ChatServer("Usage: `/nsfw username` to add the NSFW flag to their camera.")
|
||||
|
@ -58,7 +66,12 @@ func (s *Server) ProcessCommand(sub *Subscriber, msg Message) bool {
|
|||
case "/help":
|
||||
sub.ChatServer(RenderMarkdown("Moderator commands are:\n\n" +
|
||||
"* `/kick <username>` to kick from chat\n" +
|
||||
"* `/ban <username> <duration>` to ban from chat (default duration is 24 (hours))\n" +
|
||||
"* `/unban <username>` to list the ban on a user\n" +
|
||||
"* `/bans` to list current banned users and their expiration date\n" +
|
||||
"* `/nsfw <username>` to mark their camera NSFW\n" +
|
||||
"* `/op <username>` to grant operator rights to a user\n" +
|
||||
"* `/deop <username>` to remove operator rights from a user\n" +
|
||||
"* `/shutdown` to gracefully shut down (reboot) the chat server\n" +
|
||||
"* `/kickall` to kick EVERYBODY off and force them to log back in\n" +
|
||||
"* `/help` to show this message\n\n" +
|
||||
|
@ -72,8 +85,16 @@ func (s *Server) ProcessCommand(sub *Subscriber, msg Message) bool {
|
|||
Message: "The chat server is going down for a reboot NOW!",
|
||||
})
|
||||
os.Exit(1)
|
||||
return true
|
||||
case "/kickall":
|
||||
s.KickAllCommand()
|
||||
return true
|
||||
case "/op":
|
||||
s.OpCommand(words, sub)
|
||||
return true
|
||||
case "/deop":
|
||||
s.DeopCommand(words, sub)
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -151,7 +172,7 @@ func (s *Server) BanCommand(words []string, sub *Subscriber) {
|
|||
if len(words) == 1 {
|
||||
sub.ChatServer(RenderMarkdown(
|
||||
"Usage: `/ban username` to remove the user from the chat room for 24 hours (default).\n\n" +
|
||||
"Set another duration (in hours, fractions supported) like: `/ban username 0.5` for a 30-minute ban.",
|
||||
"Set another duration (in hours) like: `/ban username 2` for a 2-hour ban.",
|
||||
))
|
||||
return
|
||||
}
|
||||
|
@ -162,27 +183,112 @@ func (s *Server) BanCommand(words []string, sub *Subscriber) {
|
|||
duration = 24 * time.Hour
|
||||
)
|
||||
if len(words) >= 3 {
|
||||
if dur, err := strconv.ParseFloat(words[2], 64); err == nil {
|
||||
if dur < 1 {
|
||||
duration = time.Duration(dur*60) * time.Second
|
||||
} else {
|
||||
if dur, err := strconv.Atoi(words[2]); err == nil {
|
||||
duration = time.Duration(dur) * time.Hour
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: banning, for now it just kicks.
|
||||
_ = duration
|
||||
log.Info("Operator %s bans %s for %d hours", sub.Username, username, duration/time.Hour)
|
||||
|
||||
other, err := s.GetSubscriber(username)
|
||||
if err != nil {
|
||||
sub.ChatServer("/ban: username not found: %s", username)
|
||||
} else {
|
||||
other.ChatServer("You have been kicked from the chat room by %s", sub.Username)
|
||||
// Ban them.
|
||||
BanUser(username, duration)
|
||||
|
||||
other.ChatServer("You have been banned from the chat room by %s. You may come back after %d hours.", sub.Username, duration/time.Hour)
|
||||
other.SendJSON(Message{
|
||||
Action: ActionKick,
|
||||
})
|
||||
s.DeleteSubscriber(other)
|
||||
sub.ChatServer("%s has been kicked from the room", username)
|
||||
sub.ChatServer("%s has been banned from the room for %d hours.", username, duration/time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
// UnbanCommand handles the `/unban` operator command.
|
||||
func (s *Server) UnbanCommand(words []string, sub *Subscriber) {
|
||||
if len(words) == 1 {
|
||||
sub.ChatServer(RenderMarkdown(
|
||||
"Usage: `/unban username` to lift the ban on a user and allow them back into the chat room.",
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the command.
|
||||
var username = words[1]
|
||||
|
||||
if UnbanUser(username) {
|
||||
sub.ChatServer("The ban on %s has been lifted.", username)
|
||||
} else {
|
||||
sub.ChatServer("/unban: user %s was not found to be banned. Try `/bans` to see current banned users.", username)
|
||||
}
|
||||
}
|
||||
|
||||
// BansCommand handles the `/bans` operator command.
|
||||
func (s *Server) BansCommand(words []string, sub *Subscriber) {
|
||||
result := StringifyBannedUsers()
|
||||
sub.ChatServer(
|
||||
RenderMarkdown("The listing of banned users currently includes:\n\n" + result),
|
||||
)
|
||||
}
|
||||
|
||||
// OpCommand handles the `/op` operator command.
|
||||
func (s *Server) OpCommand(words []string, sub *Subscriber) {
|
||||
if len(words) == 1 {
|
||||
sub.ChatServer(RenderMarkdown(
|
||||
"Usage: `/op username` to grant temporary operator rights to a user.",
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the command.
|
||||
var username = words[1]
|
||||
if other, err := s.GetSubscriber(username); err != nil {
|
||||
sub.ChatServer("/op: user %s was not found.", username)
|
||||
} else {
|
||||
if other.JWTClaims == nil {
|
||||
other.JWTClaims = &ourjwt.Claims{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Subject: username,
|
||||
},
|
||||
}
|
||||
}
|
||||
other.JWTClaims.IsAdmin = true
|
||||
|
||||
// Send everyone the Who List.
|
||||
s.SendWhoList()
|
||||
|
||||
sub.ChatServer("Operator rights have been granted to %s", username)
|
||||
}
|
||||
}
|
||||
|
||||
// DeopCommand handles the `/deop` operator command.
|
||||
func (s *Server) DeopCommand(words []string, sub *Subscriber) {
|
||||
if len(words) == 1 {
|
||||
sub.ChatServer(RenderMarkdown(
|
||||
"Usage: `/deop username` to remove operator rights from a user.",
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the command.
|
||||
var username = words[1]
|
||||
if other, err := s.GetSubscriber(username); err != nil {
|
||||
sub.ChatServer("/deop: user %s was not found.", username)
|
||||
} else {
|
||||
if other.JWTClaims == nil {
|
||||
other.JWTClaims = &ourjwt.Claims{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Subject: username,
|
||||
},
|
||||
}
|
||||
}
|
||||
other.JWTClaims.IsAdmin = false
|
||||
|
||||
// Send everyone the Who List.
|
||||
s.SendWhoList()
|
||||
|
||||
sub.ChatServer("Operator rights have been taken from %s", username)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,19 @@ func (s *Server) OnLogin(sub *Subscriber, msg Message) {
|
|||
}
|
||||
msg.Username = username
|
||||
|
||||
// Is the username currently banned?
|
||||
if IsBanned(msg.Username) {
|
||||
sub.ChatServer(
|
||||
"You are currently banned from entering the chat room. Chat room bans are temporarily and usually last for " +
|
||||
"24 hours. Please try coming back later.",
|
||||
)
|
||||
sub.SendJSON(Message{
|
||||
Action: ActionKick,
|
||||
})
|
||||
s.DeleteSubscriber(sub)
|
||||
return
|
||||
}
|
||||
|
||||
// Use their username.
|
||||
sub.Username = msg.Username
|
||||
sub.authenticated = true
|
||||
|
|
Loading…
Reference in New Issue
Block a user