diff --git a/deeployer.libsonnet b/deeployer.libsonnet index d3320bc0..c4b34e38 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -62,6 +62,7 @@ } + (if adminUrl != null then { "ADMIN_API_URL": adminUrl, "ADMIN_API_TOKEN": env.ADMIN_API_TOKEN, + "ADMIN_SOCKETS_TOKEN": env.ADMIN_SOCKETS_TOKEN, } else {}) }, "front": { diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index d626fc05..871ec3b9 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -269,7 +269,7 @@ class IframeListener { registerScript(scriptUrl: string): Promise { return new Promise((resolve, reject) => { - console.log("Loading map related script at ", scriptUrl); + console.info("Loading map related script at ", scriptUrl); if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") { // Using external iframe mode ( diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 9316e37f..87f23843 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -111,7 +111,7 @@ class ConnectionManager { this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl())); try { - await this.checkAuthUserConnexion(); + await this.checkAuthUserConnexion(this._currentRoom.key); analyticsClient.loggedWithSso(); } catch (err) { console.error(err); @@ -177,6 +177,9 @@ class ConnectionManager { //before set token of user we must load room and all information. For example the mandatory authentication could be require on current room this._currentRoom = await Room.createRoom(new URL(roomPath)); + //defined last room url this room path + localUserStore.setLastRoomUrl(this._currentRoom.key); + //todo: add here some kind of warning if authToken has expired. if (!this.authToken && !this._currentRoom.authenticationMandatory) { await this.anonymousLogin(); @@ -293,7 +296,7 @@ class ConnectionManager { return this.connexionType; } - async checkAuthUserConnexion() { + async checkAuthUserConnexion(playUri: string) { //set connected store for menu at false userIsConnected.set(false); @@ -310,10 +313,12 @@ class ConnectionManager { throw "No Auth code provided"; } } - const { authToken } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce, token } }).then( + const { authToken, userUuid, textures, email } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce, token } }).then( (res) => res.data ); localUserStore.setAuthToken(authToken); + this.localUser = new LocalUser(userUuid, textures, email); + localUserStore.saveUser(this.localUser); this.authToken = authToken; //user connected, set connected store for menu at true diff --git a/front/src/Stores/ChatStore.ts b/front/src/Stores/ChatStore.ts index 6b1025be..3b097a9a 100644 --- a/front/src/Stores/ChatStore.ts +++ b/front/src/Stores/ChatStore.ts @@ -67,6 +67,9 @@ function createChatMessagesStore() { }); }, addPersonnalMessage(text: string) { + //post message iframe listener + iframeListener.sendUserInputChat(text); + newChatMessageStore.set(text); update((list) => { const lastMessage = list[list.length - 1]; diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index 70e333a8..21b636f9 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -1,7 +1,7 @@ import { v4 } from "uuid"; import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; import { BaseController } from "./BaseController"; -import { adminApi } from "../Services/AdminApi"; +import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; import { parse } from "query-string"; import { openIDClient } from "../Services/OpenIDClient"; @@ -56,7 +56,8 @@ export class AuthenticateController extends BaseController { res.onAborted(() => { console.warn("/message request was aborted"); }); - const { code, nonce, token } = parse(req.getQuery()); + const IPAddress = req.getHeader("x-forwarded-for"); + const { code, nonce, token, playUri } = parse(req.getQuery()); try { //verify connected by token if (token != undefined) { @@ -68,7 +69,7 @@ export class AuthenticateController extends BaseController { const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken); res.writeStatus("200"); this.addCorsHeaders(res); - return res.end(JSON.stringify({ authToken: token })); + return res.end(JSON.stringify({ ...data, authToken: token })); } catch (err) { console.info("User was not connected", err); } @@ -81,9 +82,14 @@ export class AuthenticateController extends BaseController { throw new Error("No email in the response"); } const authToken = jwtTokenManager.createAuthToken(email, userInfo.access_token); + + //Get user data from Admin Back Office + //This is very important to create User Local in LocalStorage in WorkAdventure + const data = await this.getUserByUserIdentifier(email, playUri as string, IPAddress); + res.writeStatus("200"); this.addCorsHeaders(res); - return res.end(JSON.stringify({ authToken })); + return res.end(JSON.stringify({ ...data, authToken })); } catch (e) { console.error("openIDCallback => ERROR", e); return this.errorToResponse(e, res); @@ -229,4 +235,26 @@ export class AuthenticateController extends BaseController { } }); } + + /** + * + * @param email + * @param playUri + * @param IPAddress + * @return FetchMemberDataByUuidResponse|object + * @private + */ + private async getUserByUserIdentifier( + email: string, + playUri: string, + IPAddress: string + ): Promise { + let data: FetchMemberDataByUuidResponse | object = {}; + try { + data = await adminApi.fetchMemberDataByUuid(email, playUri, IPAddress); + } catch (err) { + console.error("openIDCallback => fetchMemberDataByUuid", err); + } + return data; + } } diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index f73e44fd..9b6c1510 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -26,10 +26,9 @@ import { jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenMana import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; import { SocketManager, socketManager } from "../Services/SocketManager"; import { emitInBatch } from "../Services/IoSocketHelpers"; -import { ADMIN_API_TOKEN, ADMIN_API_URL, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable"; +import { ADMIN_SOCKETS_TOKEN, ADMIN_API_URL, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable"; import { Zone } from "_Model/Zone"; import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"; -import { v4 } from "uuid"; import { CharacterTexture } from "../Services/AdminApi/CharacterTexture"; export class IoSocketController { @@ -48,15 +47,19 @@ export class IoSocketController { const websocketProtocol = req.getHeader("sec-websocket-protocol"); const websocketExtensions = req.getHeader("sec-websocket-extensions"); const token = query.token; - if (token !== ADMIN_API_TOKEN) { - console.log("Admin access refused for token: " + token); + let authorizedRoomIds: string[]; + try { + const data = jwtTokenManager.verifyAdminSocketToken(token as string); + authorizedRoomIds = data.authorizedRoomIds; + } catch (e) { + console.error("Admin access refused for token: " + token); res.writeStatus("401 Unauthorized").end("Incorrect token"); return; } const roomId = query.roomId; - if (typeof roomId !== "string") { - console.error("Received"); - res.writeStatus("400 Bad Request").end("Missing room id"); + if (typeof roomId !== "string" || !authorizedRoomIds.includes(roomId)) { + console.error("Invalid room id"); + res.writeStatus("403 Bad Request").end("Invalid room id"); return; } @@ -70,8 +73,6 @@ export class IoSocketController { }, message: (ws, arrayBuffer, isBinary): void => { try { - const roomId = ws.roomId as string; - //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))); diff --git a/pusher/src/Enum/EnvironmentVariable.ts b/pusher/src/Enum/EnvironmentVariable.ts index 23d2c23f..3b55579f 100644 --- a/pusher/src/Enum/EnvironmentVariable.ts +++ b/pusher/src/Enum/EnvironmentVariable.ts @@ -4,6 +4,7 @@ const API_URL = process.env.API_URL || ""; const ADMIN_API_URL = process.env.ADMIN_API_URL || ""; const ADMIN_URL = process.env.ADMIN_URL || ""; const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken"; +export const ADMIN_SOCKETS_TOKEN = process.env.ADMIN_SOCKETS_TOKEN || "myapitoken"; const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80; const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL; const JITSI_ISS = process.env.JITSI_ISS || ""; diff --git a/pusher/src/Services/JWTTokenManager.ts b/pusher/src/Services/JWTTokenManager.ts index 2f482dbf..40b5b824 100644 --- a/pusher/src/Services/JWTTokenManager.ts +++ b/pusher/src/Services/JWTTokenManager.ts @@ -1,4 +1,4 @@ -import { ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY } from "../Enum/EnvironmentVariable"; +import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, ALLOW_ARTILLERY, SECRET_KEY } from "../Enum/EnvironmentVariable"; import { uuid } from "uuidv4"; import Jwt, { verify } from "jsonwebtoken"; import { TokenInterface } from "../Controller/AuthenticateController"; @@ -8,9 +8,16 @@ export interface AuthTokenData { identifier: string; //will be a email if logged in or an uuid if anonymous accessToken?: string; } +export interface AdminSocketTokenData { + authorizedRoomIds: string[]; //the list of rooms the client is authorized to read from. +} export const tokenInvalidException = "tokenInvalid"; class JWTTokenManager { + public verifyAdminSocketToken(token: string): AdminSocketTokenData { + return Jwt.verify(token, ADMIN_SOCKETS_TOKEN) as AdminSocketTokenData; + } + public createAuthToken(identifier: string, accessToken?: string) { return Jwt.sign({ identifier, accessToken }, SECRET_KEY, { expiresIn: "30d" }); }