Add DisconnectNow API endpoint

This commit is contained in:
Noah 2024-03-14 23:04:24 -07:00
parent c7ef254361
commit 96d61614f4
8 changed files with 282 additions and 24 deletions

View File

@ -27,16 +27,16 @@ Post your desired JWT claims to the endpoint to customize your user and it will
```json
{
"APIKey": "from settings.toml",
"Claims": {
"sub": "username",
"nick": "Display Name",
"op": false,
"img": "/static/photos/avatar.png",
"url": "/users/username",
"emoji": "🤖",
"gender": "m"
}
"APIKey": "from settings.toml",
"Claims": {
"sub": "username",
"nick": "Display Name",
"op": false,
"img": "/static/photos/avatar.png",
"url": "/users/username",
"emoji": "🤖",
"gender": "m"
}
}
```
@ -44,9 +44,9 @@ The return schema looks like:
```json
{
"OK": true,
"Error": "error string, omitted if none",
"JWT": "jwt token string"
"OK": true,
"Error": "error string, omitted if none",
"JWT": "jwt token string"
}
```
@ -58,7 +58,7 @@ It requires the AdminAPIKey to post:
```json
{
"APIKey": "from settings.toml"
"APIKey": "from settings.toml"
}
```
@ -66,8 +66,8 @@ The return schema looks like:
```json
{
"OK": true,
"Error": "error string, omitted if none"
"OK": true,
"Error": "error string, omitted if none"
}
```
@ -110,3 +110,123 @@ The JSON response to this endpoint may look like:
"Error": "if error, or this key is omitted if OK"
}
```
## POST /api/block/now
Your website can tell BareRTC to put a block between users "now." For
example, if a user on your main website adds a block on another user,
and one or both of them are presently logged into the chat room, BareRTC
can begin enforcing the block immediately so both users will disappear
from each other's view and no longer get one another's messages.
The request body payload looks like:
```json
{
"APIKey": "from your settings.toml",
"Usernames": [ "alice", "bob" ]
}
```
The pair of usernames should be the two who are blocking each other, in
any order. This will put in a two-way block between those chatters.
If you provide more than two usernames, the block is put between every
combination of usernames given.
The JSON response to this endpoint may look like:
```json
{
"OK": true,
"Error": "if error, or this key is omitted if OK"
}
```
## POST /api/disconnect/now
Your website can tell BareRTC to remove a user from the chat room "now"
in case that user is presently online in the chat.
The request body payload looks like:
```json
{
"APIKey": "from your settings.toml",
"Usernames": [ "alice" ],
"Message": "a custom ChatServer message to send them, optional",
"Kick": false,
}
```
The `Message` parameter, if provided, will be sent to that user as a
ChatServer error before they are removed from the room. You can use this
to provide them context as to why they are being kicked. For example:
"You have been logged out of chat because you deactivated your profile on
the main website."
The `Kick` boolean is whether the removal should manifest to other users
in chat as a "kick" (sending a presence message of "has been kicked from
the room!"). By default (false), BareRTC will tell the user to disconnect
and it will manifest as a regular "has left the room" event to other online
chatters.
The JSON response to this endpoint may look like:
```json
{
"OK": true,
"Removed": 1,
"Error": "if error, or this key is omitted if OK"
}
```
The "Removed" field is the count of users actually removed from chat; a zero
means the user was not presently online.
# Ajax Endpoints (User API)
## POST /api/profile
Fetch profile information from your main website about a user in the
chat room.
Note: this API request is done by the BareRTC chat front-end page, as an
ajax request for a current logged-in user. It backs the profile card pop-up
widget in the chat room when a user clicks on another user's profile.
The request body payload looks like:
```json
{
"JWTToken": "the caller's chat jwt token",
"Username": "soandso"
}
```
The JWT token is the current chat user's token. This API only works when
your BareRTC config requires the use of JWT tokens for authorization.
BareRTC will translate the request into the
["Profile Webhook"](Webhooks.md#Profile%20Webhook) to fetch the target
user's profile from your website.
The response JSON given to the chat page from /api/profile looks like:
```json
{
"OK": true,
"Error": "only on error messages",
"ProfileFields": [
{
"Name": "Age",
"Value": "30yo"
},
{
"Name": "Gender",
"Value": "Man"
},
...
]
}
```

View File

@ -468,6 +468,134 @@ func (s *Server) BlockNow() http.HandlerFunc {
})
}
// DisconnectNow (/api/disconnect/now) allows your website to remove a user from
// the chat room if they are currently online.
//
// For example: a user on your website has deactivated their account, and so
// should not be allowed to remain in the chat room.
//
// It is a POST request with a json body containing the following schema:
//
// {
// "APIKey": "from settings.toml",
// "Usernames": [ "alice", "bob" ],
// "Message": "An optional ChatServer message to send them first.",
// "Kick": false,
// }
//
// The `Message` parameter, if provided, will be sent to that user as a
// ChatServer error before they are removed from the room. You can use this
// to provide them context as to why they are being kicked. For example:
// "You have been logged out of chat because you deactivated your profile on
// the main website."
//
// The `Kick` boolean is whether the removal should manifest to other users
// in chat as a "kick" (sending a presence message of "has been kicked from
// the room!"). By default (false), BareRTC will tell the user to disconnect
// and it will manifest as a regular "has left the room" event to other online
// chatters.
func (s *Server) DisconnectNow() http.HandlerFunc {
type request struct {
APIKey string
Usernames []string
Message string
Kick bool
}
type result struct {
OK bool
Removed int
Error string `json:",omitempty"`
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// JSON writer for the response.
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
// Parse the request.
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusBadRequest)
enc.Encode(result{
Error: "Only POST methods allowed",
})
return
} else if r.Header.Get("Content-Type") != "application/json" {
w.WriteHeader(http.StatusBadRequest)
enc.Encode(result{
Error: "Only application/json content-types allowed",
})
return
}
defer r.Body.Close()
// Parse the request payload.
var (
params request
dec = json.NewDecoder(r.Body)
)
if err := dec.Decode(&params); err != nil {
w.WriteHeader(http.StatusBadRequest)
enc.Encode(result{
Error: err.Error(),
})
return
}
// Validate the API key.
if params.APIKey != config.Current.AdminAPIKey {
w.WriteHeader(http.StatusUnauthorized)
enc.Encode(result{
Error: "Authentication denied.",
})
return
}
// Check if any of these users are online, and disconnect them from the chat.
var removed int
for _, username := range params.Usernames {
if sub, err := s.GetSubscriber(username); err == nil {
// Broadcast to everybody that the user left the chat.
message := messages.PresenceExited
if params.Kick {
message = messages.PresenceKicked
}
s.Broadcast(messages.Message{
Action: messages.ActionPresence,
Username: username,
Message: message,
})
// Custom message to send to them?
if params.Message != "" {
sub.ChatServer(params.Message)
}
// Disconnect them.
sub.SendJSON(messages.Message{
Action: messages.ActionKick,
})
sub.authenticated = false
sub.Username = ""
removed++
}
}
// If any changes to blocklists were made: send the Who List.
if removed > 0 {
s.SendWhoList()
}
enc.Encode(result{
OK: true,
Removed: removed,
})
})
}
// UserProfile (/api/profile) fetches profile information about a user.
//
// This endpoint will proxy to your WebhookURL for the "profile" endpoint.

View File

@ -160,7 +160,7 @@ func (s *Server) KickCommand(words []string, sub *Subscriber) {
s.Broadcast(messages.Message{
Action: messages.ActionPresence,
Username: username,
Message: "has been kicked from the room!",
Message: messages.PresenceKicked,
})
}
}
@ -237,7 +237,7 @@ func (s *Server) BanCommand(words []string, sub *Subscriber) {
s.Broadcast(messages.Message{
Action: messages.ActionPresence,
Username: username,
Message: "has been banned!",
Message: messages.PresenceBanned,
})
other.ChatServer("You have been banned from the chat room by %s. You may come back after %d hours.", sub.Username, duration/time.Hour)

View File

@ -97,7 +97,7 @@ func (s *Server) OnLogin(sub *Subscriber, msg messages.Message) {
s.Broadcast(messages.Message{
Action: messages.ActionPresence,
Username: msg.Username,
Message: "has joined the room!",
Message: messages.PresenceJoined,
})
// Send the user back their settings.
@ -379,14 +379,14 @@ func (s *Server) OnMe(sub *Subscriber, msg messages.Message) {
s.Broadcast(messages.Message{
Action: messages.ActionPresence,
Username: sub.Username,
Message: "has exited the room!",
Message: messages.PresenceExited,
})
} else if sub.ChatStatus == "hidden" && msg.ChatStatus != "hidden" {
// Leaving hidden - fake join message
s.Broadcast(messages.Message{
Action: messages.ActionPresence,
Username: sub.Username,
Message: "has joined the room!",
Message: messages.PresenceJoined,
})
}
} else if msg.ChatStatus == "hidden" {

View File

@ -131,3 +131,12 @@ const (
VideoFlagMutualOpen // viewer wants to auto-open viewers' cameras
VideoFlagOnlyVIP // can only shows as active to VIP members
)
// Presence message templates.
const (
PresenceJoined = "has joined the room!"
PresenceExited = "has exited the room!"
PresenceKicked = "has been kicked from the room!"
PresenceBanned = "has been banned!"
PresenceTimedOut = "has timed out!"
)

View File

@ -65,7 +65,7 @@ func (s *Server) KickIdlePollUsers() {
s.Broadcast(messages.Message{
Action: messages.ActionPresence,
Username: sub.Username,
Message: "has timed out!",
Message: messages.PresenceTimedOut,
})
s.SendWhoList()
}

View File

@ -42,6 +42,7 @@ func (s *Server) Setup() error {
mux.Handle("/api/statistics", s.Statistics())
mux.Handle("/api/blocklist", s.BlockList())
mux.Handle("/api/block/now", s.BlockNow())
mux.Handle("/api/disconnect/now", s.DisconnectNow())
mux.Handle("/api/authenticate", s.Authenticate())
mux.Handle("/api/shutdown", s.ShutdownAPI())
mux.Handle("/api/profile", s.UserProfile())

View File

@ -99,7 +99,7 @@ func (s *Server) NewPollingSubscriber(ctx context.Context, cancelFunc func()) *S
s.Broadcast(messages.Message{
Action: messages.ActionPresence,
Username: sub.Username,
Message: "has exited the room!",
Message: messages.PresenceExited,
})
s.SendWhoList()
}
@ -167,7 +167,7 @@ func (sub *Subscriber) ReadLoop(s *Server) {
s.Broadcast(messages.Message{
Action: messages.ActionPresence,
Username: sub.Username,
Message: "has exited the room!",
Message: messages.PresenceExited,
})
s.SendWhoList()
}