Adding JWT authentication to Jitsi
This commit is contained in:
parent
4334ab0f54
commit
260b0ea408
@ -1,3 +1,4 @@
|
||||
DEBUG_MODE=false
|
||||
JITSI_URL=meet.jit.si
|
||||
SECRET_JITSI_KEY=
|
||||
ADMIN_API_TOKEN=123
|
||||
|
@ -12,6 +12,8 @@ import {
|
||||
WebRtcSignalToServerMessage,
|
||||
PlayGlobalMessage,
|
||||
ReportPlayerMessage,
|
||||
QueryJitsiJwtMessage,
|
||||
SendJitsiJwtMessage,
|
||||
} from "../Messages/generated/messages_pb";
|
||||
import {UserMovesMessage} from "../Messages/generated/messages_pb";
|
||||
import {TemplatedApp} from "uWebSockets.js"
|
||||
@ -20,6 +22,7 @@ import {jwtTokenManager} from "../Services/JWTTokenManager";
|
||||
import {adminApi} from "../Services/AdminApi";
|
||||
import {socketManager} from "../Services/SocketManager";
|
||||
import {emitInBatch, resetPing} from "../Services/IoSocketHelpers";
|
||||
import Jwt from "jsonwebtoken";
|
||||
|
||||
export class IoSocketController {
|
||||
private nextUserId: number = 1;
|
||||
@ -191,6 +194,8 @@ export class IoSocketController {
|
||||
socketManager.emitPlayGlobalMessage(client, message.getPlayglobalmessage() as PlayGlobalMessage);
|
||||
} else if (message.hasReportplayermessage()){
|
||||
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);
|
||||
} else if (message.hasQueryjitsijwtmessage()){
|
||||
socketManager.handleQueryJitsiJwtMessage(client, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
||||
}
|
||||
|
||||
/* Ok is false if backpressure was built up, wait for drain */
|
||||
|
@ -6,6 +6,7 @@ const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLER
|
||||
const ADMIN_API_URL = process.env.ADMIN_API_URL || 'http://admin';
|
||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken';
|
||||
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
||||
const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || '';
|
||||
|
||||
export {
|
||||
SECRET_KEY,
|
||||
@ -16,4 +17,5 @@ export {
|
||||
GROUP_RADIUS,
|
||||
ALLOW_ARTILLERY,
|
||||
CPU_OVERHEAT_THRESHOLD,
|
||||
SECRET_JITSI_KEY
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import {
|
||||
GroupDeleteMessage,
|
||||
GroupUpdateMessage,
|
||||
ItemEventMessage,
|
||||
ItemStateMessage,
|
||||
PlayGlobalMessage,
|
||||
ItemStateMessage,
|
||||
PlayGlobalMessage,
|
||||
PointMessage,
|
||||
PositionMessage,
|
||||
RoomJoinedMessage,
|
||||
@ -19,7 +19,7 @@ import {
|
||||
UserMovesMessage,
|
||||
ViewportMessage, WebRtcDisconnectMessage,
|
||||
WebRtcSignalToClientMessage,
|
||||
WebRtcSignalToServerMessage, WebRtcStartMessage
|
||||
WebRtcSignalToServerMessage, WebRtcStartMessage, QueryJitsiJwtMessage, SendJitsiJwtMessage
|
||||
} from "../Messages/generated/messages_pb";
|
||||
import {PointInterface} from "../Model/Websocket/PointInterface";
|
||||
import {User} from "../Model/User";
|
||||
@ -27,20 +27,21 @@ import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
||||
import {Group} from "../Model/Group";
|
||||
import {cpuTracker} from "./CpuTracker";
|
||||
import {isSetPlayerDetailsMessage} from "../Model/Websocket/SetPlayerDetailsMessage";
|
||||
import {GROUP_RADIUS, MINIMUM_DISTANCE} from "../Enum/EnvironmentVariable";
|
||||
import {GROUP_RADIUS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
|
||||
import {Movable} from "../Model/Movable";
|
||||
import {PositionInterface} from "../Model/PositionInterface";
|
||||
import {adminApi} from "./AdminApi";
|
||||
import Direction = PositionMessage.Direction;
|
||||
import {Gauge} from "prom-client";
|
||||
import {emitError, emitInBatch} from "./IoSocketHelpers";
|
||||
import Jwt from "jsonwebtoken";
|
||||
|
||||
class SocketManager {
|
||||
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>;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.nbClientsGauge = new Gauge({
|
||||
name: 'workadventure_nb_sockets',
|
||||
@ -55,14 +56,14 @@ class SocketManager {
|
||||
}
|
||||
|
||||
handleJoinRoom(client: ExSocketInterface): void {
|
||||
const position = client.position;
|
||||
const viewport = client.viewport;
|
||||
const position = client.position;
|
||||
const viewport = client.viewport;
|
||||
try {
|
||||
this.sockets.set(client.userId, client); //todo: should this be at the end of the function?
|
||||
this.nbClientsGauge.inc();
|
||||
// Let's log server load when a user joins
|
||||
console.log(new Date().toISOString() + ' A user joined (', socketManager.sockets.size, ' connected users)');
|
||||
|
||||
|
||||
//join new previous room
|
||||
const gameRoom = this.joinRoom(client, position);
|
||||
|
||||
@ -592,7 +593,45 @@ class SocketManager {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public handleQueryJitsiJwtMessage(client: ExSocketInterface, queryJitsiJwtMessage: QueryJitsiJwtMessage) {
|
||||
const room = queryJitsiJwtMessage.getJitsiroom();
|
||||
const tag = queryJitsiJwtMessage.getTag(); // FIXME: this is not secure. We should load the JSON for the current room and check rights associated to room instead.
|
||||
|
||||
if (SECRET_JITSI_KEY === '') {
|
||||
throw new Error('You must set the SECRET_JITSI_KEY key to the secret to generate JWT tokens for Jitsi.');
|
||||
}
|
||||
|
||||
// Let's see if the current client has
|
||||
const isAdmin = client.tags.indexOf(tag) !== -1;
|
||||
|
||||
// TODO: fix this when "moderator" property is available
|
||||
|
||||
const jwt = Jwt.sign({
|
||||
"aud": "jitsi",
|
||||
"iss": "meetworkadventure",
|
||||
"sub": "coremeet.workadventu.re",
|
||||
"room": "*"
|
||||
}, SECRET_JITSI_KEY, {
|
||||
expiresIn: '1d',
|
||||
algorithm: "HS256",
|
||||
header:
|
||||
{
|
||||
"alg": "HS256",
|
||||
"typ": "JWT"
|
||||
}
|
||||
});
|
||||
|
||||
const sendJitsiJwtMessage = new SendJitsiJwtMessage();
|
||||
sendJitsiJwtMessage.setJitsiroom(room);
|
||||
sendJitsiJwtMessage.setJwt(jwt);
|
||||
|
||||
const serverToClientMessage = new ServerToClientMessage();
|
||||
serverToClientMessage.setSendjitsijwtmessage(sendJitsiJwtMessage);
|
||||
|
||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||
}
|
||||
}
|
||||
|
||||
export const socketManager = new SocketManager();
|
||||
export const socketManager = new SocketManager();
|
||||
|
@ -72,6 +72,7 @@ services:
|
||||
environment:
|
||||
STARTUP_COMMAND_1: yarn install
|
||||
SECRET_KEY: yourSecretKey
|
||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||
ALLOW_ARTILLERY: "true"
|
||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||
volumes:
|
||||
|
@ -27,6 +27,7 @@ export enum EventMessage{
|
||||
STOP_GLOBAL_MESSAGE = "stop-global-message",
|
||||
|
||||
TELEPORT = "teleport",
|
||||
START_JITSI_ROOM = "start-jitsi-room",
|
||||
}
|
||||
|
||||
export interface PointInterface {
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
WebRtcSignalToServerMessage,
|
||||
WebRtcStartMessage,
|
||||
ReportPlayerMessage,
|
||||
TeleportMessageMessage
|
||||
TeleportMessageMessage, QueryJitsiJwtMessage, SendJitsiJwtMessage
|
||||
} from "../Messages/generated/messages_pb"
|
||||
|
||||
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||
@ -150,6 +150,8 @@ export class RoomConnection implements RoomConnection {
|
||||
this.dispatch(EventMessage.STOP_GLOBAL_MESSAGE, message.getStopglobalmessage());
|
||||
} else if (message.hasTeleportmessagemessage()) {
|
||||
this.dispatch(EventMessage.TELEPORT, message.getTeleportmessagemessage());
|
||||
} else if (message.hasSendjitsijwtmessage()) {
|
||||
this.dispatch(EventMessage.START_JITSI_ROOM, message.getSendjitsijwtmessage());
|
||||
} else {
|
||||
throw new Error('Unknown message received');
|
||||
}
|
||||
@ -501,6 +503,25 @@ export class RoomConnection implements RoomConnection {
|
||||
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||
}
|
||||
|
||||
public emitQueryJitsiJwtMessage(jitsiRoom: string, tag: string|undefined ): void {
|
||||
const queryJitsiJwtMessage = new QueryJitsiJwtMessage();
|
||||
queryJitsiJwtMessage.setJitsiroom(jitsiRoom);
|
||||
if (tag !== undefined) {
|
||||
queryJitsiJwtMessage.setTag(tag);
|
||||
}
|
||||
|
||||
const clientToServerMessage = new ClientToServerMessage();
|
||||
clientToServerMessage.setQueryjitsijwtmessage(queryJitsiJwtMessage);
|
||||
|
||||
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||
}
|
||||
|
||||
public onStartJitsiRoom(callback: (jwt: string, room: string) => void): void {
|
||||
this.onMessage(EventMessage.START_JITSI_ROOM, (message: SendJitsiJwtMessage) => {
|
||||
callback(message.getJwt(), message.getJitsiroom());
|
||||
});
|
||||
}
|
||||
|
||||
public hasTag(tag: string): boolean {
|
||||
return this.tags.includes(tag);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {ITiledMap} from "../Map/ITiledMap";
|
||||
|
||||
export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined) => void;
|
||||
export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined, allProps: Map<string, string | boolean | number>) => void;
|
||||
|
||||
/**
|
||||
* A wrapper around a ITiledMap interface to provide additional capabilities.
|
||||
@ -35,14 +35,14 @@ export class GameMap {
|
||||
for (const [newPropName, newPropValue] of newProps.entries()) {
|
||||
const oldPropValue = oldProps.get(newPropName);
|
||||
if (oldPropValue !== newPropValue) {
|
||||
this.trigger(newPropName, oldPropValue, newPropValue);
|
||||
this.trigger(newPropName, oldPropValue, newPropValue, newProps);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [oldPropName, oldPropValue] of oldProps.entries()) {
|
||||
if (!newProps.has(oldPropName)) {
|
||||
// We found a property that disappeared
|
||||
this.trigger(oldPropName, oldPropValue, undefined);
|
||||
this.trigger(oldPropName, oldPropValue, undefined, newProps);
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,11 +74,11 @@ export class GameMap {
|
||||
return properties;
|
||||
}
|
||||
|
||||
private trigger(propName: string, oldValue: string | number | boolean | undefined, newValue: string | number | boolean | undefined) {
|
||||
private trigger(propName: string, oldValue: string | number | boolean | undefined, newValue: string | number | boolean | undefined, allProps: Map<string, string | boolean | number>) {
|
||||
const callbacksArray = this.callbacks.get(propName);
|
||||
if (callbacksArray !== undefined) {
|
||||
for (const callback of callbacksArray) {
|
||||
callback(newValue, oldValue);
|
||||
callback(newValue, oldValue, allProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,6 +137,8 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
private outlinedItem: ActionableItem|null = null;
|
||||
private userInputManager!: UserInputManager;
|
||||
|
||||
private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
static createFromUrl(room: Room, mapUrlFile: string, gameSceneKey: string|null = null): GameScene {
|
||||
// We use the map URL as a key
|
||||
if (gameSceneKey === null) {
|
||||
@ -460,34 +462,14 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
CoWebsiteManager.loadCoWebsite(newValue as string);
|
||||
}
|
||||
});
|
||||
let jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue) => {
|
||||
this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue, allProps) => {
|
||||
if (newValue === undefined) {
|
||||
this.connection.setSilent(false);
|
||||
jitsiApi?.dispose();
|
||||
CoWebsiteManager.closeCoWebsite();
|
||||
mediaManager.showGameOverlay();
|
||||
this.stopJitsi();
|
||||
} else {
|
||||
CoWebsiteManager.insertCoWebsite((cowebsiteDiv => {
|
||||
const domain = JITSI_URL;
|
||||
const options = {
|
||||
roomName: this.instance + "-" + newValue,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
parentNode: cowebsiteDiv,
|
||||
configOverwrite: {
|
||||
prejoinPageEnabled: false
|
||||
},
|
||||
interfaceConfigOverwrite: {
|
||||
SHOW_CHROME_EXTENSION_BANNER: false,
|
||||
MOBILE_APP_PROMO: false
|
||||
}
|
||||
};
|
||||
jitsiApi = new (window as any).JitsiMeetExternalAPI(domain, options); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
jitsiApi.executeCommand('displayName', gameManager.getPlayerName());
|
||||
}));
|
||||
this.connection.setSilent(true);
|
||||
mediaManager.hideGameOverlay();
|
||||
// TODO: get jitsiRoomAdminTag
|
||||
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
|
||||
|
||||
this.connection.emitQueryJitsiJwtMessage(this.instance + "-" + newValue, adminTag);
|
||||
}
|
||||
})
|
||||
|
||||
@ -597,6 +579,10 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
item.fire(message.event, message.state, message.parameters);
|
||||
}));
|
||||
|
||||
connection.onStartJitsiRoom((jwt, room) => {
|
||||
this.startJitsi(room, jwt);
|
||||
});
|
||||
|
||||
// When connection is performed, let's connect SimplePeer
|
||||
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic);
|
||||
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
|
||||
@ -1191,4 +1177,35 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||
public onCenterChange(): void {
|
||||
this.updateCameraOffset();
|
||||
}
|
||||
|
||||
public startJitsi(roomName: string, jwt: string): void {
|
||||
CoWebsiteManager.insertCoWebsite((cowebsiteDiv => {
|
||||
const domain = JITSI_URL;
|
||||
const options = {
|
||||
roomName: roomName,
|
||||
jwt: jwt,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
parentNode: cowebsiteDiv,
|
||||
configOverwrite: {
|
||||
prejoinPageEnabled: false
|
||||
},
|
||||
interfaceConfigOverwrite: {
|
||||
SHOW_CHROME_EXTENSION_BANNER: false,
|
||||
MOBILE_APP_PROMO: false
|
||||
}
|
||||
};
|
||||
this.jitsiApi = new (window as any).JitsiMeetExternalAPI(domain, options); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
this.jitsiApi.executeCommand('displayName', gameManager.getPlayerName());
|
||||
}));
|
||||
this.connection.setSilent(true);
|
||||
mediaManager.hideGameOverlay();
|
||||
}
|
||||
|
||||
public stopJitsi(): void {
|
||||
this.connection.setSilent(false);
|
||||
this.jitsiApi?.dispose();
|
||||
CoWebsiteManager.closeCoWebsite();
|
||||
mediaManager.showGameOverlay();
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,11 @@ message ReportPlayerMessage {
|
||||
string reportComment = 2;
|
||||
}
|
||||
|
||||
message QueryJitsiJwtMessage {
|
||||
string jitsiRoom = 1;
|
||||
string tag = 2; // FIXME: rather than reading the tag from the query, we should read it from the current map!
|
||||
}
|
||||
|
||||
message ClientToServerMessage {
|
||||
oneof message {
|
||||
UserMovesMessage userMovesMessage = 2;
|
||||
@ -65,6 +70,7 @@ message ClientToServerMessage {
|
||||
PlayGlobalMessage playGlobalMessage = 9;
|
||||
StopGlobalMessage stopGlobalMessage = 10;
|
||||
ReportPlayerMessage reportPlayerMessage = 11;
|
||||
QueryJitsiJwtMessage queryJitsiJwtMessage = 12;
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,6 +173,11 @@ message TeleportMessageMessage{
|
||||
string map = 1;
|
||||
}
|
||||
|
||||
message SendJitsiJwtMessage {
|
||||
string jitsiRoom = 1;
|
||||
string jwt = 2;
|
||||
}
|
||||
|
||||
message ServerToClientMessage {
|
||||
oneof message {
|
||||
BatchMessage batchMessage = 1;
|
||||
@ -179,5 +190,6 @@ message ServerToClientMessage {
|
||||
PlayGlobalMessage playGlobalMessage = 8;
|
||||
StopGlobalMessage stopGlobalMessage = 9;
|
||||
TeleportMessageMessage teleportMessageMessage = 10;
|
||||
SendJitsiJwtMessage sendJitsiJwtMessage = 11;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user