Merge branch 'develop' of github.com:thecodingmachine/workadventure into fix/deploy-cleanup

# Conflicts:
#	front/Dockerfile
This commit is contained in:
David Négrier 2021-02-01 14:00:07 +01:00
commit 686427f6fe
222 changed files with 1355 additions and 12017 deletions

View file

@ -10,8 +10,8 @@
"runprod": "node --max-old-space-size=4096 ./dist/server.js",
"profile": "tsc && node --prof ./dist/server.js",
"test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts"
"lint": "DEBUG= node_modules/.bin/eslint src/ . --ext .ts",
"fix": "DEBUG= node_modules/.bin/eslint --fix src/ . --ext .ts"
},
"repository": {
"type": "git",
@ -52,7 +52,7 @@
"multer": "^1.4.2",
"prom-client": "^12.0.0",
"query-string": "^6.13.3",
"systeminformation": "^4.30.5",
"systeminformation": "^4.31.1",
"ts-node-dev": "^1.0.0-pre.44",
"typescript": "^3.8.3",
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",

View file

@ -60,10 +60,7 @@ export class AuthenticateController extends BaseController {
}));
} catch (e) {
console.error("An error happened", e)
res.writeStatus(e.status || "500 Internal Server Error");
this.addCorsHeaders(res);
res.end('An error happened');
this.errorToResponse(e, res);
}

View file

@ -8,4 +8,21 @@ export class BaseController {
res.writeHeader('access-control-allow-methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.writeHeader('access-control-allow-origin', '*');
}
/**
* Turns any exception into a HTTP response (and logs the error)
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected errorToResponse(e: any, res: HttpResponse): void {
console.error("An error happened", e);
if (e.response) {
res.writeStatus(e.response.status+" "+e.response.statusText);
this.addCorsHeaders(res);
res.end("An error occurred: "+e.response.status+" "+e.response.statusText);
} else {
res.writeStatus("500 Internal Server Error")
this.addCorsHeaders(res);
res.end("An error occurred");
}
}
}

View file

@ -91,18 +91,11 @@ export class IoSocketController {
if(message.event === 'user-message') {
const messageToEmit = (message.message as { message: string, type: string, userUuid: string });
switch (message.message.type) {
case 'ban': {
socketManager.emitSendUserMessage(messageToEmit.userUuid, messageToEmit.message, roomId);
break;
}
case 'banned': {
socketManager.emitBan(messageToEmit.userUuid, messageToEmit.message, roomId);
break;
}
default: {
break;
}
if(messageToEmit.type === 'banned'){
socketManager.emitBan(messageToEmit.userUuid, messageToEmit.message, messageToEmit.type);
}
if(messageToEmit.type === 'ban') {
socketManager.emitSendUserMessage(messageToEmit.userUuid, messageToEmit.message, messageToEmit.type);
}
}
}catch (err) {
@ -148,6 +141,7 @@ export class IoSocketController {
const websocketKey = req.getHeader('sec-websocket-key');
const websocketProtocol = req.getHeader('sec-websocket-protocol');
const websocketExtensions = req.getHeader('sec-websocket-extensions');
const IPAddress = req.getHeader('x-forwarded-for');
const roomId = query.roomId;
if (typeof roomId !== 'string') {
@ -176,7 +170,7 @@ export class IoSocketController {
characterLayers = [ characterLayers ];
}
const userUuid = await jwtTokenManager.getUserUuidFromToken(token);
const userUuid = await jwtTokenManager.getUserUuidFromToken(token, IPAddress, roomId);
let memberTags: string[] = [];
let memberTextures: CharacterTexture[] = [];
@ -217,6 +211,7 @@ export class IoSocketController {
url,
token,
userUuid,
IPAddress,
roomId,
name,
characterLayers: characterLayerObjs,
@ -336,6 +331,7 @@ export class IoSocketController {
client.userId = this.nextUserId;
this.nextUserId++;
client.userUuid = ws.userUuid;
client.IPAddress = ws.IPAddress;
client.token = ws.token;
client.batchedMessages = new BatchMessage();
client.batchTimeout = null;

View file

@ -59,10 +59,7 @@ export class MapController extends BaseController{
this.addCorsHeaders(res);
res.end(JSON.stringify(mapDetails));
} catch (e) {
console.error(e.message || e);
res.writeStatus("500 Internal Server Error")
this.addCorsHeaders(res);
res.end("An error occurred");
this.errorToResponse(e, res);
}
})();

View file

@ -1,5 +1,4 @@
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
const URL_ROOM_STARTED = "/Floor0/floor0.json";
const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64;
const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false;
@ -16,7 +15,6 @@ export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as strin
export {
SECRET_KEY,
URL_ROOM_STARTED,
MINIMUM_DISTANCE,
API_URL,
ADMIN_API_URL,

View file

@ -24,6 +24,7 @@ export interface ExSocketInterface extends WebSocket, Identificable {
roomId: string;
//userId: number; // A temporary (autoincremented) identifier for this user
userUuid: string; // A unique identifier for this user
IPAddress: string; // IP address
name: string;
characterLayers: CharacterLayer[];
position: PointInterface;

View file

@ -14,6 +14,11 @@ export interface AdminApiData {
textures: CharacterTexture[]
}
export interface AdminBannedData {
is_banned: boolean,
message: string
}
export interface CharacterTexture {
id: number,
level: number,
@ -110,6 +115,18 @@ class AdminApi {
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
});
}
async verifyBanUser(organizationMemberToken: string, ipAddress: string, organization: string, world: string): Promise<AdminBannedData> {
if (!ADMIN_API_URL) {
return Promise.reject('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.
return Axios.get(ADMIN_API_URL + '/api/check-moderate-user/'+organization+'/'+world+'?ipAddress='+ipAddress+'&token='+organizationMemberToken,
{headers: {"Authorization": `${ADMIN_API_TOKEN}`}}
).then((data) => {
return data.data;
});
}
}
export const adminApi = new AdminApi();

View file

@ -2,7 +2,7 @@ import {ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY} from "../Enum/EnvironmentVar
import {uuid} from "uuidv4";
import Jwt from "jsonwebtoken";
import {TokenInterface} from "../Controller/AuthenticateController";
import {adminApi, AdminApiData} from "../Services/AdminApi";
import {adminApi, AdminBannedData} from "../Services/AdminApi";
class JWTTokenManager {
@ -10,7 +10,7 @@ class JWTTokenManager {
return Jwt.sign({userUuid: userUuid}, SECRET_KEY, {expiresIn: '200d'}); //todo: add a mechanic to refresh or recreate token
}
public async getUserUuidFromToken(token: unknown): Promise<string> {
public async getUserUuidFromToken(token: unknown, ipAddress?: string, room?: string): Promise<string> {
if (!token) {
throw new Error('An authentication error happened, a user tried to connect without a token.');
@ -50,14 +50,22 @@ class JWTTokenManager {
if (ADMIN_API_URL) {
//verify user in admin
adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => {
resolve(tokenInterface.userUuid);
}).catch((err) => {
//anonymous user
if(err.response && err.response.status && err.response.status === 404){
let promise = new Promise((resolve) => resolve());
if(ipAddress && room) {
promise = this.verifyBanUser(tokenInterface.userUuid, ipAddress, room);
}
promise.then(() => {
adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => {
resolve(tokenInterface.userUuid);
return;
}
}).catch((err) => {
//anonymous user
if (err.response && err.response.status && err.response.status === 404) {
resolve(tokenInterface.userUuid);
return;
}
reject(err);
});
}).catch((err) => {
reject(err);
});
} else {
@ -67,6 +75,27 @@ class JWTTokenManager {
});
}
private verifyBanUser(userUuid: string, ipAddress: string, room: string): Promise<AdminBannedData> {
const parts = room.split('/');
if (parts.length < 3 || parts[0] !== '@') {
return Promise.resolve({
is_banned: false,
message: ''
});
}
const organization = parts[1];
const world = parts[2];
return adminApi.verifyBanUser(userUuid, ipAddress, organization, world).then((data: AdminBannedData) => {
if (data && data.is_banned) {
throw new Error('User was banned');
}
return data;
}).catch((err) => {
throw err;
});
}
private isValidToken(token: object): token is TokenInterface {
return !(typeof((token as TokenInterface).userUuid) !== 'string');
}

View file

@ -2,11 +2,8 @@ import {PusherRoom} from "../Model/PusherRoom";
import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface";
import {
GroupDeleteMessage,
GroupUpdateMessage,
ItemEventMessage,
ItemStateMessage,
PlayGlobalMessage,
PointMessage,
PositionMessage,
RoomJoinedMessage,
ServerToClientMessage,
@ -14,23 +11,19 @@ import {
SilentMessage,
SubMessage,
ReportPlayerMessage,
UserJoinedMessage,
UserLeftMessage,
UserMovedMessage,
UserMovesMessage,
ViewportMessage,
WebRtcDisconnectMessage,
WebRtcSignalToClientMessage,
WebRtcSignalToServerMessage,
WebRtcStartMessage,
QueryJitsiJwtMessage,
SendJitsiJwtMessage,
SendUserMessage,
JoinRoomMessage,
CharacterLayerMessage,
PusherToBackMessage,
AdminPusherToBackMessage,
ServerToAdminClientMessage, AdminMessage, BanMessage
ServerToAdminClientMessage,
SendUserMessage,
BanUserMessage, UserJoinedRoomMessage, UserLeftRoomMessage
} from "../Messages/generated/messages_pb";
import {PointInterface} from "../Model/Websocket/PointInterface";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
@ -79,23 +72,33 @@ export class SocketManager implements ZoneEventListener {
}
async handleAdminRoom(client: ExAdminSocketInterface, roomId: string): Promise<void> {
console.log('Calling adminRoom')
const apiClient = await apiClientRepository.getClient(roomId);
const adminRoomStream = apiClient.adminRoom();
client.adminConnection = adminRoomStream;
adminRoomStream.on('data', (message: ServerToAdminClientMessage) => {
if (message.hasUseruuidjoinedroom()) {
const userUuid = message.getUseruuidjoinedroom();
if (message.hasUserjoinedroom()) {
const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
if (!client.disconnecting) {
client.send('MemberJoin:'+userUuid+';'+roomId);
client.send(JSON.stringify({
type: 'MemberJoin',
data: {
uuid: userJoinedRoomMessage.getUuid(),
name: userJoinedRoomMessage.getName(),
ipAddress: userJoinedRoomMessage.getIpaddress(),
roomId: roomId,
}
}));
}
} else if (message.hasUseruuidleftroom()) {
const userUuid = message.getUseruuidleftroom();
} else if (message.hasUserleftroom()) {
const userLeftRoomMessage = message.getUserleftroom() as UserLeftRoomMessage;
if (!client.disconnecting) {
client.send('MemberLeave:'+userUuid+';'+roomId);
client.send(JSON.stringify({
type: 'MemberLeave',
data: {
uuid: userLeftRoomMessage.getUuid()
}
}));
}
} else {
throw new Error('Unexpected admin message');
@ -145,15 +148,16 @@ export class SocketManager implements ZoneEventListener {
}
async handleJoinRoom(client: ExSocketInterface): Promise<void> {
const position = client.position;
const viewport = client.viewport;
try {
const joinRoomMessage = new JoinRoomMessage();
joinRoomMessage.setUseruuid(client.userUuid);
joinRoomMessage.setIpaddress(client.IPAddress);
joinRoomMessage.setRoomid(client.roomId);
joinRoomMessage.setName(client.name);
joinRoomMessage.setPositionmessage(ProtobufUtils.toPositionMessage(client.position));
joinRoomMessage.setTagList(client.tags);
for (const characterLayer of client.characterLayers) {
const characterLayerMessage = new CharacterLayerMessage();
characterLayerMessage.setName(characterLayer.name);
@ -540,51 +544,54 @@ export class SocketManager implements ZoneEventListener {
client.send(serverToClientMessage.serializeBinary().buffer, true);
}
public async emitSendUserMessage(userUuid: string, message: string, roomId: string): Promise<void> {
public emitSendUserMessage(userUuid: string, message: string, type: string): void {
const client = this.searchClientByUuid(userUuid);
if(!client){
throw Error('client not found');
}
const backConnection = await apiClientRepository.getClient(roomId);
const adminMessage = new AdminMessage();
adminMessage.setRecipientuuid(userUuid);
const adminMessage = new SendUserMessage();
adminMessage.setMessage(message);
adminMessage.setRoomid(roomId);
adminMessage.setType(type);
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setSendusermessage(adminMessage);
client.backConnection.write(pusherToBackMessage);
/*const backConnection = await apiClientRepository.getClient(client.roomId);
const adminMessage = new AdminMessage();
adminMessage.setMessage(message);
adminMessage.setRoomid(client.roomId);
adminMessage.setRecipientuuid(client.userUuid);
backConnection.sendAdminMessage(adminMessage, (error) => {
if (error !== null) {
console.error('Error while sending admin message', error);
}
});
/*
const socket = this.searchClientByUuid(messageToSend.userUuid);
if(!socket){
throw 'socket was not found';
}
const sendUserMessage = new SendUserMessage();
sendUserMessage.setMessage(messageToSend.message);
sendUserMessage.setType(messageToSend.type);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setSendusermessage(sendUserMessage);
if (!socket.disconnecting) {
socket.send(serverToClientMessage.serializeBinary().buffer, true);
}
return socket;*/
});*/
}
public async emitBan(userUuid: string, message: string, roomId: string): Promise<void> {
const backConnection = await apiClientRepository.getClient(roomId);
public emitBan(userUuid: string, message: string, type: string): void {
const client = this.searchClientByUuid(userUuid);
if(!client){
throw Error('client not found');
}
const banMessage = new BanMessage();
banMessage.setRecipientuuid(userUuid);
banMessage.setRoomid(roomId);
const banUserMessage = new BanUserMessage();
banUserMessage.setMessage(message);
banUserMessage.setType(type);
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setBanusermessage(banUserMessage);
client.backConnection.write(pusherToBackMessage);
backConnection.ban(banMessage, (error) => {
/*const backConnection = await apiClientRepository.getClient(client.roomId);
const adminMessage = new AdminMessage();
adminMessage.setMessage(message);
adminMessage.setRoomid(client.roomId);
adminMessage.setRecipientuuid(client.userUuid);
backConnection.sendAdminMessage(adminMessage, (error) => {
if (error !== null) {
console.error('Error while sending ban message', error);
console.error('Error while sending admin message', error);
}
});
});*/
}
/**

View file

@ -2723,10 +2723,10 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
systeminformation@^4.30.5:
version "4.30.5"
resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-4.30.5.tgz#2219c305c8be56a2cfa527a5519c45bc81d4916c"
integrity sha512-aYWs8yttl8ePpr6VOQ/Ak8cznuc9L/NQODVhbOKhInX73ZMLvV2BS86Mzr7LLfmUteVFR36CTDNQgiJgRqq+SQ==
systeminformation@^4.31.1:
version "4.31.1"
resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-4.31.1.tgz#2e02c26987494d4b6a4d2d83138724593bc98d50"
integrity sha512-dVCDWNMN8ncMZo5vbMCA5dpAdMgzafK2ucuJy5LFmGtp1cG6farnPg8QNvoOSky9SkFoEX1Aw0XhcOFV6TnLYA==
table@^5.2.3:
version "5.4.6"