From 317e0d37875e9c791ada5e0e622a1084dadb363e Mon Sep 17 00:00:00 2001 From: CEC Date: Thu, 14 Apr 2022 11:33:00 +0200 Subject: [PATCH] Implement all use of AdminService Implement and generalize adminApi and localAdmin from adminInterface --- .../src/Controller/AuthenticateController.ts | 9 +- pusher/src/Controller/IoSocketController.ts | 13 +-- pusher/src/Controller/MapController.ts | 68 ++------------- pusher/src/Services/AdminApi.ts | 66 ++++++++------- pusher/src/Services/AdminInterface.ts | 82 ++++++++++++++++++- pusher/src/Services/LocalAdmin.ts | 65 ++++++++++++++- pusher/src/Services/SocketManager.ts | 8 +- 7 files changed, 197 insertions(+), 114 deletions(-) diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index 88e9d6ff..54bd4400 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -1,6 +1,5 @@ import { v4 } from "uuid"; import { BaseHttpController } from "./BaseHttpController"; -import { adminApi } from "../Services/AdminApi"; import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; import { parse } from "query-string"; import { openIDClient } from "../Services/OpenIDClient"; @@ -320,7 +319,7 @@ export class AuthenticateController extends BaseHttpController { (async () => { 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? const organizationMemberToken: string | null = param.organizationMemberToken; @@ -328,13 +327,15 @@ export class AuthenticateController extends BaseHttpController { try { 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 email = data.email; const roomUrl = data.roomUrl; const mapUrlStart = data.mapUrlStart; const authToken = jwtTokenManager.createAuthToken(email || userUuid); + + console.info(data); res.json({ authToken, userUuid, @@ -420,7 +421,7 @@ export class AuthenticateController extends BaseHttpController { //get login profile res.status(302); - res.setHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken)); + res.setHeader("Location", adminService.getProfileUrl(authTokenData.accessToken)); res.send(""); return; } catch (error) { diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index 97af0faf..a8847d9c 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -26,7 +26,7 @@ import { import { UserMovesMessage } from "../Messages/generated/messages_pb"; import { parse } from "query-string"; import { AdminSocketTokenData, jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenManager"; -import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; +import { FetchMemberDataByUuidResponse } from "../Services/AdminApi"; import { socketManager } from "../Services/SocketManager"; import { emitInBatch } from "../Services/IoSocketHelpers"; 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 { WokaDetail } from "../Messages/JsonMessages/PlayerTextures"; import { z } from "zod"; +import {adminService} from "../Services/AdminService"; /** * The object passed between the "open" and the "upgrade" methods when opening a websocket @@ -236,7 +237,7 @@ export class IoSocketController { const websocketExtensions = req.getHeader("sec-websocket-extensions"); const IPAddress = req.getHeader("x-forwarded-for"); - adminApi.setLocale(req.getHeader("accept-language")); + adminService.locale = req.getHeader("accept-language"); const roomId = query.roomId; try { @@ -305,7 +306,7 @@ export class IoSocketController { if (ADMIN_API_URL) { try { try { - userData = await adminApi.fetchMemberDataByUuid( + userData = await adminService.fetchMemberDataByUuid( userIdentifier, roomId, IPAddress, @@ -328,9 +329,9 @@ export class IoSocketController { { rejected: true, reason: "error", - message: err?.response?.data.code, - status: err?.response?.status, - error: err?.response?.data, + message: err.response.data.code, + status: err.response.status, + error: err.response.data, roomId, } as UpgradeFailedData, websocketKey, diff --git a/pusher/src/Controller/MapController.ts b/pusher/src/Controller/MapController.ts index 3893b4b9..c56fbb16 100644 --- a/pusher/src/Controller/MapController.ts +++ b/pusher/src/Controller/MapController.ts @@ -1,11 +1,8 @@ -import { adminApi } from "../Services/AdminApi"; -import { ADMIN_API_URL, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"; -import { GameRoomPolicyTypes } from "../Model/PusherRoom"; -import { isMapDetailsData, MapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; -import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; -import { InvalidTokenError } from "./InvalidTokenError"; +import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"; +import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; import { parse } from "query-string"; import { BaseHttpController } from "./BaseHttpController"; +import {adminService} from "../Services/AdminService"; export class MapController extends BaseHttpController { // Returns a map mapping map name to file name of the map @@ -107,68 +104,15 @@ export class MapController extends BaseHttpController { return; } - adminApi.setLocale(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; - } + adminService.locale = req.header("accept-language"); (async () => { 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( - await adminApi.fetchMapDetails(query.playUri as string, userId) + await adminService.fetchMapDetails(query.playUri as string, query.authToken as string) ); - if (DISABLE_ANONYMOUS) { - mapDetails.authenticationMandatory = true; - } + if (DISABLE_ANONYMOUS) mapDetails.authenticationMandatory = true; res.json(mapDetails); return; diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index 403688ac..a4b3ed5f 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -7,6 +7,8 @@ import { z } from "zod"; import { isWokaDetail } from "../Messages/JsonMessages/PlayerTextures"; import qs from "qs"; import { AdminInterface } from "./AdminInterface"; +import {AuthTokenData, jwtTokenManager} from "./JWTTokenManager"; +import {InvalidTokenError} from "../Controller/InvalidTokenError"; export interface AdminBannedData { is_banned: boolean; @@ -27,19 +29,35 @@ export const isFetchMemberDataByUuidResponse = z.object({ export type FetchMemberDataByUuidResponse = z.infer; class AdminApi implements AdminInterface { - private locale: string = "en"; - setLocale(locale: string) { - console.info("PUSHER LOCALE SET TO :", locale); - this.locale = locale; - } - /** - * @var playUri: is url of the room - * @var userId: can to be undefined or email or uuid - * @return MapDetailsData|RoomRedirect - */ - async fetchMapDetails(playUri: string, userId?: string): Promise { - if (!ADMIN_API_URL) { - return Promise.reject(new Error("No admin backoffice set!")); + locale: string = "en"; + + async fetchMapDetails(playUri: string, authToken?: string): Promise { + let userId: string | undefined = undefined; + if (authToken != undefined) { + let authTokenData: AuthTokenData; + try { + authTokenData = jwtTokenManager.verifyJWTToken(authToken); + userId = authTokenData.identifier; + } catch (e) { + try { + // Decode token, in this case we don't need to create new token. + 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 } = { @@ -76,9 +94,6 @@ class AdminApi implements AdminInterface { ipAddress: string, characterLayers: string[] ): Promise { - if (!ADMIN_API_URL) { - return Promise.reject(new Error("No admin backoffice set!")); - } const res = await Axios.get>(ADMIN_API_URL + "/api/room/access", { params: { userIdentifier, @@ -106,9 +121,6 @@ class AdminApi implements AdminInterface { } async fetchMemberDataByToken(organizationMemberToken: string, playUri: string | null): Promise { - 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. const res = await Axios.get(ADMIN_API_URL + "/api/login-url/" + organizationMemberToken, { params: { playUri }, @@ -132,9 +144,6 @@ class AdminApi implements AdminInterface { reporterUserUuid: string, reportWorldSlug: string ) { - if (!ADMIN_API_URL) { - return Promise.reject(new Error("No admin backoffice set!")); - } return Axios.post( `${ADMIN_API_URL}/api/report`, { @@ -150,9 +159,6 @@ class AdminApi implements AdminInterface { } async verifyBanUser(userUuid: string, ipAddress: string, roomUrl: string): Promise { - 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 + @@ -170,10 +176,6 @@ class AdminApi implements AdminInterface { } async getUrlRoomsFromSameWorld(roomUrl: string): Promise { - 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), { headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": this.locale }, }).then((data) => { @@ -181,10 +183,6 @@ class AdminApi implements AdminInterface { }); } - /** - * - * @param accessToken - */ getProfileUrl(accessToken: string): string { if (!OPID_PROFILE_SCREEN_PROVIDER) { throw new Error("No admin backoffice set!"); @@ -192,7 +190,7 @@ class AdminApi implements AdminInterface { return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`; } - async logoutOauth(token: string) { + async logoutOauth(token: string): Promise{ await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`); } } diff --git a/pusher/src/Services/AdminInterface.ts b/pusher/src/Services/AdminInterface.ts index 66121316..88e01d61 100644 --- a/pusher/src/Services/AdminInterface.ts +++ b/pusher/src/Services/AdminInterface.ts @@ -1,10 +1,90 @@ -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 { + 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( userIdentifier: string, playUri: string, ipAddress: string, characterLayers: string[] ): Promise; + + /** + * @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; + + /** + * @param organizationMemberToken + * @param playUri + * @return AdminApiData + */ + fetchMemberDataByToken( + organizationMemberToken: string, + playUri: string | null + ): Promise; + + /** + * @param reportedUserUuid + * @param reportedUserComment + * @param reporterUserUuid + * @param reportWorldSlug + */ + reportPlayer( + reportedUserUuid: string, + reportedUserComment: string, + reporterUserUuid: string, + reportWorldSlug: string + ): Promise; + + /** + * @param userUuid + * @param ipAddress + * @param roomUrl + * @return AdminBannedData + */ + verifyBanUser( + userUuid: string, + ipAddress: string, + roomUrl: string + ): Promise; + + /** + * @param roomUrl + * @return string[] + */ + getUrlRoomsFromSameWorld( + roomUrl: string + ): Promise; + + /** + * @param accessToken + * @return string + */ + getProfileUrl( + accessToken: string + ): string; + + /** + * @param token + */ + logoutOauth( + token: string + ): Promise; } diff --git a/pusher/src/Services/LocalAdmin.ts b/pusher/src/Services/LocalAdmin.ts index 74edb1c3..263378b6 100644 --- a/pusher/src/Services/LocalAdmin.ts +++ b/pusher/src/Services/LocalAdmin.ts @@ -1,14 +1,18 @@ -import {FetchMemberDataByUuidResponse} from "./AdminApi"; +import {AdminBannedData, FetchMemberDataByUuidResponse} from "./AdminApi"; 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 {ADMIN_API_URL, DISABLE_ANONYMOUS, OPID_PROFILE_SCREEN_PROVIDER} from "../Enum/EnvironmentVariable"; +import {AdminApiData} from "../Messages/JsonMessages/AdminApiData"; +import Axios from "axios"; /** * A local class mocking a real admin if no admin is configured. */ class LocalAdmin implements AdminInterface { + locale: string = "en"; + fetchMemberDataByUuid( userIdentifier: string, // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -32,7 +36,7 @@ class LocalAdmin implements AdminInterface { fetchMapDetails( playUri: string, // eslint-disable-next-line @typescript-eslint/no-unused-vars - userId?: string + authToken?: string ): Promise { const roomUrl = new URL(playUri); @@ -57,6 +61,61 @@ class LocalAdmin implements AdminInterface { 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 { + 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 { + return Promise.reject(new Error("No admin backoffice set!")); + } + + async getUrlRoomsFromSameWorld( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + roomUrl: string + ): Promise { + 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{ + return Promise.reject(new Error("No admin backoffice set!")); + } } export const localAdmin = new LocalAdmin(); diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 545144b7..f42d6324 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -44,7 +44,6 @@ import { } from "../Messages/generated/messages_pb"; import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; import { ADMIN_API_URL, JITSI_ISS, JITSI_URL, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable"; -import { adminApi } from "./AdminApi"; import { emitInBatch } from "./IoSocketHelpers"; import Jwt from "jsonwebtoken"; import { clientEventsEmitter } from "./ClientEventsEmitter"; @@ -55,6 +54,7 @@ import Debug from "debug"; import { ExAdminSocketInterface } from "../Model/Websocket/ExAdminSocketInterface"; import { compressors } from "hyper-express"; import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; +import {adminService} from "./AdminService"; const debug = Debug("socket"); @@ -358,7 +358,7 @@ export class SocketManager implements ZoneEventListener { async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) { try { - await adminApi.reportPlayer( + await adminService.reportPlayer( reportPlayerMessage.getReporteduseruuid(), reportPlayerMessage.getReportcomment(), client.userUuid, @@ -444,7 +444,7 @@ export class SocketManager implements ZoneEventListener { } public async updateRoomWithAdminData(room: PusherRoom): Promise { - const data = await adminApi.fetchMapDetails(room.roomUrl); + const data = await adminService.fetchMapDetails(room.roomUrl); const mapDetailsData = isMapDetailsData.safeParse(data); if (mapDetailsData.success) { @@ -702,7 +702,7 @@ export class SocketManager implements ZoneEventListener { let tabUrlRooms: string[]; if (playGlobalMessageEvent.getBroadcasttoworld()) { - tabUrlRooms = await adminApi.getUrlRoomsFromSameWorld(clientRoomUrl); + tabUrlRooms = await adminService.getUrlRoomsFromSameWorld(clientRoomUrl); } else { tabUrlRooms = [clientRoomUrl]; }