diff --git a/README.md b/README.md index 57edc91..945a1ac 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ On first run it will create the default settings.toml file for you which you may Title = "BareRTC" Branding = "BareRTC" WebsiteURL = "https://www.example.com" +UseXForwardedFor = true [JWT] 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: * 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! + * **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). * 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. diff --git a/pkg/config/config.go b/pkg/config/config.go index aad6cd9..88c700e 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -10,8 +10,14 @@ import ( "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. type Config struct { + Version int // will re-save your settings.toml on migrations + JWT struct { Enabled bool Strict bool @@ -22,6 +28,8 @@ type Config struct { Branding string WebsiteURL string + UseXForwardedFor bool + PublicChannels []Channel } @@ -88,6 +96,16 @@ func LoadSettings() error { } _, 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 } diff --git a/pkg/handlers.go b/pkg/handlers.go index 025ee34..eb49afc 100644 --- a/pkg/handlers.go +++ b/pkg/handlers.go @@ -42,7 +42,14 @@ func (s *Server) OnLogin(sub *Subscriber, msg Message) { 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. var duplicate bool diff --git a/pkg/pages.go b/pkg/pages.go index 50bbb5b..e4247b3 100644 --- a/pkg/pages.go +++ b/pkg/pages.go @@ -4,6 +4,7 @@ import ( "fmt" "html/template" "net/http" + "strings" "git.kirsle.net/apps/barertc/pkg/config" "git.kirsle.net/apps/barertc/pkg/jwt" @@ -71,7 +72,12 @@ func IndexPage() http.HandlerFunc { } // 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) }) } diff --git a/pkg/util/ip_address.go b/pkg/util/ip_address.go new file mode 100644 index 0000000..64f904e --- /dev/null +++ b/pkg/util/ip_address.go @@ -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 +} diff --git a/pkg/websocket.go b/pkg/websocket.go index 93870fa..945167e 100644 --- a/pkg/websocket.go +++ b/pkg/websocket.go @@ -11,6 +11,7 @@ import ( "git.kirsle.net/apps/barertc/pkg/jwt" "git.kirsle.net/apps/barertc/pkg/log" + "git.kirsle.net/apps/barertc/pkg/util" "nhooyr.io/websocket" ) @@ -35,14 +36,18 @@ func (sub *Subscriber) ReadLoop(s *Server) { for { msgType, data, err := sub.conn.Read(sub.ctx) 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.Broadcast(Message{ - Action: ActionPresence, - Username: sub.Username, - Message: "has exited the room!", - }) - s.SendWhoList() + + // Notify if this user was auth'd + if sub.authenticated { + s.Broadcast(Message{ + Action: ActionPresence, + Username: sub.Username, + Message: "has exited the room!", + }) + s.SendWhoList() + } return } @@ -53,9 +58,9 @@ func (sub *Subscriber) ReadLoop(s *Server) { // Read the user's posted 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 { - log.Error("Message error: %s", err) + log.Error("Read(%d=%s) Message error: %s", sub.ID, sub.Username, err) continue } @@ -90,7 +95,7 @@ func (sub *Subscriber) SendJSON(v interface{}) error { if err != nil { 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) } @@ -115,15 +120,18 @@ func (sub *Subscriber) ChatServer(message string, v ...interface{}) { // WebSocket handles the /ws websocket connection. func (s *Server) WebSocket() http.HandlerFunc { 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) if err != nil { w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "Websocket error: %s", err) + fmt.Fprintf(w, "Could not accept websocket connection: %s", err) return } 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 // until it is closed. @@ -141,7 +149,7 @@ func (s *Server) WebSocket() http.HandlerFunc { } s.AddSubscriber(sub) - // defer s.DeleteSubscriber(sub) + defer s.DeleteSubscriber(sub) go sub.ReadLoop(s) pinger := time.NewTicker(PingInterval) @@ -174,7 +182,7 @@ func (s *Server) AddSubscriber(sub *Subscriber) { // Assign a unique ID. SubscriberID++ sub.ID = SubscriberID - log.Debug("AddSubscriber: %s", sub.ID) + log.Debug("AddSubscriber: ID #%d", sub.ID) s.subscribersMu.Lock() 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 // caller already holds a lock, pass the optional `true` parameter for isLocked. func (s *Server) IterSubscribers(isLocked ...bool) []*Subscriber { - log.Debug("IterSubscribers START..") - var result = []*Subscriber{} // Has the caller already taken the read lock or do we get it? if locked := len(isLocked) > 0 && isLocked[0]; !locked { - log.Debug("Taking the lock") s.subscribersMu.RLock() defer s.subscribersMu.RUnlock() } @@ -225,7 +230,6 @@ func (s *Server) IterSubscribers(isLocked ...bool) []*Subscriber { result = append(result, sub) } - log.Debug("IterSubscribers STOP..") return result } diff --git a/web/static/js/BareRTC.js b/web/static/js/BareRTC.js index 7849a6b..1521b96 100644 --- a/web/static/js/BareRTC.js +++ b/web/static/js/BareRTC.js @@ -347,7 +347,8 @@ const app = Vue.createApp({ // Dial the WebSocket connection. dial() { - // console.log("Dialing WebSocket..."); + this.ChatClient("Establishing connection to server..."); + const proto = location.protocol === 'https:' ? 'wss' : 'ws'; const conn = new WebSocket(`${proto}://${location.host}/ws`);