diff --git a/api/src/modules/events/import-data/import-progress.emitter.ts b/api/src/modules/events/import-data/import-progress.emitter.ts index e359777e0..17daa625f 100644 --- a/api/src/modules/events/import-data/import-progress.emitter.ts +++ b/api/src/modules/events/import-data/import-progress.emitter.ts @@ -65,14 +65,4 @@ export class ImportProgressEmitter { ), ); } - - //TODO: Check with Andres how to handle finished and specially failed events - - // emitImportFinished(): void { - // this.eventBus.publish(new ImportProgressUpdateEvent(100)); - // } - // - // emitImportFailed(): void { - // this.eventBus.publish(new ImportProgressUpdateEvent(100)); - // } } diff --git a/api/src/modules/notifications/websockets/websockets.module.ts b/api/src/modules/notifications/websockets/websockets.module.ts index 741ccb982..aba4054cc 100644 --- a/api/src/modules/notifications/websockets/websockets.module.ts +++ b/api/src/modules/notifications/websockets/websockets.module.ts @@ -1,11 +1,15 @@ -import { Module } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { NestWebsocketsService } from 'modules/notifications/websockets/websockets.service'; +import { AuthenticationModule } from 'modules/authentication/authentication.module'; +import { WsAuthGuard } from './ws-auth.guard'; export const IWebSocketServiceToken: string = 'IWebSocketService'; @Module({ + imports: [forwardRef(() => AuthenticationModule)], providers: [ { provide: IWebSocketServiceToken, useClass: NestWebsocketsService }, + WsAuthGuard, ], exports: [IWebSocketServiceToken], }) diff --git a/api/src/modules/notifications/websockets/websockets.service.ts b/api/src/modules/notifications/websockets/websockets.service.ts index f9f598fe9..b5b445e95 100644 --- a/api/src/modules/notifications/websockets/websockets.service.ts +++ b/api/src/modules/notifications/websockets/websockets.service.ts @@ -2,35 +2,52 @@ import { Logger } from '@nestjs/common'; import { WebSocketGateway, WebSocketServer, - OnGatewayConnection, + WsException, + OnGatewayInit, } from '@nestjs/websockets'; -import { Server } from 'socket.io'; +import { Server, Socket } from 'socket.io'; import { IWebSocketService } from 'modules/notifications/websockets/websockets.service.interface'; import { EVENT_KINDS, SocketPayload, } from 'modules/notifications/websockets/types'; +import { WsAuthGuard } from 'modules/notifications/websockets/ws-auth.guard'; +import { User } from 'modules/users/user.entity'; -//TODO: Implement authentication on client connection - +// @WebSocketGateway({ cors: true }) -export class NestWebsocketsService - implements IWebSocketService, OnGatewayConnection -{ +export class NestWebsocketsService implements IWebSocketService, OnGatewayInit { logger: Logger = new Logger(NestWebsocketsService.name); + constructor(private readonly wsAuthGuard: WsAuthGuard) {} + @WebSocketServer() server: Server; onModuleInit(): void { - this.server.on('connection', (socket) => { - this.logger.log(`Client connected: ${socket.id}`); - }); + this.logger.log('Websockets service initialized'); } - handleConnection(client: any, ...args: any[]): any { - this.logger.log(`Client connected: ${client.id}`); - this.logger.warn('token in header', client.handshake.headers); + /** + * @note: Check WsAuthGuard notes. Since NestJS has limitations with websockets, we are following this approach: + * https://www.youtube.com/watch?v=4h9-c6D5Pos&ab_channel=jemini-io + * + * Exception messages are not propagated to the client and creating a exception filter does not seems to work. + */ + + afterInit(client: Socket): void { + client.use(async (req, next) => { + try { + const user: User = await this.wsAuthGuard.verifyUser( + req as unknown as Socket, + ); + this.logger.log(`User ${user.email} connected to Socket`); + next(); + } catch (e: any) { + this.logger.error(e); + next(new WsException('Unauthorized')); + } + }); } emit(event: EVENT_KINDS, payload: any): void { diff --git a/api/src/modules/notifications/websockets/ws-auth.guard.ts b/api/src/modules/notifications/websockets/ws-auth.guard.ts new file mode 100644 index 000000000..98ead5808 --- /dev/null +++ b/api/src/modules/notifications/websockets/ws-auth.guard.ts @@ -0,0 +1,40 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { AuthenticationService } from 'modules/authentication/authentication.service'; +import { Socket } from 'socket.io'; +import { WsException } from '@nestjs/websockets'; +import { DataSource } from 'typeorm'; +import { User } from 'modules/users/user.entity'; + +/** + * @description As of today, NestJS doesnt treat websockets as first class citizens, meaning that the useful + * guard decorators that we have for HTTPS request wont work for webscokets gatewau, so we need to implement something hand crafter + * but trying to follow the same pattern as the HTTP guards. + * + * @note: Chek https://github.com/nestjs/nest/issues/882 + */ + +@Injectable() +export class WsAuthGuard { + logger: Logger = new Logger(WsAuthGuard.name); + + constructor( + private readonly auth: AuthenticationService, + private readonly datasource: DataSource, + ) {} + + async verifyUser(client: Socket): Promise { + const { token } = client.handshake?.auth; + if (!token) { + throw new WsException('Unauthorized'); + } + try { + const { sub } = this.auth.verifyToken(token as string); + const user: User = await this.datasource + .getRepository(User) + .findOneOrFail({ where: { email: sub } }); + return user; + } catch (e) { + throw new WsException('Unauthorized'); + } + } +}