This commit is contained in:
arp 2020-10-06 15:37:00 +02:00
parent 1270ae6817
commit 0b78eb6277
7 changed files with 102 additions and 208 deletions

View file

@ -3,20 +3,12 @@ import {ADMIN_API_TOKEN, ADMIN_API_URL, SECRET_KEY, URL_ROOM_STARTED} from "../E
import { uuid } from 'uuidv4';
import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
import {BaseController} from "./BaseController";
import Axios from "axios";
import {adminApi, AdminApiData} from "../Services/AdminApi";
export interface TokenInterface {
userUuid: string
}
interface AdminApiData {
organizationSlug: string
worldSlug: string
roomSlug: string
mapUrlStart: string
userUuid: string
}
export class AuthenticateController extends BaseController {
constructor(private App : TemplatedApp) {
@ -51,13 +43,7 @@ export class AuthenticateController extends BaseController {
let newUrl: string|null = null;
if (organizationMemberToken) {
if (!ADMIN_API_URL) {
return res.status(401).send('No admin backoffice set!');
}
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
const data = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
).then((res): AdminApiData => res.data);
const data = await adminApi.fetchMemberDataByToken(organizationMemberToken);
userUuid = data.userUuid;
mapUrlStart = data.mapUrlStart;

View file

@ -1,23 +1,14 @@
import * as http from "http";
import {MessageUserPosition, Point} from "../Model/Websocket/MessageUserPosition"; //TODO fix import by "_Model/.."
import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
import Jwt, {JsonWebTokenError} from "jsonwebtoken";
import Jwt from "jsonwebtoken";
import {SECRET_KEY, MINIMUM_DISTANCE, GROUP_RADIUS, ALLOW_ARTILLERY} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
import {World} from "../Model/World";
import {GameRoom} from "../Model/GameRoom";
import {Group} from "../Model/Group";
import {User} from "../Model/User";
import {isSetPlayerDetailsMessage,} from "../Model/Websocket/SetPlayerDetailsMessage";
import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined";
import si from "systeminformation";
import {Gauge} from "prom-client";
import {TokenInterface} from "../Controller/AuthenticateController";
import {isJoinRoomMessageInterface} from "../Model/Websocket/JoinRoomMessage";
import {PointInterface} from "../Model/Websocket/PointInterface";
import {isWebRtcSignalMessageInterface} from "../Model/Websocket/WebRtcSignalMessage";
import {UserInGroupInterface} from "../Model/Websocket/UserInGroupInterface";
import {isItemEventMessageInterface} from "../Model/Websocket/ItemEventMessage";
import {uuid} from 'uuidv4';
import {GroupUpdateInterface} from "_Model/Websocket/GroupUpdateInterface";
import {Movable} from "../Model/Movable";
import {
PositionMessage,
@ -42,14 +33,17 @@ import {
SilentMessage,
WebRtcSignalToClientMessage,
WebRtcSignalToServerMessage,
WebRtcStartMessage, WebRtcDisconnectMessage, PlayGlobalMessage
WebRtcStartMessage,
WebRtcDisconnectMessage,
PlayGlobalMessage,
} from "../Messages/generated/messages_pb";
import {UserMovesMessage} from "../Messages/generated/messages_pb";
import Direction = PositionMessage.Direction;
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {App, HttpRequest, TemplatedApp, WebSocket} from "uWebSockets.js"
import {HttpRequest, TemplatedApp} from "uWebSockets.js"
import {parse} from "query-string";
import {cpuTracker} from "../Services/CpuTracker";
import {adminApi} from "../Services/AdminApi";
function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void {
socket.batchedMessages.addPayload(payload);
@ -71,7 +65,7 @@ function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void {
}
export class IoSocketController {
private Worlds: Map<string, World> = new Map<string, World>();
private Worlds: Map<string, GameRoom> = new Map<string, GameRoom>();
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
private nbClientsGauge: Gauge<string>;
private nbClientsPerRoomGauge: Gauge<string>;
@ -100,32 +94,11 @@ export class IoSocketController {
return true;
}
/**
*
* @param token
*/
/* searchClientByToken(token: string): ExSocketInterface | null {
const clients: ExSocketInterface[] = Object.values(this.Io.sockets.sockets) as ExSocketInterface[];
for (let i = 0; i < clients.length; i++) {
const client = clients[i];
if (client.token !== token) {
continue
}
return client;
}
return null;
}*/
private async authenticate(req: HttpRequest): Promise<{ token: string, userUuid: string }> {
//console.log(socket.handshake.query.token);
const query = parse(req.getQuery());
if (!query.token) {
private async getUserUuidFromToken(token: unknown): Promise<string> {
if (!token) {
throw new Error('An authentication error happened, a user tried to connect without a token.');
}
const token = query.token;
if (typeof(token) !== "string") {
throw new Error('Token is expected to be a string');
}
@ -133,22 +106,15 @@ export class IoSocketController {
if(token === 'test') {
if (ALLOW_ARTILLERY) {
return {
token,
userUuid: uuid()
}
return uuid();
} else {
throw new Error("In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'");
}
}
/*if(this.searchClientByToken(socket.handshake.query.token)){
console.error('An authentication error happened, a user tried to connect while its token is already connected.');
return next(new Error('Authentication error'));
}*/
const promise = new Promise<{ token: string, userUuid: string }>((resolve, reject) => {
return new Promise<string>((resolve, reject) => {
Jwt.verify(token, SECRET_KEY, {},(err, tokenDecoded) => {
const tokenInterface = tokenDecoded as TokenInterface;
if (err) {
console.error('An authentication error happened, invalid JsonWebToken.', err);
reject(new Error('An authentication error happened, invalid JsonWebToken. '+err.message));
@ -159,25 +125,19 @@ export class IoSocketController {
reject(new Error('Empty token found.'));
return;
}
const tokenInterface = tokenDecoded as TokenInterface;
if (!this.isValidToken(tokenInterface)) {
reject(new Error('Authentication error, invalid token structure.'));
return;
}
resolve({
token,
userUuid: tokenInterface.userUuid
});
resolve(tokenInterface.userUuid);
});
});
return promise;
}
ioConnection() {
this.app.ws('/*', {
this.app.ws('/room', {
/* Options */
//compression: uWS.SHARED_COMPRESSOR,
@ -197,7 +157,21 @@ export class IoSocketController {
});
try {
const result = await this.authenticate(req);
const query = parse(req.getQuery());
const moderated = query.moderated || false;
const roomId = query.roomId || null;
const token = query.token;
const userUuid = await this.getUserUuidFromToken(token);
this.handleJoinRoom(client, message.getJoinroommessage() as JoinRoomMessage);
const isGranted = await adminApi.memberIsGrantedAccessToRoom(client.userUuid, roomId);
if (!isGranted) {
throw Error('Client cannot acces this ressource.');
}
if (upgradeAborted.aborted) {
console.log("Ouch! Client disconnected before we could upgrade it!");
@ -209,8 +183,8 @@ export class IoSocketController {
res.upgrade({
// Data passed here is accessible on the "websocket" socket object.
url: req.getUrl(),
token: result.token,
userUuid: result.userUuid
token,
userUuid
},
/* Spell these correctly */
req.getHeader('sec-websocket-key'),
@ -250,13 +224,11 @@ export class IoSocketController {
console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)');
},
message: (ws, arrayBuffer, isBinary) => {
message: (ws, arrayBuffer, isBinary): void => {
const client = ws as ExSocketInterface;
const message = ClientToServerMessage.deserializeBinary(new Uint8Array(arrayBuffer));
if (message.hasJoinroommessage()) {
this.handleJoinRoom(client, message.getJoinroommessage() as JoinRoomMessage);
} else if (message.hasViewportmessage()) {
if (message.hasViewportmessage()) {
this.handleViewport(client, message.getViewportmessage() as ViewportMessage);
} else if (message.hasUsermovesmessage()) {
this.handleUserMovesMessage(client, message.getUsermovesmessage() as UserMovesMessage);
@ -333,26 +305,22 @@ export class IoSocketController {
console.warn(message);
}
private handleJoinRoom(Client: ExSocketInterface, message: JoinRoomMessage): void {
private async handleJoinRoom(client: ExSocketInterface, message: JoinRoomMessage): Promise<void> {
try {
/*if (!isJoinRoomMessageInterface(message.toObject())) {
console.log(message.toObject())
this.emitError(Client, 'Invalid JOIN_ROOM message received: ' + message.toObject().toString());
return;
}*/
const roomId = message.getRoomid();
if (Client.roomId === roomId) {
if (client.roomId === roomId) {
return;
}
//leave previous room
//this.leaveRoom(Client); // Useless now, there is only one room per connection
//join new previous room
const world = this.joinRoom(Client, roomId, ProtobufUtils.toPointInterface(message.getPosition() as PositionMessage));
const gameRoom = await this.joinRoom(client, roomId, ProtobufUtils.toPointInterface(message.getPosition() as PositionMessage));
const things = world.setViewport(Client, (message.getViewport() as ViewportMessage).toObject());
const things = gameRoom.setViewport(client, (message.getViewport() as ViewportMessage).toObject());
const roomJoinedMessage = new RoomJoinedMessage();
@ -382,7 +350,7 @@ export class IoSocketController {
}
}
for (const [itemId, item] of world.getItemsState().entries()) {
for (const [itemId, item] of gameRoom.getItemsState().entries()) {
const itemStateMessage = new ItemStateMessage();
itemStateMessage.setItemid(itemId);
itemStateMessage.setStatejson(JSON.stringify(item));
@ -393,8 +361,8 @@ export class IoSocketController {
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
if (!Client.disconnecting) {
Client.send(serverToClientMessage.serializeBinary().buffer, true);
if (!client.disconnecting) {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}
} catch (e) {
console.error('An error occurred on "join_room" event');
@ -600,7 +568,7 @@ export class IoSocketController {
if(Client.roomId){
try {
//user leave previous world
const world: World | undefined = this.Worlds.get(Client.roomId);
const world: GameRoom | undefined = this.Worlds.get(Client.roomId);
if (world) {
world.leave(Client);
if (world.isEmpty()) {
@ -616,17 +584,17 @@ export class IoSocketController {
}
}
private joinRoom(Client : ExSocketInterface, roomId: string, position: PointInterface): World {
private joinRoom(client : ExSocketInterface, roomId: string, position: PointInterface): GameRoom {
//join user in room
//Client.join(roomId);
this.nbClientsPerRoomGauge.inc({ room: roomId });
Client.roomId = roomId;
Client.position = position;
client.roomId = roomId;
client.position = position;
//check and create new world for a room
let world = this.Worlds.get(roomId)
if(world === undefined){
world = new World((user1: User, group: Group) => {
world = new GameRoom((user1: User, group: Group) => {
this.joinWebRtcRoom(user1, group);
}, (user1: User, group: Group) => {
this.disConnectedUser(user1, group);
@ -689,10 +657,10 @@ export class IoSocketController {
// Dispatch groups position to newly connected user
world.getGroups().forEach((group: Group) => {
this.emitCreateUpdateGroupEvent(Client, group);
this.emitCreateUpdateGroupEvent(client, group);
});
//join world
world.join(Client, Client.position);
world.join(client, client.position);
return world;
}
@ -882,7 +850,7 @@ export class IoSocketController {
}
public getWorlds(): Map<string, World> {
public getWorlds(): Map<string, GameRoom> {
return this.Worlds;
}
}