NestJS WebSocket Gateway - how get full path for initial connection?

Viewed 614

I'm adding a socket.io "chat" implementation to our NestJS app, currently serving a range of HTTP REST APIs. We have fairly complex tenant-based auth using guards for our REST APIs. Users can belong to one or many tenants, and they target a given tenats via the API URL, which can be either subdomain or path-based, depending on the deployment environment, for example:

//Subdomain based
https://tenant1.api.server.com/endpoint
https://tenant2.api.server.com/endpoint
//Path based
https://api.server.com/tenant1/endpoint
https://api.server.com/tenant2/endpoint

This all works fine for REST APIs, allowing us to determine the intended tenant (and validate the user access to that tenant) within guards.

The new socket.io implementation is being exposed on the same port at endpoint "/socket", meaning that possible full paths for connection could be:

https://tenant1.api.server.com/socket
https://api.server.com/tenant1/socket

Ideally I want to validate the user (via JWT) and the access to the group during the connection of the websocket (and if they are not validated they get immediately disconnected). I have been struggling to implement with guards, so I have done JWT/user validate in the socket gateway, which works ok. For the tenant validation, as per the above, I need the FULL URL that was used for the connection, because I will either be looking at the subdomain OR the path, depending on the deployment. I can get the host from the client handshake headers, but cannot find any way to get at the path. Is there a way to get the full path either from the handshake, or perhaps from Nest? I think perhaps I am limited in what I have access to in the handleConnection method when implementing OnGatewayConnection.

Code so far:

@WebSocketGateway({
  namespace: 'socket',
  cors: {
    origin: '*',
  },
})
export class ChannelsGateway
  implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {

  @WebSocketServer() public server: Server

  //Init using separate socket service (allowing for other components to push messages)
  afterInit(server: Server) {
    this.socketService.socket = server
    Logger.debug('Socket.io initialized')
  }

  //Method for handling the client initial connection
  async handleConnection(client: Socket, ...args: any[]) {
    //This line gets me the host, such as tenant1.api.server.com but without the path
    const host = client.handshake.headers.host

    //Get bearer token from authorizaton header and validate
    //Disconnect and return if not validated
    const bearerJwt = client.handshake.headers.authorization
    const decodedToken = await validateJwt(bearerJwt).catch(error => {
      client.disconnect()
    })
    if (!decodedToken) {
      return
    }

    //What can I use to get at the path, such as:
    //api.server.com/tenant1/socket
    //tenant1.api.server.com/socket
    
    //Then I can extract the "tenant1" with existing code and validate access
    //Else disconnect the client


    //Further code to establish a private room for the user, etc...
  }

  //other methods for receiving messages, etc...
}
0 Answers
Related