Commit 360f855c authored by Andrei Mihu's avatar Andrei Mihu
Browse files

Add config options to enforce a single socket per user, and a single match per socket.

parent 015f920d
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -4,7 +4,8 @@ All notable changes to this project are documented below.
The format is based on [keep a changelog](http://keepachangelog.com) and this project uses [semantic versioning](http://semver.org).

## [Unreleased]

### Added
- New config options to enforce a single socket per user, and a single match per socket.

## [3.6.0] - 2021-09-09
### Added
+5 −0
Original line number Diff line number Diff line
@@ -137,6 +137,9 @@ func CheckConfig(logger *zap.Logger, config Config) map[string]string {
	if config.GetSession().EncryptionKey == config.GetSession().RefreshEncryptionKey {
		logger.Fatal("Encryption key and refresh token encryption cannot match", zap.Strings("param", []string{"session.encryption_key", "session.refresh_encryption_key"}))
	}
	if config.GetSession().SingleMatch && !config.GetSession().SingleSocket {
		logger.Fatal("Single match cannot be enabled without single socket", zap.Strings("param", []string{"session.single_match", "session.single_socket"}))
	}
	if config.GetRuntime().HTTPKey == "" {
		logger.Fatal("Runtime HTTP key must be set", zap.String("param", "runtime.http_key"))
	}
@@ -618,6 +621,8 @@ type SessionConfig struct {
	TokenExpirySec        int64  `yaml:"token_expiry_sec" json:"token_expiry_sec" usage:"Token expiry in seconds."`
	RefreshEncryptionKey  string `yaml:"refresh_encryption_key" json:"refresh_encryption_key" usage:"The encryption key used to produce the client refresh token."`
	RefreshTokenExpirySec int64  `yaml:"refresh_token_expiry_sec" json:"refresh_token_expiry_sec" usage:"Refresh token expiry in seconds."`
	SingleSocket          bool   `yaml:"single_socket" json:"single_socket" usage:"Only allow one socket per user. Older sessions are disconnected. Default false."`
	SingleMatch           bool   `yaml:"single_match" json:"single_match" usage:"Only allow one match per user. Older matches receive a leave. Requires single socket to enable. Default false."`
}

// NewSessionConfig creates a new SessionConfig struct.
+7 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import (
	"google.golang.org/protobuf/types/known/wrapperspb"
)

var matchStreamModes = map[uint8]struct{}{StreamModeMatchAuthoritative: {}, StreamModeMatchRelayed: {}}

type matchDataFilter struct {
	userID    uuid.UUID
	sessionID uuid.UUID
@@ -171,6 +173,11 @@ func (p *Pipeline) matchJoin(logger *zap.Logger, session Session, envelope *rtap
				// Presence creation was rejected due to `allowIfFirstForSession` flag, session is gone so no need to reply.
				return
			}

			if p.config.GetSession().SingleMatch {
				// Kick the user from any other matches they may be part of.
				p.tracker.UntrackLocalByModes(session.ID(), matchStreamModes, stream)
			}
		}

		// Whether the user has just (successfully) joined the match or was already a member, return the match info anyway.
+16 −0
Original line number Diff line number Diff line
@@ -63,6 +63,7 @@ type SessionRegistry interface {
	Add(session Session)
	Remove(sessionID uuid.UUID)
	Disconnect(ctx context.Context, sessionID uuid.UUID, reason ...runtime.PresenceReason) error
	SingleSession(ctx context.Context, tracker Tracker, userID, sessionID uuid.UUID)
}

type LocalSessionRegistry struct {
@@ -119,3 +120,18 @@ func (r *LocalSessionRegistry) Disconnect(ctx context.Context, sessionID uuid.UU
	}
	return nil
}

func (r *LocalSessionRegistry) SingleSession(ctx context.Context, tracker Tracker, userID, sessionID uuid.UUID) {
	sessionIDs := tracker.ListLocalSessionIDByStream(PresenceStream{Mode: StreamModeNotifications, Subject: userID})
	for _, foundSessionID := range sessionIDs {
		if foundSessionID == sessionID {
			// Allow the current session, only disconnect any older ones.
			continue
		}
		session, ok := r.sessions.Load(foundSessionID)
		if ok {
			// No need to remove the session from the map, session.Close() will do that.
			session.(Session).Close("server-side session disconnect", runtime.PresenceReasonDisconnect)
		}
	}
}
+5 −0
Original line number Diff line number Diff line
@@ -112,6 +112,11 @@ func NewSocketWsAcceptor(logger *zap.Logger, config Config, sessionRegistry Sess
			tracker.Track(session.Context(), sessionID, PresenceStream{Mode: StreamModeNotifications, Subject: userID}, userID, PresenceMeta{Format: format, Username: username, Hidden: true}, true)
		}

		if config.GetSession().SingleSocket {
			// Kick any other sockets for this user.
			go sessionRegistry.SingleSession(session.Context(), tracker, userID, sessionID)
		}

		// Allow the server to begin processing incoming messages from this session.
		session.Consume()

Loading