Wrap websockets with HyperExpress
This commit is contained in:
parent
658781e02e
commit
f993aa4f5a
@ -41,22 +41,20 @@
|
|||||||
"homepage": "https://github.com/thecodingmachine/workadventure#readme",
|
"homepage": "https://github.com/thecodingmachine/workadventure#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.21.2",
|
"axios": "^0.21.2",
|
||||||
"busboy": "^0.3.1",
|
|
||||||
"circular-json": "^0.5.9",
|
"circular-json": "^0.5.9",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
"generic-type-guard": "^3.2.0",
|
"generic-type-guard": "^3.2.0",
|
||||||
"google-protobuf": "^3.13.0",
|
"google-protobuf": "^3.13.0",
|
||||||
"grpc": "^1.24.4",
|
"grpc": "^1.24.4",
|
||||||
|
"hyper-express": "^5.8.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
"openid-client": "^4.7.4",
|
"openid-client": "^4.7.4",
|
||||||
"prom-client": "^12.0.0",
|
"prom-client": "^12.0.0",
|
||||||
"query-string": "^6.13.3",
|
"query-string": "^6.13.3",
|
||||||
"uWebSockets.js": "uNetworking/uWebSockets.js#v20.4.0",
|
|
||||||
"uuidv4": "^6.0.7"
|
"uuidv4": "^6.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/busboy": "^0.2.3",
|
|
||||||
"@types/circular-json": "^0.4.0",
|
"@types/circular-json": "^0.4.0",
|
||||||
"@types/debug": "^4.1.5",
|
"@types/debug": "^4.1.5",
|
||||||
"@types/google-protobuf": "^3.7.3",
|
"@types/google-protobuf": "^3.7.3",
|
||||||
|
@ -4,31 +4,31 @@ import { AuthenticateController } from "./Controller/AuthenticateController"; //
|
|||||||
import { MapController } from "./Controller/MapController";
|
import { MapController } from "./Controller/MapController";
|
||||||
import { PrometheusController } from "./Controller/PrometheusController";
|
import { PrometheusController } from "./Controller/PrometheusController";
|
||||||
import { DebugController } from "./Controller/DebugController";
|
import { DebugController } from "./Controller/DebugController";
|
||||||
import { App as uwsApp } from "./Server/sifrr.server";
|
|
||||||
import { AdminController } from "./Controller/AdminController";
|
import { AdminController } from "./Controller/AdminController";
|
||||||
import { OpenIdProfileController } from "./Controller/OpenIdProfileController";
|
import { OpenIdProfileController } from "./Controller/OpenIdProfileController";
|
||||||
|
import HyperExpress from "hyper-express";
|
||||||
|
import { cors } from "./Middleware/Cors";
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
public app: uwsApp;
|
public app: HyperExpress.compressors.TemplatedApp;
|
||||||
public ioSocketController: IoSocketController;
|
|
||||||
public authenticateController: AuthenticateController;
|
|
||||||
public mapController: MapController;
|
|
||||||
public prometheusController: PrometheusController;
|
|
||||||
private debugController: DebugController;
|
|
||||||
private adminController: AdminController;
|
|
||||||
private openIdProfileController: OpenIdProfileController;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.app = new uwsApp();
|
const webserver = new HyperExpress.Server();
|
||||||
|
this.app = webserver.uws_instance;
|
||||||
|
|
||||||
//create socket controllers
|
// Global middlewares
|
||||||
this.ioSocketController = new IoSocketController(this.app);
|
webserver.use(cors);
|
||||||
this.authenticateController = new AuthenticateController(this.app);
|
|
||||||
this.mapController = new MapController(this.app);
|
// Socket controllers
|
||||||
this.prometheusController = new PrometheusController(this.app);
|
new IoSocketController(this.app);
|
||||||
this.debugController = new DebugController(this.app);
|
|
||||||
this.adminController = new AdminController(this.app);
|
// Http controllers
|
||||||
this.openIdProfileController = new OpenIdProfileController(this.app);
|
new AuthenticateController(webserver);
|
||||||
|
new MapController(webserver);
|
||||||
|
new PrometheusController(webserver);
|
||||||
|
new DebugController(webserver);
|
||||||
|
new AdminController(webserver);
|
||||||
|
new OpenIdProfileController(webserver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,45 +1,22 @@
|
|||||||
import { BaseController } from "./BaseController";
|
|
||||||
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
|
||||||
import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable";
|
|
||||||
import { apiClientRepository } from "../Services/ApiClientRepository";
|
import { apiClientRepository } from "../Services/ApiClientRepository";
|
||||||
import {
|
import {
|
||||||
AdminRoomMessage,
|
AdminRoomMessage,
|
||||||
WorldFullWarningToRoomMessage,
|
WorldFullWarningToRoomMessage,
|
||||||
RefreshRoomPromptMessage,
|
RefreshRoomPromptMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
|
import { adminToken } from "../Middleware/AdminToken";
|
||||||
|
import { BaseHttpController } from "./BaseHttpController";
|
||||||
|
|
||||||
export class AdminController extends BaseController {
|
export class AdminController extends BaseHttpController {
|
||||||
constructor(private App: TemplatedApp) {
|
routes() {
|
||||||
super();
|
|
||||||
this.App = App;
|
|
||||||
this.receiveGlobalMessagePrompt();
|
this.receiveGlobalMessagePrompt();
|
||||||
this.receiveRoomEditionPrompt();
|
this.receiveRoomEditionPrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveRoomEditionPrompt() {
|
receiveRoomEditionPrompt() {
|
||||||
this.App.options("/room/refresh", (res: HttpResponse, req: HttpRequest) => {
|
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
this.App.post("/room/refresh", async (res: HttpResponse, req: HttpRequest) => {
|
this.app.post("/room/refresh", { middlewares: [adminToken] }, async (req, res) => {
|
||||||
res.onAborted(() => {
|
const body = await req.json();
|
||||||
console.warn("/message request was aborted");
|
|
||||||
});
|
|
||||||
|
|
||||||
const token = req.getHeader("admin-token");
|
|
||||||
const body = await res.json();
|
|
||||||
|
|
||||||
if (ADMIN_API_TOKEN === "") {
|
|
||||||
res.writeStatus("401 Unauthorized").end("No token configured!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (token !== ADMIN_API_TOKEN) {
|
|
||||||
console.error("Admin access refused for token: " + token);
|
|
||||||
res.writeStatus("401 Unauthorized").end("Incorrect token");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof body.roomId !== "string") {
|
if (typeof body.roomId !== "string") {
|
||||||
@ -58,41 +35,18 @@ export class AdminController extends BaseController {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.errorToResponse(err, res);
|
this.castErrorToResponse(err, res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.writeStatus("200");
|
res.send("ok");
|
||||||
res.end("ok");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveGlobalMessagePrompt() {
|
receiveGlobalMessagePrompt() {
|
||||||
this.App.options("/message", (res: HttpResponse, req: HttpRequest) => {
|
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
this.App.post("/message", async (res: HttpResponse, req: HttpRequest) => {
|
this.app.post("/message", { middlewares: [adminToken] }, async (req, res) => {
|
||||||
res.onAborted(() => {
|
const body = await req.json();
|
||||||
console.warn("/message request was aborted");
|
|
||||||
});
|
|
||||||
|
|
||||||
const token = req.getHeader("admin-token");
|
|
||||||
const body = await res.json();
|
|
||||||
|
|
||||||
if (ADMIN_API_TOKEN === "") {
|
|
||||||
res.writeStatus("401 Unauthorized").end("No token configured!");
|
|
||||||
res.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (token !== ADMIN_API_TOKEN) {
|
|
||||||
console.error("Admin access refused for token: " + token);
|
|
||||||
res.writeStatus("401 Unauthorized").end("Incorrect token");
|
|
||||||
res.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof body.text !== "string") {
|
if (typeof body.text !== "string") {
|
||||||
@ -133,13 +87,11 @@ export class AdminController extends BaseController {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.errorToResponse(err, res);
|
this.castErrorToResponse(err, res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.writeStatus("200");
|
res.send("ok");
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.end("ok");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
import { BaseHttpController } from "./BaseHttpController";
|
||||||
import { BaseController } from "./BaseController";
|
|
||||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||||
import { parse } from "query-string";
|
import { parse } from "query-string";
|
||||||
import { openIDClient } from "../Services/OpenIDClient";
|
import { openIDClient } from "../Services/OpenIDClient";
|
||||||
import { DISABLE_ANONYMOUS, FRONT_URL } from "../Enum/EnvironmentVariable";
|
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||||
import { RegisterData } from "../Messages/JsonMessages/RegisterData";
|
import { RegisterData } from "../Messages/JsonMessages/RegisterData";
|
||||||
|
|
||||||
export interface TokenInterface {
|
export interface TokenInterface {
|
||||||
userUuid: string;
|
userUuid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AuthenticateController extends BaseController {
|
export class AuthenticateController extends BaseHttpController {
|
||||||
constructor(private App: TemplatedApp) {
|
routes() {
|
||||||
super();
|
|
||||||
this.openIDLogin();
|
this.openIDLogin();
|
||||||
this.openIDCallback();
|
this.openIDCallback();
|
||||||
this.register();
|
this.register();
|
||||||
@ -24,13 +22,9 @@ export class AuthenticateController extends BaseController {
|
|||||||
|
|
||||||
openIDLogin() {
|
openIDLogin() {
|
||||||
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
this.App.get("/login-screen", async (res: HttpResponse, req: HttpRequest) => {
|
this.app.get("/login-screen", async (req, res) => {
|
||||||
res.onAborted(() => {
|
|
||||||
console.warn("/message request was aborted");
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { nonce, state, playUri, redirect } = parse(req.getQuery());
|
const { nonce, state, playUri, redirect } = parse(req.path_query);
|
||||||
if (!state || !nonce) {
|
if (!state || !nonce) {
|
||||||
throw new Error("missing state and nonce URL parameters");
|
throw new Error("missing state and nonce URL parameters");
|
||||||
}
|
}
|
||||||
@ -41,24 +35,22 @@ export class AuthenticateController extends BaseController {
|
|||||||
playUri as string | undefined,
|
playUri as string | undefined,
|
||||||
redirect as string | undefined
|
redirect as string | undefined
|
||||||
);
|
);
|
||||||
res.writeStatus("302");
|
res.status(302);
|
||||||
res.writeHeader("Location", loginUri);
|
res.setHeader("Location", loginUri);
|
||||||
return res.end();
|
return res;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("openIDLogin => e", e);
|
console.error("openIDLogin => e", e);
|
||||||
return this.errorToResponse(e, res);
|
this.castErrorToResponse(e, res);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openIDCallback() {
|
openIDCallback() {
|
||||||
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
this.App.get("/login-callback", async (res: HttpResponse, req: HttpRequest) => {
|
this.app.get("/login-callback", async (req, res) => {
|
||||||
res.onAborted(() => {
|
const IPAddress = req.header("x-forwarded-for");
|
||||||
console.warn("/message request was aborted");
|
const { code, nonce, token, playUri } = parse(req.path_query);
|
||||||
});
|
|
||||||
const IPAddress = req.getHeader("x-forwarded-for");
|
|
||||||
const { code, nonce, token, playUri } = parse(req.getQuery());
|
|
||||||
try {
|
try {
|
||||||
//verify connected by token
|
//verify connected by token
|
||||||
if (token != undefined) {
|
if (token != undefined) {
|
||||||
@ -77,23 +69,16 @@ export class AuthenticateController extends BaseController {
|
|||||||
//if not nonce and code, user connected in anonymous
|
//if not nonce and code, user connected in anonymous
|
||||||
//get data with identifier and return token
|
//get data with identifier and return token
|
||||||
if (!code && !nonce) {
|
if (!code && !nonce) {
|
||||||
res.writeStatus("200");
|
return res.json(JSON.stringify({ ...resUserData, authToken: token }));
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.writeHeader("Content-Type", "application/json");
|
|
||||||
return res.end(JSON.stringify({ ...resUserData, authToken: token }));
|
|
||||||
}
|
}
|
||||||
console.error("Token cannot to be check on OpenId provider");
|
console.error("Token cannot to be check on OpenId provider");
|
||||||
res.writeStatus("500");
|
res.status(500);
|
||||||
res.writeHeader("Access-Control-Allow-Origin", FRONT_URL);
|
res.send("User cannot to be connected on openid provider");
|
||||||
res.end("User cannot to be connected on openid provider");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken);
|
const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken);
|
||||||
res.writeStatus("200");
|
return res.json({ ...resCheckTokenAuth, ...resUserData, authToken: token });
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.writeHeader("Content-Type", "application/json");
|
|
||||||
return res.end(JSON.stringify({ ...resCheckTokenAuth, ...resUserData, authToken: token }));
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.info("User was not connected", err);
|
console.info("User was not connected", err);
|
||||||
}
|
}
|
||||||
@ -106,9 +91,8 @@ export class AuthenticateController extends BaseController {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
//if no access on openid provider, return error
|
//if no access on openid provider, return error
|
||||||
console.error("User cannot to be connected on OpenId provider => ", err);
|
console.error("User cannot to be connected on OpenId provider => ", err);
|
||||||
res.writeStatus("500");
|
res.status(500);
|
||||||
res.writeHeader("Access-Control-Allow-Origin", FRONT_URL);
|
res.send("User cannot to be connected on openid provider");
|
||||||
res.end("User cannot to be connected on openid provider");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const email = userInfo.email || userInfo.sub;
|
const email = userInfo.email || userInfo.sub;
|
||||||
@ -121,23 +105,16 @@ export class AuthenticateController extends BaseController {
|
|||||||
//This is very important to create User Local in LocalStorage in WorkAdventure
|
//This is very important to create User Local in LocalStorage in WorkAdventure
|
||||||
const data = await this.getUserByUserIdentifier(email, playUri as string, IPAddress);
|
const data = await this.getUserByUserIdentifier(email, playUri as string, IPAddress);
|
||||||
|
|
||||||
res.writeStatus("200");
|
return res.json({ ...data, authToken });
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.writeHeader("Content-Type", "application/json");
|
|
||||||
return res.end(JSON.stringify({ ...data, authToken }));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("openIDCallback => ERROR", e);
|
console.error("openIDCallback => ERROR", e);
|
||||||
return this.errorToResponse(e, res);
|
return this.castErrorToResponse(e, res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
this.App.get("/logout-callback", async (res: HttpResponse, req: HttpRequest) => {
|
this.app.get("/logout-callback", async (req, res) => {
|
||||||
res.onAborted(() => {
|
const { token } = parse(req.path_query);
|
||||||
console.warn("/message request was aborted");
|
|
||||||
});
|
|
||||||
|
|
||||||
const { token } = parse(req.getQuery());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||||
@ -147,29 +124,17 @@ export class AuthenticateController extends BaseController {
|
|||||||
await openIDClient.logoutUser(authTokenData.accessToken);
|
await openIDClient.logoutUser(authTokenData.accessToken);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("openIDCallback => logout-callback", error);
|
console.error("openIDCallback => logout-callback", error);
|
||||||
} finally {
|
|
||||||
res.writeStatus("200");
|
|
||||||
this.addCorsHeaders(res);
|
|
||||||
// eslint-disable-next-line no-unsafe-finally
|
|
||||||
return res.end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Try to login with an admin token
|
//Try to login with an admin token
|
||||||
private register() {
|
private register() {
|
||||||
this.App.options("/register", (res: HttpResponse, req: HttpRequest) => {
|
this.app.post("/register", (req, res) => {
|
||||||
this.addCorsHeaders(res);
|
|
||||||
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.App.post("/register", (res: HttpResponse, req: HttpRequest) => {
|
|
||||||
(async () => {
|
(async () => {
|
||||||
res.onAborted(() => {
|
const param = await req.json();
|
||||||
console.warn("Login request was aborted");
|
|
||||||
});
|
|
||||||
const param = await res.json();
|
|
||||||
|
|
||||||
//todo: what to do if the organizationMemberToken is already used?
|
//todo: what to do if the organizationMemberToken is already used?
|
||||||
const organizationMemberToken: string | null = param.organizationMemberToken;
|
const organizationMemberToken: string | null = param.organizationMemberToken;
|
||||||
@ -184,11 +149,7 @@ export class AuthenticateController extends BaseController {
|
|||||||
const textures = data.textures;
|
const textures = data.textures;
|
||||||
|
|
||||||
const authToken = jwtTokenManager.createAuthToken(email || userUuid);
|
const authToken = jwtTokenManager.createAuthToken(email || userUuid);
|
||||||
res.writeStatus("200 OK");
|
res.json({
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.writeHeader("Content-Type", "application/json");
|
|
||||||
res.end(
|
|
||||||
JSON.stringify({
|
|
||||||
authToken,
|
authToken,
|
||||||
userUuid,
|
userUuid,
|
||||||
email,
|
email,
|
||||||
@ -196,11 +157,10 @@ export class AuthenticateController extends BaseController {
|
|||||||
mapUrlStart,
|
mapUrlStart,
|
||||||
organizationMemberToken,
|
organizationMemberToken,
|
||||||
textures,
|
textures,
|
||||||
} as RegisterData)
|
} as RegisterData);
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("register => ERROR", e);
|
console.error("register => ERROR", e);
|
||||||
this.errorToResponse(e, res);
|
this.castErrorToResponse(e, res);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
@ -208,44 +168,25 @@ export class AuthenticateController extends BaseController {
|
|||||||
|
|
||||||
//permit to login on application. Return token to connect on Websocket IO.
|
//permit to login on application. Return token to connect on Websocket IO.
|
||||||
private anonymLogin() {
|
private anonymLogin() {
|
||||||
this.App.options("/anonymLogin", (res: HttpResponse, req: HttpRequest) => {
|
this.app.post("/anonymLogin", (req, res) => {
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.App.post("/anonymLogin", (res: HttpResponse, req: HttpRequest) => {
|
|
||||||
res.onAborted(() => {
|
|
||||||
console.warn("Login request was aborted");
|
|
||||||
});
|
|
||||||
|
|
||||||
if (DISABLE_ANONYMOUS) {
|
if (DISABLE_ANONYMOUS) {
|
||||||
res.writeStatus("403 FORBIDDEN");
|
res.status(403);
|
||||||
res.end();
|
return res;
|
||||||
} else {
|
} else {
|
||||||
const userUuid = v4();
|
const userUuid = v4();
|
||||||
const authToken = jwtTokenManager.createAuthToken(userUuid);
|
const authToken = jwtTokenManager.createAuthToken(userUuid);
|
||||||
res.writeStatus("200 OK");
|
return res.json({
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.writeHeader("Content-Type", "application/json");
|
|
||||||
res.end(
|
|
||||||
JSON.stringify({
|
|
||||||
authToken,
|
authToken,
|
||||||
userUuid,
|
userUuid,
|
||||||
})
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
profileCallback() {
|
profileCallback() {
|
||||||
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
// @ts-ignore
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
this.App.get("/profile-callback", async (res: HttpResponse, req: HttpRequest) => {
|
this.app.get("/profile-callback", async (req, res) => {
|
||||||
res.onAborted(() => {
|
const { token } = parse(req.path_query);
|
||||||
console.warn("/message request was aborted");
|
|
||||||
});
|
|
||||||
const { token } = parse(req.getQuery());
|
|
||||||
try {
|
try {
|
||||||
//verify connected by token
|
//verify connected by token
|
||||||
if (token != undefined) {
|
if (token != undefined) {
|
||||||
@ -257,18 +198,17 @@ export class AuthenticateController extends BaseController {
|
|||||||
await openIDClient.checkTokenAuth(authTokenData.accessToken);
|
await openIDClient.checkTokenAuth(authTokenData.accessToken);
|
||||||
|
|
||||||
//get login profile
|
//get login profile
|
||||||
res.writeStatus("302");
|
res.status(302);
|
||||||
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken));
|
res.setHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken));
|
||||||
this.addCorsHeaders(res);
|
return;
|
||||||
// eslint-disable-next-line no-unsafe-finally
|
|
||||||
return res.end();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return this.errorToResponse(error, res);
|
this.castErrorToResponse(error, res);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("profileCallback => ERROR", error);
|
console.error("profileCallback => ERROR", error);
|
||||||
this.errorToResponse(error, res);
|
this.castErrorToResponse(error, res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
import { HttpResponse } from "uWebSockets.js";
|
|
||||||
import { FRONT_URL } from "../Enum/EnvironmentVariable";
|
|
||||||
|
|
||||||
export class BaseController {
|
|
||||||
protected addCorsHeaders(res: HttpResponse): void {
|
|
||||||
res.writeHeader("access-control-allow-headers", "Origin, X-Requested-With, Content-Type, Accept");
|
|
||||||
res.writeHeader("access-control-allow-methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
|
|
||||||
res.writeHeader("access-control-allow-origin", FRONT_URL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turns any exception into a HTTP response (and logs the error)
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
protected errorToResponse(e: any, res: HttpResponse): void {
|
|
||||||
if (e && e.message) {
|
|
||||||
let url = e?.config?.url;
|
|
||||||
if (url !== undefined) {
|
|
||||||
url = " for URL: " + url;
|
|
||||||
} else {
|
|
||||||
url = "";
|
|
||||||
}
|
|
||||||
console.error("ERROR: " + e.message + url);
|
|
||||||
} else if (typeof e === "string") {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
if (e.stack) {
|
|
||||||
console.error(e.stack);
|
|
||||||
}
|
|
||||||
if (e.response) {
|
|
||||||
res.writeStatus(e.response.status + " " + e.response.statusText);
|
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.end(
|
|
||||||
"An error occurred: " +
|
|
||||||
e.response.status +
|
|
||||||
" " +
|
|
||||||
(e.response.data && e.response.data.message ? e.response.data.message : e.response.statusText)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
res.writeStatus("500 Internal Server Error");
|
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.end("An error occurred");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
45
pusher/src/Controller/BaseHttpController.ts
Normal file
45
pusher/src/Controller/BaseHttpController.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { Server } from "hyper-express";
|
||||||
|
import Response from "hyper-express/types/components/http/Response";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export class BaseHttpController {
|
||||||
|
constructor(protected app: Server) {
|
||||||
|
this.routes();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected routes() {
|
||||||
|
/* Define routes on children */
|
||||||
|
}
|
||||||
|
|
||||||
|
protected castErrorToResponse(e: unknown, res: Response): void {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
let url: string | undefined;
|
||||||
|
if (axios.isAxiosError(e)) {
|
||||||
|
url = e.config.url;
|
||||||
|
if (url !== undefined) {
|
||||||
|
url = " for URL: " + url;
|
||||||
|
} else {
|
||||||
|
url = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error("ERROR: " + e.message + url);
|
||||||
|
console.error(e.stack);
|
||||||
|
} else if (typeof e === "string") {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
res.status(500);
|
||||||
|
res.send("An error occurred");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +1,24 @@
|
|||||||
import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable";
|
||||||
import { IoSocketController } from "_Controller/IoSocketController";
|
|
||||||
import { stringify } from "circular-json";
|
import { stringify } from "circular-json";
|
||||||
import { HttpRequest, HttpResponse } from "uWebSockets.js";
|
|
||||||
import { parse } from "query-string";
|
import { parse } from "query-string";
|
||||||
import { App } from "../Server/sifrr.server";
|
|
||||||
import { socketManager } from "../Services/SocketManager";
|
import { socketManager } from "../Services/SocketManager";
|
||||||
|
import { BaseHttpController } from "./BaseHttpController";
|
||||||
|
|
||||||
export class DebugController {
|
export class DebugController extends BaseHttpController {
|
||||||
constructor(private App: App) {
|
routes() {
|
||||||
this.getDump();
|
this.app.get("/dump", (req, res) => {
|
||||||
}
|
const query = parse(req.path_query);
|
||||||
|
|
||||||
getDump() {
|
|
||||||
this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => {
|
|
||||||
const query = parse(req.getQuery());
|
|
||||||
|
|
||||||
if (ADMIN_API_TOKEN === "") {
|
if (ADMIN_API_TOKEN === "") {
|
||||||
return res.writeStatus("401 Unauthorized").end("No token configured!");
|
return res.status(401).send("No token configured!");
|
||||||
}
|
}
|
||||||
if (query.token !== ADMIN_API_TOKEN) {
|
if (query.token !== ADMIN_API_TOKEN) {
|
||||||
return res.writeStatus("401 Unauthorized").end("Invalid token sent!");
|
return res.status(401).send("Invalid token sent!");
|
||||||
}
|
}
|
||||||
|
|
||||||
const worlds = Object.fromEntries(socketManager.getWorlds().entries());
|
const worlds = Object.fromEntries(socketManager.getWorlds().entries());
|
||||||
|
|
||||||
return res
|
return res.json(
|
||||||
.writeStatus("200 OK")
|
|
||||||
.writeHeader("Content-Type", "application/json")
|
|
||||||
.end(
|
|
||||||
stringify(worlds, (key: unknown, value: unknown) => {
|
stringify(worlds, (key: unknown, value: unknown) => {
|
||||||
if (value instanceof Map) {
|
if (value instanceof Map) {
|
||||||
const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
|
const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { CharacterLayer, ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
|
import { CharacterLayer, ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
|
||||||
import { GameRoomPolicyTypes, PusherRoom } from "../Model/PusherRoom";
|
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
||||||
import { PointInterface } from "../Model/Websocket/PointInterface";
|
import { PointInterface } from "../Model/Websocket/PointInterface";
|
||||||
import {
|
import {
|
||||||
SetPlayerDetailsMessage,
|
SetPlayerDetailsMessage,
|
||||||
@ -23,7 +23,6 @@ import {
|
|||||||
VariableMessage,
|
VariableMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import { UserMovesMessage } from "../Messages/generated/messages_pb";
|
import { UserMovesMessage } from "../Messages/generated/messages_pb";
|
||||||
import { TemplatedApp } from "uWebSockets.js";
|
|
||||||
import { parse } from "query-string";
|
import { parse } from "query-string";
|
||||||
import { AdminSocketTokenData, jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenManager";
|
import { AdminSocketTokenData, jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenManager";
|
||||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||||
@ -36,11 +35,12 @@ import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture";
|
|||||||
import { isAdminMessageInterface } from "../Model/Websocket/Admin/AdminMessages";
|
import { isAdminMessageInterface } from "../Model/Websocket/Admin/AdminMessages";
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import { InvalidTokenError } from "../Controller/InvalidTokenError";
|
import { InvalidTokenError } from "../Controller/InvalidTokenError";
|
||||||
|
import HyperExpress from "hyper-express";
|
||||||
|
|
||||||
export class IoSocketController {
|
export class IoSocketController {
|
||||||
private nextUserId: number = 1;
|
private nextUserId: number = 1;
|
||||||
|
|
||||||
constructor(private readonly app: TemplatedApp) {
|
constructor(private readonly app: HyperExpress.compressors.TemplatedApp) {
|
||||||
this.ioConnection();
|
this.ioConnection();
|
||||||
if (ADMIN_SOCKETS_TOKEN) {
|
if (ADMIN_SOCKETS_TOKEN) {
|
||||||
this.adminRoomSocket();
|
this.adminRoomSocket();
|
||||||
|
@ -1,41 +1,21 @@
|
|||||||
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
|
||||||
import { BaseController } from "./BaseController";
|
|
||||||
import { parse } from "query-string";
|
|
||||||
import { adminApi } from "../Services/AdminApi";
|
import { adminApi } from "../Services/AdminApi";
|
||||||
import { ADMIN_API_URL, DISABLE_ANONYMOUS, FRONT_URL } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_URL, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||||
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
||||||
import { isMapDetailsData, MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
import { isMapDetailsData, MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||||
import { socketManager } from "../Services/SocketManager";
|
|
||||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||||
import { v4 } from "uuid";
|
|
||||||
import { InvalidTokenError } from "./InvalidTokenError";
|
import { InvalidTokenError } from "./InvalidTokenError";
|
||||||
|
import { parse } from "query-string";
|
||||||
|
import { BaseHttpController } from "./BaseHttpController";
|
||||||
|
|
||||||
export class MapController extends BaseController {
|
export class MapController extends BaseHttpController {
|
||||||
constructor(private App: TemplatedApp) {
|
|
||||||
super();
|
|
||||||
this.App = App;
|
|
||||||
this.getMapUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a map mapping map name to file name of the map
|
// Returns a map mapping map name to file name of the map
|
||||||
getMapUrl() {
|
routes() {
|
||||||
this.App.options("/map", (res: HttpResponse, req: HttpRequest) => {
|
this.app.get("/map", (req, res) => {
|
||||||
this.addCorsHeaders(res);
|
const query = parse(req.path_query);
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.App.get("/map", (res: HttpResponse, req: HttpRequest) => {
|
|
||||||
res.onAborted(() => {
|
|
||||||
console.warn("/map request was aborted");
|
|
||||||
});
|
|
||||||
|
|
||||||
const query = parse(req.getQuery());
|
|
||||||
|
|
||||||
if (typeof query.playUri !== "string") {
|
if (typeof query.playUri !== "string") {
|
||||||
console.error("Expected playUri parameter in /map endpoint");
|
console.error("Expected playUri parameter in /map endpoint");
|
||||||
res.writeStatus("400 Bad request");
|
res.status(400);
|
||||||
this.addCorsHeaders(res);
|
res.send("Expected playUri parameter");
|
||||||
res.end("Expected playUri parameter");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,20 +25,14 @@ export class MapController extends BaseController {
|
|||||||
|
|
||||||
const match = /\/_\/[^/]+\/(.+)/.exec(roomUrl.pathname);
|
const match = /\/_\/[^/]+\/(.+)/.exec(roomUrl.pathname);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
res.writeStatus("404 Not Found");
|
res.status(404);
|
||||||
this.addCorsHeaders(res);
|
res.json({});
|
||||||
res.writeHeader("Content-Type", "application/json");
|
|
||||||
res.end(JSON.stringify({}));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapUrl = roomUrl.protocol + "//" + match[1];
|
const mapUrl = roomUrl.protocol + "//" + match[1];
|
||||||
|
|
||||||
res.writeStatus("200 OK");
|
res.json({
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.writeHeader("Content-Type", "application/json");
|
|
||||||
res.end(
|
|
||||||
JSON.stringify({
|
|
||||||
mapUrl,
|
mapUrl,
|
||||||
policy_type: GameRoomPolicyTypes.ANONYMOUS_POLICY,
|
policy_type: GameRoomPolicyTypes.ANONYMOUS_POLICY,
|
||||||
roomSlug: null, // Deprecated
|
roomSlug: null, // Deprecated
|
||||||
@ -67,8 +41,7 @@ export class MapController extends BaseController {
|
|||||||
textures: [],
|
textures: [],
|
||||||
contactPage: null,
|
contactPage: null,
|
||||||
authenticationMandatory: DISABLE_ANONYMOUS,
|
authenticationMandatory: DISABLE_ANONYMOUS,
|
||||||
} as MapDetailsData)
|
} as MapDetailsData);
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -90,12 +63,12 @@ export class MapController extends BaseController {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof InvalidTokenError) {
|
if (e instanceof InvalidTokenError) {
|
||||||
// The token was not good, redirect user on login page
|
// The token was not good, redirect user on login page
|
||||||
res.writeStatus("401 Unauthorized");
|
res.status(401);
|
||||||
res.writeHeader("Access-Control-Allow-Origin", FRONT_URL);
|
res.send("Token decrypted error");
|
||||||
res.end("Token decrypted error");
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
return this.errorToResponse(e, res);
|
this.castErrorToResponse(e, res);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,12 +79,9 @@ export class MapController extends BaseController {
|
|||||||
mapDetails.authenticationMandatory = true;
|
mapDetails.authenticationMandatory = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.writeStatus("200 OK");
|
res.json(mapDetails);
|
||||||
this.addCorsHeaders(res);
|
|
||||||
res.writeHeader("Content-Type", "application/json");
|
|
||||||
res.end(JSON.stringify(mapDetails));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.errorToResponse(e, res);
|
this.castErrorToResponse(e, res);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
|
@ -1,26 +1,13 @@
|
|||||||
import { BaseController } from "./BaseController";
|
|
||||||
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
|
||||||
import { parse } from "query-string";
|
import { parse } from "query-string";
|
||||||
import { openIDClient } from "../Services/OpenIDClient";
|
import { openIDClient } from "../Services/OpenIDClient";
|
||||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
|
||||||
import { adminApi } from "../Services/AdminApi";
|
|
||||||
import { OPID_CLIENT_ISSUER } from "../Enum/EnvironmentVariable";
|
import { OPID_CLIENT_ISSUER } from "../Enum/EnvironmentVariable";
|
||||||
import { IntrospectionResponse } from "openid-client";
|
import { BaseHttpController } from "./BaseHttpController";
|
||||||
|
|
||||||
export class OpenIdProfileController extends BaseController {
|
export class OpenIdProfileController extends BaseHttpController {
|
||||||
constructor(private App: TemplatedApp) {
|
routes() {
|
||||||
super();
|
|
||||||
this.profileOpenId();
|
|
||||||
}
|
|
||||||
|
|
||||||
profileOpenId() {
|
|
||||||
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
this.App.get("/profile", async (res: HttpResponse, req: HttpRequest) => {
|
this.app.get("/profile", async (req, res) => {
|
||||||
res.onAborted(() => {
|
const { accessToken } = parse(req.path_query);
|
||||||
console.warn("/message request was aborted");
|
|
||||||
});
|
|
||||||
|
|
||||||
const { accessToken } = parse(req.getQuery());
|
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
throw Error("Access token expected cannot to be check on Hydra");
|
throw Error("Access token expected cannot to be check on Hydra");
|
||||||
}
|
}
|
||||||
@ -29,7 +16,7 @@ export class OpenIdProfileController extends BaseController {
|
|||||||
if (!resCheckTokenAuth.email) {
|
if (!resCheckTokenAuth.email) {
|
||||||
throw new Error("Email was not found");
|
throw new Error("Email was not found");
|
||||||
}
|
}
|
||||||
res.end(
|
res.send(
|
||||||
this.buildHtml(
|
this.buildHtml(
|
||||||
OPID_CLIENT_ISSUER,
|
OPID_CLIENT_ISSUER,
|
||||||
resCheckTokenAuth.email as string,
|
resCheckTokenAuth.email as string,
|
||||||
@ -38,7 +25,7 @@ export class OpenIdProfileController extends BaseController {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("profileCallback => ERROR", error);
|
console.error("profileCallback => ERROR", error);
|
||||||
this.errorToResponse(error, res);
|
this.castErrorToResponse(error, res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,23 @@
|
|||||||
import { App } from "../Server/sifrr.server";
|
|
||||||
import { HttpRequest, HttpResponse } from "uWebSockets.js";
|
|
||||||
import { register, collectDefaultMetrics } from "prom-client";
|
import { register, collectDefaultMetrics } from "prom-client";
|
||||||
|
import { Server } from "hyper-express";
|
||||||
|
import { BaseHttpController } from "./BaseHttpController";
|
||||||
|
import Request from "hyper-express/types/components/http/Request";
|
||||||
|
import Response from "hyper-express/types/components/http/Response";
|
||||||
|
|
||||||
export class PrometheusController {
|
export class PrometheusController extends BaseHttpController {
|
||||||
constructor(private App: App) {
|
constructor(app: Server) {
|
||||||
|
super(app);
|
||||||
collectDefaultMetrics({
|
collectDefaultMetrics({
|
||||||
gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5], // These are the default buckets.
|
gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5], // These are the default buckets.
|
||||||
});
|
});
|
||||||
|
|
||||||
this.App.get("/metrics", this.metrics.bind(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private metrics(res: HttpResponse, req: HttpRequest): void {
|
routes() {
|
||||||
res.writeHeader("Content-Type", register.contentType);
|
this.app.get("/metrics", this.metrics.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private metrics(req: Request, res: Response): void {
|
||||||
|
res.setHeader("Content-Type", register.contentType);
|
||||||
res.end(register.metrics());
|
res.end(register.metrics());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
22
pusher/src/Middleware/AdminToken.ts
Normal file
22
pusher/src/Middleware/AdminToken.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import Request from "hyper-express/types/components/http/Request";
|
||||||
|
import Response from "hyper-express/types/components/http/Response";
|
||||||
|
import { MiddlewareNext, MiddlewarePromise } from "hyper-express/types/components/router/Router";
|
||||||
|
import { ADMIN_API_TOKEN } from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
|
export function adminToken(req: Request, res: Response, next?: MiddlewareNext): MiddlewarePromise {
|
||||||
|
const token = req.header("admin-token");
|
||||||
|
|
||||||
|
if (ADMIN_API_TOKEN === "") {
|
||||||
|
res.status(401).end("No token configured!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (token !== ADMIN_API_TOKEN) {
|
||||||
|
console.error("Admin access refused for token: " + token);
|
||||||
|
res.status(401).end("Incorrect token");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
14
pusher/src/Middleware/Cors.ts
Normal file
14
pusher/src/Middleware/Cors.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import Request from "hyper-express/types/components/http/Request";
|
||||||
|
import Response from "hyper-express/types/components/http/Response";
|
||||||
|
import { MiddlewareNext, MiddlewarePromise } from "hyper-express/types/components/router/Router";
|
||||||
|
import { FRONT_URL } from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
|
export function cors(req: Request, res: Response, next?: MiddlewareNext): MiddlewarePromise {
|
||||||
|
res.setHeader("access-control-allow-headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||||
|
res.setHeader("access-control-allow-methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
|
||||||
|
res.setHeader("access-control-allow-origin", FRONT_URL);
|
||||||
|
|
||||||
|
if (next) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
@ -9,13 +9,13 @@ import {
|
|||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
SubMessage,
|
SubMessage,
|
||||||
} from "../../Messages/generated/messages_pb";
|
} from "../../Messages/generated/messages_pb";
|
||||||
import { WebSocket } from "uWebSockets.js";
|
import { compressors } from "hyper-express";
|
||||||
import { ClientDuplexStream } from "grpc";
|
import { ClientDuplexStream } from "grpc";
|
||||||
import { Zone } from "_Model/Zone";
|
import { Zone } from "_Model/Zone";
|
||||||
|
|
||||||
export type AdminConnection = ClientDuplexStream<AdminPusherToBackMessage, ServerToAdminClientMessage>;
|
export type AdminConnection = ClientDuplexStream<AdminPusherToBackMessage, ServerToAdminClientMessage>;
|
||||||
|
|
||||||
export interface ExAdminSocketInterface extends WebSocket {
|
export interface ExAdminSocketInterface extends compressors.WebSocket {
|
||||||
adminConnection: AdminConnection;
|
adminConnection: AdminConnection;
|
||||||
disconnecting: boolean;
|
disconnecting: boolean;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import { WebSocket } from "uWebSockets.js";
|
|||||||
import { ClientDuplexStream } from "grpc";
|
import { ClientDuplexStream } from "grpc";
|
||||||
import { Zone } from "_Model/Zone";
|
import { Zone } from "_Model/Zone";
|
||||||
import { CharacterTexture } from "../../Messages/JsonMessages/CharacterTexture";
|
import { CharacterTexture } from "../../Messages/JsonMessages/CharacterTexture";
|
||||||
|
import { compressors } from "hyper-express";
|
||||||
|
|
||||||
export type BackConnection = ClientDuplexStream<PusherToBackMessage, ServerToClientMessage>;
|
export type BackConnection = ClientDuplexStream<PusherToBackMessage, ServerToClientMessage>;
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ export interface CharacterLayer {
|
|||||||
url: string | undefined;
|
url: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExSocketInterface extends WebSocket, Identificable {
|
export interface ExSocketInterface extends compressors.WebSocket, Identificable {
|
||||||
token: string;
|
token: string;
|
||||||
roomId: string;
|
roomId: string;
|
||||||
//userId: number; // A temporary (autoincremented) identifier for this user
|
//userId: number; // A temporary (autoincremented) identifier for this user
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import { App as _App, AppOptions } from "uWebSockets.js";
|
|
||||||
import BaseApp from "./baseapp";
|
|
||||||
import { extend } from "./utils";
|
|
||||||
import { UwsApp } from "./types";
|
|
||||||
|
|
||||||
class App extends (<UwsApp>_App) {
|
|
||||||
constructor(options: AppOptions = {}) {
|
|
||||||
super(options); // eslint-disable-line constructor-super
|
|
||||||
extend(this, new BaseApp());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
@ -1,109 +0,0 @@
|
|||||||
import { Readable } from "stream";
|
|
||||||
import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js";
|
|
||||||
|
|
||||||
import formData from "./formdata";
|
|
||||||
import { stob } from "./utils";
|
|
||||||
import { Handler } from "./types";
|
|
||||||
import { join } from "path";
|
|
||||||
|
|
||||||
const contTypes = ["application/x-www-form-urlencoded", "multipart/form-data"];
|
|
||||||
const noOp = () => true;
|
|
||||||
|
|
||||||
const handleBody = (res: HttpResponse, req: HttpRequest) => {
|
|
||||||
const contType = req.getHeader("content-type");
|
|
||||||
|
|
||||||
res.bodyStream = function () {
|
|
||||||
const stream = new Readable();
|
|
||||||
stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method
|
|
||||||
|
|
||||||
this.onData((ab: ArrayBuffer, isLast: boolean) => {
|
|
||||||
// uint and then slicing is bit faster than slice and then uint
|
|
||||||
stream.push(new Uint8Array(ab.slice((ab as any).byteOffset, ab.byteLength))); // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
if (isLast) {
|
|
||||||
stream.push(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return stream;
|
|
||||||
};
|
|
||||||
|
|
||||||
res.body = () => stob(res.bodyStream());
|
|
||||||
|
|
||||||
if (contType.includes("application/json")) res.json = async () => JSON.parse(await res.body());
|
|
||||||
if (contTypes.map((t) => contType.includes(t)).includes(true)) res.formData = formData.bind(res, contType);
|
|
||||||
};
|
|
||||||
|
|
||||||
class BaseApp {
|
|
||||||
_sockets = new Map();
|
|
||||||
ws!: TemplatedApp["ws"];
|
|
||||||
get!: TemplatedApp["get"];
|
|
||||||
_post!: TemplatedApp["post"];
|
|
||||||
_put!: TemplatedApp["put"];
|
|
||||||
_patch!: TemplatedApp["patch"];
|
|
||||||
_listen!: TemplatedApp["listen"];
|
|
||||||
|
|
||||||
post(pattern: string, handler: Handler) {
|
|
||||||
if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`);
|
|
||||||
this._post(pattern, (res, req) => {
|
|
||||||
handleBody(res, req);
|
|
||||||
handler(res, req);
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
put(pattern: string, handler: Handler) {
|
|
||||||
if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`);
|
|
||||||
this._put(pattern, (res, req) => {
|
|
||||||
handleBody(res, req);
|
|
||||||
|
|
||||||
handler(res, req);
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
patch(pattern: string, handler: Handler) {
|
|
||||||
if (typeof handler !== "function") throw Error(`handler should be a function, given ${typeof handler}.`);
|
|
||||||
this._patch(pattern, (res, req) => {
|
|
||||||
handleBody(res, req);
|
|
||||||
|
|
||||||
handler(res, req);
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
listen(h: string | number, p: Function | number = noOp, cb?: Function) {
|
|
||||||
if (typeof p === "number" && typeof h === "string") {
|
|
||||||
this._listen(h, p, (socket) => {
|
|
||||||
this._sockets.set(p, socket);
|
|
||||||
if (cb === undefined) {
|
|
||||||
throw new Error("cb undefined");
|
|
||||||
}
|
|
||||||
cb(socket);
|
|
||||||
});
|
|
||||||
} else if (typeof h === "number" && typeof p === "function") {
|
|
||||||
this._listen(h, (socket) => {
|
|
||||||
this._sockets.set(h, socket);
|
|
||||||
p(socket);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw Error("Argument types: (host: string, port: number, cb?: Function) | (port: number, cb?: Function)");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
close(port: null | number = null) {
|
|
||||||
if (port) {
|
|
||||||
this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port));
|
|
||||||
this._sockets.delete(port);
|
|
||||||
} else {
|
|
||||||
this._sockets.forEach((app) => {
|
|
||||||
us_listen_socket_close(app);
|
|
||||||
});
|
|
||||||
this._sockets.clear();
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BaseApp;
|
|
@ -1,99 +0,0 @@
|
|||||||
import { createWriteStream } from "fs";
|
|
||||||
import { join, dirname } from "path";
|
|
||||||
import Busboy from "busboy";
|
|
||||||
import mkdirp from "mkdirp";
|
|
||||||
|
|
||||||
function formData(
|
|
||||||
contType: string,
|
|
||||||
options: busboy.BusboyConfig & {
|
|
||||||
abortOnLimit?: boolean;
|
|
||||||
tmpDir?: string;
|
|
||||||
onFile?: (
|
|
||||||
fieldname: string,
|
|
||||||
file: NodeJS.ReadableStream,
|
|
||||||
filename: string,
|
|
||||||
encoding: string,
|
|
||||||
mimetype: string
|
|
||||||
) => string;
|
|
||||||
onField?: (fieldname: string, value: any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
filename?: (oldName: string) => string;
|
|
||||||
} = {}
|
|
||||||
) {
|
|
||||||
console.log("Enter form data");
|
|
||||||
options.headers = {
|
|
||||||
"content-type": contType,
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const busb = new Busboy(options);
|
|
||||||
const ret = {};
|
|
||||||
|
|
||||||
this.bodyStream().pipe(busb);
|
|
||||||
|
|
||||||
busb.on("limit", () => {
|
|
||||||
if (options.abortOnLimit) {
|
|
||||||
reject(Error("limit"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
busb.on("file", function (fieldname, file, filename, encoding, mimetype) {
|
|
||||||
const value: { filePath: string | undefined; filename: string; encoding: string; mimetype: string } = {
|
|
||||||
filename,
|
|
||||||
encoding,
|
|
||||||
mimetype,
|
|
||||||
filePath: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof options.tmpDir === "string") {
|
|
||||||
if (typeof options.filename === "function") filename = options.filename(filename);
|
|
||||||
const fileToSave = join(options.tmpDir, filename);
|
|
||||||
mkdirp(dirname(fileToSave));
|
|
||||||
|
|
||||||
file.pipe(createWriteStream(fileToSave));
|
|
||||||
value.filePath = fileToSave;
|
|
||||||
}
|
|
||||||
if (typeof options.onFile === "function") {
|
|
||||||
value.filePath = options.onFile(fieldname, file, filename, encoding, mimetype) || value.filePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
setRetValue(ret, fieldname, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
busb.on("field", function (fieldname, value) {
|
|
||||||
if (typeof options.onField === "function") options.onField(fieldname, value);
|
|
||||||
|
|
||||||
setRetValue(ret, fieldname, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
busb.on("finish", function () {
|
|
||||||
resolve(ret);
|
|
||||||
});
|
|
||||||
|
|
||||||
busb.on("error", reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setRetValue(
|
|
||||||
ret: { [x: string]: any }, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
fieldname: string,
|
|
||||||
value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
) {
|
|
||||||
if (fieldname.endsWith("[]")) {
|
|
||||||
fieldname = fieldname.slice(0, fieldname.length - 2);
|
|
||||||
if (Array.isArray(ret[fieldname])) {
|
|
||||||
ret[fieldname].push(value);
|
|
||||||
} else {
|
|
||||||
ret[fieldname] = [value];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (Array.isArray(ret[fieldname])) {
|
|
||||||
ret[fieldname].push(value);
|
|
||||||
} else if (ret[fieldname]) {
|
|
||||||
ret[fieldname] = [ret[fieldname], value];
|
|
||||||
} else {
|
|
||||||
ret[fieldname] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default formData;
|
|
@ -1,13 +0,0 @@
|
|||||||
import { SSLApp as _SSLApp, AppOptions } from "uWebSockets.js";
|
|
||||||
import BaseApp from "./baseapp";
|
|
||||||
import { extend } from "./utils";
|
|
||||||
import { UwsApp } from "./types";
|
|
||||||
|
|
||||||
class SSLApp extends (<UwsApp>_SSLApp) {
|
|
||||||
constructor(options: AppOptions) {
|
|
||||||
super(options); // eslint-disable-line constructor-super
|
|
||||||
extend(this, new BaseApp());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SSLApp;
|
|
@ -1,11 +0,0 @@
|
|||||||
import { AppOptions, TemplatedApp, HttpResponse, HttpRequest } from "uWebSockets.js";
|
|
||||||
|
|
||||||
export type UwsApp = {
|
|
||||||
(options: AppOptions): TemplatedApp;
|
|
||||||
new (options: AppOptions): TemplatedApp;
|
|
||||||
prototype: TemplatedApp;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Handler = (res: HttpResponse, req: HttpRequest) => void;
|
|
||||||
|
|
||||||
export {};
|
|
@ -1,36 +0,0 @@
|
|||||||
import { ReadStream } from "fs";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
function extend(who: any, from: any, overwrite = true) {
|
|
||||||
const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat(Object.keys(from));
|
|
||||||
ownProps.forEach((prop) => {
|
|
||||||
if (prop === "constructor" || from[prop] === undefined) return;
|
|
||||||
if (who[prop] && overwrite) {
|
|
||||||
who[`_${prop}`] = who[prop];
|
|
||||||
}
|
|
||||||
if (typeof from[prop] === "function") who[prop] = from[prop].bind(who);
|
|
||||||
else who[prop] = from[prop];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function stob(stream: ReadStream): Promise<Buffer> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const buffers: Buffer[] = [];
|
|
||||||
stream.on("data", buffers.push.bind(buffers));
|
|
||||||
|
|
||||||
stream.on("end", () => {
|
|
||||||
switch (buffers.length) {
|
|
||||||
case 0:
|
|
||||||
resolve(Buffer.allocUnsafe(0));
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
resolve(buffers[0]);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
resolve(Buffer.concat(buffers));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export { extend, stob };
|
|
@ -1,19 +0,0 @@
|
|||||||
import { parse } from "query-string";
|
|
||||||
import { HttpRequest } from "uWebSockets.js";
|
|
||||||
import App from "./server/app";
|
|
||||||
import SSLApp from "./server/sslapp";
|
|
||||||
import * as types from "./server/types";
|
|
||||||
|
|
||||||
const getQuery = (req: HttpRequest) => {
|
|
||||||
return parse(req.getQuery());
|
|
||||||
};
|
|
||||||
|
|
||||||
export { App, SSLApp, getQuery };
|
|
||||||
export * from "./server/types";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
App,
|
|
||||||
SSLApp,
|
|
||||||
getQuery,
|
|
||||||
...types,
|
|
||||||
};
|
|
@ -1,6 +1,5 @@
|
|||||||
import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL, OPID_PROFILE_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL, OPID_PROFILE_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable";
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
|
|
||||||
import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture";
|
import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture";
|
||||||
import { MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
import { MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||||
import { RoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
|
import { RoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
|
||||||
|
@ -53,6 +53,7 @@ import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"
|
|||||||
import { WebSocket } from "uWebSockets.js";
|
import { WebSocket } from "uWebSockets.js";
|
||||||
import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
|
import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
|
||||||
import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture";
|
import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture";
|
||||||
|
import { compressors } from "hyper-express";
|
||||||
|
|
||||||
const debug = Debug("socket");
|
const debug = Debug("socket");
|
||||||
|
|
||||||
@ -619,7 +620,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
emitInBatch(listener, subMessage);
|
emitInBatch(listener, subMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitWorldFullMessage(client: WebSocket) {
|
public emitWorldFullMessage(client: compressors.WebSocket) {
|
||||||
const errorMessage = new WorldFullMessage();
|
const errorMessage = new WorldFullMessage();
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
@ -630,7 +631,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitTokenExpiredMessage(client: WebSocket) {
|
public emitTokenExpiredMessage(client: compressors.WebSocket) {
|
||||||
const errorMessage = new TokenExpiredMessage();
|
const errorMessage = new TokenExpiredMessage();
|
||||||
|
|
||||||
const serverToClientMessage = new ServerToClientMessage();
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
@ -641,7 +642,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitConnexionErrorMessage(client: WebSocket, message: string) {
|
public emitConnexionErrorMessage(client: compressors.WebSocket, message: string) {
|
||||||
const errorMessage = new WorldConnexionMessage();
|
const errorMessage = new WorldConnexionMessage();
|
||||||
errorMessage.setMessage(message);
|
errorMessage.setMessage(message);
|
||||||
|
|
||||||
|
827
pusher/yarn.lock
827
pusher/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user