126 lines
4.0 KiB
TypeScript
126 lines
4.0 KiB
TypeScript
import { CanActivate, ExecutionContext, Injectable, Logger, Inject } from '@nestjs/common';
|
|
import { WsException } from '@nestjs/websockets';
|
|
import { Socket } from 'socket.io';
|
|
import { JwtService } from '@nestjs/jwt';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { IAdminTokenPayload } from '../../auth/interfaces/IToken-payload';
|
|
|
|
export interface AuthenticatedSocket extends Socket {
|
|
adminId?: string;
|
|
restId?: string;
|
|
}
|
|
|
|
@Injectable()
|
|
export class WsAdminAuthGuard implements CanActivate {
|
|
private readonly logger = new Logger(WsAdminAuthGuard.name);
|
|
|
|
constructor(
|
|
@Inject(JwtService)
|
|
private readonly jwtService: JwtService,
|
|
@Inject(ConfigService)
|
|
private readonly configService: ConfigService,
|
|
) {}
|
|
|
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
const client: Socket = context.switchToWs().getClient<Socket>();
|
|
|
|
try {
|
|
const token = this.extractToken(client);
|
|
|
|
if (!token) {
|
|
this.logger.warn('No token provided in WebSocket connection', {
|
|
socketId: client.id,
|
|
auth: client.handshake.auth,
|
|
query: client.handshake.query,
|
|
});
|
|
throw new WsException('Authentication required');
|
|
}
|
|
|
|
const secret = this.configService.getOrThrow<string>('JWT_SECRET');
|
|
const payload = await this.jwtService.verifyAsync<IAdminTokenPayload>(token, {
|
|
secret,
|
|
});
|
|
|
|
if (!payload.adminId || !payload.restId) {
|
|
this.logger.error('Invalid token payload structure', payload);
|
|
throw new WsException('Invalid token payload');
|
|
}
|
|
|
|
// Attach admin info to socket
|
|
(client as AuthenticatedSocket).adminId = payload.adminId;
|
|
(client as AuthenticatedSocket).restId = payload.restId;
|
|
|
|
this.logger.log(`Admin authenticated via WebSocket: ${payload.adminId}, restId: ${payload.restId}`);
|
|
|
|
return true;
|
|
} catch (error) {
|
|
if (error instanceof WsException) {
|
|
throw error;
|
|
}
|
|
this.logger.error('WebSocket authentication error', {
|
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
socketId: client.id,
|
|
});
|
|
throw new WsException('Authentication failed');
|
|
}
|
|
}
|
|
|
|
private extractToken(client: Socket): string | undefined {
|
|
// Try to get token from handshake auth (recommended for socket.io)
|
|
const auth = client.handshake.auth;
|
|
const authToken = (auth?.token as string | undefined) || (auth?.authorization as string | undefined);
|
|
|
|
if (authToken) {
|
|
// If it's "Bearer <token>", extract just the token
|
|
if (typeof authToken === 'string' && authToken.startsWith('Bearer ')) {
|
|
return authToken.substring(7);
|
|
}
|
|
return authToken;
|
|
}
|
|
|
|
// Try to get from query parameters (fallback)
|
|
const queryToken = client.handshake.query?.token || client.handshake.query?.authorization;
|
|
|
|
if (queryToken) {
|
|
if (typeof queryToken === 'string' && queryToken.startsWith('Bearer ')) {
|
|
return queryToken.substring(7);
|
|
}
|
|
return queryToken as string;
|
|
}
|
|
|
|
// Try to get from headers (if available)
|
|
const headers = client.handshake.headers;
|
|
const authHeader = headers.authorization || headers['authorization'];
|
|
|
|
if (authHeader && typeof authHeader === 'string') {
|
|
const [type, token] = authHeader.split(' ');
|
|
if (type?.toLowerCase() === 'bearer' && token) {
|
|
return token;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
// private extractToken(client: Socket): string | undefined {
|
|
// const tryGet = (...values: any[]) => values.find(v => typeof v === 'string' && v.length > 0);
|
|
|
|
// let token = tryGet(
|
|
// client.handshake.auth?.token,
|
|
// client.handshake.auth?.authorization,
|
|
// client.handshake.query?.token,
|
|
// client.handshake.query?.authorization,
|
|
// client.handshake.headers.authorization,
|
|
// client.handshake.headers['authorization'],
|
|
// );
|
|
|
|
// if (!token) return undefined;
|
|
|
|
// if (token.startsWith('Bearer ')) {
|
|
// token = token.slice(7);
|
|
// }
|
|
|
|
// return token;
|
|
// }
|
|
}
|