Merge branch 'localAdmin' into codeAPI

This commit is contained in:
CEC 2022-04-20 15:37:02 +02:00
commit 5a416d265e
13 changed files with 324 additions and 126 deletions

View File

@ -9,7 +9,7 @@ class AnalyticsClient {
constructor() { constructor() {
if (POSTHOG_API_KEY && POSTHOG_URL) { if (POSTHOG_API_KEY && POSTHOG_URL) {
this.posthogPromise = import("posthog-js").then(({ default: posthog }) => { this.posthogPromise = import("posthog-js").then(({ default: posthog }) => {
posthog.init(POSTHOG_API_KEY, { api_host: POSTHOG_URL, disable_cookie: true }); posthog.init(POSTHOG_API_KEY, { api_host: POSTHOG_URL });
//the posthog toolbar need a reference in window to be able to work //the posthog toolbar need a reference in window to be able to work
window.posthog = posthog; window.posthog = posthog;
return posthog; return posthog;

View File

@ -1,7 +1,13 @@
<script lang="ts"> <script lang="ts">
import { gameManager } from "../../Phaser/Game/GameManager"; import { gameManager } from "../../Phaser/Game/GameManager";
import { SelectCompanionScene, SelectCompanionSceneName } from "../../Phaser/Login/SelectCompanionScene"; import { SelectCompanionScene, SelectCompanionSceneName } from "../../Phaser/Login/SelectCompanionScene";
import { menuIconVisiblilityStore, menuVisiblilityStore, userIsConnected } from "../../Stores/MenuStore"; import {
menuIconVisiblilityStore,
menuVisiblilityStore,
userIsConnected,
profileAvailable,
getProfileUrl,
} from "../../Stores/MenuStore";
import { selectCompanionSceneVisibleStore } from "../../Stores/SelectCompanionStore"; import { selectCompanionSceneVisibleStore } from "../../Stores/SelectCompanionStore";
import { LoginScene, LoginSceneName } from "../../Phaser/Login/LoginScene"; import { LoginScene, LoginSceneName } from "../../Phaser/Login/LoginScene";
import { loginSceneVisibleStore } from "../../Stores/LoginSceneStore"; import { loginSceneVisibleStore } from "../../Stores/LoginSceneStore";
@ -9,7 +15,6 @@
import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene"; import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene";
import { connectionManager } from "../../Connexion/ConnectionManager"; import { connectionManager } from "../../Connexion/ConnectionManager";
import { PROFILE_URL } from "../../Enum/EnvironmentVariable"; import { PROFILE_URL } from "../../Enum/EnvironmentVariable";
import { localUserStore } from "../../Connexion/LocalUserStore";
import { EnableCameraScene, EnableCameraSceneName } from "../../Phaser/Login/EnableCameraScene"; import { EnableCameraScene, EnableCameraSceneName } from "../../Phaser/Login/EnableCameraScene";
import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore"; import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore";
import btnProfileSubMenuCamera from "../images/btn-menu-profile-camera.svg"; import btnProfileSubMenuCamera from "../images/btn-menu-profile-camera.svg";
@ -47,10 +52,6 @@
return connectionManager.logout(); return connectionManager.logout();
} }
function getProfileUrl() {
return PROFILE_URL + `?token=${localUserStore.getAuthToken()}`;
}
function openEnableCameraScene() { function openEnableCameraScene() {
disableMenuStores(); disableMenuStores();
enableCameraSceneVisibilityStore.showEnableCameraScene(); enableCameraSceneVisibilityStore.showEnableCameraScene();
@ -81,7 +82,7 @@
</div> </div>
<div class="content"> <div class="content">
{#if $userIsConnected} {#if $userIsConnected && $profileAvailable}
<section> <section>
{#if PROFILE_URL != undefined} {#if PROFILE_URL != undefined}
<iframe title="profile" src={getProfileUrl()} /> <iframe title="profile" src={getProfileUrl()} />

View File

@ -290,12 +290,12 @@ class ConnectionManager {
); );
connection.onConnectError((error: object) => { connection.onConnectError((error: object) => {
console.log("An error occurred while connecting to socket server. Retrying"); console.log("onConnectError => An error occurred while connecting to socket server. Retrying");
reject(error); reject(error);
}); });
connection.connectionErrorStream.subscribe((event: CloseEvent) => { connection.connectionErrorStream.subscribe((event: CloseEvent) => {
console.log("An error occurred while connecting to socket server. Retrying"); console.log("connectionErrorStream => An error occurred while connecting to socket server. Retrying");
reject( reject(
new Error( new Error(
"An error occurred while connecting to socket server. Retrying. Code: " + "An error occurred while connecting to socket server. Retrying. Code: " +

View File

@ -23,6 +23,7 @@ export const DISPLAY_TERMS_OF_USE = getEnvConfig("DISPLAY_TERMS_OF_USE") == "tru
export const NODE_ENV = getEnvConfig("NODE_ENV") || "development"; export const NODE_ENV = getEnvConfig("NODE_ENV") || "development";
export const CONTACT_URL = getEnvConfig("CONTACT_URL") || undefined; export const CONTACT_URL = getEnvConfig("CONTACT_URL") || undefined;
export const PROFILE_URL = getEnvConfig("PROFILE_URL") || undefined; export const PROFILE_URL = getEnvConfig("PROFILE_URL") || undefined;
export const IDENTITY_URL = getEnvConfig("IDENTITY_URL") || undefined;
export const POSTHOG_API_KEY: string = (getEnvConfig("POSTHOG_API_KEY") as string) || ""; export const POSTHOG_API_KEY: string = (getEnvConfig("POSTHOG_API_KEY") as string) || "";
export const POSTHOG_URL = getEnvConfig("POSTHOG_URL") || undefined; export const POSTHOG_URL = getEnvConfig("POSTHOG_URL") || undefined;
export const DISABLE_ANONYMOUS: boolean = getEnvConfig("DISABLE_ANONYMOUS") === "true"; export const DISABLE_ANONYMOUS: boolean = getEnvConfig("DISABLE_ANONYMOUS") === "true";

View File

@ -1,17 +1,27 @@
import { get, writable } from "svelte/store"; import { get, writable } from "svelte/store";
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
import { userIsAdminStore } from "./GameStore"; import { userIsAdminStore } from "./GameStore";
import { CONTACT_URL } from "../Enum/EnvironmentVariable"; import { CONTACT_URL, IDENTITY_URL, PROFILE_URL } from "../Enum/EnvironmentVariable";
import { analyticsClient } from "../Administration/AnalyticsClient"; import { analyticsClient } from "../Administration/AnalyticsClient";
import type { Translation } from "../i18n/i18n-types"; import type { Translation } from "../i18n/i18n-types";
import axios from "axios";
import { localUserStore } from "../Connexion/LocalUserStore";
export const menuIconVisiblilityStore = writable(false); export const menuIconVisiblilityStore = writable(false);
export const menuVisiblilityStore = writable(false); export const menuVisiblilityStore = writable(false);
menuVisiblilityStore.subscribe((value) => {
if (value) analyticsClient.openedMenu();
});
export const menuInputFocusStore = writable(false); export const menuInputFocusStore = writable(false);
export const userIsConnected = writable(false); export const userIsConnected = writable(false);
export const profileAvailable = writable(true);
menuVisiblilityStore.subscribe((value) => {
if (value) analyticsClient.openedMenu();
if (userIsConnected && value && IDENTITY_URL != null) {
axios.get(getMeUrl()).catch((err) => {
console.error("menuVisiblilityStore => err => ", err);
profileAvailable.set(false);
});
}
});
let warningContainerTimeout: Timeout | null = null; let warningContainerTimeout: Timeout | null = null;
function createWarningContainerStore() { function createWarningContainerStore() {
@ -173,3 +183,11 @@ export function handleMenuUnregisterEvent(menuName: string) {
subMenusStore.removeScriptingMenu(menuName); subMenusStore.removeScriptingMenu(menuName);
customMenuIframe.delete(menuName); customMenuIframe.delete(menuName);
} }
export function getProfileUrl() {
return PROFILE_URL + `?token=${localUserStore.getAuthToken()}`;
}
export function getMeUrl() {
return IDENTITY_URL + `?token=${localUserStore.getAuthToken()}`;
}

View File

@ -28,6 +28,7 @@ export default defineConfig({
"ADMIN_URL", "ADMIN_URL",
"CONTACT_URL", "CONTACT_URL",
"PROFILE_URL", "PROFILE_URL",
"IDENTITY_URL",
"ICON_URL", "ICON_URL",
"DEBUG_MODE", "DEBUG_MODE",
"STUN_SERVER", "STUN_SERVER",

View File

@ -1,6 +1,6 @@
import { v4 } from "uuid"; import { v4 } from "uuid";
import { BaseHttpController } from "./BaseHttpController"; import { BaseHttpController } from "./BaseHttpController";
import { adminApi } from "../Services/AdminApi"; import { FetchMemberDataByUuidResponse } from "../Services/AdminApi";
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
import { parse } from "query-string"; import { parse } from "query-string";
import { openIDClient } from "../Services/OpenIDClient"; import { openIDClient } from "../Services/OpenIDClient";
@ -19,6 +19,7 @@ export class AuthenticateController extends BaseHttpController {
this.register(); this.register();
this.anonymLogin(); this.anonymLogin();
this.profileCallback(); this.profileCallback();
this.me();
} }
openIDLogin() { openIDLogin() {
@ -180,7 +181,7 @@ export class AuthenticateController extends BaseHttpController {
if (!code && !nonce) { if (!code && !nonce) {
return res.json({ ...resUserData, authToken: token }); return res.json({ ...resUserData, authToken: token });
} }
console.error("Token cannot to be check on OpenId provider"); console.error("Token cannot be checked on OpenId provider");
res.status(500); res.status(500);
res.send("User cannot to be connected on openid provider"); res.send("User cannot to be connected on openid provider");
return; return;
@ -255,7 +256,7 @@ export class AuthenticateController extends BaseHttpController {
try { try {
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false); const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
if (authTokenData.accessToken == undefined) { if (authTokenData.accessToken == undefined) {
throw Error("Token cannot to be logout on Hydra"); throw Error("Token cannot be logout on Hydra");
} }
await openIDClient.logoutUser(authTokenData.accessToken); await openIDClient.logoutUser(authTokenData.accessToken);
} catch (error) { } catch (error) {
@ -320,7 +321,7 @@ export class AuthenticateController extends BaseHttpController {
(async () => { (async () => {
const param = await req.json(); const param = await req.json();
adminApi.setLocale(req.header("accept-language")); adminService.locale = req.header("accept-language");
//todo: what to do if the organizationMemberToken is already used? //todo: what to do if the organizationMemberToken is already used?
const organizationMemberToken: string | null = param.organizationMemberToken; const organizationMemberToken: string | null = param.organizationMemberToken;
@ -328,13 +329,15 @@ export class AuthenticateController extends BaseHttpController {
try { try {
if (typeof organizationMemberToken != "string") throw new Error("No organization token"); if (typeof organizationMemberToken != "string") throw new Error("No organization token");
const data = await adminApi.fetchMemberDataByToken(organizationMemberToken, playUri); const data = await adminService.fetchMemberDataByToken(organizationMemberToken, playUri);
const userUuid = data.userUuid; const userUuid = data.userUuid;
const email = data.email; const email = data.email;
const roomUrl = data.roomUrl; const roomUrl = data.roomUrl;
const mapUrlStart = data.mapUrlStart; const mapUrlStart = data.mapUrlStart;
const authToken = jwtTokenManager.createAuthToken(email || userUuid); const authToken = jwtTokenManager.createAuthToken(email || userUuid);
console.info(data);
res.json({ res.json({
authToken, authToken,
userUuid, userUuid,
@ -414,13 +417,13 @@ export class AuthenticateController extends BaseHttpController {
try { try {
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false); const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
if (authTokenData.accessToken == undefined) { if (authTokenData.accessToken == undefined) {
throw Error("Token cannot to be check on Hydra"); throw Error("Token cannot be checked on OpenID connect provider");
} }
await openIDClient.checkTokenAuth(authTokenData.accessToken); await openIDClient.checkTokenAuth(authTokenData.accessToken);
//get login profile //get login profile
res.status(302); res.status(302);
res.setHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken)); res.setHeader("Location", adminService.getProfileUrl(authTokenData.accessToken));
res.send(""); res.send("");
return; return;
} catch (error) { } catch (error) {
@ -434,4 +437,81 @@ export class AuthenticateController extends BaseHttpController {
} }
}); });
} }
/**
* @openapi
* /me:
* get:
* description: ???
* parameters:
* - name: "token"
* in: "query"
* description: "A JWT authentication token ???"
* required: true
* type: "string"
* responses:
* 200:
* description: Data of user connected
*/
me() {
// @ts-ignore
this.app.get("/me", async (req, res): void => {
const { token } = parse(req.path_query);
try {
//verify connected by token
if (token != undefined) {
try {
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
if (authTokenData.accessToken == undefined) {
throw Error("Token cannot to be checked on Hydra");
}
const me = await openIDClient.checkTokenAuth(authTokenData.accessToken);
//get login profile
res.status(200);
res.json({ ...me });
return;
} catch (error) {
this.castErrorToResponse(error, res);
return;
}
}
} catch (error) {
console.error("me => ERROR", error);
this.castErrorToResponse(error, res);
return;
}
});
}
/**
*
* @param email
* @param playUri
* @param IPAddress
* @return
|object
* @private
*/
private async getUserByUserIdentifier(
email: string,
playUri: string,
IPAddress: string
): Promise<FetchMemberDataByUuidResponse | object> {
let data: FetchMemberDataByUuidResponse = {
email: email,
userUuid: email,
tags: [],
messages: [],
visitCardUrl: null,
textures: [],
userRoomToken: undefined,
};
try {
data = await adminService.fetchMemberDataByUuid(email, playUri, IPAddress, []);
} catch (err) {
console.error("openIDCallback => fetchMemberDataByUuid", err);
}
return data;
}
} }

View File

@ -26,7 +26,7 @@ import {
import { UserMovesMessage } from "../Messages/generated/messages_pb"; import { UserMovesMessage } from "../Messages/generated/messages_pb";
import { parse } from "query-string"; import { parse } from "query-string";
import { AdminSocketTokenData, jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenManager"; import { AdminSocketTokenData, jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenManager";
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; import { FetchMemberDataByUuidResponse } from "../Services/AdminApi";
import { socketManager } from "../Services/SocketManager"; import { socketManager } from "../Services/SocketManager";
import { emitInBatch } from "../Services/IoSocketHelpers"; import { emitInBatch } from "../Services/IoSocketHelpers";
import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable"; import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable";
@ -40,6 +40,7 @@ import { localWokaService } from "../Services/LocalWokaService";
import { WebSocket } from "uWebSockets.js"; import { WebSocket } from "uWebSockets.js";
import { WokaDetail } from "../Messages/JsonMessages/PlayerTextures"; import { WokaDetail } from "../Messages/JsonMessages/PlayerTextures";
import { z } from "zod"; import { z } from "zod";
import { adminService } from "../Services/AdminService";
import { ErrorApiData, isErrorApiData } from "../Messages/JsonMessages/ErrorApiData"; import { ErrorApiData, isErrorApiData } from "../Messages/JsonMessages/ErrorApiData";
/** /**
@ -245,7 +246,7 @@ export class IoSocketController {
const websocketExtensions = req.getHeader("sec-websocket-extensions"); const websocketExtensions = req.getHeader("sec-websocket-extensions");
const IPAddress = req.getHeader("x-forwarded-for"); const IPAddress = req.getHeader("x-forwarded-for");
adminApi.setLocale(req.getHeader("accept-language")); adminService.locale = req.getHeader("accept-language");
const roomId = query.roomId; const roomId = query.roomId;
try { try {
@ -314,7 +315,7 @@ export class IoSocketController {
if (ADMIN_API_URL) { if (ADMIN_API_URL) {
try { try {
try { try {
userData = await adminApi.fetchMemberDataByUuid( userData = await adminService.fetchMemberDataByUuid(
userIdentifier, userIdentifier,
roomId, roomId,
IPAddress, IPAddress,

View File

@ -1,11 +1,8 @@
import { adminApi } from "../Services/AdminApi"; import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
import { ADMIN_API_URL, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"; import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
import { isMapDetailsData, MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
import { InvalidTokenError } from "./InvalidTokenError";
import { parse } from "query-string"; import { parse } from "query-string";
import { BaseHttpController } from "./BaseHttpController"; import { BaseHttpController } from "./BaseHttpController";
import { adminService } from "../Services/AdminService";
export class MapController extends BaseHttpController { export class MapController extends BaseHttpController {
// Returns a map mapping map name to file name of the map // Returns a map mapping map name to file name of the map
@ -107,68 +104,15 @@ export class MapController extends BaseHttpController {
return; return;
} }
adminApi.setLocale(req.header("accept-language")); adminService.locale = req.header("accept-language");
// If no admin URL is set, let's react on '/_/[instance]/[map url]' URLs
if (!ADMIN_API_URL) {
const roomUrl = new URL(query.playUri);
const match = /\/_\/[^/]+\/(.+)/.exec(roomUrl.pathname);
if (!match) {
res.status(404);
res.json({});
return;
}
const mapUrl = roomUrl.protocol + "//" + match[1];
res.json({
mapUrl,
policy_type: GameRoomPolicyTypes.ANONYMOUS_POLICY,
roomSlug: null, // Deprecated
group: null,
tags: [],
contactPage: null,
authenticationMandatory: DISABLE_ANONYMOUS,
} as MapDetailsData);
return;
}
(async () => { (async () => {
try { try {
let userId: string | undefined = undefined;
if (query.authToken != undefined) {
let authTokenData: AuthTokenData;
try {
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string);
userId = authTokenData.identifier;
} catch (e) {
try {
// Decode token, in this case we don't need to create new token.
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string, true);
userId = authTokenData.identifier;
console.info("JWT expire, but decoded", userId);
} catch (e) {
if (e instanceof InvalidTokenError) {
// The token was not good, redirect user on login page
res.status(401);
res.send("Token decrypted error");
return;
} else {
this.castErrorToResponse(e, res);
return;
}
}
}
}
const mapDetails = isMapDetailsData.parse( const mapDetails = isMapDetailsData.parse(
await adminApi.fetchMapDetails(query.playUri as string, userId) await adminService.fetchMapDetails(query.playUri as string, query.authToken as string)
); );
if (DISABLE_ANONYMOUS) { if (DISABLE_ANONYMOUS) mapDetails.authenticationMandatory = true;
mapDetails.authenticationMandatory = true;
}
res.json(mapDetails); res.json(mapDetails);
return; return;

View File

@ -7,6 +7,8 @@ import { z } from "zod";
import { isWokaDetail } from "../Messages/JsonMessages/PlayerTextures"; import { isWokaDetail } from "../Messages/JsonMessages/PlayerTextures";
import qs from "qs"; import qs from "qs";
import { AdminInterface } from "./AdminInterface"; import { AdminInterface } from "./AdminInterface";
import { AuthTokenData, jwtTokenManager } from "./JWTTokenManager";
import { InvalidTokenError } from "../Controller/InvalidTokenError";
export interface AdminBannedData { export interface AdminBannedData {
is_banned: boolean; is_banned: boolean;
@ -27,19 +29,35 @@ export const isFetchMemberDataByUuidResponse = z.object({
export type FetchMemberDataByUuidResponse = z.infer<typeof isFetchMemberDataByUuidResponse>; export type FetchMemberDataByUuidResponse = z.infer<typeof isFetchMemberDataByUuidResponse>;
class AdminApi implements AdminInterface { class AdminApi implements AdminInterface {
private locale: string = "en"; locale: string = "en";
setLocale(locale: string) {
console.info("PUSHER LOCALE SET TO :", locale); async fetchMapDetails(playUri: string, authToken?: string): Promise<MapDetailsData | RoomRedirect> {
this.locale = locale; let userId: string | undefined = undefined;
} if (authToken != undefined) {
/** let authTokenData: AuthTokenData;
* @var playUri: is url of the room try {
* @var userId: can to be undefined or email or uuid authTokenData = jwtTokenManager.verifyJWTToken(authToken);
* @return MapDetailsData|RoomRedirect userId = authTokenData.identifier;
*/ } catch (e) {
async fetchMapDetails(playUri: string, userId?: string): Promise<MapDetailsData | RoomRedirect> { try {
if (!ADMIN_API_URL) { // Decode token, in this case we don't need to create new token.
return Promise.reject(new Error("No admin backoffice set!")); authTokenData = jwtTokenManager.verifyJWTToken(authToken, true);
userId = authTokenData.identifier;
console.info("JWT expire, but decoded", userId);
} catch (e) {
if (e instanceof InvalidTokenError) {
throw new Error("Token decrypted error");
// The token was not good, redirect user on login page
//res.status(401);
//res.send("Token decrypted error");
//return;
} else {
throw new Error("Error on decryption of token :" + e);
//this.castErrorToResponse(e, res);
//return;
}
}
}
} }
const params: { playUri: string; userId?: string } = { const params: { playUri: string; userId?: string } = {
@ -76,9 +94,6 @@ class AdminApi implements AdminInterface {
ipAddress: string, ipAddress: string,
characterLayers: string[] characterLayers: string[]
): Promise<FetchMemberDataByUuidResponse> { ): Promise<FetchMemberDataByUuidResponse> {
if (!ADMIN_API_URL) {
return Promise.reject(new Error("No admin backoffice set!"));
}
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/room/access", { const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/room/access", {
params: { params: {
userIdentifier, userIdentifier,
@ -106,9 +121,6 @@ class AdminApi implements AdminInterface {
} }
async fetchMemberDataByToken(organizationMemberToken: string, playUri: string | null): Promise<AdminApiData> { async fetchMemberDataByToken(organizationMemberToken: string, playUri: string | null): Promise<AdminApiData> {
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. //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/login-url/" + organizationMemberToken, { const res = await Axios.get(ADMIN_API_URL + "/api/login-url/" + organizationMemberToken, {
params: { playUri }, params: { playUri },
@ -132,9 +144,6 @@ class AdminApi implements AdminInterface {
reporterUserUuid: string, reporterUserUuid: string,
reportWorldSlug: string reportWorldSlug: string
) { ) {
if (!ADMIN_API_URL) {
return Promise.reject(new Error("No admin backoffice set!"));
}
return Axios.post( return Axios.post(
`${ADMIN_API_URL}/api/report`, `${ADMIN_API_URL}/api/report`,
{ {
@ -150,9 +159,6 @@ class AdminApi implements AdminInterface {
} }
async verifyBanUser(userUuid: string, ipAddress: string, roomUrl: 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. //todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
return Axios.get( return Axios.get(
ADMIN_API_URL + ADMIN_API_URL +
@ -170,10 +176,6 @@ class AdminApi implements AdminInterface {
} }
async getUrlRoomsFromSameWorld(roomUrl: string): Promise<string[]> { async getUrlRoomsFromSameWorld(roomUrl: string): Promise<string[]> {
if (!ADMIN_API_URL) {
return Promise.reject(new Error("No admin backoffice set!"));
}
return Axios.get(ADMIN_API_URL + "/api/room/sameWorld" + "?roomUrl=" + encodeURIComponent(roomUrl), { return Axios.get(ADMIN_API_URL + "/api/room/sameWorld" + "?roomUrl=" + encodeURIComponent(roomUrl), {
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": this.locale }, headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": this.locale },
}).then((data) => { }).then((data) => {
@ -181,10 +183,6 @@ class AdminApi implements AdminInterface {
}); });
} }
/**
*
* @param accessToken
*/
getProfileUrl(accessToken: string): string { getProfileUrl(accessToken: string): string {
if (!OPID_PROFILE_SCREEN_PROVIDER) { if (!OPID_PROFILE_SCREEN_PROVIDER) {
throw new Error("No admin backoffice set!"); throw new Error("No admin backoffice set!");
@ -192,7 +190,7 @@ class AdminApi implements AdminInterface {
return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`; return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`;
} }
async logoutOauth(token: string) { async logoutOauth(token: string): Promise<void> {
await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`); await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`);
} }
} }

View File

@ -1,10 +1,74 @@
import { FetchMemberDataByUuidResponse } from "./AdminApi"; import { AdminBannedData, FetchMemberDataByUuidResponse } from "./AdminApi";
import { MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
import { RoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
import { AdminApiData } from "../Messages/JsonMessages/AdminApiData";
export interface AdminInterface { export interface AdminInterface {
locale: string;
/**
* @var playUri: is url of the room
* @var userIdentifier: can to be undefined or email or uuid
* @var ipAddress
* @var characterLayers
* @return MapDetailsData|RoomRedirect
*/
fetchMemberDataByUuid( fetchMemberDataByUuid(
userIdentifier: string, userIdentifier: string,
playUri: string, playUri: string,
ipAddress: string, ipAddress: string,
characterLayers: string[] characterLayers: string[]
): Promise<FetchMemberDataByUuidResponse>; ): Promise<FetchMemberDataByUuidResponse>;
/**
* @var playUri: is url of the room
* @var userId: can to be undefined or email or uuid
* @return MapDetailsData|RoomRedirect
*/
fetchMapDetails(playUri: string, authToken?: string): Promise<MapDetailsData | RoomRedirect>;
/**
* @param organizationMemberToken
* @param playUri
* @return AdminApiData
*/
fetchMemberDataByToken(organizationMemberToken: string, playUri: string | null): Promise<AdminApiData>;
/**
* @param reportedUserUuid
* @param reportedUserComment
* @param reporterUserUuid
* @param reportWorldSlug
*/
reportPlayer(
reportedUserUuid: string,
reportedUserComment: string,
reporterUserUuid: string,
reportWorldSlug: string
): Promise<unknown>;
/**
* @param userUuid
* @param ipAddress
* @param roomUrl
* @return AdminBannedData
*/
verifyBanUser(userUuid: string, ipAddress: string, roomUrl: string): Promise<AdminBannedData>;
/**
* @param roomUrl
* @return string[]
*/
getUrlRoomsFromSameWorld(roomUrl: string): Promise<string[]>;
/**
* @param accessToken
* @return string
*/
getProfileUrl(accessToken: string): string;
/**
* @param token
*/
logoutOauth(token: string): Promise<void>;
} }

View File

@ -1,10 +1,17 @@
import { FetchMemberDataByUuidResponse } from "./AdminApi"; import { AdminBannedData, FetchMemberDataByUuidResponse } from "./AdminApi";
import { AdminInterface } from "./AdminInterface"; import { AdminInterface } from "./AdminInterface";
import { MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
import { RoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
import { AdminApiData } from "../Messages/JsonMessages/AdminApiData";
/** /**
* A local class mocking a real admin if no admin is configured. * A local class mocking a real admin if no admin is configured.
*/ */
class LocalAdmin implements AdminInterface { class LocalAdmin implements AdminInterface {
locale: string = "en";
fetchMemberDataByUuid( fetchMemberDataByUuid(
userIdentifier: string, userIdentifier: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -24,6 +31,89 @@ class LocalAdmin implements AdminInterface {
userRoomToken: undefined, userRoomToken: undefined,
}); });
} }
fetchMapDetails(
playUri: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
authToken?: string
): Promise<MapDetailsData | RoomRedirect> {
const roomUrl = new URL(playUri);
const match = /\/_\/[^/]+\/(.+)/.exec(roomUrl.pathname);
if (!match) {
throw new Error("URL format is not good");
}
const mapUrl = roomUrl.protocol + "//" + match[1];
return Promise.resolve({
mapUrl,
policy_type: GameRoomPolicyTypes.ANONYMOUS_POLICY,
tags: [],
authenticationMandatory: DISABLE_ANONYMOUS,
roomSlug: null,
contactPage: null,
group: null,
iframeAuthentication: null,
loadingLogo: null,
loginSceneLogo: null,
});
}
async fetchMemberDataByToken(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
organizationMemberToken: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
playUri: string | null
): Promise<AdminApiData> {
return Promise.reject(new Error("No admin backoffice set!"));
}
reportPlayer(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
reportedUserUuid: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
reportedUserComment: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
reporterUserUuid: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
reportWorldSlug: string
) {
return Promise.reject(new Error("No admin backoffice set!"));
}
async verifyBanUser(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
userUuid: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
ipAddress: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
roomUrl: string
): Promise<AdminBannedData> {
return Promise.reject(new Error("No admin backoffice set!"));
}
async getUrlRoomsFromSameWorld(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
roomUrl: string
): Promise<string[]> {
return Promise.reject(new Error("No admin backoffice set!"));
}
getProfileUrl(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
accessToken: string
): string {
new Error("No admin backoffice set!");
return "";
}
async logoutOauth(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
token: string
): Promise<void> {
return Promise.reject(new Error("No admin backoffice set!"));
}
} }
export const localAdmin = new LocalAdmin(); export const localAdmin = new LocalAdmin();

View File

@ -44,7 +44,6 @@ import {
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
import { ADMIN_API_URL, JITSI_ISS, JITSI_URL, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable"; import { ADMIN_API_URL, JITSI_ISS, JITSI_URL, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
import { adminApi } from "./AdminApi";
import { emitInBatch } from "./IoSocketHelpers"; import { emitInBatch } from "./IoSocketHelpers";
import Jwt from "jsonwebtoken"; import Jwt from "jsonwebtoken";
import { clientEventsEmitter } from "./ClientEventsEmitter"; import { clientEventsEmitter } from "./ClientEventsEmitter";
@ -55,6 +54,7 @@ import Debug from "debug";
import { ExAdminSocketInterface } from "../Model/Websocket/ExAdminSocketInterface"; import { ExAdminSocketInterface } from "../Model/Websocket/ExAdminSocketInterface";
import { compressors } from "hyper-express"; import { compressors } from "hyper-express";
import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
import { adminService } from "./AdminService";
import { ErrorApiData } from "../Messages/JsonMessages/ErrorApiData"; import { ErrorApiData } from "../Messages/JsonMessages/ErrorApiData";
const debug = Debug("socket"); const debug = Debug("socket");
@ -359,7 +359,7 @@ export class SocketManager implements ZoneEventListener {
async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) { async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
try { try {
await adminApi.reportPlayer( await adminService.reportPlayer(
reportPlayerMessage.getReporteduseruuid(), reportPlayerMessage.getReporteduseruuid(),
reportPlayerMessage.getReportcomment(), reportPlayerMessage.getReportcomment(),
client.userUuid, client.userUuid,
@ -445,7 +445,7 @@ export class SocketManager implements ZoneEventListener {
} }
public async updateRoomWithAdminData(room: PusherRoom): Promise<void> { public async updateRoomWithAdminData(room: PusherRoom): Promise<void> {
const data = await adminApi.fetchMapDetails(room.roomUrl); const data = await adminService.fetchMapDetails(room.roomUrl);
const mapDetailsData = isMapDetailsData.safeParse(data); const mapDetailsData = isMapDetailsData.safeParse(data);
if (mapDetailsData.success) { if (mapDetailsData.success) {
@ -696,7 +696,7 @@ export class SocketManager implements ZoneEventListener {
let tabUrlRooms: string[]; let tabUrlRooms: string[];
if (playGlobalMessageEvent.getBroadcasttoworld()) { if (playGlobalMessageEvent.getBroadcasttoworld()) {
tabUrlRooms = await adminApi.getUrlRoomsFromSameWorld(clientRoomUrl); tabUrlRooms = await adminService.getUrlRoomsFromSameWorld(clientRoomUrl);
} else { } else {
tabUrlRooms = [clientRoomUrl]; tabUrlRooms = [clientRoomUrl];
} }