A bit more logging to debug WebSocket issues

This commit is contained in:
Noah 2023-02-08 20:01:06 -08:00
parent b82ca3d34b
commit a55b4b2b49
7 changed files with 82 additions and 21 deletions

View File

@ -31,6 +31,7 @@ On first run it will create the default settings.toml file for you which you may
Title = "BareRTC" Title = "BareRTC"
Branding = "BareRTC" Branding = "BareRTC"
WebsiteURL = "https://www.example.com" WebsiteURL = "https://www.example.com"
UseXForwardedFor = true
[JWT] [JWT]
Enabled = false Enabled = false
@ -57,6 +58,7 @@ A description of the config directives includes:
* **WebsiteURL** is the base URL of your actual website which is used in a couple of places: * **WebsiteURL** is the base URL of your actual website which is used in a couple of places:
* The About page will link to your website. * The About page will link to your website.
* If using [JWT authentication](#authentication), avatar and profile URLs may be relative (beginning with a "/") and will append to your website URL to safe space on the JWT token size! * If using [JWT authentication](#authentication), avatar and profile URLs may be relative (beginning with a "/") and will append to your website URL to safe space on the JWT token size!
* **UseXForwardedFor**: set it to true and (for logging) the user's remote IP will use the X-Real-IP header or the first address in X-Forwarded-For. Set this if you run the app behind a proxy like nginx if you want IPs not to be all localhost.
* **JWT**: settings for JWT [Authentication](#authentication). * **JWT**: settings for JWT [Authentication](#authentication).
* Enabled (bool): activate the JWT token authentication feature. * Enabled (bool): activate the JWT token authentication feature.
* Strict (bool): if true, **only** valid signed JWT tokens may log in. If false, users with no/invalid token can enter their own username without authentication. * Strict (bool): if true, **only** valid signed JWT tokens may log in. If false, users with no/invalid token can enter their own username without authentication.

View File

@ -10,8 +10,14 @@ import (
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
) )
// Version of the config format - when new fields are added, it will attempt
// to write the settings.toml to disk so new defaults populate.
var currentVersion = 1
// Config for your BareRTC app. // Config for your BareRTC app.
type Config struct { type Config struct {
Version int // will re-save your settings.toml on migrations
JWT struct { JWT struct {
Enabled bool Enabled bool
Strict bool Strict bool
@ -22,6 +28,8 @@ type Config struct {
Branding string Branding string
WebsiteURL string WebsiteURL string
UseXForwardedFor bool
PublicChannels []Channel PublicChannels []Channel
} }
@ -88,6 +96,16 @@ func LoadSettings() error {
} }
_, err = toml.Decode(string(data), &Current) _, err = toml.Decode(string(data), &Current)
// Have we added new config fields? Save the settings.toml.
if Current.Version < currentVersion {
log.Warn("New options are available for your settings.toml file. Your settings will be re-saved now.")
Current.Version = currentVersion
if err := WriteSettings(); err != nil {
log.Error("Couldn't write your settings.toml file: %s", err)
}
}
return err return err
} }

View File

@ -42,7 +42,14 @@ func (s *Server) OnLogin(sub *Subscriber, msg Message) {
sub.JWTClaims = claims sub.JWTClaims = claims
} }
log.Info("JWT claims: %+v", claims) if claims.Subject != "" {
log.Debug("JWT claims: %+v", claims)
}
// Somehow no username?
if msg.Username == "" {
msg.Username = "anonymous"
}
// Ensure the username is unique, or rename it. // Ensure the username is unique, or rename it.
var duplicate bool var duplicate bool

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"net/http" "net/http"
"strings"
"git.kirsle.net/apps/barertc/pkg/config" "git.kirsle.net/apps/barertc/pkg/config"
"git.kirsle.net/apps/barertc/pkg/jwt" "git.kirsle.net/apps/barertc/pkg/jwt"
@ -71,7 +72,12 @@ func IndexPage() http.HandlerFunc {
} }
// END load the template // END load the template
log.Info("Index route hit") log.Info("GET / [%s] %s", r.RemoteAddr, strings.Join([]string{
r.Header.Get("X-Forwarded-For"),
r.Header.Get("X-Real-IP"),
r.Header.Get("User-Agent"),
util.IPAddress(r),
}, " "))
tmpl.ExecuteTemplate(w, "index", values) tmpl.ExecuteTemplate(w, "index", values)
}) })
} }

23
pkg/util/ip_address.go Normal file
View File

@ -0,0 +1,23 @@
package util
import (
"net/http"
"strings"
"git.kirsle.net/apps/barertc/pkg/config"
)
/*
IPAddress returns the best guess at the user's IP address, as a string for logging.
*/
func IPAddress(r *http.Request) string {
if config.Current.UseXForwardedFor {
if realIP := r.Header.Get("X-Real-IP"); realIP != "" {
return realIP
}
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
return strings.SplitN(xff, " ", 1)[0]
}
}
return r.RemoteAddr
}

View File

@ -11,6 +11,7 @@ import (
"git.kirsle.net/apps/barertc/pkg/jwt" "git.kirsle.net/apps/barertc/pkg/jwt"
"git.kirsle.net/apps/barertc/pkg/log" "git.kirsle.net/apps/barertc/pkg/log"
"git.kirsle.net/apps/barertc/pkg/util"
"nhooyr.io/websocket" "nhooyr.io/websocket"
) )
@ -35,14 +36,18 @@ func (sub *Subscriber) ReadLoop(s *Server) {
for { for {
msgType, data, err := sub.conn.Read(sub.ctx) msgType, data, err := sub.conn.Read(sub.ctx)
if err != nil { if err != nil {
log.Error("ReadLoop error(%s): %+v", sub.Username, err) log.Error("ReadLoop error(%d=%s): %+v", sub.ID, sub.Username, err)
s.DeleteSubscriber(sub) s.DeleteSubscriber(sub)
s.Broadcast(Message{
Action: ActionPresence, // Notify if this user was auth'd
Username: sub.Username, if sub.authenticated {
Message: "has exited the room!", s.Broadcast(Message{
}) Action: ActionPresence,
s.SendWhoList() Username: sub.Username,
Message: "has exited the room!",
})
s.SendWhoList()
}
return return
} }
@ -53,9 +58,9 @@ func (sub *Subscriber) ReadLoop(s *Server) {
// Read the user's posted message. // Read the user's posted message.
var msg Message var msg Message
log.Debug("Read(%s): %s", sub.Username, data) log.Debug("Read(%d=%s): %s", sub.ID, sub.Username, data)
if err := json.Unmarshal(data, &msg); err != nil { if err := json.Unmarshal(data, &msg); err != nil {
log.Error("Message error: %s", err) log.Error("Read(%d=%s) Message error: %s", sub.ID, sub.Username, err)
continue continue
} }
@ -90,7 +95,7 @@ func (sub *Subscriber) SendJSON(v interface{}) error {
if err != nil { if err != nil {
return err return err
} }
log.Debug("SendJSON(%s): %s", sub.Username, data) log.Debug("SendJSON(%d=%s): %s", sub.ID, sub.Username, data)
return sub.conn.Write(sub.ctx, websocket.MessageText, data) return sub.conn.Write(sub.ctx, websocket.MessageText, data)
} }
@ -115,15 +120,18 @@ func (sub *Subscriber) ChatServer(message string, v ...interface{}) {
// WebSocket handles the /ws websocket connection. // WebSocket handles the /ws websocket connection.
func (s *Server) WebSocket() http.HandlerFunc { func (s *Server) WebSocket() http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := util.IPAddress(r)
log.Info("WebSocket connection from %s - %s", ip, r.Header.Get("User-Agent"))
log.Debug("Headers: %+v", r.Header)
c, err := websocket.Accept(w, r, nil) c, err := websocket.Accept(w, r, nil)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Websocket error: %s", err) fmt.Fprintf(w, "Could not accept websocket connection: %s", err)
return return
} }
defer c.Close(websocket.StatusInternalError, "the sky is falling") defer c.Close(websocket.StatusInternalError, "the sky is falling")
log.Debug("WebSocket: %s has connected", r.RemoteAddr) log.Debug("WebSocket: %s has connected", ip)
// CloseRead starts a goroutine that will read from the connection // CloseRead starts a goroutine that will read from the connection
// until it is closed. // until it is closed.
@ -141,7 +149,7 @@ func (s *Server) WebSocket() http.HandlerFunc {
} }
s.AddSubscriber(sub) s.AddSubscriber(sub)
// defer s.DeleteSubscriber(sub) defer s.DeleteSubscriber(sub)
go sub.ReadLoop(s) go sub.ReadLoop(s)
pinger := time.NewTicker(PingInterval) pinger := time.NewTicker(PingInterval)
@ -174,7 +182,7 @@ func (s *Server) AddSubscriber(sub *Subscriber) {
// Assign a unique ID. // Assign a unique ID.
SubscriberID++ SubscriberID++
sub.ID = SubscriberID sub.ID = SubscriberID
log.Debug("AddSubscriber: %s", sub.ID) log.Debug("AddSubscriber: ID #%d", sub.ID)
s.subscribersMu.Lock() s.subscribersMu.Lock()
s.subscribers[sub] = struct{}{} s.subscribers[sub] = struct{}{}
@ -210,13 +218,10 @@ func (s *Server) DeleteSubscriber(sub *Subscriber) {
// IterSubscribers loops over the subscriber list with a read lock. If the // IterSubscribers loops over the subscriber list with a read lock. If the
// caller already holds a lock, pass the optional `true` parameter for isLocked. // caller already holds a lock, pass the optional `true` parameter for isLocked.
func (s *Server) IterSubscribers(isLocked ...bool) []*Subscriber { func (s *Server) IterSubscribers(isLocked ...bool) []*Subscriber {
log.Debug("IterSubscribers START..")
var result = []*Subscriber{} var result = []*Subscriber{}
// Has the caller already taken the read lock or do we get it? // Has the caller already taken the read lock or do we get it?
if locked := len(isLocked) > 0 && isLocked[0]; !locked { if locked := len(isLocked) > 0 && isLocked[0]; !locked {
log.Debug("Taking the lock")
s.subscribersMu.RLock() s.subscribersMu.RLock()
defer s.subscribersMu.RUnlock() defer s.subscribersMu.RUnlock()
} }
@ -225,7 +230,6 @@ func (s *Server) IterSubscribers(isLocked ...bool) []*Subscriber {
result = append(result, sub) result = append(result, sub)
} }
log.Debug("IterSubscribers STOP..")
return result return result
} }

View File

@ -347,7 +347,8 @@ const app = Vue.createApp({
// Dial the WebSocket connection. // Dial the WebSocket connection.
dial() { dial() {
// console.log("Dialing WebSocket..."); this.ChatClient("Establishing connection to server...");
const proto = location.protocol === 'https:' ? 'wss' : 'ws'; const proto = location.protocol === 'https:' ? 'wss' : 'ws';
const conn = new WebSocket(`${proto}://${location.host}/ws`); const conn = new WebSocket(`${proto}://${location.host}/ws`);