Merge branch 'master' of github.com:thecodingmachine/workadventure into develop

# Conflicts:
#	front/src/Connexion/ConnectionManager.ts
#	pusher/src/Controller/AuthenticateController.ts
#	pusher/src/Controller/IoSocketController.ts
#	pusher/src/Services/JWTTokenManager.ts
This commit is contained in:
David Négrier 2021-11-15 16:30:45 +01:00
commit 4e042389c5
8 changed files with 64 additions and 18 deletions

View File

@ -62,6 +62,7 @@
} + (if adminUrl != null then { } + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl, "ADMIN_API_URL": adminUrl,
"ADMIN_API_TOKEN": env.ADMIN_API_TOKEN, "ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
"ADMIN_SOCKETS_TOKEN": env.ADMIN_SOCKETS_TOKEN,
} else {}) } else {})
}, },
"front": { "front": {

View File

@ -269,7 +269,7 @@ class IframeListener {
registerScript(scriptUrl: string): Promise<void> { registerScript(scriptUrl: string): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((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") { if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
// Using external iframe mode ( // Using external iframe mode (

View File

@ -111,7 +111,7 @@ class ConnectionManager {
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl())); this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
try { try {
await this.checkAuthUserConnexion(); await this.checkAuthUserConnexion(this._currentRoom.key);
analyticsClient.loggedWithSso(); analyticsClient.loggedWithSso();
} catch (err) { } catch (err) {
console.error(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 //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)); 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. //todo: add here some kind of warning if authToken has expired.
if (!this.authToken && !this._currentRoom.authenticationMandatory) { if (!this.authToken && !this._currentRoom.authenticationMandatory) {
await this.anonymousLogin(); await this.anonymousLogin();
@ -293,7 +296,7 @@ class ConnectionManager {
return this.connexionType; return this.connexionType;
} }
async checkAuthUserConnexion() { async checkAuthUserConnexion(playUri: string) {
//set connected store for menu at false //set connected store for menu at false
userIsConnected.set(false); userIsConnected.set(false);
@ -310,10 +313,12 @@ class ConnectionManager {
throw "No Auth code provided"; 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 (res) => res.data
); );
localUserStore.setAuthToken(authToken); localUserStore.setAuthToken(authToken);
this.localUser = new LocalUser(userUuid, textures, email);
localUserStore.saveUser(this.localUser);
this.authToken = authToken; this.authToken = authToken;
//user connected, set connected store for menu at true //user connected, set connected store for menu at true

View File

@ -67,6 +67,9 @@ function createChatMessagesStore() {
}); });
}, },
addPersonnalMessage(text: string) { addPersonnalMessage(text: string) {
//post message iframe listener
iframeListener.sendUserInputChat(text);
newChatMessageStore.set(text); newChatMessageStore.set(text);
update((list) => { update((list) => {
const lastMessage = list[list.length - 1]; const lastMessage = list[list.length - 1];

View File

@ -1,7 +1,7 @@
import { v4 } from "uuid"; import { v4 } from "uuid";
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
import { BaseController } from "./BaseController"; import { BaseController } from "./BaseController";
import { adminApi } from "../Services/AdminApi"; import { adminApi, 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";
@ -56,7 +56,8 @@ export class AuthenticateController extends BaseController {
res.onAborted(() => { res.onAborted(() => {
console.warn("/message request was aborted"); 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 { try {
//verify connected by token //verify connected by token
if (token != undefined) { if (token != undefined) {
@ -68,7 +69,7 @@ export class AuthenticateController extends BaseController {
const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken); const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken);
res.writeStatus("200"); res.writeStatus("200");
this.addCorsHeaders(res); this.addCorsHeaders(res);
return res.end(JSON.stringify({ authToken: token })); return res.end(JSON.stringify({ ...data, authToken: token }));
} catch (err) { } catch (err) {
console.info("User was not connected", 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"); throw new Error("No email in the response");
} }
const authToken = jwtTokenManager.createAuthToken(email, userInfo.access_token); 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"); res.writeStatus("200");
this.addCorsHeaders(res); this.addCorsHeaders(res);
return res.end(JSON.stringify({ authToken })); return res.end(JSON.stringify({ ...data, authToken }));
} catch (e) { } catch (e) {
console.error("openIDCallback => ERROR", e); console.error("openIDCallback => ERROR", e);
return this.errorToResponse(e, res); 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<FetchMemberDataByUuidResponse | object> {
let data: FetchMemberDataByUuidResponse | object = {};
try {
data = await adminApi.fetchMemberDataByUuid(email, playUri, IPAddress);
} catch (err) {
console.error("openIDCallback => fetchMemberDataByUuid", err);
}
return data;
}
} }

View File

@ -26,10 +26,9 @@ import { jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenMana
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
import { SocketManager, socketManager } from "../Services/SocketManager"; import { SocketManager, socketManager } from "../Services/SocketManager";
import { emitInBatch } from "../Services/IoSocketHelpers"; 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 { Zone } from "_Model/Zone";
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"; import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
import { v4 } from "uuid";
import { CharacterTexture } from "../Services/AdminApi/CharacterTexture"; import { CharacterTexture } from "../Services/AdminApi/CharacterTexture";
export class IoSocketController { export class IoSocketController {
@ -48,15 +47,19 @@ export class IoSocketController {
const websocketProtocol = req.getHeader("sec-websocket-protocol"); const websocketProtocol = req.getHeader("sec-websocket-protocol");
const websocketExtensions = req.getHeader("sec-websocket-extensions"); const websocketExtensions = req.getHeader("sec-websocket-extensions");
const token = query.token; const token = query.token;
if (token !== ADMIN_API_TOKEN) { let authorizedRoomIds: string[];
console.log("Admin access refused for token: " + token); 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"); res.writeStatus("401 Unauthorized").end("Incorrect token");
return; return;
} }
const roomId = query.roomId; const roomId = query.roomId;
if (typeof roomId !== "string") { if (typeof roomId !== "string" || !authorizedRoomIds.includes(roomId)) {
console.error("Received"); console.error("Invalid room id");
res.writeStatus("400 Bad Request").end("Missing room id"); res.writeStatus("403 Bad Request").end("Invalid room id");
return; return;
} }
@ -70,8 +73,6 @@ export class IoSocketController {
}, },
message: (ws, arrayBuffer, isBinary): void => { message: (ws, arrayBuffer, isBinary): void => {
try { try {
const roomId = ws.roomId as string;
//TODO refactor message type and data //TODO refactor message type and data
const message: { event: string; message: { type: string; message: unknown; userUuid: string } } = const message: { event: string; message: { type: string; message: unknown; userUuid: string } } =
JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(arrayBuffer))); JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(arrayBuffer)));

View File

@ -4,6 +4,7 @@ const API_URL = process.env.API_URL || "";
const ADMIN_API_URL = process.env.ADMIN_API_URL || ""; const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
const ADMIN_URL = process.env.ADMIN_URL || ""; const ADMIN_URL = process.env.ADMIN_URL || "";
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken"; 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 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_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
const JITSI_ISS = process.env.JITSI_ISS || ""; const JITSI_ISS = process.env.JITSI_ISS || "";

View File

@ -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 { uuid } from "uuidv4";
import Jwt, { verify } from "jsonwebtoken"; import Jwt, { verify } from "jsonwebtoken";
import { TokenInterface } from "../Controller/AuthenticateController"; 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 identifier: string; //will be a email if logged in or an uuid if anonymous
accessToken?: string; accessToken?: string;
} }
export interface AdminSocketTokenData {
authorizedRoomIds: string[]; //the list of rooms the client is authorized to read from.
}
export const tokenInvalidException = "tokenInvalid"; export const tokenInvalidException = "tokenInvalid";
class JWTTokenManager { class JWTTokenManager {
public verifyAdminSocketToken(token: string): AdminSocketTokenData {
return Jwt.verify(token, ADMIN_SOCKETS_TOKEN) as AdminSocketTokenData;
}
public createAuthToken(identifier: string, accessToken?: string) { public createAuthToken(identifier: string, accessToken?: string) {
return Jwt.sign({ identifier, accessToken }, SECRET_KEY, { expiresIn: "30d" }); return Jwt.sign({ identifier, accessToken }, SECRET_KEY, { expiresIn: "30d" });
} }