secure DISABLE_ANONYMOUS
This commit is contained in:
parent
e3470d3474
commit
f984897e80
@ -14,7 +14,6 @@ export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
|
||||
export const REDIS_HOST = process.env.REDIS_HOST || undefined;
|
||||
export const REDIS_PORT = parseInt(process.env.REDIS_PORT || "6379") || 6379;
|
||||
export const REDIS_PASSWORD = process.env.REDIS_PASSWORD || undefined;
|
||||
export const DEBUG_IGNORE_SSL = process.env.DEBUG_IGNORE_SSL ? process.env.DEBUG_IGNORE_SSL == "true" : false;
|
||||
export const DEBUG_BACK_IGNORE_LOCAL = process.env.DEBUG_BACK_IGNORE_LOCAL ? process.env.DEBUG_BACK_IGNORE_LOCAL == "true" : false;
|
||||
|
||||
export {
|
||||
|
@ -1,13 +1,11 @@
|
||||
import Axios from "axios";
|
||||
import { AxiosRequestConfig } from "axios";
|
||||
import https from "https";
|
||||
import ipaddr from "ipaddr.js";
|
||||
import { Resolver } from "dns";
|
||||
import { promisify } from "util";
|
||||
import { LocalUrlError } from "./LocalUrlError";
|
||||
import { ITiledMap } from "@workadventure/tiled-map-type-guard";
|
||||
import { isTiledMap } from "@workadventure/tiled-map-type-guard/dist";
|
||||
import { DEBUG_IGNORE_SSL, DEBUG_BACK_IGNORE_LOCAL } from "../Enum/EnvironmentVariable";
|
||||
import { DEBUG_BACK_IGNORE_LOCAL } from "../Enum/EnvironmentVariable";
|
||||
|
||||
class MapFetcher {
|
||||
async fetchMap(mapUrl: string): Promise<ITiledMap> {
|
||||
@ -17,18 +15,6 @@ class MapFetcher {
|
||||
throw new LocalUrlError('URL for map "' + mapUrl + '" targets a local map');
|
||||
}
|
||||
|
||||
const axiosConfig: AxiosRequestConfig = {
|
||||
maxContentLength: 50 * 1024 * 1024, // Max content length: 50MB. Maps should not be bigger
|
||||
timeout: 10000, // Timeout after 10 seconds
|
||||
};
|
||||
|
||||
if (DEBUG_IGNORE_SSL) {
|
||||
const agent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
axiosConfig.httpsAgent = agent;
|
||||
}
|
||||
|
||||
// Note: mapUrl is provided by the client. A possible attack vector would be to use a rogue DNS server that
|
||||
// returns local URLs. Alas, Axios cannot pin a URL to a given IP. So "isLocalUrl" and Axios.get could potentially
|
||||
// target to different servers (and one could trick Axios.get into loading resources on the internal network
|
||||
@ -36,18 +22,16 @@ class MapFetcher {
|
||||
// We can deem this problem not that important because:
|
||||
// - We make sure we are only passing "GET" requests
|
||||
// - The result of the query is never displayed to the end user
|
||||
const res = await Axios.get(mapUrl, axiosConfig);
|
||||
const res = await Axios.get(mapUrl, {
|
||||
maxContentLength: 50 * 1024 * 1024, // Max content length: 50MB. Maps should not be bigger
|
||||
timeout: 10000, // Timeout after 10 seconds
|
||||
});
|
||||
|
||||
try {
|
||||
if (!isTiledMap(res.data)) {
|
||||
//TODO fixme
|
||||
//throw new Error("Invalid map format for map " + mapUrl);
|
||||
console.error("Invalid map format for map " + mapUrl);
|
||||
}
|
||||
} catch (e) {
|
||||
if (!isTiledMap(res.data)) {
|
||||
//TODO fixme
|
||||
//throw new Error("Invalid map format for map " + mapUrl);
|
||||
console.error("Invalid map format for map " + mapUrl);
|
||||
}
|
||||
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
@ -44,13 +44,22 @@ class ConnectionManager {
|
||||
|
||||
//TODO fix me to redirect this URL by pusher
|
||||
if (!this._currentRoom) {
|
||||
console.error("cannot get currentRoom!");
|
||||
loginSceneVisibleIframeStore.set(false);
|
||||
return null;
|
||||
}
|
||||
|
||||
const redirectUrl = `${PUSHER_URL}/login-screen?state=${state}&nonce=${nonce}&playUri=${this._currentRoom.key}`;
|
||||
window.location.assign(redirectUrl);
|
||||
// also allow OIDC login without admin API by using pusher
|
||||
let redirectUrl : URL;
|
||||
if (this._currentRoom.iframeAuthentication) {
|
||||
redirectUrl = new URL(`${this._currentRoom.iframeAuthentication}`);
|
||||
} else {
|
||||
// need origin if PUSHER_URL is relative (in Single-Domain-Deployment)
|
||||
redirectUrl = new URL(`${PUSHER_URL}/login-screen`, (!PUSHER_URL.startsWith('http:') || !PUSHER_URL.startsWith('https:')) ? window.location.origin : undefined);
|
||||
}
|
||||
redirectUrl.searchParams.append("state", state);
|
||||
redirectUrl.searchParams.append("nonce", nonce);
|
||||
redirectUrl.searchParams.append("playUri", this._currentRoom.key);
|
||||
window.location.assign(redirectUrl.toString());
|
||||
return redirectUrl;
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ export class Room {
|
||||
private _iframeAuthentication?: string;
|
||||
private _mapUrl: string | undefined;
|
||||
private _textures: CharacterTexture[] | undefined;
|
||||
// private instance: string | undefined;
|
||||
private instance: string | undefined;
|
||||
private readonly _search: URLSearchParams;
|
||||
private _contactPage: string | undefined;
|
||||
private _group: string | null = null;
|
||||
@ -75,13 +75,12 @@ export class Room {
|
||||
const baseUrl = new URL(currentRoomUrl);
|
||||
|
||||
const currentRoom = new Room(baseUrl);
|
||||
// let instance: string = "global";
|
||||
// if (currentRoom.isPublic) {
|
||||
// instance = currentRoom.instance as string;
|
||||
//}
|
||||
let instance: string = "global";
|
||||
if (currentRoom.isPublic) {
|
||||
instance = currentRoom.instance as string;
|
||||
}
|
||||
|
||||
// baseUrl.pathname = "/_/" + instance + "/" + absoluteExitSceneUrl.host + absoluteExitSceneUrl.pathname;
|
||||
baseUrl.pathname = "/_/" + absoluteExitSceneUrl.host + absoluteExitSceneUrl.pathname;
|
||||
baseUrl.pathname = "/_/" + instance + "/" + absoluteExitSceneUrl.host + absoluteExitSceneUrl.pathname;
|
||||
if (absoluteExitSceneUrl.hash) {
|
||||
baseUrl.hash = absoluteExitSceneUrl.hash;
|
||||
}
|
||||
@ -119,8 +118,6 @@ export class Room {
|
||||
* - In a private URL: [organizationId/worldId]
|
||||
*/
|
||||
public getInstance(): string {
|
||||
return "";
|
||||
/*
|
||||
if (this.instance !== undefined) {
|
||||
return this.instance;
|
||||
}
|
||||
@ -131,12 +128,12 @@ export class Room {
|
||||
this.instance = match[1];
|
||||
return this.instance;
|
||||
} else {
|
||||
const match = /@\/([^/]+)\/([^/]+)\/.+/.exec(this.id);
|
||||
if (!match) throw new Error('Could not extract instance from "' + this.id + '"');
|
||||
this.instance = match[1] + "/" + match[2];
|
||||
//const match = /@\/([^/]+)\/([^/]+)\/.+/.exec(this.id);
|
||||
//if (!match) throw new Error('Could not extract instance from "' + this.id + '"');
|
||||
//this.instance = match[1] + "/" + match[2];
|
||||
this.instance = ""
|
||||
return this.instance;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public isDisconnected(): boolean {
|
||||
|
@ -37,7 +37,7 @@ export class GameManager {
|
||||
|
||||
//If player name was not set show login scene with player name
|
||||
//If Room si not public and Auth was not set, show login scene to authenticate user (OpenID - SSO - Anonymous)
|
||||
if (!this.playerName || !localUserStore.getAuthToken()) {
|
||||
if (!this.playerName || (this.startRoom.authenticationMandatory && !localUserStore.getAuthToken())) {
|
||||
return LoginSceneName;
|
||||
} else if (!this.characterLayers || !this.characterLayers.length) {
|
||||
return SelectCharacterSceneName;
|
||||
|
@ -303,17 +303,12 @@ export class GameScene extends DirtyScene {
|
||||
//remove loader in progress
|
||||
removeLoader(this);
|
||||
|
||||
if (this.roomUrl == localUserStore.getLastRoomUrl()) {
|
||||
localUserStore.setLastRoomUrl(null);
|
||||
}
|
||||
|
||||
//display an error scene
|
||||
this.scene.start(ErrorSceneName, {
|
||||
title: "Network error",
|
||||
subTitle: "An error occurred while loading resource:",
|
||||
message: this.originalMapUrl ?? file.src,
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
this.load.scenePlugin("AnimatedTiles", AnimatedTiles, "animatedTiles", "animatedTiles");
|
||||
@ -460,12 +455,6 @@ export class GameScene extends DirtyScene {
|
||||
//initialise map
|
||||
this.Map = this.add.tilemap(this.MapUrlFile);
|
||||
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf("/"));
|
||||
|
||||
if (!this.mapFile) {
|
||||
localUserStore.setLastRoomUrl(null);
|
||||
throw new Error("invalid map");
|
||||
}
|
||||
|
||||
this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => {
|
||||
this.Terrains.push(
|
||||
this.Map.addTilesetImage(
|
||||
|
@ -23,7 +23,9 @@ export class LoginScene extends ResizableScene {
|
||||
loginSceneVisibleIframeStore.set(false);
|
||||
//If authentication is mandatory, push authentication iframe
|
||||
if (
|
||||
localUserStore.getAuthToken() == undefined
|
||||
localUserStore.getAuthToken() == undefined &&
|
||||
gameManager.currentStartedRoom &&
|
||||
gameManager.currentStartedRoom.authenticationMandatory
|
||||
) {
|
||||
connectionManager.loadOpenIDScreen();
|
||||
loginSceneVisibleIframeStore.set(true);
|
||||
|
@ -38,7 +38,7 @@ class UrlManager {
|
||||
}
|
||||
|
||||
public pushRoomIdToUrl(room: Room): void {
|
||||
if (window.location.pathname === room.id || room.isPublic) return;
|
||||
if (window.location.pathname === room.id) return;
|
||||
//Set last room visited! (connected or nor, must to be saved in localstorage and cache API)
|
||||
localUserStore.setLastRoomUrl(room.key);
|
||||
const hash = window.location.hash;
|
||||
|
@ -5,9 +5,7 @@ import { adminApi } from "../Services/AdminApi";
|
||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { parse } from "query-string";
|
||||
import { openIDClient } from "../Services/OpenIDClient";
|
||||
import { DEBUG_IGNORE_SSL, PUSHER_DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"
|
||||
import { AxiosRequestConfig } from "axios";
|
||||
import https from "https";
|
||||
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"
|
||||
|
||||
export interface TokenInterface {
|
||||
userUuid: string;
|
||||
@ -173,26 +171,16 @@ export class AuthenticateController extends BaseController {
|
||||
res.end();
|
||||
});
|
||||
|
||||
this.App.post("/anonymLogin", async (res: HttpResponse, req: HttpRequest) => {
|
||||
this.App.post("/anonymLogin", (res: HttpResponse, req: HttpRequest) => {
|
||||
res.onAborted(() => {
|
||||
console.warn("Login request was aborted");
|
||||
});
|
||||
|
||||
if (PUSHER_DISABLE_ANONYMOUS) {
|
||||
if (DISABLE_ANONYMOUS) {
|
||||
res.writeStatus("403 FORBIDDEN");
|
||||
res.end();
|
||||
} else {
|
||||
let userUuid = v4();
|
||||
|
||||
const axiosConfig: AxiosRequestConfig = {};
|
||||
|
||||
if (DEBUG_IGNORE_SSL) {
|
||||
const agent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
axiosConfig.httpsAgent = agent;
|
||||
}
|
||||
|
||||
const userUuid = v4();
|
||||
const authToken = jwtTokenManager.createAuthToken(userUuid);
|
||||
res.writeStatus("200 OK");
|
||||
this.addCorsHeaders(res);
|
||||
|
@ -26,7 +26,7 @@ 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, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable";
|
||||
import { ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||
import { Zone } from "_Model/Zone";
|
||||
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
|
||||
import { v4 } from "uuid";
|
||||
@ -175,6 +175,11 @@ export class IoSocketController {
|
||||
|
||||
const tokenData =
|
||||
token && typeof token === "string" ? jwtTokenManager.verifyJWTToken(token) : null;
|
||||
|
||||
if (DISABLE_ANONYMOUS && !tokenData) {
|
||||
throw new Error("Expecting token");
|
||||
}
|
||||
|
||||
const userIdentifier = tokenData ? tokenData.identifier : "";
|
||||
|
||||
let memberTags: string[] = [];
|
||||
|
@ -2,9 +2,9 @@ import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||
import { BaseController } from "./BaseController";
|
||||
import { parse } from "query-string";
|
||||
import { adminApi } from "../Services/AdminApi";
|
||||
import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
||||
import { PusherRoom, GameRoomPolicyTypes } from "../Model/PusherRoom";
|
||||
import { MapDetailsData } from "../Services/AdminApi/MapDetailsData";
|
||||
import { ADMIN_API_URL, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
||||
import { isMapDetailsData, MapDetailsData } from "../Services/AdminApi/MapDetailsData";
|
||||
import { socketManager } from "../Services/SocketManager";
|
||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { v4 } from "uuid";
|
||||
@ -65,6 +65,7 @@ export class MapController extends BaseController {
|
||||
tags: [],
|
||||
textures: [],
|
||||
contactPage: undefined,
|
||||
authenticationMandatory : DISABLE_ANONYMOUS,
|
||||
} as MapDetailsData)
|
||||
);
|
||||
|
||||
@ -88,6 +89,10 @@ export class MapController extends BaseController {
|
||||
}
|
||||
const mapDetails = await adminApi.fetchMapDetails(query.playUri as string, userId);
|
||||
|
||||
if (isMapDetailsData(mapDetails) && DISABLE_ANONYMOUS) {
|
||||
(mapDetails as MapDetailsData).authenticationMandatory = true;
|
||||
}
|
||||
|
||||
res.writeStatus("200 OK");
|
||||
this.addCorsHeaders(res);
|
||||
res.end(JSON.stringify(mapDetails));
|
||||
|
@ -12,12 +12,11 @@ const PUSHER_HTTP_PORT = parseInt(process.env.PUSHER_HTTP_PORT || "8080") || 808
|
||||
export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 120; // maximum time (in second) without activity before a socket is closed. Should be greater than 60 seconds in order to cope for Chrome intensive throttling (https://developer.chrome.com/blog/timer-throttling-in-chrome-88/#intensive-throttling)
|
||||
|
||||
export const FRONT_URL = process.env.FRONT_URL || "http://localhost";
|
||||
export const PUSHER_DISABLE_ANONYMOUS = process.env.PUSHER_DISABLE_ANONYMOUS ? process.env.PUSHER_DISABLE_ANONYMOUS == "true" : false;
|
||||
export const DISABLE_ANONYMOUS = process.env.DISABLE_ANONYMOUS ? process.env.DISABLE_ANONYMOUS == "true" : false;
|
||||
export const PUSHER_FORCE_ROOM_UPDATE = process.env.PUSHER_FORCE_ROOM_UPDATE ? process.env.PUSHER_FORCE_ROOM_UPDATE == "true" : false;
|
||||
export const OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID || "";
|
||||
export const OIDC_CLIENT_SECRET = process.env.OIDC_CLIENT_SECRET || "";
|
||||
export const OIDC_CLIENT_ISSUER = process.env.OIDC_CLIENT_ISSUER || "";
|
||||
export const DEBUG_IGNORE_SSL = process.env.DEBUG_IGNORE_SSL ? process.env.DEBUG_IGNORE_SSL == "true" : false;
|
||||
|
||||
export {
|
||||
SECRET_KEY,
|
||||
|
@ -16,6 +16,7 @@ export const isMapDetailsData = new tg.IsInterface()
|
||||
tags: tg.isArray(tg.isString),
|
||||
textures: tg.isArray(isCharacterTexture),
|
||||
contactPage: tg.isUnion(tg.isString, tg.isUndefined),
|
||||
authenticationMandatory: tg.isUnion(tg.isBoolean, tg.isUndefined),
|
||||
})
|
||||
.get();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user