Merge pull request #2075 from thecodingmachine/codeAPI

Refactoring Error Screen
This commit is contained in:
David Négrier
2022-04-22 16:52:43 +02:00
committed by GitHub
20 changed files with 651 additions and 184 deletions
@@ -1,6 +1,6 @@
import { v4 } from "uuid";
import { BaseHttpController } from "./BaseHttpController";
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
import { FetchMemberDataByUuidResponse } from "../Services/AdminApi";
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
import { parse } from "query-string";
import { openIDClient } from "../Services/OpenIDClient";
@@ -172,7 +172,8 @@ export class AuthenticateController extends BaseHttpController {
authTokenData.identifier,
playUri as string,
IPAddress,
[]
[],
req.header("accept-language")
);
if (authTokenData.accessToken == undefined) {
@@ -224,7 +225,13 @@ export class AuthenticateController extends BaseHttpController {
//Get user data from Admin Back Office
//This is very important to create User Local in LocalStorage in WorkAdventure
const data = await adminService.fetchMemberDataByUuid(email, playUri as string, IPAddress, []);
const data = await adminService.fetchMemberDataByUuid(
email,
playUri as string,
IPAddress,
[],
req.header("accept-language")
);
return res.json({ ...data, authToken, username: userInfo?.username, locale: userInfo?.locale });
} catch (e) {
@@ -327,13 +334,18 @@ 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,
req.header("accept-language")
);
const userUuid = data.userUuid;
const email = data.email;
const roomUrl = data.roomUrl;
const mapUrlStart = data.mapUrlStart;
const authToken = jwtTokenManager.createAuthToken(email || userUuid);
res.json({
authToken,
userUuid,
@@ -419,7 +431,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) {
@@ -485,7 +497,8 @@ export class AuthenticateController extends BaseHttpController {
* @param email
* @param playUri
* @param IPAddress
* @return FetchMemberDataByUuidResponse|object
* @return
|object
* @private
*/
private async getUserByUserIdentifier(
@@ -503,7 +516,7 @@ export class AuthenticateController extends BaseHttpController {
userRoomToken: undefined,
};
try {
data = await adminApi.fetchMemberDataByUuid(email, playUri, IPAddress, []);
data = await adminService.fetchMemberDataByUuid(email, playUri, IPAddress, []);
} catch (err) {
console.error("openIDCallback => fetchMemberDataByUuid", err);
}
+10 -6
View File
@@ -1,6 +1,7 @@
import { Server } from "hyper-express";
import Response from "hyper-express/types/components/http/Response";
import axios from "axios";
import { isErrorApiData } from "../Messages/JsonMessages/ErrorApiData";
export class BaseHttpController {
constructor(protected app: Server) {
@@ -31,12 +32,15 @@ export class BaseHttpController {
if (axios.isAxiosError(e) && e.response) {
res.status(e.response.status);
res.send(
"An error occurred: " +
e.response.status +
" " +
(e.response.data && e.response.data.message ? e.response.data.message : e.response.statusText)
);
const errorType = isErrorApiData.safeParse(e?.response?.data);
if (!errorType.success) {
res.send(
"An error occurred: " +
e.response.status +
" " +
(e.response.data && e.response.data.message ? e.response.data.message : e.response.statusText)
);
} else res.json(errorType.data);
return;
} else {
res.status(500);
+38 -22
View File
@@ -25,7 +25,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";
@@ -39,6 +39,8 @@ 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";
import { ErrorApiData, isErrorApiData } from "../Messages/JsonMessages/ErrorApiData";
/**
* The object passed between the "open" and the "upgrade" methods when opening a websocket
@@ -66,13 +68,21 @@ interface UpgradeData {
};
}
interface UpgradeFailedData {
interface UpgradeFailedInvalidData {
rejected: true;
reason: "tokenInvalid" | "textureInvalid" | null;
message: string;
roomId: string;
}
interface UpgradeFailedErrorData {
rejected: true;
reason: "error";
error: ErrorApiData;
}
type UpgradeFailedData = UpgradeFailedErrorData | UpgradeFailedInvalidData;
export class IoSocketController {
private nextUserId: number = 1;
@@ -234,6 +244,7 @@ export class IoSocketController {
const websocketProtocol = req.getHeader("sec-websocket-protocol");
const websocketExtensions = req.getHeader("sec-websocket-extensions");
const IPAddress = req.getHeader("x-forwarded-for");
const locale = req.getHeader("accept-language");
const roomId = query.roomId;
try {
@@ -302,41 +313,46 @@ export class IoSocketController {
if (ADMIN_API_URL) {
try {
try {
userData = await adminApi.fetchMemberDataByUuid(
userData = await adminService.fetchMemberDataByUuid(
userIdentifier,
roomId,
IPAddress,
characterLayers
characterLayers,
locale
);
} catch (err) {
if (Axios.isAxiosError(err)) {
if (err?.response?.status == 404) {
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
console.warn(
'Cannot find user with email "' +
(userIdentifier || "anonymous") +
'". Performing an anonymous login instead.'
);
} else if (err?.response?.status == 403) {
// If we get an HTTP 403, the world is full. We need to broadcast a special error to the client.
// we finish immediately the upgrade then we will close the socket as soon as it starts opening.
const errorType = isErrorApiData.safeParse(err?.response?.data);
if (errorType.success) {
return res.upgrade(
{
rejected: true,
message: err?.response?.data.message,
reason: "error",
status: err?.response?.status,
roomId,
},
error: errorType.data,
} as UpgradeFailedData,
websocketKey,
websocketProtocol,
websocketExtensions,
context
);
} else {
return res.upgrade(
{
rejected: true,
reason: null,
status: 500,
message: err?.response?.data,
roomId: roomId,
} as UpgradeFailedData,
websocketKey,
websocketProtocol,
websocketExtensions,
context
);
}
} else {
throw err;
}
throw err;
}
memberMessages = userData.messages;
memberTags = userData.tags;
@@ -480,8 +496,8 @@ export class IoSocketController {
socketManager.emitTokenExpiredMessage(ws);
} else if (ws.reason === "textureInvalid") {
socketManager.emitInvalidTextureMessage(ws);
} else if (ws.message === "World is full") {
socketManager.emitWorldFullMessage(ws);
} else if (ws.reason === "error") {
socketManager.emitErrorScreenMessage(ws, ws.error);
} else {
socketManager.emitConnexionErrorMessage(ws, ws.message);
}
+9 -61
View File
@@ -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,66 +104,17 @@ export class MapController extends BaseHttpController {
return;
}
// 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;
}
(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,
req.header("accept-language")
)
);
if (DISABLE_ANONYMOUS) {
mapDetails.authenticationMandatory = true;
}
if (DISABLE_ANONYMOUS) mapDetails.authenticationMandatory = true;
res.json(mapDetails);
return;