mirror of
https://github.com/crystelf/crystelf-core.git
synced 2025-07-04 14:49:19 +00:00
在WebSocket认证失败时,主动关闭连接以提高安全性。重构了Redis服务中的方法,使其更加通用,并更新了相关的导入和调用。同时,调整了数据持久化路径的生成方式,支持自定义文件名。
101 lines
3.4 KiB
TypeScript
101 lines
3.4 KiB
TypeScript
import WebSocket, { WebSocketServer } from 'ws';
|
|
import config from '../../utils/core/config';
|
|
import logger from '../../utils/core/logger';
|
|
import { AuthenticatedSocket, AuthMessage, WSMessage } from '../../types/ws';
|
|
import WsTools from '../../utils/ws/wsTools';
|
|
import wsHandler from './handler';
|
|
import { clearInterval } from 'node:timers';
|
|
import wsClientManager from './wsClientManager';
|
|
|
|
class WSServer {
|
|
private readonly wss: WebSocketServer;
|
|
private readonly port = Number(config.get('WS_PORT'));
|
|
private readonly secret = config.get('WS_SECRET');
|
|
|
|
constructor() {
|
|
this.wss = new WebSocketServer({ port: this.port });
|
|
this.init();
|
|
logger.info(`WS Server listening on ws://localhost:${this.port}`);
|
|
}
|
|
|
|
private init(): void {
|
|
this.wss.on('connection', (socket: AuthenticatedSocket, req) => {
|
|
const ip = req.socket.remoteAddress || 'unknown';
|
|
logger.info(`收到来自 ${ip} 的 WebSocket 连接请求..`);
|
|
|
|
socket.heartbeat = WsTools.setUpHeartbeat(socket);
|
|
|
|
socket.on('message', async (raw) => {
|
|
logger.debug(`Received raw message from ${ip}: ${raw.toString()}`);
|
|
|
|
const msg = WsTools.parseMessage<WSMessage>(raw);
|
|
if (!msg) return this.handleInvalidMessage(socket, ip);
|
|
|
|
await this.routeMessage(socket, msg, ip);
|
|
});
|
|
|
|
socket.on('close', () => {
|
|
logger.info(`ws断开连接 ${ip} (${socket.clientId || 'unauthenticated'})`);
|
|
this.handleDisconnect(socket);
|
|
});
|
|
|
|
socket.on('error', (err) => {
|
|
logger.error(`WS error from ${ip}: ${err.message}`);
|
|
});
|
|
});
|
|
}
|
|
|
|
private async handleInvalidMessage(socket: WebSocket, ip: string) {
|
|
logger.warn(`Invalid message received from ${ip}`);
|
|
await WsTools.send(socket, {
|
|
type: 'error',
|
|
message: 'Invalid message format',
|
|
});
|
|
}
|
|
|
|
private async routeMessage(socket: AuthenticatedSocket, msg: WSMessage, ip: string) {
|
|
if (!socket.isAuthed) {
|
|
if (this.isAuthMessage(msg)) {
|
|
logger.info(`Attempting auth from ${ip} as ${msg.clientId}`);
|
|
await this.handleAuth(socket, msg, ip);
|
|
} else {
|
|
logger.warn(`Received message before auth from ${ip}: ${JSON.stringify(msg)}`);
|
|
await this.handleInvalidMessage(socket, ip);
|
|
}
|
|
return;
|
|
}
|
|
|
|
logger.debug(`Routing message from ${socket.clientId}: ${JSON.stringify(msg)}`);
|
|
await wsHandler.handle(socket, socket.clientId!, msg);
|
|
}
|
|
|
|
private isAuthMessage(msg: WSMessage): msg is AuthMessage {
|
|
return msg.type === 'auth';
|
|
}
|
|
|
|
private async handleAuth(socket: AuthenticatedSocket, msg: AuthMessage, ip: string) {
|
|
if (msg.secret === this.secret) {
|
|
socket.isAuthed = true;
|
|
socket.clientId = msg.clientId;
|
|
wsClientManager.add(msg.clientId, socket);
|
|
logger.info(`Auth success from ${ip}, clientId: ${msg.clientId}`);
|
|
await WsTools.send(socket, { type: 'auth', success: true });
|
|
} else {
|
|
logger.warn(`Auth failed from ${ip} (invalid secret), clientId: ${msg.clientId}`);
|
|
await WsTools.send(socket, { type: 'auth', success: false });
|
|
socket.close(4001, 'Authentication failed');
|
|
}
|
|
}
|
|
|
|
private handleDisconnect(socket: AuthenticatedSocket) {
|
|
if (socket.heartbeat) clearInterval(socket.heartbeat);
|
|
if (socket.clientId) {
|
|
wsClientManager.remove(socket.clientId);
|
|
logger.info(`Removed client ${socket.clientId} from manager`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const wsServer = new WSServer();
|
|
export default wsServer;
|