Release 1.4.14 (#1370)

* New version of cache management (#1365)

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Exit scene acess denied detected (#1369)

* Add auth token user to get right in admin and check if user have right

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Update error show

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Update token generation (#1372)

- Permit only decode token to get map details,
 - If user have token expired, set the token to null and reload the page. This feature will be updated when authentication stategy will be finished.

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>
This commit is contained in:
grégoire parant 2021-08-15 23:13:48 +02:00 committed by GitHub
parent 45a56c2e02
commit 005a3c5a0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 96 additions and 16 deletions

View File

@ -1,4 +1,4 @@
let CACHE_NAME = 'workavdenture-cache-v1.2'; let CACHE_NAME = 'workavdenture-cache-v1.4.14';
let urlsToCache = [ let urlsToCache = [
'/' '/'
]; ];

View File

@ -22,7 +22,6 @@
let isMobile : boolean|null; let isMobile : boolean|null;
const unsubscribe = obtainedMediaConstraintIsMobileStore.subscribe(value => { const unsubscribe = obtainedMediaConstraintIsMobileStore.subscribe(value => {
console.log('unsubscribe => obtainedMediaConstraintIsMobileStore', value);
isMobile = value; isMobile = value;
}); });
onDestroy(unsubscribe); onDestroy(unsubscribe);

View File

@ -29,11 +29,24 @@ class ConnectionManager {
}); });
} }
public loadOpenIDScreen() { /**
localUserStore.setAuthToken(null); * @return Promise<void>
*/
public loadOpenIDScreen(): Promise<void> {
const state = localUserStore.generateState(); const state = localUserStore.generateState();
const nonce = localUserStore.generateNonce(); const nonce = localUserStore.generateNonce();
window.location.assign(`http://${PUSHER_URL}/login-screen?state=${state}&nonce=${nonce}`); localUserStore.setAuthToken(null);
//TODO refactor this and don't realise previous call
return Axios.get(`http://${PUSHER_URL}/login-screen?state=${state}&nonce=${nonce}`)
.then(() => {
window.location.assign(`http://${PUSHER_URL}/login-screen?state=${state}&nonce=${nonce}`);
})
.catch((err) => {
console.error(err, "We don't have URL to regenerate authentication user");
//TODO show modal login
window.location.reload();
});
} }
public logout() { public logout() {

View File

@ -1,6 +1,7 @@
import Axios from "axios"; import Axios from "axios";
import { PUSHER_URL } from "../Enum/EnvironmentVariable"; import { PUSHER_URL } from "../Enum/EnvironmentVariable";
import type { CharacterTexture } from "./LocalUser"; import type { CharacterTexture } from "./LocalUser";
import { localUserStore } from "./LocalUserStore";
export class MapDetail { export class MapDetail {
constructor(public readonly mapUrl: string, public readonly textures: CharacterTexture[] | undefined) {} constructor(public readonly mapUrl: string, public readonly textures: CharacterTexture[] | undefined) {}
@ -87,6 +88,7 @@ export class Room {
const result = await Axios.get(`${PUSHER_URL}/map`, { const result = await Axios.get(`${PUSHER_URL}/map`, {
params: { params: {
playUri: this.roomUrl.toString(), playUri: this.roomUrl.toString(),
authToken: localUserStore.getAuthToken(),
}, },
}); });

View File

@ -94,6 +94,7 @@ import { userIsAdminStore } from "../../Stores/GameStore";
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore"; import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
import { get } from "svelte/store"; import { get } from "svelte/store";
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager"; import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
import { helpCameraSettingsVisibleStore } from "../../Stores/HelpCameraSettingsStore";
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface | null; initPosition: PointInterface | null;
@ -814,13 +815,24 @@ export class GameScene extends DirtyScene {
private triggerOnMapLayerPropertyChange() { private triggerOnMapLayerPropertyChange() {
this.gameMap.onPropertyChange("exitSceneUrl", (newValue, oldValue) => { this.gameMap.onPropertyChange("exitSceneUrl", (newValue, oldValue) => {
if (newValue) if (newValue) {
this.onMapExit( this.onMapExit(
Room.getRoomPathFromExitSceneUrl(newValue as string, window.location.toString(), this.MapUrlFile) Room.getRoomPathFromExitSceneUrl(newValue as string, window.location.toString(), this.MapUrlFile)
); );
} else {
setTimeout(() => {
layoutManagerActionStore.removeAction("roomAccessDenied");
}, 2000);
}
}); });
this.gameMap.onPropertyChange("exitUrl", (newValue, oldValue) => { this.gameMap.onPropertyChange("exitUrl", (newValue, oldValue) => {
if (newValue) this.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString())); if (newValue) {
this.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString()));
} else {
setTimeout(() => {
layoutManagerActionStore.removeAction("roomAccessDenied");
}, 2000);
}
}); });
this.gameMap.onPropertyChange("openWebsite", (newValue, oldValue, allProps) => { this.gameMap.onPropertyChange("openWebsite", (newValue, oldValue, allProps) => {
if (newValue === undefined) { if (newValue === undefined) {
@ -1290,6 +1302,18 @@ ${escapedMessage}
targetRoom = await Room.createRoom(roomUrl); targetRoom = await Room.createRoom(roomUrl);
} catch (e /*: unknown*/) { } catch (e /*: unknown*/) {
console.error('Error while fetching new room "' + roomUrl.toString() + '"', e); console.error('Error while fetching new room "' + roomUrl.toString() + '"', e);
//show information room access denied
layoutManagerActionStore.addAction({
uuid: "roomAccessDenied",
type: "warning",
message: "Room access denied. You don't have right to access on this room.",
callback: () => {
layoutManagerActionStore.removeAction("roomAccessDenied");
},
userInputManager: this.userInputManager,
});
this.mapTransitioning = false; this.mapTransitioning = false;
return; return;
} }

View File

@ -1,6 +1,6 @@
import { gameManager } from "../Game/GameManager"; import { gameManager } from "../Game/GameManager";
import { Scene } from "phaser"; import { Scene } from "phaser";
import { ErrorScene } from "../Reconnecting/ErrorScene"; import { ErrorScene, ErrorSceneName } from "../Reconnecting/ErrorScene";
import { WAError } from "../Reconnecting/WAError"; import { WAError } from "../Reconnecting/WAError";
import { waScaleManager } from "../Services/WaScaleManager"; import { waScaleManager } from "../Services/WaScaleManager";
@ -36,6 +36,17 @@ export class EntryScene extends Scene {
), ),
this.scene this.scene
); );
} else if (err.response && err.response.status == 403) {
ErrorScene.showError(
new WAError(
"Connection rejected",
"You cannot join the World. Try again later" +
(err.response.data ? ". \n\r \n\r" + `${err.response.data}` : "") +
".",
"If you want more information, you may contact administrator or contact us at: hello@workadventu.re"
),
this.scene
);
} else { } else {
ErrorScene.showError(err, this.scene); ErrorScene.showError(err, this.scene);
} }

View File

@ -90,7 +90,11 @@ export class ErrorScene extends Phaser.Scene {
// Axios HTTP error // Axios HTTP error
// client received an error response (5xx, 4xx) // client received an error response (5xx, 4xx)
scene.start(ErrorSceneName, { scene.start(ErrorSceneName, {
title: "HTTP " + error.response.status + " - " + error.response.statusText, title:
"HTTP " +
error.response.status +
" - " +
(error.response.data ? error.response.data : error.response.statusText),
subTitle: "An error occurred while accessing URL:", subTitle: "An error occurred while accessing URL:",
message: error.response.config.url, message: error.response.config.url,
}); });

View File

@ -29,7 +29,12 @@ export class BaseController {
if (e.response) { if (e.response) {
res.writeStatus(e.response.status + " " + e.response.statusText); res.writeStatus(e.response.status + " " + e.response.statusText);
this.addCorsHeaders(res); this.addCorsHeaders(res);
res.end("An error occurred: " + e.response.status + " " + e.response.statusText); res.end(
"An error occurred: " +
e.response.status +
" " +
(e.response.data && e.response.data.message ? e.response.data.message : e.response.statusText)
);
} else { } else {
res.writeStatus("500 Internal Server Error"); res.writeStatus("500 Internal Server Error");
this.addCorsHeaders(res); this.addCorsHeaders(res);

View File

@ -174,7 +174,7 @@ export class IoSocketController {
} }
const tokenData = const tokenData =
token && typeof token === "string" ? jwtTokenManager.decodeJWTToken(token) : null; token && typeof token === "string" ? jwtTokenManager.verifyJWTToken(token) : null;
const userIdentifier = tokenData ? tokenData.identifier : ""; const userIdentifier = tokenData ? tokenData.identifier : "";
let memberTags: string[] = []; let memberTags: string[] = [];

View File

@ -5,6 +5,9 @@ import { adminApi } from "../Services/AdminApi";
import { ADMIN_API_URL } from "../Enum/EnvironmentVariable"; import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
import { GameRoomPolicyTypes } from "../Model/PusherRoom"; import { GameRoomPolicyTypes } from "../Model/PusherRoom";
import { MapDetailsData } from "../Services/AdminApi/MapDetailsData"; import { MapDetailsData } from "../Services/AdminApi/MapDetailsData";
import { socketManager } from "../Services/SocketManager";
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
import { v4 } from "uuid";
export class MapController extends BaseController { export class MapController extends BaseController {
constructor(private App: TemplatedApp) { constructor(private App: TemplatedApp) {
@ -67,7 +70,20 @@ export class MapController extends BaseController {
(async () => { (async () => {
try { try {
const mapDetails = await adminApi.fetchMapDetails(query.playUri as string); 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) {
// 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);
}
}
const mapDetails = await adminApi.fetchMapDetails(query.playUri as string, userId);
res.writeStatus("200 OK"); res.writeStatus("200 OK");
this.addCorsHeaders(res); this.addCorsHeaders(res);

View File

@ -31,13 +31,19 @@ export interface FetchMemberDataByUuidResponse {
} }
class AdminApi { class AdminApi {
async fetchMapDetails(playUri: string): Promise<MapDetailsData | RoomRedirect> { /**
* @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<MapDetailsData | RoomRedirect> {
if (!ADMIN_API_URL) { if (!ADMIN_API_URL) {
return Promise.reject(new Error("No admin backoffice set!")); return Promise.reject(new Error("No admin backoffice set!"));
} }
const params: { playUri: string } = { const params: { playUri: string; userId?: string } = {
playUri, playUri,
userId,
}; };
const res = await Axios.get(ADMIN_API_URL + "/api/map", { const res = await Axios.get(ADMIN_API_URL + "/api/map", {

View File

@ -15,9 +15,9 @@ class JWTTokenManager {
return Jwt.sign({ identifier }, SECRET_KEY, { expiresIn: "200d" }); return Jwt.sign({ identifier }, SECRET_KEY, { expiresIn: "200d" });
} }
public decodeJWTToken(token: string): AuthTokenData { public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData {
try { try {
return Jwt.verify(token, SECRET_KEY, { ignoreExpiration: false }) as AuthTokenData; return Jwt.verify(token, SECRET_KEY, { ignoreExpiration }) as AuthTokenData;
} catch (e) { } catch (e) {
throw { reason: tokenInvalidException, message: e.message }; throw { reason: tokenInvalidException, message: e.message };
} }