Skip to content

Commit

Permalink
implement basic ws client connection auth
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeh committed Apr 23, 2024
1 parent 8c7ab9b commit f394ac0
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 24 deletions.
10 changes: 0 additions & 10 deletions api/src/modules/events/import-data/import-progress.emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
// }
}
Original file line number Diff line number Diff line change
@@ -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],
})
Expand Down
43 changes: 30 additions & 13 deletions api/src/modules/notifications/websockets/websockets.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
40 changes: 40 additions & 0 deletions api/src/modules/notifications/websockets/ws-auth.guard.ts
Original file line number Diff line number Diff line change
@@ -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<User> {
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');
}
}
}

0 comments on commit f394ac0

Please sign in to comment.