Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The Guard is not work in Websocket Gateway #9231

Closed
4 of 15 tasks
jackykwandesign opened this issue Feb 20, 2022 · 2 comments
Closed
4 of 15 tasks

The Guard is not work in Websocket Gateway #9231

jackykwandesign opened this issue Feb 20, 2022 · 2 comments
Labels
needs triage This issue has not been looked into

Comments

@jackykwandesign
Copy link

jackykwandesign commented Feb 20, 2022

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

When i apply Guard on controller, it can intercept and get the context for further processing, but Gateway just skip the guard.

The Guard is not work in Gateway level or handleConnection, only work in SubscribeMessage Level.

But if i work will Controller, the Guard work perfectly

Minimum reproduction code

https://github.com/jackykwandesign/gateway-guard-issue/tree/main

Steps to reproduce

  1. install fe (react) and be (nest.js)
  2. Run fe to connect ws of backend, guard not trigger
  3. Click Test button in fe, emit "message" to be, you will see context is print by the Guard in SubscribeMessage Level
  4. Call localhost:3000/ in get method will also trigger Guard in Controller Level

Expected behavior

  1. The Guard in Gateway level should apply to all SubscribeMessage decorated Message

Bonus:
2. Provide a lifecycle function to work with Guard to accept or reject connection
Since websocket is for high speed communication with less overhead as we can, can we do Auth checking in a life cycle before Connect ? Then the socket will be authed for any further process we want?

I found in Socket.io there is middleware, can we do that and apply to specific namespace only?

Package

  • I don't know. Or some 3rd-party package
  • @nestjs/common
  • @nestjs/core
  • @nestjs/microservices
  • @nestjs/platform-express
  • @nestjs/platform-fastify
  • @nestjs/platform-socket.io
  • @nestjs/platform-ws
  • @nestjs/testing
  • @nestjs/websockets
  • Other (see below)

Other package

No response

NestJS version

8.1.6

Packages versions

{
  "name": "gateway-guard-issue",
  "version": "0.0.1",
  "description": "",
  "author": "",
  "private": true,
  "license": "UNLICENSED",
  "scripts": {
    "prebuild": "rimraf dist",
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  },
  "dependencies": {
    "@nestjs/common": "^8.0.0",
    "@nestjs/core": "^8.0.0",
    "@nestjs/platform-express": "^8.0.0",
    "@nestjs/platform-socket.io": "^8.3.1",
    "@nestjs/websockets": "^8.3.1",
    "reflect-metadata": "^0.1.13",
    "rimraf": "^3.0.2",
    "rxjs": "^7.2.0"
  },
  "devDependencies": {
    "@nestjs/cli": "^8.0.0",
    "@nestjs/schematics": "^8.0.0",
    "@nestjs/testing": "^8.0.0",
    "@types/express": "^4.17.13",
    "@types/jest": "27.0.2",
    "@types/node": "^16.0.0",
    "@types/supertest": "^2.0.11",
    "@typescript-eslint/eslint-plugin": "^5.0.0",
    "@typescript-eslint/parser": "^5.0.0",
    "eslint": "^8.0.1",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^4.0.0",
    "jest": "^27.2.5",
    "prettier": "^2.3.2",
    "source-map-support": "^0.5.20",
    "supertest": "^6.1.3",
    "ts-jest": "^27.0.3",
    "ts-loader": "^9.2.3",
    "ts-node": "^10.0.0",
    "tsconfig-paths": "^3.10.1",
    "typescript": "^4.3.5"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }
}

Node.js version

16.13.1

In which operating systems have you tested?

  • macOS
  • Windows
  • Linux

Other

No response

@jackykwandesign
Copy link
Author

jackykwandesign commented Feb 20, 2022

This is my workaround solution, perfectly before handleConnection and without any adapter
I create a namespace specific middleware function, use jwtService from jwtModule and my userService as parameter

auth.middleware.ts

import { JwtService } from '@nestjs/jwt';
import { Socket } from 'socket.io';
import { JwtPayload } from 'src/auth/jwt/jwt-payload.interface';
import { Player } from 'src/typs';
import { UserService } from 'src/user/user.service';

export interface AuthSocket extends Socket {
  user: Player;
}
export type SocketMiddleware = (socket: Socket, next: (err?: Error) => void) => void
export const WSAuthMiddleware = (jwtService:JwtService, userService:UserService):SocketMiddleware =>{
  return async (socket:AuthSocket, next) => {
    try {
      const jwtPayload = jwtService.verify(
        socket.handshake.auth.jwt ?? '',
      ) as JwtPayload;
      const userResult = await userService.getUser(jwtPayload.userID);
      if (userResult.isSuccess) {
        socket.user = userResult.data;
        next();
      } else {
        next({
          name: 'Unauthorizaed',
          message: 'Unauthorizaed',
        });
      }
    } catch (error) {
      next({
        name: 'Unauthorizaed',
        message: 'Unauthorizaed',
      });
    }
  }
}

ws.gateway.ts

import {
  UseFilters,
  UseGuards,
  UsePipes,
  ValidationPipe,
} from '@nestjs/common';
import {
  ConnectedSocket,
  SubscribeMessage,
  WebSocketGateway,
  WebSocketServer,
  MessageBody,
} from '@nestjs/websockets';
import { Server as SocketIOServer, Socket } from 'socket.io';
import { CreateGameDataDTO } from './dto/message-dto';
import { WsClientEvent } from '../typs';
import { WSCommExceptionsFilter } from './WSFilter.filter';
import { NestGateway } from '@nestjs/websockets/interfaces/nest-gateway.interface';
import { JwtService } from '@nestjs/jwt';
import { UserService } from 'src/user/user.service';
import { AuthSocket, WSAuthMiddleware } from './auth.middleware';

@UsePipes(new ValidationPipe())
@UseFilters(new WSCommExceptionsFilter())
@WebSocketGateway({
  transports: ['websocket'],
  cors: {
    origin: '*',
  },
  namespace: '/', //this is the namespace, which manager.socket(nsp) connect to
})
export class WsRoomGateway implements NestGateway {

  constructor(
    private readonly jwtService:JwtService,
    private readonly userService:UserService,
  ){}
  @WebSocketServer()
  server: SocketIOServer;

  afterInit(server: SocketIOServer) {
    const middle = WSAuthMiddleware(this.jwtService, this.userService)
    server.use(middle)
    console.log(`WS ${WsRoomGateway.name} init`);
  }
  handleDisconnect(client: Socket) {
    console.log('client disconnect', client.id);
  }

  handleConnection(client: AuthSocket, ...args: any[]) {
    console.log('client connect', client.id, client.user);
  }

  @SubscribeMessage(WsClientEvent.CREATE_ROOM)
  handleCreateRoom(
    @ConnectedSocket() client: AuthSocket,
    @MessageBody() body: CreateGameDataDTO,
  ) {
    client.send('test', body);
  }
}

In console, when user senc connect request with jwt Token in auth option of socket.io-client or anywhere you want accessable in Socket, it will be authed before handleConnect, and user obj will be availble in cutomized Socket AuthSocket
image
image

If user not pass the jwt Token, the next(err) will reject the connection, make whole websocket connection much clear, no need to do Guard auth in all @SubscribeMessage

image

@kamilmysliwiec
Copy link
Member

Please search through some of our old issues on this (this has been discussed several times in the past).

@nestjs nestjs locked and limited conversation to collaborators Feb 21, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
needs triage This issue has not been looked into
Projects
None yet
Development

No branches or pull requests

2 participants