socketio server and base events
This commit is contained in:
parent
7e96b6ca0c
commit
1151ec45dc
12 changed files with 327 additions and 30 deletions
49
package-lock.json
generated
49
package-lock.json
generated
|
@ -31,7 +31,7 @@
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rss-parser": "^3.13.0",
|
"rss-parser": "^3.13.0",
|
||||||
"socket.io": "^4.7.5",
|
"socket.io": "^4.8.1",
|
||||||
"speakeasy": "^2.0.0",
|
"speakeasy": "^2.0.0",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"typeorm": "^0.3.20",
|
"typeorm": "^0.3.20",
|
||||||
|
@ -285,11 +285,6 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/cookie": {
|
|
||||||
"version": "0.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
|
||||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
|
||||||
},
|
|
||||||
"node_modules/@types/cors": {
|
"node_modules/@types/cors": {
|
||||||
"version": "2.8.17",
|
"version": "2.8.17",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
|
||||||
|
@ -682,6 +677,7 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
|
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^4.5.0 || >= 5.9"
|
"node": "^4.5.0 || >= 5.9"
|
||||||
}
|
}
|
||||||
|
@ -1465,16 +1461,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/engine.io": {
|
"node_modules/engine.io": {
|
||||||
"version": "6.5.5",
|
"version": "6.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
|
||||||
"integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
|
"integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/cookie": "^0.4.1",
|
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
"@types/node": ">=10.0.0",
|
"@types/node": ">=10.0.0",
|
||||||
"accepts": "~1.3.4",
|
"accepts": "~1.3.4",
|
||||||
"base64id": "2.0.0",
|
"base64id": "2.0.0",
|
||||||
"cookie": "~0.4.1",
|
"cookie": "~0.7.2",
|
||||||
"cors": "~2.8.5",
|
"cors": "~2.8.5",
|
||||||
"debug": "~4.3.1",
|
"debug": "~4.3.1",
|
||||||
"engine.io-parser": "~5.2.1",
|
"engine.io-parser": "~5.2.1",
|
||||||
|
@ -1488,24 +1484,27 @@
|
||||||
"version": "5.2.3",
|
"version": "5.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||||
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/engine.io/node_modules/cookie": {
|
"node_modules/engine.io/node_modules/cookie": {
|
||||||
"version": "0.4.2",
|
"version": "0.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||||
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
|
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/engine.io/node_modules/debug": {
|
"node_modules/engine.io/node_modules/debug": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "2.1.2"
|
"ms": "^2.1.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0"
|
"node": ">=6.0"
|
||||||
|
@ -1516,11 +1515,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/engine.io/node_modules/ms": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
|
||||||
},
|
|
||||||
"node_modules/entities": {
|
"node_modules/entities": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
|
||||||
|
@ -3810,15 +3804,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/socket.io": {
|
"node_modules/socket.io": {
|
||||||
"version": "4.7.5",
|
"version": "4.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz",
|
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
|
||||||
"integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==",
|
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.4",
|
"accepts": "~1.3.4",
|
||||||
"base64id": "~2.0.0",
|
"base64id": "~2.0.0",
|
||||||
"cors": "~2.8.5",
|
"cors": "~2.8.5",
|
||||||
"debug": "~4.3.2",
|
"debug": "~4.3.2",
|
||||||
"engine.io": "~6.5.2",
|
"engine.io": "~6.6.0",
|
||||||
"socket.io-adapter": "~2.5.2",
|
"socket.io-adapter": "~2.5.2",
|
||||||
"socket.io-parser": "~4.2.4"
|
"socket.io-parser": "~4.2.4"
|
||||||
},
|
},
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rss-parser": "^3.13.0",
|
"rss-parser": "^3.13.0",
|
||||||
"socket.io": "^4.7.5",
|
"socket.io": "^4.8.1",
|
||||||
"speakeasy": "^2.0.0",
|
"speakeasy": "^2.0.0",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"typeorm": "^0.3.20",
|
"typeorm": "^0.3.20",
|
||||||
|
|
|
@ -3,6 +3,7 @@ import MissionService from "../../../service/operation/missionService";
|
||||||
import MissionFactory from "../../../factory/admin/operation/mission";
|
import MissionFactory from "../../../factory/admin/operation/mission";
|
||||||
import { DeleteMissionCommand, UpdateMissionCommand } from "../../../command/operation/mission/missionCommand";
|
import { DeleteMissionCommand, UpdateMissionCommand } from "../../../command/operation/mission/missionCommand";
|
||||||
import MissionCommandHandler from "../../../command/operation/mission/missionCommandHandler";
|
import MissionCommandHandler from "../../../command/operation/mission/missionCommandHandler";
|
||||||
|
import SocketServer from "../../../websocket";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description get all missions
|
* @description get all missions
|
||||||
|
@ -47,7 +48,7 @@ export async function getMissionById(req: Request, res: Response): Promise<any>
|
||||||
export async function createMission(req: Request, res: Response): Promise<any> {
|
export async function createMission(req: Request, res: Response): Promise<any> {
|
||||||
let missionId = await MissionCommandHandler.create();
|
let missionId = await MissionCommandHandler.create();
|
||||||
|
|
||||||
// TODO: push notification to clients that new mission was created
|
SocketServer.broadcastNewMission(missionId);
|
||||||
|
|
||||||
res.status(200).send(missionId);
|
res.status(200).send(missionId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
|
import { createServer } from "http";
|
||||||
|
|
||||||
import { BACKUP_AUTO_RESTORE, configCheck, SERVER_PORT } from "./env.defaults";
|
import { BACKUP_AUTO_RESTORE, configCheck, SERVER_PORT } from "./env.defaults";
|
||||||
configCheck();
|
configCheck();
|
||||||
|
@ -31,7 +32,10 @@ dataSource.initialize().then(async () => {
|
||||||
const app = express();
|
const app = express();
|
||||||
import router from "./routes/index";
|
import router from "./routes/index";
|
||||||
router(app);
|
router(app);
|
||||||
app.listen(process.env.NODE_ENV ? SERVER_PORT : 5000, () => {
|
const httpServer = createServer(app);
|
||||||
|
import SocketServer from "./websocket";
|
||||||
|
SocketServer.init(httpServer);
|
||||||
|
httpServer.listen(process.env.NODE_ENV ? SERVER_PORT : 5000, () => {
|
||||||
console.log(`${new Date().toISOString()}: listening on port ${process.env.NODE_ENV ? SERVER_PORT : 5000}`);
|
console.log(`${new Date().toISOString()}: listening on port ${process.env.NODE_ENV ? SERVER_PORT : 5000}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
27
src/storage/missionMap.ts
Normal file
27
src/storage/missionMap.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
export interface missionStoreModel {
|
||||||
|
missionId: string;
|
||||||
|
doc: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description store credentials to socket to prevent auth data change while connected
|
||||||
|
*/
|
||||||
|
export abstract class MissionMap {
|
||||||
|
private static store = new Map<string, missionStoreModel>();
|
||||||
|
|
||||||
|
public static write(identifier: string, data: missionStoreModel, overwrite: boolean = false): void {
|
||||||
|
if (!this.exists(identifier) || overwrite) this.store.set(identifier, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static read(identifier: string): missionStoreModel {
|
||||||
|
return this.store.get(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static exists(identifier: string): boolean {
|
||||||
|
return this.store.has(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static delete(identifier: string): void {
|
||||||
|
this.store.delete(identifier);
|
||||||
|
}
|
||||||
|
}
|
33
src/storage/socketMap.ts
Normal file
33
src/storage/socketMap.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { PermissionObject } from "../type/permissionTypes";
|
||||||
|
|
||||||
|
export interface socketStoreModel {
|
||||||
|
socketId: string;
|
||||||
|
userId: string;
|
||||||
|
username: string;
|
||||||
|
isOwner: string;
|
||||||
|
permissions: PermissionObject;
|
||||||
|
isWebApiRequest: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description store credentials to socket to prevent auth data change while connected
|
||||||
|
*/
|
||||||
|
export abstract class SocketMap {
|
||||||
|
private static store = new Map<string, socketStoreModel>();
|
||||||
|
|
||||||
|
public static write(identifier: string, data: socketStoreModel, overwrite: boolean = false): void {
|
||||||
|
if (!this.exists(identifier) || overwrite) this.store.set(identifier, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static read(identifier: string): socketStoreModel {
|
||||||
|
return this.store.get(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static exists(identifier: string): boolean {
|
||||||
|
return this.store.has(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static delete(identifier: string): void {
|
||||||
|
this.store.delete(identifier);
|
||||||
|
}
|
||||||
|
}
|
35
src/websocket/endpoints/base.ts
Normal file
35
src/websocket/endpoints/base.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { Server, Socket } from "socket.io";
|
||||||
|
import { SocketMap } from "../../storage/socketMap";
|
||||||
|
import CustomRequestException from "../../exceptions/customRequestException";
|
||||||
|
|
||||||
|
export default (io: Server, socket: Socket) => {
|
||||||
|
socket.on("ping", (callback: Function) => {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("error", (err) => {
|
||||||
|
let status = 500;
|
||||||
|
let msg = "Internal Server Error";
|
||||||
|
|
||||||
|
if (err instanceof CustomRequestException) {
|
||||||
|
status = err.statusCode;
|
||||||
|
msg = err.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err instanceof CustomRequestException) {
|
||||||
|
console.log("WS Custom Handler", status, msg);
|
||||||
|
} else {
|
||||||
|
console.log("WS Error Handler", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit("error", msg);
|
||||||
|
socket.leave("home");
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("disconnecting", () => {
|
||||||
|
console.log("socket disconnection: ", socket.id);
|
||||||
|
SocketMap.delete(socket.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("disconnect", () => {});
|
||||||
|
};
|
36
src/websocket/endpoints/roomManagement.ts
Normal file
36
src/websocket/endpoints/roomManagement.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { Server, Socket } from "socket.io";
|
||||||
|
import { SocketMap } from "../../storage/socketMap";
|
||||||
|
import CustomRequestException from "../../exceptions/customRequestException";
|
||||||
|
import { handleEvent } from "../handleEvent";
|
||||||
|
|
||||||
|
export default (io: Server, socket: Socket) => {
|
||||||
|
socket.on(
|
||||||
|
"join:mission",
|
||||||
|
|
||||||
|
handleEvent(
|
||||||
|
{ type: "read", section: "operation", module: "mission" },
|
||||||
|
async (data: any) => {
|
||||||
|
socket.rooms.forEach((room) => {
|
||||||
|
if (room !== socket.id && room != "home") {
|
||||||
|
socket.leave(room);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
socket.join(data);
|
||||||
|
socket.emit("status-mission:room", { status: "success" });
|
||||||
|
// get mission data
|
||||||
|
// create yjs sync doc
|
||||||
|
// provide yjs sync doc to client
|
||||||
|
return {
|
||||||
|
type: "package-mission",
|
||||||
|
answer: "mission-data by yjs",
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return { type: "status-join:room", answer: { status: "failed", msg: error.message } };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
socket
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
69
src/websocket/handleEvent.ts
Normal file
69
src/websocket/handleEvent.ts
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import { Server, Socket } from "socket.io";
|
||||||
|
import { PermissionObject, PermissionType, PermissionSection, PermissionModule } from "../type/permissionTypes";
|
||||||
|
import { SocketMap } from "../storage/socketMap";
|
||||||
|
import PermissionHelper from "../helpers/permissionHelper";
|
||||||
|
import ForbiddenRequestException from "../exceptions/forbiddenRequestException";
|
||||||
|
|
||||||
|
export type EventResponseType = {
|
||||||
|
answer: any;
|
||||||
|
type:
|
||||||
|
| `package-${string}`
|
||||||
|
| `status-${string}:${string}`
|
||||||
|
| "display"
|
||||||
|
| "warning"
|
||||||
|
| "reload"
|
||||||
|
| "deleted"
|
||||||
|
| "action required";
|
||||||
|
room?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PermissionPass =
|
||||||
|
| {
|
||||||
|
type: PermissionType;
|
||||||
|
section: PermissionSection;
|
||||||
|
module?: PermissionModule;
|
||||||
|
}
|
||||||
|
| "admin";
|
||||||
|
|
||||||
|
export let handleEvent = (
|
||||||
|
permissions: PermissionPass,
|
||||||
|
handler: (params: any) => Promise<EventResponseType>,
|
||||||
|
socket: Socket
|
||||||
|
) => {
|
||||||
|
return async (args: any) => {
|
||||||
|
try {
|
||||||
|
const socketData = SocketMap.read(socket.id);
|
||||||
|
if (permissions == "admin") {
|
||||||
|
if (!socketData.isOwner && !socketData.permissions.admin) {
|
||||||
|
throw new ForbiddenRequestException(`missing admin permission`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!PermissionHelper.can(socketData.permissions, permissions.type, permissions.section, permissions.module)) {
|
||||||
|
throw new ForbiddenRequestException(`missing required permission`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { answer, type, room } = await handler(args);
|
||||||
|
if (room === undefined || room == "") {
|
||||||
|
socket.emit(type, answer);
|
||||||
|
} else {
|
||||||
|
socket.to(room).emit(type, answer);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
socket.emit("error", e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
socket.on(
|
||||||
|
"event",
|
||||||
|
{ permissions }
|
||||||
|
handleResponse(
|
||||||
|
async (data:any) => {
|
||||||
|
throw new Error("failed")
|
||||||
|
},
|
||||||
|
socket,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
*/
|
38
src/websocket/index.ts
Normal file
38
src/websocket/index.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import helmet from "helmet";
|
||||||
|
import { Server as httpServerType } from "http";
|
||||||
|
import { Server } from "socket.io";
|
||||||
|
import authenticateSocket from "./middleware/authenticateSocket";
|
||||||
|
import base from "./endpoints/base";
|
||||||
|
import perRequestCheck from "./middleware/perRequestCheck";
|
||||||
|
import roomManagement from "./endpoints/roomManagement";
|
||||||
|
|
||||||
|
export default abstract class SocketServer {
|
||||||
|
private static io: Server;
|
||||||
|
|
||||||
|
public static init(httpServer: httpServerType) {
|
||||||
|
this.io = new Server(httpServer, {
|
||||||
|
cors: {
|
||||||
|
origin: "*",
|
||||||
|
methods: ["GET", "POST"],
|
||||||
|
credentials: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.io.engine.use(helmet());
|
||||||
|
|
||||||
|
this.io
|
||||||
|
.of("/")
|
||||||
|
.use(authenticateSocket)
|
||||||
|
.on("connection", (socket) => {
|
||||||
|
socket.use((packet, next) => authenticateSocket(socket, next));
|
||||||
|
socket.use((packet, next) => perRequestCheck(socket, packet, next));
|
||||||
|
|
||||||
|
base(this.io, socket);
|
||||||
|
roomManagement(this.io, socket);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static broadcastNewMission(id: string) {
|
||||||
|
this.io.emit("created-new-mission", id);
|
||||||
|
}
|
||||||
|
}
|
48
src/websocket/middleware/authenticateSocket.ts
Normal file
48
src/websocket/middleware/authenticateSocket.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
import BadRequestException from "../../exceptions/badRequestException";
|
||||||
|
import UnauthorizedRequestException from "../../exceptions/unauthorizedRequestException";
|
||||||
|
import InternalException from "../../exceptions/internalException";
|
||||||
|
import { JWTHelper } from "../../helpers/jwtHelper";
|
||||||
|
import { Socket } from "socket.io";
|
||||||
|
import { SocketMap } from "../../storage/socketMap";
|
||||||
|
|
||||||
|
export default async function authenticateSocket(socket: Socket, next: Function) {
|
||||||
|
const token = socket.handshake.auth.token;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
throw new BadRequestException("Provide valid Authorization Header");
|
||||||
|
}
|
||||||
|
|
||||||
|
let decoded: string | jwt.JwtPayload;
|
||||||
|
await JWTHelper.validate(token)
|
||||||
|
.then((result) => {
|
||||||
|
decoded = result;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (err == "jwt expired") {
|
||||||
|
throw new UnauthorizedRequestException("Token expired", err);
|
||||||
|
} else {
|
||||||
|
throw new BadRequestException("Failed Authorization Header decoding", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof decoded == "string" || !decoded) {
|
||||||
|
throw new InternalException("process failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoded?.sub == "api_token_retrieve") {
|
||||||
|
throw new BadRequestException("This token is only authorized to get temporary access tokens via GET /api/webapi");
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketMap.write(socket.id, {
|
||||||
|
socketId: socket.id,
|
||||||
|
userId: decoded.userId,
|
||||||
|
username: decoded.username,
|
||||||
|
isOwner: decoded.isOwner,
|
||||||
|
permissions: decoded.permissions,
|
||||||
|
isWebApiRequest: decoded?.sub == "webapi_access_token",
|
||||||
|
});
|
||||||
|
socket.join("home");
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
11
src/websocket/middleware/perRequestCheck.ts
Normal file
11
src/websocket/middleware/perRequestCheck.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { Event, Socket } from "socket.io";
|
||||||
|
import { SocketMap } from "../../storage/socketMap";
|
||||||
|
import UnauthorizedRequestException from "../../exceptions/unauthorizedRequestException";
|
||||||
|
|
||||||
|
export default async (socket: Socket, [event, ...args]: Event, next: any) => {
|
||||||
|
if (SocketMap.exists(socket.id)) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
next(new UnauthorizedRequestException("not authorized for connection"));
|
||||||
|
}
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue