relay: Add multi-user login support (#258)

* relay: add multi-user login support

* relay: Fix close handling, connect username
This commit is contained in:
extremeheat 2022-08-24 00:41:12 -04:00 committed by GitHub
commit c395f0b05b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 38 additions and 5 deletions

View file

@ -170,7 +170,7 @@ For documentation on the protocol, and packets/fields see the [the protocol doc]
### Proxy docs ### Proxy docs
You can create a proxy ("Relay") to create a machine-in-the-middle (MITM) connection to a server. You can observe and intercept packets as they go through. The Relay is a server+client combo with some special packet handling and forwarding that takes care of the authentication and encryption on the server side. You'll be asked to login if `offline` is not specified once you connect. You can create a proxy ("Relay") to create a machine-in-the-middle (MITM) connection to a server. You can observe and intercept packets as they go through. The Relay is a server+client combo with some special packet handling and forwarding that takes care of the authentication and encryption on the server side. Clients will be asked to login if `offline` is not specified on connection.
```js ```js
const { Relay } = require('bedrock-protocol') const { Relay } = require('bedrock-protocol')

12
index.d.ts vendored
View file

@ -131,6 +131,10 @@ declare module "bedrock-protocol" {
* Close the connection. Already called by disconnect. Call this to manually close RakNet connection. * Close the connection. Already called by disconnect. Call this to manually close RakNet connection.
*/ */
close() close()
on(event: 'login', cb: () => void)
on(event: 'join', cb: () => void)
on(event: 'close', cb: (reason: string) => void)
} }
export class Server extends EventEmitter { export class Server extends EventEmitter {
@ -161,6 +165,14 @@ declare module "bedrock-protocol" {
} }
// Whether to enable chunk caching (default: false) // Whether to enable chunk caching (default: false)
enableChunkCaching?: boolean enableChunkCaching?: boolean
// Only allow one client to connect at a time (default: false)
forceSinge: boolean
// Dispatched when a new client has logged in, and we need authentication
// tokens to join the backend server. Cached after the first login.
// If this is not specified, the client will be disconnected with a login prompt.
onMsaCode(data, client)
} }
export class Relay extends Server { export class Relay extends Server {

View file

@ -159,7 +159,7 @@ class Relay extends Server {
constructor (options) { constructor (options) {
super(options) super(options)
this.RelayPlayer = options.relayPlayer || RelayPlayer this.RelayPlayer = options.relayPlayer || RelayPlayer
this.forceSingle = true this.forceSingle = options.forceSingle
this.upstreams = new Map() this.upstreams = new Map()
this.conLog = debug this.conLog = debug
this.enableChunkCaching = options.enableChunkCaching this.enableChunkCaching = options.enableChunkCaching
@ -175,12 +175,18 @@ class Relay extends Server {
const options = { const options = {
authTitle: this.options.authTitle, authTitle: this.options.authTitle,
offline: this.options.destination.offline ?? this.options.offline, offline: this.options.destination.offline ?? this.options.offline,
username: this.options.offline ? ds.profile.name : null, username: this.options.offline ? ds.profile.name : ds.profile.xuid,
version: this.options.version, version: this.options.version,
realms: this.options.destination.realms, realms: this.options.destination.realms,
host: this.options.destination.host, host: this.options.destination.host,
port: this.options.destination.port, port: this.options.destination.port,
onMsaCode: this.options.onMsaCode, onMsaCode: (code) => {
if (this.options.onMsaCode) {
this.options.onMsaCode(code, ds)
} else {
ds.disconnect("It's your first time joining. Please sign in and reconnect to join this server:\n\n" + code.message)
}
},
profilesFolder: this.options.profilesFolder, profilesFolder: this.options.profilesFolder,
backend: this.options.backend, backend: this.options.backend,
autoInitPlayer: false autoInitPlayer: false
@ -213,6 +219,16 @@ class Relay extends Server {
this.emit('join', /* client connected to proxy */ ds, /* backend server */ client) this.emit('join', /* client connected to proxy */ ds, /* backend server */ client)
}) })
client.on('error', (err) => {
ds.disconnect('Server error: ' + err.message)
debug(clientAddr, 'was disconnected because of error', err)
this.upstreams.delete(clientAddr.hash)
})
client.on('close', (reason) => {
ds.disconnect('Backend server closed connection')
this.upstreams.delete(clientAddr.hash)
})
this.upstreams.set(clientAddr.hash, client) this.upstreams.set(clientAddr.hash, client)
} }
@ -225,7 +241,7 @@ class Relay extends Server {
this.conLog('closed upstream connection', clientAddr) this.conLog('closed upstream connection', clientAddr)
} }
// Called when a new player connects to our proxy server. Once the player has authenticted, // Called when a new player connects to our proxy server. Once the player has authenticated,
// we can open an upstream connection to the backend server. // we can open an upstream connection to the backend server.
onOpenConnection = (conn) => { onOpenConnection = (conn) => {
if (this.forceSingle && this.clientCount > 0) { if (this.forceSingle && this.clientCount > 0) {
@ -240,6 +256,11 @@ class Relay extends Server {
player.on('login', () => { player.on('login', () => {
this.openUpstreamConnection(player, conn.address) this.openUpstreamConnection(player, conn.address)
}) })
player.on('close', (reason) => {
this.conLog('player disconnected', conn.address, reason)
this.clientCount--
delete this.clients[conn.address]
})
} }
} }