Re-sign JWT tokens for safer server deployments

ipad-testing
Noah 2023-04-19 19:55:39 -07:00
parent d6860160f4
commit fb11295168
5 changed files with 50 additions and 3 deletions

View File

@ -25,7 +25,11 @@ It is very much in the style of the old-school Flash based webcam chat rooms of
* Specify multiple Public Channels that all users have access to.
* Users can open direct message (one-on-one) conversations with each other.
* No long-term server side state: messages are pushed out as they come in.
* Users may share pictures and GIFs from their computer, which are pushed out as `data:` URLs (images scaled and metadata stripped by server) directly to connected chatters with no storage required.
* Users may broadcast their webcam which shows a camera icon by their name in the Who List. Users may click on those icons to open multiple camera feeds of other users they are interested in.
* Mutual webcam options: users may opt that anyone who views their cam must also be sharing their own camera first.
* Users may mark their own cameras as explicit/NSFW which marks the icon in red so other users can get a warning before clicking in (if NSFW is enabled in the settings.toml)
* Users may boot people off their camera, and to the booted person it appears the same as if the broadcaster had turned their camera off completely - the chat server lies about the camera status so the booted user can't easily tell they'd been booted.
* Mobile friendly: works best on iPads and above but adapts to smaller screens well.
* WebRTC means peer-to-peer video streaming so cheap on hosting costs!
* Simple integration with your existing userbase via signed JWT tokens.
@ -44,12 +48,16 @@ Some important features still lacking:
On first run it will create the default settings.toml file for you which you may then customize to your liking:
```toml
Version = 2
Title = "BareRTC"
Branding = "BareRTC"
WebsiteURL = "https://www.example.com"
UseXForwardedFor = true
CORSHosts = ["https://www.example.com"]
PermitNSFW = true
UseXForwardedFor = true
WebSocketReadLimit = 41943040
MaxImageWidth = 1280
PreviewImageWidth = 360
[JWT]
Enabled = false
@ -79,6 +87,9 @@ A description of the config directives includes:
* **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.
* **CORSHosts**: your website's domain names that will be allowed to access [JSON APIs](#JSON APIs), like `/api/statistics`.
* **PermitNSFW**: for user webcam streams, expressly permit "NSFW" content if the user opts in to mark their feed as such. Setting this will enable pop-up modals regarding NSFW video and give broadcasters an opt-in button, which will warn other users before they click in to watch.
* **WebSocketReadLimit**: sets a size limit for WebSocket messages - it essentially also caps the max upload size for shared images (add a buffer as images will be base64 encoded on upload).
* **MaxImageWidth**: for pictures shared in chat the server will resize them down to no larger than this width for the full size view.
* **PreviewImageWidth**: to not flood the chat, the image in chat is this wide and users can click it to see the MaxImageWidth in a lightbox modal.
* **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.

View File

@ -2,4 +2,4 @@ package barertc
import "time"
const PingInterval = 15 * time.Second
const PingInterval = 30 * time.Second

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"html/template"
"time"
"git.kirsle.net/apps/barertc/pkg/config"
"github.com/golang-jwt/jwt/v4"
@ -53,3 +54,18 @@ func ParseAndValidate(tokenStr string) (*Claims, bool, error) {
return claims, authOK, nil
}
// ReSign will sign a new JWT token for existing claims. The chat server does this to send refreshed tokens
// to the front-end so the server can reboot gracefully, clients reconnect and not be told their auth had
// expired. New token expires after 5 minutes.
func (c Claims) ReSign() (string, error) {
// Refresh timestamps.
c.ExpiresAt = jwt.NewNumericDate(time.Now().Add(5 * time.Minute))
c.IssuedAt = jwt.NewNumericDate(time.Now())
c.NotBefore = jwt.NewNumericDate(time.Now())
// Generate the signed token and return it.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
ss, err := token.SignedString([]byte(config.Current.JWT.SecretKey))
return ss, err
}

View File

@ -188,8 +188,19 @@ func (s *Server) WebSocket() http.HandlerFunc {
return
}
case <-pinger.C:
// Send a ping, and a refreshed JWT token if the user sent one.
var token string
if sub.JWTClaims != nil {
if jwt, err := sub.JWTClaims.ReSign(); err != nil {
log.Error("ReSign JWT token for %s: %s", sub.Username, err)
} else {
token = jwt
}
}
sub.SendJSON(Message{
Action: ActionPing,
Action: ActionPing,
JWTToken: token,
})
case <-ctx.Done():
pinger.Stop()

View File

@ -608,6 +608,15 @@ const app = Vue.createApp({
this.disconnect = true;
break;
case "ping":
// New JWT token?
if (msg.jwt) {
this.jwt.token = msg.jwt;
}
// Reset disconnect retry counter: if we were on long enough to get
// a ping, we're well connected and can reconnect no matter how many
// times the chat server is rebooted.
this.disconnectCount = 0;
break;
default:
console.error("Unexpected action: %s", JSON.stringify(msg));