Migrating away from the notion of public/private URL in WorkAdventure Github repository

The notion of public/private repositories (with /_/ and /@/ URLs) is specific to the SAAS version of WorkAdventure.
It would be better to avoid leaking the organization/world/room structure of the private SAAS URLs inside the WorkAdventure Github project.

Rather than sending http://admin_host/api/map?organizationSlug=...&worldSlug=...&roomSlug=...., we are now sending /api/map&playUri=...
where playUri is the full URL of the current game.
This allows the backend to act as a complete router.
The front (and the pusher) will be able to completely ignore the specifics of URL building (with /@/ and /_/ URLs, etc...)
Those details will live only in the admin server, which is way cleaner (and way more powerful).
This commit is contained in:
David Négrier 2021-07-13 19:09:07 +02:00
parent f2ca7b2b16
commit c9fa9b9a92
20 changed files with 292 additions and 343 deletions

View file

@ -3,9 +3,7 @@ import Axios from "axios";
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
export interface AdminApiData {
organizationSlug: string;
worldSlug: string;
roomSlug: string;
roomUrl: string;
mapUrlStart: string;
tags: string[];
policy_type: number;
@ -43,24 +41,15 @@ export interface FetchMemberDataByUuidResponse {
}
class AdminApi {
async fetchMapDetails(
organizationSlug: string,
worldSlug: string,
roomSlug: string | undefined
): Promise<MapDetailsData> {
async fetchMapDetails(playUri: string): Promise<MapDetailsData> {
if (!ADMIN_API_URL) {
return Promise.reject(new Error("No admin backoffice set!"));
}
const params: { organizationSlug: string; worldSlug: string; roomSlug?: string } = {
organizationSlug,
worldSlug,
const params: { playUri: string } = {
playUri,
};
if (roomSlug) {
params.roomSlug = roomSlug;
}
const res = await Axios.get(ADMIN_API_URL + "/api/map", {
headers: { Authorization: `${ADMIN_API_TOKEN}` },
params,
@ -121,26 +110,20 @@ class AdminApi {
);
}
async verifyBanUser(
organizationMemberToken: string,
ipAddress: string,
organization: string,
world: string
): Promise<AdminBannedData> {
async verifyBanUser(userUuid: string, ipAddress: string, roomUrl: string): Promise<AdminBannedData> {
if (!ADMIN_API_URL) {
return Promise.reject(new Error("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 +
"/api/ban" +
"?ipAddress=" +
ipAddress +
encodeURIComponent(ipAddress) +
"&token=" +
organizationMemberToken,
encodeURIComponent(userUuid) +
"&roomUrl=" +
encodeURIComponent(roomUrl),
{ headers: { Authorization: `${ADMIN_API_TOKEN}` } }
).then((data) => {
return data.data;

View file

@ -9,7 +9,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, ipAddress?: string, room?: string): Promise<string> {
public async getUserUuidFromToken(token: unknown, ipAddress?: string, roomUrl?: string): Promise<string> {
if (!token) {
throw new Error("An authentication error happened, a user tried to connect without a token.");
}
@ -50,8 +50,8 @@ class JWTTokenManager {
if (ADMIN_API_URL) {
//verify user in admin
let promise = new Promise((resolve) => resolve());
if (ipAddress && room) {
promise = this.verifyBanUser(tokenInterface.userUuid, ipAddress, room);
if (ipAddress && roomUrl) {
promise = this.verifyBanUser(tokenInterface.userUuid, ipAddress, roomUrl);
}
promise
.then(() => {
@ -79,19 +79,9 @@ 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];
private verifyBanUser(userUuid: string, ipAddress: string, roomUrl: string): Promise<AdminBannedData> {
return adminApi
.verifyBanUser(userUuid, ipAddress, organization, world)
.verifyBanUser(userUuid, ipAddress, roomUrl)
.then((data: AdminBannedData) => {
if (data && data.is_banned) {
throw new Error("User was banned");

View file

@ -32,7 +32,7 @@ import {
EmotePromptMessage,
} from "../Messages/generated/messages_pb";
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
import { JITSI_ISS, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
import { ADMIN_API_URL, JITSI_ISS, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
import { adminApi, CharacterTexture } from "./AdminApi";
import { emitInBatch } from "./IoSocketHelpers";
import Jwt from "jsonwebtoken";
@ -358,23 +358,24 @@ export class SocketManager implements ZoneEventListener {
}
}
async getOrCreateRoom(roomId: string): Promise<PusherRoom> {
async getOrCreateRoom(roomUrl: string): Promise<PusherRoom> {
//check and create new world for a room
let world = this.rooms.get(roomId);
if (world === undefined) {
world = new PusherRoom(roomId, this);
if (!world.public) {
await this.updateRoomWithAdminData(world);
let room = this.rooms.get(roomUrl);
if (room === undefined) {
room = new PusherRoom(roomUrl, this);
if (ADMIN_API_URL) {
await this.updateRoomWithAdminData(room);
}
this.rooms.set(roomId, world);
this.rooms.set(roomUrl, room);
}
return Promise.resolve(world);
return room;
}
public async updateRoomWithAdminData(world: PusherRoom): Promise<void> {
const data = await adminApi.fetchMapDetails(world.organizationSlug, world.worldSlug, world.roomSlug);
world.tags = data.tags;
world.policyType = Number(data.policy_type);
public async updateRoomWithAdminData(room: PusherRoom): Promise<void> {
const data = await adminApi.fetchMapDetails(room.roomUrl);
room.tags = data.tags;
room.policyType = Number(data.policy_type);
}
emitPlayGlobalMessage(client: ExSocketInterface, playglobalmessage: PlayGlobalMessage) {