The notion of public/private repositories (with /_/ and /@/ URLs) is specific to the SAAS version of WorkAdventure. It would be better to avoid leaking the organization/world/room structure of the private SAAS URLs inside the WorkAdventure Github project. Rather than sending http://admin_host/api/map?organizationSlug=...&worldSlug=...&roomSlug=...., we are now sending /api/map&playUri=... where playUri is the full URL of the current game. This allows the backend to act as a complete router. The front (and the pusher) will be able to completely ignore the specifics of URL building (with /@/ and /_/ URLs, etc...) Those details will live only in the admin server, which is way cleaner (and way more powerful).
101 lines
4.1 KiB
TypeScript
101 lines
4.1 KiB
TypeScript
import { ADMIN_API_URL, ALLOW_ARTILLERY, SECRET_KEY } from "../Enum/EnvironmentVariable";
|
|
import { uuid } from "uuidv4";
|
|
import Jwt from "jsonwebtoken";
|
|
import { TokenInterface } from "../Controller/AuthenticateController";
|
|
import { adminApi, AdminBannedData } from "../Services/AdminApi";
|
|
|
|
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 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.");
|
|
}
|
|
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");
|
|
}
|
|
}
|
|
|
|
export const jwtTokenManager = new JWTTokenManager();
|