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

This commit is contained in:
_Bastler
2021-11-16 10:56:33 +01:00
49 changed files with 755 additions and 489 deletions
+41 -13
View File
@@ -1,11 +1,11 @@
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";
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
export interface TokenInterface {
userUuid: string;
@@ -56,19 +56,20 @@ 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) {
try {
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
if (authTokenData.hydraAccessToken == undefined) {
if (authTokenData.accessToken == undefined) {
throw Error("Token cannot to be check on Hydra");
}
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken);
res.writeStatus("200");
this.addCorsHeaders(res);
return res.end(JSON.stringify({ authToken: token, username: authTokenData.username, userUuid : authTokenData.identifier }));
return res.end(JSON.stringify({ ...resCheckTokenAuth, username: authTokenData.username, authToken: token }));
} catch (err) {
console.info("User was not connected", err);
}
@@ -81,9 +82,14 @@ export class AuthenticateController extends BaseController {
throw new Error("No sub in the response");
}
const authToken = jwtTokenManager.createAuthToken(sub, userInfo.access_token, userInfo.username);
//Get user data from Admin Back Office
//This is very important to create User Local in LocalStorage in WorkAdventure
const data = await this.getUserByUserIdentifier(sub, playUri as string, IPAddress);
res.writeStatus("200");
this.addCorsHeaders(res);
return res.end(JSON.stringify({ authToken: authToken, username: userInfo.username, userUuid : sub }));
return res.end(JSON.stringify({ ...data, authToken, username: userInfo.username, userUuid : sub }));
} catch (e) {
console.error("openIDCallback => ERROR", e);
return this.errorToResponse(e, res);
@@ -100,10 +106,10 @@ export class AuthenticateController extends BaseController {
try {
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
if (authTokenData.hydraAccessToken == undefined) {
if (authTokenData.accessToken == undefined) {
throw Error("Token cannot to be logout on Hydra");
}
await openIDClient.logoutUser(authTokenData.hydraAccessToken);
await openIDClient.logoutUser(authTokenData.accessToken);
} catch (error) {
console.error("openIDCallback => logout-callback", error);
} finally {
@@ -202,20 +208,20 @@ export class AuthenticateController extends BaseController {
res.onAborted(() => {
console.warn("/message request was aborted");
});
const { userIdentify, token } = parse(req.getQuery());
const { token } = parse(req.getQuery());
try {
//verify connected by token
if (token != undefined) {
try {
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
if (authTokenData.hydraAccessToken == undefined) {
if (authTokenData.accessToken == undefined) {
throw Error("Token cannot to be check on Hydra");
}
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
await openIDClient.checkTokenAuth(authTokenData.accessToken);
//get login profile
res.writeStatus("302");
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.hydraAccessToken));
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken));
this.addCorsHeaders(res);
// eslint-disable-next-line no-unsafe-finally
return res.end();
@@ -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;
}
}
+11 -10
View File
@@ -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, SOCKET_IDLE_TIMER, DISABLE_ANONYMOUS } 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)));
@@ -250,7 +251,7 @@ export class IoSocketController {
roomId
);
console.error(e);
throw new Error("User cannot access this room");
throw new Error("User cannot access this world");
}
}
+3 -3
View File
@@ -52,7 +52,7 @@ export class MapController extends BaseController {
return;
}
const mapUrl = roomUrl.protocol + "//" + match[ 1 ];
const mapUrl = roomUrl.protocol + "//" + match[1];
res.writeStatus("200 OK");
this.addCorsHeaders(res);
@@ -65,7 +65,7 @@ export class MapController extends BaseController {
tags: [],
textures: [],
contactPage: undefined,
authenticationMandatory : DISABLE_ANONYMOUS,
authenticationMandatory: DISABLE_ANONYMOUS,
} as MapDetailsData)
);
@@ -90,7 +90,7 @@ 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;
mapDetails.authenticationMandatory = true;
}
res.writeStatus("200 OK");
@@ -0,0 +1,80 @@
import { BaseController } from "./BaseController";
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
import { parse } from "query-string";
import { openIDClient } from "../Services/OpenIDClient";
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
import { adminApi } from "../Services/AdminApi";
import { OPID_CLIENT_ISSUER } from "../Enum/EnvironmentVariable";
import { IntrospectionResponse } from "openid-client";
export class OpenIdProfileController extends BaseController {
constructor(private App: TemplatedApp) {
super();
this.profileOpenId();
}
profileOpenId() {
//eslint-disable-next-line @typescript-eslint/no-misused-promises
this.App.get("/profile", async (res: HttpResponse, req: HttpRequest) => {
res.onAborted(() => {
console.warn("/message request was aborted");
});
const { accessToken } = parse(req.getQuery());
if (!accessToken) {
throw Error("Access token expected cannot to be check on Hydra");
}
try {
const resCheckTokenAuth = await openIDClient.checkTokenAuth(accessToken as string);
if (!resCheckTokenAuth.email) {
throw "Email was not found";
}
res.end(
this.buildHtml(
OPID_CLIENT_ISSUER,
resCheckTokenAuth.email as string,
resCheckTokenAuth.picture as string | undefined
)
);
} catch (error) {
console.error("profileCallback => ERROR", error);
this.errorToResponse(error, res);
}
});
}
buildHtml(domain: string, email: string, pictureUrl?: string) {
return `
<!DOCTYPE>
<html>
<head>
<style>
*{
font-family: PixelFont-7, monospace;
}
body{
text-align: center;
color: white;
}
section{
margin: 20px;
}
</style>
</head>
<body>
<div class="container">
<section>
<img src="${pictureUrl ? pictureUrl : "/images/profile"}">
</section>
<section>
Profile validated by domain: <span style="font-weight: bold">${domain}</span>
</section>
<section>
Your email: <span style="font-weight: bold">${email}</span>
</section>
</div>
</body>
</html>
`;
}
}