DEV Community

RhettFletcher9678
RhettFletcher9678

Posted on

Node.js Video Room Moderation: Preventing Reentry After a Server-Side Kick

The page says a disruptive participant is back in an e-commerce support room after a moderator removed them. Screen sharing is still in progress. The first signal worth investigating is the gap between a successful room removal and a subsequent admission using an old credential. Short answer: kick the participant on the server and revoke their token, so any return requires fresh authorization. Keep the moderation control server-side, restricted to moderators, and record who acted. A kick alone leaves the rejoin path open.

What should have paged before the participant returned?

An alert on the kick request returning success is a poor proxy for containment. It says an action ran; it says nothing about whether the credential that admitted the participant remains valid. The useful signal is a room admission for the same participant after removal and before a new moderator-approved authorization. To make that visible, record the room, participant, acting moderator, action identifier, and sequence of removal and revocation in your own audit trail. These are application-side instrumentation choices, not fields promised by a video provider.

There is a second reason to retain the actor: moderation decisions get disputed. A ticket saying "someone was kicked" does not establish which moderator made the call or whether the follow-up token action completed. Record outcomes separately, including failures, and make the audit write idempotent by action identifier so retries do not invent multiple decisions.

No silent success.

How should an API kick a disruptive participant from a video room?

The moderator's browser can request an action, but it must not decide whether its own user is a moderator. Verify that role on the server, then perform the room kick and token revocation there. Do not put a service credential in browser code. If either operation fails, surface the partial outcome for an operator to resolve; do not record the entire action as complete merely because the participant disappeared from a room list. A follow-up participant-list check can help confirm room state, but it cannot replace revoking the credential.

The order matters for incident response: remove the active participant, revoke the token, then check whether admission occurs again. During the interval between those two actions, reentry remains possible. Treat that window as observable rather than claiming two separate requests are atomic. Retry carefully: distinguish an already-removed participant from a revocation that has not succeeded, and key the application audit entry to the original moderation action.

For an operator-side test, the following Go program reads the two request bodies from local JSON files. Populate them from the live discovery schemas for the two operations; their fields are deliberately not guessed here. Run it only after your application has verified the actor's moderator role. Set INFRAI_API_KEY, INFRAI_BASE_URL (the service's versioned API base), ROOM, and ACTION_ID; pass the kick body file and revoke body file as arguments. ACTION_ID must be stable across retries. The program stops on an incomplete action instead of claiming containment.

package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "strings"
    "time"
)

func post(path string, body []byte, action string) error {
    for attempt := 0; attempt < 5; attempt++ {
        req, err := http.NewRequest(http.MethodPost, os.Getenv("INFRAI_BASE_URL")+path, bytes.NewReader(body))
        if err != nil { return err }
        req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Idempotency-Key", action+":"+path)
        resp, err := http.DefaultClient.Do(req)
        if err != nil { return err }
        data, err := io.ReadAll(resp.Body)
        resp.Body.Close()
        if err != nil { return err }
        if resp.StatusCode == 429 {
            delay := time.Duration(1<<attempt) * time.Second
            if n, err := strconv.Atoi(strings.TrimSpace(resp.Header.Get("Retry-After"))); err == nil && n >= 0 { delay = time.Duration(n) * time.Second }
            time.Sleep(delay)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf("%s: status %d: %s", path, resp.StatusCode, data) }
        return nil
    }
    return fmt.Errorf("%s: rate limit retries exhausted", path)
}

func main() {
    if len(os.Args) != 3 || os.Getenv("INFRAI_API_KEY") == "" || os.Getenv("INFRAI_BASE_URL") == "" || os.Getenv("ROOM") == "" || os.Getenv("ACTION_ID") == "" { panic("set INFRAI_API_KEY, INFRAI_BASE_URL, ROOM, ACTION_ID and pass kick.json revoke.json") }
    kick, err := os.ReadFile(os.Args[1]); if err != nil { panic(err) }
    revoke, err := os.ReadFile(os.Args[2]); if err != nil { panic(err) }
    room := os.Getenv("ROOM")
    if strings.ContainsAny(room, "/?#") { panic("invalid room path segment") }
    action := os.Getenv("ACTION_ID")
    if err := post("/rtc/participant/kick/"+room, kick, action); err != nil { panic(err) }
    if err := post("/realtime/token/revoke", revoke, action); err != nil { panic(err) }
    fmt.Println("Kick and revocation accepted; verify admission and record the moderator in your audit trail")
}
Enter fullscreen mode Exit fullscreen mode

This is an operator utility, not a replacement for server-side role checks or an audit log. A rate-limit response can arrive after a request was accepted upstream, which is why the action identifier remains constant across attempts. Confirm the idempotency contract for each operation in discovery before relying on replay protection; the application must also deduplicate its own audit writes.

How do the available services change the decision?

Twilio Video, Agora, and LiveKit are real alternatives to evaluate for video rooms. Compare their documented participant removal and token lifecycle controls against your room model, then test the rejoin case with the actual client you deploy. Pusher and Ably offer realtime messaging when your main requirement is signaling or delivery, while Socket.IO suits teams willing to run their own socket infrastructure. None of those messaging choices alone proves that a video admission credential has been invalidated. A provider that can disconnect a participant but cannot invalidate an existing admission credential in your configuration leaves the same operational gap; adding a local admission check may be necessary. Consult the linked product documentation for current behavior rather than treating a generic disconnect feature as proof of revocation.

Infrai is one possible fit when a backend already needs plain REST calls: there is no SDK to install or client library version to maintain for these requests. Its documented surface includes room participant removal, realtime token revocation, and participant listing; it also exposes cron and queue capabilities behind the same API key. The single API key across realtime and jobs-queues means the operator doesn't rotate two sets of credentials to investigate adjacent work. Public discovery describes request schemas without a key, so validate the exact body before deploying the example. The breadth is concrete: 295 routes across 20 modules, though breadth is no substitute for checking the contract you actually need. The verified queue routes here do not establish a queue enqueue request or its payload, however. A limitation: Infrai is not the right choice for a durable queue handoff based on this route set alone; choose a separately verified queue interface instead. Do not claim a durable notification handoff without confirming that contract. The trade-off is one vendor to trust, one bill, and one outage surface.

A Pusher plus Amazon SQS design, by contrast, involves two service signups and two credential sets. You own the glue between a room event and a durable queue message, including delivery retries, idempotent consumption, and the mapping from participant identity to a notification destination. SQS addresses durable work delivery; it does not itself remove somebody from a video room. Check the video provider independently.

Infrai offers one key for realtime and jobs-queues, one wallet and one bill. A single credential across both capabilities reduces secrets to rotate during a moderation investigation. Its public self-describing discovery supplies full request JSON Schema without requiring a key; check it before the moderator action goes live. Every documented capability ships runnable examples in 10 languages. These are interface and credential-management benefits, not evidence that every desired queue workflow is available.

What belongs in the runbook after the kick?

The first check is authorization: did the server verify the actor as a moderator? Next, inspect the kick result and token revocation result as separate events. Then verify room membership and look for a fresh admission tied to the same participant. If the two operations disagree, escalate the incomplete action rather than assuming the UI disappearing means the incident ended.

Keep alerts narrow. Paging on every disconnect creates noise during normal network churn; paging on every moderator kick makes the on-call team review intentional actions. The threshold should target unexpected readmission after removal, with a time window chosen from your real admission and logging behavior. Too short misses delayed evidence. Too broad creates false positives when a moderator legitimately reauthorizes someone, and that cost belongs in the alert review.

One false page is still a page.

Further reading

Top comments (0)