Skip to content

Commit

Permalink
feat: add jwt on ws connection
Browse files Browse the repository at this point in the history
  • Loading branch information
erickmarx committed Jul 1, 2024
1 parent 06bd432 commit 0718457
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 15 deletions.
12 changes: 6 additions & 6 deletions src/modules/auth/auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ export class AuthGuard implements CanActivate {

const isPublic = this.isPublic(context);

if (isPublic) return true;
if (!isPublic) {
const token = this.extractTokenFromHeader(request);

const token = this.extractTokenFromHeader(request);
const payload = await this.jwtStrategies.auth.verify(token);

const payload = await this.jwtStrategies.auth.verify(token);
await this.isEmailConfirmed(payload);

await this.isEmailConfirmed(payload);

request['user'] = payload;
request['user'] = payload;
}

return true;
}
Expand Down
4 changes: 3 additions & 1 deletion src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { JwtStrategies } from './jwt.strategies';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';
import { ProfileModule } from '../profile/profile.module';
import { WSAuthGuard } from './ws-auth.guard';

@Module({
imports: [UserModule, JwtModule, MailModule, ProfileModule],
Expand All @@ -16,7 +17,8 @@ import { ProfileModule } from '../profile/profile.module';
AuthService,
JwtStrategies,
{ provide: APP_GUARD, useClass: AuthGuard },
WSAuthGuard,
],
exports: [JwtStrategies],
exports: [JwtStrategies, WSAuthGuard],
})
export class AuthModule {}
15 changes: 15 additions & 0 deletions src/modules/auth/exceptions/ws-auth-business.exceptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { WsException } from '@nestjs/websockets';

export class WSAuthBusinessExceptions {
static userNotFoundException() {
return new WsException('Usuario não encontrado');
}

static emailNotVerifiedException() {
return new WsException('Usuário não foi confirmado.');
}

static invalidTokenException() {
return new WsException('Token inválido.');
}
}
59 changes: 59 additions & 0 deletions src/modules/auth/ws-auth.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { JwtStrategies } from '~/modules/auth/jwt.strategies';
import { JwtAuthPayload } from './interfaces/jwt-auth-payload.interface';
import { UserService } from '../user/user.service';
import { WSAuthBusinessExceptions } from './exceptions/ws-auth-business.exceptions';
import { IGetConfirmed } from '../user/interfaces/get-confirmed.interface';
import { Socket } from 'socket.io';

@Injectable()
export class WSAuthGuard implements CanActivate {
constructor(
private jwtStrategies: JwtStrategies,
private userService: UserService,
) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const socket: Socket = context.switchToWs().getClient();

const token = this.extractTokenFromHeader(socket);

let payload: JwtAuthPayload;

try {
payload = await this.jwtStrategies.auth.verify(token);
} catch (e) {
throw WSAuthBusinessExceptions.invalidTokenException();
}

await this.isEmailConfirmed(payload);

socket['user'] = payload;

return true;
}

private async isEmailConfirmed(payload: JwtAuthPayload): Promise<void> {
let user: IGetConfirmed;

try {
user = await this.userService.getConfirmedUser(payload.userId);
} catch (e) {
throw WSAuthBusinessExceptions.userNotFoundException();
}

if (!user.emailConfirmed) {
throw WSAuthBusinessExceptions.emailNotVerifiedException();
}
}

private extractTokenFromHeader(socket: Socket): string {
const [type, token] =
socket.handshake.headers.authorization?.split(' ') ?? [];

if (!token || type !== 'Bearer')
throw WSAuthBusinessExceptions.invalidTokenException();

return token;
}
}
11 changes: 8 additions & 3 deletions src/modules/chat/chat.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,29 @@ import { IGatewayConnection } from './interface/gateway-connection.interface';
import { ISocket } from './interface/socket.interface';
import { ChatService } from './chat.service';
import { IServer } from './interface/server.interface';
import { UseFilters, UseGuards } from '@nestjs/common';
import { WSAuthGuard } from '../auth/ws-auth.guard';
import { WSExceptionFilter } from '../../shared/interceptor/ws-exception-filter.interface';

@WebSocketGateway({ namespace: 'chat' })
@UseGuards(WSAuthGuard)
@UseFilters(WSExceptionFilter)
export class ChatGateway implements IGatewayConnection {
@WebSocketServer()
private server: IServer;

constructor(private chatService: ChatService) {}

handleConnection(client: ISocket): void {
async handleConnection(client: ISocket): Promise<void> {
if (!this.server?.profiles) {
this.server.profiles = new Map<string, string>();
}

this.chatService.handleConnection(client, this.server);
await this.chatService.handleConnection(client, this.server);
}

handleDisconnect(client: ISocket): void {
this.chatService.handleConnection(client, this.server);
this.chatService.handleDisconnect(client, this.server);
}

@SubscribeMessage('health')
Expand Down
7 changes: 6 additions & 1 deletion src/modules/chat/chat.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Module } from '@nestjs/common';
import { ChatGateway } from './chat.gateway';
import { ChatService } from './chat.service';
import { AuthModule } from '../auth/auth.module';
import { UserModule } from '../user/user.module';

@Module({ providers: [ChatGateway, ChatService] })
@Module({
imports: [UserModule, AuthModule],
providers: [ChatGateway, ChatService],
})
export class ChatModule {}
14 changes: 10 additions & 4 deletions src/modules/chat/chat.service.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { Injectable } from '@nestjs/common';
import { ISocket } from './interface/socket.interface';
import { IServer } from './interface/server.interface';
import { WsException } from '@nestjs/websockets';
import { JwtStrategies } from '../auth/jwt.strategies';

@Injectable()
export class ChatService {
handleConnection(client: ISocket, server: IServer) {
const auth = client.handshake.query.profile as string;
constructor(private jwtStrategies: JwtStrategies) {}

if (!auth) throw new Error('Unauthorized');
async handleConnection(client: ISocket, server: IServer) {
const auth = client.handshake.headers.authorization?.split(' ')[1];

client.profileId = auth;
if (!auth) throw new WsException('Unauthorized');

const { profileId } = await this.jwtStrategies.auth.verify(auth);

client.profileId = profileId;

server.profiles.set(client.profileId, client.id);
}
Expand Down
12 changes: 12 additions & 0 deletions src/shared/interceptor/ws-exception-filter.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseWsExceptionFilter, WsException } from '@nestjs/websockets';

@Catch(WsException)
export class WSExceptionFilter extends BaseWsExceptionFilter {
catch(exception: WsException, host: ArgumentsHost) {
const callback = host.getArgByIndex(2);
if (callback && typeof callback === 'function') {
callback({ exception: exception.message });
}
}
}

0 comments on commit 0718457

Please sign in to comment.