Merge pull request #342 from thecodingmachine/ban-message

Ban feature
This commit is contained in:
David Négrier 2020-10-20 10:26:14 +02:00 committed by GitHub
commit a4a19a60e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 227 additions and 14 deletions

View File

@ -12,14 +12,13 @@ import {
WebRtcSignalToServerMessage, WebRtcSignalToServerMessage,
PlayGlobalMessage, PlayGlobalMessage,
ReportPlayerMessage, ReportPlayerMessage,
QueryJitsiJwtMessage, QueryJitsiJwtMessage
SendJitsiJwtMessage,
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import {UserMovesMessage} from "../Messages/generated/messages_pb"; import {UserMovesMessage} from "../Messages/generated/messages_pb";
import {TemplatedApp} from "uWebSockets.js" import {TemplatedApp} from "uWebSockets.js"
import {parse} from "query-string"; import {parse} from "query-string";
import {jwtTokenManager} from "../Services/JWTTokenManager"; import {jwtTokenManager} from "../Services/JWTTokenManager";
import {adminApi} from "../Services/AdminApi"; import {adminApi, fetchMemberDataByUuidResponse} from "../Services/AdminApi";
import {socketManager} from "../Services/SocketManager"; import {socketManager} from "../Services/SocketManager";
import {emitInBatch, resetPing} from "../Services/IoSocketHelpers"; import {emitInBatch, resetPing} from "../Services/IoSocketHelpers";
import Jwt from "jsonwebtoken"; import Jwt from "jsonwebtoken";
@ -72,7 +71,33 @@ export class IoSocketController {
clientEventsEmitter.registerToClientLeave(ws.clientLeaveCallback); clientEventsEmitter.registerToClientLeave(ws.clientLeaveCallback);
}, },
message: (ws, arrayBuffer, isBinary): void => { message: (ws, arrayBuffer, isBinary): void => {
console.log('m', ws); //todo: add admin actions such as ban here try {
//TODO refactor message type and data
const message: {event: string, message: {type: string, message: unknown, userUuid: string}} =
JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(arrayBuffer)));
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);
break;
}
case 'banned': {
const socketUser = socketManager.emitSendUserMessage(messageToEmit);
setTimeout(() => {
socketUser.close();
}, 10000);
break;
}
default: {
break;
}
}
}
}catch (err) {
console.error(err);
}
}, },
close: (ws, code, message) => { close: (ws, code, message) => {
//todo make sure this code unregister the right listeners //todo make sure this code unregister the right listeners
@ -206,6 +231,23 @@ export class IoSocketController {
const client = this.initClient(ws); //todo: into the upgrade instead? const client = this.initClient(ws); //todo: into the upgrade instead?
socketManager.handleJoinRoom(client); socketManager.handleJoinRoom(client);
resetPing(client); resetPing(client);
//get data information and shwo messages
adminApi.fetchMemberDataByUuid(client.userUuid).then((res: fetchMemberDataByUuidResponse) => {
if (!res.messages) {
return;
}
res.messages.forEach((c: unknown) => {
const messageToSend = c as { type: string, message: string };
socketManager.emitSendUserMessage({
userUuid: client.userUuid,
type: messageToSend.type,
message: messageToSend.message
})
});
}).catch((err) => {
console.error('fetchMemberDataByUuid => err', err);
});
}, },
message: (ws, arrayBuffer, isBinary): void => { message: (ws, arrayBuffer, isBinary): void => {
const client = ws as ExSocketInterface; const client = ws as ExSocketInterface;

View File

@ -9,11 +9,13 @@ export interface AdminApiData {
tags: string[] tags: string[]
policy_type: number policy_type: number
userUuid: string userUuid: string
messages?: unknown[]
} }
export interface fetchMemberDataByUuidResponse { export interface fetchMemberDataByUuidResponse {
uuid: string; uuid: string;
tags: string[]; tags: string[];
messages: unknown[];
} }
class AdminApi { class AdminApi {
@ -45,7 +47,7 @@ class AdminApi {
if (!ADMIN_API_URL) { if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!'); return Promise.reject('No admin backoffice set!');
} }
const res = await Axios.get(ADMIN_API_URL+'/membership/'+uuid, const res = await Axios.get(ADMIN_API_URL+'/api/membership/'+uuid,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} } { headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
) )
return res.data; return res.data;
@ -62,6 +64,17 @@ class AdminApi {
return res.data; return res.data;
} }
async fetchCheckUserByToken(organizationMemberToken: string): Promise<AdminApiData> {
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.
const res = await Axios.get(ADMIN_API_URL+'/api/check-user/'+organizationMemberToken,
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
)
return res.data;
}
reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string) { reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string) {
return Axios.post(`${ADMIN_API_URL}/api/report`, { return Axios.post(`${ADMIN_API_URL}/api/report`, {
reportedUserUuid, reportedUserUuid,

View File

@ -2,6 +2,7 @@ import {ALLOW_ARTILLERY, SECRET_KEY} from "../Enum/EnvironmentVariable";
import {uuid} from "uuidv4"; import {uuid} from "uuidv4";
import Jwt from "jsonwebtoken"; import Jwt from "jsonwebtoken";
import {TokenInterface} from "../Controller/AuthenticateController"; import {TokenInterface} from "../Controller/AuthenticateController";
import {adminApi, AdminApiData} from "../Services/AdminApi";
class JWTTokenManager { class JWTTokenManager {
@ -41,12 +42,23 @@ class JWTTokenManager {
return; return;
} }
//verify token
if (!this.isValidToken(tokenInterface)) { if (!this.isValidToken(tokenInterface)) {
reject(new Error('Authentication error, invalid token structure.')); reject(new Error('Authentication error, invalid token structure.'));
return; return;
} }
//verify user in admin
adminApi.fetchCheckUserByToken(tokenInterface.userUuid).then(() => {
resolve(tokenInterface.userUuid); resolve(tokenInterface.userUuid);
}).catch((err) => {
//anonymous user
if(err.response && err.response.status && err.response.status === 404){
resolve(tokenInterface.userUuid);
return;
}
reject(new Error('Authentication error, invalid token structure. ' + err));
});
}); });
}); });
} }

View File

@ -19,7 +19,11 @@ import {
UserMovesMessage, UserMovesMessage,
ViewportMessage, WebRtcDisconnectMessage, ViewportMessage, WebRtcDisconnectMessage,
WebRtcSignalToClientMessage, WebRtcSignalToClientMessage,
WebRtcSignalToServerMessage, WebRtcStartMessage, QueryJitsiJwtMessage, SendJitsiJwtMessage WebRtcSignalToServerMessage,
WebRtcStartMessage,
QueryJitsiJwtMessage,
SendJitsiJwtMessage,
SendUserMessage
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import {PointInterface} from "../Model/Websocket/PointInterface"; import {PointInterface} from "../Model/Websocket/PointInterface";
import {User} from "../Model/User"; import {User} from "../Model/User";
@ -668,6 +672,25 @@ class SocketManager {
client.send(serverToClientMessage.serializeBinary().buffer, true); client.send(serverToClientMessage.serializeBinary().buffer, true);
} }
public emitSendUserMessage(messageToSend: {userUuid: string, message: string, type: string}): ExSocketInterface {
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;
}
} }
export const socketManager = new SocketManager(); export const socketManager = new SocketManager();

View File

@ -125,6 +125,9 @@
<audio id="audio-webrtc-in"> <audio id="audio-webrtc-in">
<source src="/resources/objects/webrtc-in.mp3" type="audio/mp3"> <source src="/resources/objects/webrtc-in.mp3" type="audio/mp3">
</audio> </audio>
<audio id="report-message">
<source src="/resources/objects/report-message.mp3" type="audio/mp3">
</audio>
</body> </body>
</html> </html>

Binary file not shown.

View File

@ -654,4 +654,6 @@ div.modal-report-user{
width: 100%; width: 100%;
text-align: left; text-align: left;
padding: 30px; padding: 30px;
max-width: calc(800px - 60px); /* size of modal - padding*/
} }

View File

@ -0,0 +1,67 @@
import {TypeMessageInterface} from "./UserMessageManager";
import {HtmlUtils} from "../WebRtc/HtmlUtils";
export class TypeMessageExt implements TypeMessageInterface{
private nbSecond = 0;
private maxNbSecond = 10;
private titleMessage = 'IMPORTANT !';
showMessage(message: string): void {
const div : HTMLDivElement = document.createElement('div');
div.classList.add('modal-report-user');
div.id = 'report-message-user';
div.style.backgroundColor = '#000000e0';
const img : HTMLImageElement = document.createElement('img');
img.src = 'resources/logos/report.svg';
div.appendChild(img);
const title : HTMLParagraphElement = document.createElement('p');
title.id = 'title-report-user';
title.innerText = `${this.titleMessage} (${this.maxNbSecond})`;
div.appendChild(title);
const p : HTMLParagraphElement = document.createElement('p');
p.id = 'body-report-user'
p.innerText = message;
div.appendChild(p);
const mainSectionDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
mainSectionDiv.appendChild(div);
const reportMessageAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('report-message');
reportMessageAudio.play();
this.nbSecond = this.maxNbSecond;
setTimeout((c) => {
this.forMessage(title);
}, 1000);
}
forMessage(title: HTMLParagraphElement){
this.nbSecond -= 1;
title.innerText = `${this.titleMessage} (${this.nbSecond})`;
if(this.nbSecond > 0){
setTimeout(() => {
this.forMessage(title);
}, 1000);
}else{
title.innerText = this.titleMessage;
const imgCancel : HTMLImageElement = document.createElement('img');
imgCancel.id = 'cancel-report-user';
imgCancel.src = 'resources/logos/close.svg';
const div = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('report-message-user');
div.appendChild(imgCancel);
imgCancel.addEventListener('click', () => {
div.remove();
});
}
}
}
export class Ban extends TypeMessageExt {
}
export class Banned extends TypeMessageExt {
}

View File

@ -0,0 +1,36 @@
import {RoomConnection} from "../Connexion/RoomConnection";
import * as TypeMessages from "./TypeMessage";
export interface TypeMessageInterface {
showMessage(message: string): void;
}
export class UserMessageManager {
typeMessages: Map<string, TypeMessageInterface> = new Map<string, TypeMessageInterface>();
constructor(private Connection: RoomConnection) {
const valueTypeMessageTab = Object.values(TypeMessages);
Object.keys(TypeMessages).forEach((value: string, index: number) => {
const typeMessageInstance: TypeMessageInterface = (new valueTypeMessageTab[index]() as TypeMessageInterface);
this.typeMessages.set(value.toLowerCase(), typeMessageInstance);
});
this.initialise();
}
initialise() {
//receive signal to show message
this.Connection.receiveUserMessage((type: string, message: string) => {
this.showMessage(type, message);
});
}
showMessage(type: string, message: string) {
const classTypeMessage = this.typeMessages.get(type.toLowerCase());
if (!classTypeMessage) {
console.error('Message unknown');
return;
}
classTypeMessage.showMessage(message);
}
}

View File

@ -27,6 +27,7 @@ export enum EventMessage{
STOP_GLOBAL_MESSAGE = "stop-global-message", STOP_GLOBAL_MESSAGE = "stop-global-message",
TELEPORT = "teleport", TELEPORT = "teleport",
USER_MESSAGE = "user-message",
START_JITSI_ROOM = "start-jitsi-room", START_JITSI_ROOM = "start-jitsi-room",
} }

View File

@ -22,7 +22,7 @@ import {
WebRtcSignalToServerMessage, WebRtcSignalToServerMessage,
WebRtcStartMessage, WebRtcStartMessage,
ReportPlayerMessage, ReportPlayerMessage,
TeleportMessageMessage, QueryJitsiJwtMessage, SendJitsiJwtMessage TeleportMessageMessage, QueryJitsiJwtMessage, SendJitsiJwtMessage, SendUserMessage
} from "../Messages/generated/messages_pb" } from "../Messages/generated/messages_pb"
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer"; import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
@ -35,8 +35,6 @@ import {
RoomJoinedMessageInterface, RoomJoinedMessageInterface,
ViewportInterface, WebRtcDisconnectMessageInterface, ViewportInterface, WebRtcDisconnectMessageInterface,
WebRtcSignalReceivedMessageInterface, WebRtcSignalReceivedMessageInterface,
WebRtcSignalSentMessageInterface,
WebRtcStartMessageInterface
} from "./ConnexionModels"; } from "./ConnexionModels";
export class RoomConnection implements RoomConnection { export class RoomConnection implements RoomConnection {
@ -152,6 +150,8 @@ export class RoomConnection implements RoomConnection {
this.dispatch(EventMessage.TELEPORT, message.getTeleportmessagemessage()); this.dispatch(EventMessage.TELEPORT, message.getTeleportmessagemessage());
} else if (message.hasSendjitsijwtmessage()) { } else if (message.hasSendjitsijwtmessage()) {
this.dispatch(EventMessage.START_JITSI_ROOM, message.getSendjitsijwtmessage()); this.dispatch(EventMessage.START_JITSI_ROOM, message.getSendjitsijwtmessage());
} else if (message.hasSendusermessage()) {
this.dispatch(EventMessage.USER_MESSAGE, message.getSendusermessage());
} else { } else {
throw new Error('Unknown message received'); throw new Error('Unknown message received');
} }
@ -479,8 +479,13 @@ export class RoomConnection implements RoomConnection {
}); });
} }
public receiveUserMessage(callback: (type: string, message: string) => void) {
return this.onMessage(EventMessage.USER_MESSAGE, (message: SendUserMessage) => {
callback(message.getType(), message.getMessage());
});
}
public emitGlobalMessage(message: PlayGlobalMessageInterface){ public emitGlobalMessage(message: PlayGlobalMessageInterface){
console.log('emitGlobalMessage', message);
const playGlobalMessage = new PlayGlobalMessage(); const playGlobalMessage = new PlayGlobalMessage();
playGlobalMessage.setId(message.id); playGlobalMessage.setId(message.id);
playGlobalMessage.setType(message.type); playGlobalMessage.setType(message.type);

View File

@ -51,6 +51,7 @@ import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
import {connectionManager} from "../../Connexion/ConnectionManager"; import {connectionManager} from "../../Connexion/ConnectionManager";
import {RoomConnection} from "../../Connexion/RoomConnection"; import {RoomConnection} from "../../Connexion/RoomConnection";
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager"; import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
import {UserMessageManager} from "../../Administration/UserMessageManager";
import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager"; import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager";
import {ResizableScene} from "../Login/ResizableScene"; import {ResizableScene} from "../Login/ResizableScene";
import {Room} from "../../Connexion/Room"; import {Room} from "../../Connexion/Room";
@ -114,6 +115,7 @@ export class GameScene extends ResizableScene implements CenterListener {
private connection!: RoomConnection; private connection!: RoomConnection;
private simplePeer!: SimplePeer; private simplePeer!: SimplePeer;
private GlobalMessageManager!: GlobalMessageManager; private GlobalMessageManager!: GlobalMessageManager;
private UserMessageManager!: UserMessageManager;
private ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager; private ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>; private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
private connectionAnswerPromiseResolve!: (value?: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void; private connectionAnswerPromiseResolve!: (value?: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
@ -600,6 +602,7 @@ export class GameScene extends ResizableScene implements CenterListener {
// When connection is performed, let's connect SimplePeer // When connection is performed, let's connect SimplePeer
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic); this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic);
this.GlobalMessageManager = new GlobalMessageManager(this.connection); this.GlobalMessageManager = new GlobalMessageManager(this.connection);
this.UserMessageManager = new UserMessageManager(this.connection);
const self = this; const self = this;
this.simplePeer.registerPeerConnectionListener({ this.simplePeer.registerPeerConnectionListener({

View File

@ -178,6 +178,11 @@ message SendJitsiJwtMessage {
string jwt = 2; string jwt = 2;
} }
message SendUserMessage{
string type = 1;
string message = 2;
}
message ServerToClientMessage { message ServerToClientMessage {
oneof message { oneof message {
BatchMessage batchMessage = 1; BatchMessage batchMessage = 1;
@ -191,5 +196,6 @@ message ServerToClientMessage {
StopGlobalMessage stopGlobalMessage = 9; StopGlobalMessage stopGlobalMessage = 9;
TeleportMessageMessage teleportMessageMessage = 10; TeleportMessageMessage teleportMessageMessage = 10;
SendJitsiJwtMessage sendJitsiJwtMessage = 11; SendJitsiJwtMessage sendJitsiJwtMessage = 11;
SendUserMessage sendUserMessage = 12;
} }
} }