Merge branch 'develop' into GlobalMessageToWorld
# Conflicts: # CHANGELOG.md # front/src/Api/Events/IframeEvent.ts # front/src/Components/App.svelte # pusher/src/Services/SocketManager.ts
This commit is contained in:
commit
14b4229019
48 changed files with 1346 additions and 1634 deletions
|
@ -7,6 +7,7 @@ import { RoomRedirect } from "./AdminApi/RoomRedirect";
|
|||
|
||||
export interface AdminApiData {
|
||||
roomUrl: string;
|
||||
email: string | null;
|
||||
mapUrlStart: string;
|
||||
tags: string[];
|
||||
policy_type: number;
|
||||
|
@ -21,7 +22,7 @@ export interface AdminBannedData {
|
|||
}
|
||||
|
||||
export interface FetchMemberDataByUuidResponse {
|
||||
uuid: string;
|
||||
userUuid: string;
|
||||
tags: string[];
|
||||
visitCardUrl: string | null;
|
||||
textures: CharacterTexture[];
|
||||
|
@ -46,12 +47,16 @@ class AdminApi {
|
|||
return res.data;
|
||||
}
|
||||
|
||||
async fetchMemberDataByUuid(uuid: string, roomId: string): Promise<FetchMemberDataByUuidResponse> {
|
||||
async fetchMemberDataByUuid(
|
||||
userIdentifier: string | null,
|
||||
roomId: string,
|
||||
ipAddress: string
|
||||
): Promise<FetchMemberDataByUuidResponse> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject(new Error("No admin backoffice set!"));
|
||||
}
|
||||
const res = await Axios.get(ADMIN_API_URL + "/api/room/access", {
|
||||
params: { uuid, roomId },
|
||||
params: { userIdentifier, roomId, ipAddress },
|
||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
||||
});
|
||||
return res.data;
|
||||
|
|
|
@ -1,100 +1,25 @@
|
|||
import { ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY } from "../Enum/EnvironmentVariable";
|
||||
import { uuid } from "uuidv4";
|
||||
import Jwt from "jsonwebtoken";
|
||||
import Jwt, { verify } from "jsonwebtoken";
|
||||
import { TokenInterface } from "../Controller/AuthenticateController";
|
||||
import { adminApi, AdminBannedData } from "../Services/AdminApi";
|
||||
|
||||
export interface AuthTokenData {
|
||||
identifier: string; //will be a email if logged in or an uuid if anonymous
|
||||
}
|
||||
export const tokenInvalidException = "tokenInvalid";
|
||||
|
||||
class JWTTokenManager {
|
||||
public createJWTToken(userUuid: string) {
|
||||
return Jwt.sign({ userUuid: userUuid }, SECRET_KEY, { expiresIn: "200d" }); //todo: add a mechanic to refresh or recreate token
|
||||
public createAuthToken(identifier: string) {
|
||||
return Jwt.sign({ identifier }, SECRET_KEY, { expiresIn: "3d" });
|
||||
}
|
||||
|
||||
public async getUserUuidFromToken(token: unknown, ipAddress?: string, roomUrl?: string): Promise<string> {
|
||||
if (!token) {
|
||||
throw new Error("An authentication error happened, a user tried to connect without a token.");
|
||||
public decodeJWTToken(token: string): AuthTokenData {
|
||||
try {
|
||||
return Jwt.verify(token, SECRET_KEY, { ignoreExpiration: false }) as AuthTokenData;
|
||||
} catch (e) {
|
||||
throw { reason: tokenInvalidException, message: e.message };
|
||||
}
|
||||
if (typeof token !== "string") {
|
||||
throw new Error("Token is expected to be a string");
|
||||
}
|
||||
|
||||
if (token === "test") {
|
||||
if (ALLOW_ARTILLERY) {
|
||||
return uuid();
|
||||
} else {
|
||||
throw new Error(
|
||||
"In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
Jwt.verify(token, SECRET_KEY, {}, (err, tokenDecoded) => {
|
||||
const tokenInterface = tokenDecoded as TokenInterface;
|
||||
if (err) {
|
||||
console.error("An authentication error happened, invalid JsonWebToken.", err);
|
||||
reject(new Error("An authentication error happened, invalid JsonWebToken. " + err.message));
|
||||
return;
|
||||
}
|
||||
if (tokenDecoded === undefined) {
|
||||
console.error("Empty token found.");
|
||||
reject(new Error("Empty token found."));
|
||||
return;
|
||||
}
|
||||
|
||||
//verify token
|
||||
if (!this.isValidToken(tokenInterface)) {
|
||||
reject(new Error("Authentication error, invalid token structure."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ADMIN_API_URL) {
|
||||
//verify user in admin
|
||||
let promise = new Promise((resolve) => resolve());
|
||||
if (ipAddress && roomUrl) {
|
||||
promise = this.verifyBanUser(tokenInterface.userUuid, ipAddress, roomUrl);
|
||||
}
|
||||
promise
|
||||
.then(() => {
|
||||
adminApi
|
||||
.fetchCheckUserByToken(tokenInterface.userUuid)
|
||||
.then(() => {
|
||||
resolve(tokenInterface.userUuid);
|
||||
})
|
||||
.catch((err) => {
|
||||
//anonymous user
|
||||
if (err.response && err.response.status && err.response.status === 404) {
|
||||
resolve(tokenInterface.userUuid);
|
||||
return;
|
||||
}
|
||||
reject(err);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
resolve(tokenInterface.userUuid);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private verifyBanUser(userUuid: string, ipAddress: string, roomUrl: string): Promise<AdminBannedData> {
|
||||
return adminApi
|
||||
.verifyBanUser(userUuid, ipAddress, roomUrl)
|
||||
.then((data: AdminBannedData) => {
|
||||
if (data && data.is_banned) {
|
||||
throw new Error("User was banned");
|
||||
}
|
||||
return data;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
private isValidToken(token: object): token is TokenInterface {
|
||||
return !(typeof (token as TokenInterface).userUuid !== "string");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
43
pusher/src/Services/OpenIDClient.ts
Normal file
43
pusher/src/Services/OpenIDClient.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { Issuer, Client } from "openid-client";
|
||||
import { OPID_CLIENT_ID, OPID_CLIENT_SECRET, OPID_CLIENT_ISSUER, FRONT_URL } from "../Enum/EnvironmentVariable";
|
||||
|
||||
const opidRedirectUri = FRONT_URL + "/jwt";
|
||||
|
||||
class OpenIDClient {
|
||||
private issuerPromise: Promise<Client> | null = null;
|
||||
|
||||
private initClient(): Promise<Client> {
|
||||
if (!this.issuerPromise) {
|
||||
this.issuerPromise = Issuer.discover(OPID_CLIENT_ISSUER).then((issuer) => {
|
||||
return new issuer.Client({
|
||||
client_id: OPID_CLIENT_ID,
|
||||
client_secret: OPID_CLIENT_SECRET,
|
||||
redirect_uris: [opidRedirectUri],
|
||||
response_types: ["code"],
|
||||
});
|
||||
});
|
||||
}
|
||||
return this.issuerPromise;
|
||||
}
|
||||
|
||||
public authorizationUrl(state: string, nonce: string) {
|
||||
return this.initClient().then((client) => {
|
||||
return client.authorizationUrl({
|
||||
scope: "openid email",
|
||||
prompt: "login",
|
||||
state: state,
|
||||
nonce: nonce,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string }> {
|
||||
return this.initClient().then((client) => {
|
||||
return client.callback(opidRedirectUri, { code }, { nonce }).then((tokenSet) => {
|
||||
return client.userinfo(tokenSet);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const openIDClient = new OpenIDClient();
|
|
@ -30,6 +30,7 @@ import {
|
|||
ViewportMessage,
|
||||
WebRtcSignalToServerMessage,
|
||||
WorldConnexionMessage,
|
||||
TokenExpiredMessage,
|
||||
VariableMessage,
|
||||
ErrorMessage,
|
||||
WorldFullMessage,
|
||||
|
@ -117,7 +118,7 @@ export class SocketManager implements ZoneEventListener {
|
|||
console.warn("Admin connection lost to back server");
|
||||
// Let's close the front connection if the back connection is closed. This way, we can retry connecting from the start.
|
||||
if (!client.disconnecting) {
|
||||
this.closeWebsocketConnection(client, 1011, "Connection lost to back server");
|
||||
this.closeWebsocketConnection(client, 1011, "Admin Connection lost to back server");
|
||||
}
|
||||
console.log("A user left");
|
||||
})
|
||||
|
@ -140,24 +141,6 @@ export class SocketManager implements ZoneEventListener {
|
|||
}
|
||||
}
|
||||
|
||||
getAdminSocketDataFor(roomId: string): AdminSocketData {
|
||||
throw new Error("Not reimplemented yet");
|
||||
/*const data:AdminSocketData = {
|
||||
rooms: {},
|
||||
users: {},
|
||||
}
|
||||
const room = this.Worlds.get(roomId);
|
||||
if (room === undefined) {
|
||||
return data;
|
||||
}
|
||||
const users = room.getUsers();
|
||||
data.rooms[roomId] = users.size;
|
||||
users.forEach(user => {
|
||||
data.users[user.uuid] = true
|
||||
})
|
||||
return data;*/
|
||||
}
|
||||
|
||||
async handleJoinRoom(client: ExSocketInterface): Promise<void> {
|
||||
const viewport = client.viewport;
|
||||
try {
|
||||
|
@ -587,7 +570,20 @@ export class SocketManager implements ZoneEventListener {
|
|||
const serverToClientMessage = new ServerToClientMessage();
|
||||
serverToClientMessage.setWorldfullmessage(errorMessage);
|
||||
|
||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||
if (!client.disconnecting) {
|
||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||
}
|
||||
}
|
||||
|
||||
public emitTokenExpiredMessage(client: WebSocket) {
|
||||
const errorMessage = new TokenExpiredMessage();
|
||||
|
||||
const serverToClientMessage = new ServerToClientMessage();
|
||||
serverToClientMessage.setTokenexpiredmessage(errorMessage);
|
||||
|
||||
if (!client.disconnecting) {
|
||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||
}
|
||||
}
|
||||
|
||||
public emitConnexionErrorMessage(client: WebSocket, message: string) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue