Verified Commit a724bb12 authored by Maxime FRIESS's avatar Maxime FRIESS 💙
Browse files

[version] 1.1.0

parents 4b3b3cde 4ba2f9b6
{
"name": "seb-gateway",
"version": "1.0.0",
"version": "1.1.0",
"main": "index.ts",
"repository": "https://git.unistra.fr/amicale-core/seb-gateway",
"author": "M4x1m3 <M4x1me@protonmail.com>",
......
......@@ -14,7 +14,7 @@ class _API {
}
start(): void {
this.logger.info(`Starting api server on ${HardConfig.getHttpIp()}:${HardConfig.getHttpPort()}...`);
this.logger.info(`Starting http server on ${HardConfig.getHttpIp()}:${HardConfig.getHttpPort()}...`);
this.server.listen(HardConfig.getHttpPort(), HardConfig.getHttpIp());
}
......@@ -79,6 +79,10 @@ class _API {
return false;
}
}
getServer(): Server {
return this.server;
}
}
const API = new _API();
......
......@@ -17,9 +17,10 @@
* along with Seb-Gateway. If not, see <https://www.gnu.org/licenses/>.
*/
import { IncomingMessage } from 'http';
import { RawData, WebSocket } from 'ws';
import HardConfig from './config/HardConfig';
import Gateway from "./Gateway";
import SebAPI from './SebAPI';
import Loggers, { Logger } from "./utils/Logger";
let lastId = 0;
......@@ -31,22 +32,25 @@ class Client {
private id: number;
private logger: Logger;
private ws: WebSocket;
private ws?: WebSocket;
private alive: boolean;
private logged: boolean;
private intents: string[];
constructor(ws: WebSocket) {
constructor(intents: string[]) {
this.id = lastId++;
this.logger = Loggers.getLogger("Client", this.id);
this.logger.info("Accepted connection.");
this.alive = true;
this.intents = intents;
}
setWS(ws: WebSocket, req: IncomingMessage): void {
this.ws = ws;
this.ws.on("close", this.close.bind(this));
this.ws.on("pong", this.heartbeat.bind(this));
this.ws.on("message", this.message.bind(this));
this.alive = true;
this.logged = false;
this.intents = [];
const ip = HardConfig.getHttpProxy() ? (req.headers['x-forwarded-for'] as string | undefined)?.split(',')[0].trim() : req.socket.remoteAddress;
this.logger.info(`Accepted connection from ${ip}.`);
}
/**
......@@ -64,33 +68,17 @@ class Client {
* @param {*} data
*/
send(data: any): void {
this.ws.send(data);
this.ws?.send(data);
}
sendEvent(name: string, type: string, data: any) {
if (!this.intents.includes(name))
if (!this.intents.includes(name) && !this.intents.includes('*'))
return;
const d: any = {p: "event", d: {resource: name, action: type, data: data}};
this.send(JSON.stringify(d));
}
private getEffectiveIntents(asked_for: string[], permissions: string[]): string[] {
if (permissions.includes("*.show") || permissions.includes("*.*"))
return asked_for;
let output: string[] = [];
for (let intent of asked_for) {
if (permissions.includes(intent + ".*") || permissions.includes(intent + ".show")) {
output.push(intent)
}
}
return output;
}
/**
* Handler called when a message is received
*/
......@@ -103,24 +91,7 @@ class Client {
this.send(JSON.stringify({ "p": "pong", "d": {} }));
break;
case "status":
this.send(JSON.stringify({ "p": "status", "d": { "logged": this.logged } }));
break;
case "login":
const token: string = incomming_data?.d?.token;
const profile: any = await SebAPI.profile(token);
if (this.logged) {
this.send(JSON.stringify({ "e": "already_logged" }));
return;
}
if (profile.good) {
this.logged = true;
this.intents = this.getEffectiveIntents(incomming_data?.d?.intents ?? [], profile?.data?.data?.permissions ?? []);
this.send(JSON.stringify({ "p": "login", "d": { "good": true, "username": profile?.data?.data?.username, "intents": this.intents } }));
} else {
this.send(JSON.stringify({ "p": "login", "d": { "good": false } }));
}
this.send(JSON.stringify({ "p": "status", "d": { "intents": this.intents } }));
break;
default:
this.send(JSON.stringify({ "e": "unknown_packet" }));
......@@ -145,10 +116,10 @@ class Client {
*/
ping(): void {
if (!this.alive)
return this.ws.terminate();
return this.ws?.terminate();
this.alive = false;
this.ws.ping();
this.ws?.ping();
}
/**
......
......@@ -17,25 +17,23 @@
* along with Seb-Gateway. If not, see <https://www.gnu.org/licenses/>.
*/
import { createServer, IncomingMessage, Server } from 'http';
import { IncomingMessage } from 'http';
import { Socket } from 'net';
import { WebSocket, WebSocketServer } from 'ws';
import API from './API';
import Client from './Client';
import HardConfig from './config/HardConfig';
import SebAPI from './SebAPI';
import Loggers, { Logger } from './utils/Logger';
/**
* Gateway server. Accepts WebSockets connection and dispatches event to them.
*/
class _Gateway {
private server: Server;
private ws: WebSocketServer;
private logger: Logger;
private ws?: WebSocketServer;
private clients: { [index: string]: Client };
constructor() {
this.server = createServer();
this.ws = new WebSocketServer({ server: this.server });
this.ws.on('connection', this.connect.bind(this));
this.logger = Loggers.getLogger("Gateway");
this.clients = {};
}
......@@ -46,22 +44,81 @@ class _Gateway {
* @param {WebSocket} ws
* @param {http.IncomingMessage} req
*/
private connect(ws: WebSocket, req: IncomingMessage): void {
const ip = HardConfig.getWsProxy() ? (req.headers['x-forwarded-for'] as string | undefined)?.split(',')[0].trim() : req.socket.remoteAddress;
this.logger.info(`Accepted connection from ${ip}.`);
const client = new Client(ws);
private connect(ws: WebSocket, req: IncomingMessage, client: Client): void {
this.clients[client.getId()] = client;
client.setWS(ws, req);
}
/**
* Starts the server
*/
start(): void {
this.logger.info(`Starting gateway on ${HardConfig.getWsIp()}:${HardConfig.getWsPort()}...`);
this.server.listen(HardConfig.getWsPort(), HardConfig.getWsIp());
this.logger.info(`Starting gateway...`);
this.ws = new WebSocketServer({ noServer: true });
API.getServer().on('upgrade', this.upgrade.bind(this));
this.ws.on('connection', this.connect.bind(this));
setInterval(this.ping.bind(this), 10000);
}
private getEffectiveIntents(asked_for: string[], permissions: string[]): string[] {
if (permissions.includes("*.show") || permissions.includes("*.*"))
return asked_for;
let output: string[] = [];
for (let intent of asked_for) {
if (permissions.includes(intent + ".*") || permissions.includes(intent + ".show")) {
output.push(intent)
}
}
return output;
}
private async authenticate(req: IncomingMessage): Promise<Client> {
const url: URL = new URL(req.url ?? "", `http://${req.headers.host}`);
const intents_str = url.searchParams.get('intents');
if (intents_str === null) {
throw new Error();
}
let profile: any;
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
const token = req.headers.authorization.split(' ')[1];
profile = await SebAPI.profile({
mode: 'bearer',
token: token
});
} else {
let cookies = req.headers.cookie;
profile = await SebAPI.profile({
mode: 'cookie',
cookies: cookies
});
}
if (profile.good) {
let intents: string[] = this.getEffectiveIntents(intents_str.split(",") ?? [], profile?.data?.data?.permissions ?? []);
return new Client(intents);
} else {
throw new Error();
}
}
private async upgrade(req: IncomingMessage, socket: Socket, head: Buffer): Promise<void> {
try {
const client = await this.authenticate(req);
this.ws?.handleUpgrade(req, socket, head, ((ws: WebSocket) => {
this.ws?.emit('connection', ws, req, client);
}).bind(this));
} catch (e: any) {
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
socket.destroy();
}
}
/**
* Pings all connecred clients
*/
......@@ -106,7 +163,7 @@ class _Gateway {
this.clients[k].sendEvent(res, typ, data);
}
this.logger.info(`Broadcasted event ${name}`);
// this.logger.info(`Broadcasted event ${name}`);
return true;
}
}
......
import axios, { Method } from "axios";
import axios, { AxiosRequestHeaders, Method } from "axios";
import HardConfig from "./config/HardConfig";
import Loggers, { Logger } from "./utils/Logger";
interface Authorization {
mode: 'cookie' | 'bearer';
cookies?: string;
token?: string;
}
class _SebAPI {
private logger: Logger;
......@@ -10,12 +16,11 @@ class _SebAPI {
this.logger = Loggers.getLogger("SebAPI");
}
private async request(method: Method, path: string, token: string): Promise<any> {
private async request(method: Method, path: string, auth: Authorization): Promise<any> {
this.logger.info(method + " " + path);
try {
console.log(HardConfig.getSebURL().replace(/\/$/, '') + path);
let res = await axios.request({ method: method, headers: { 'Authorization': 'Bearer ' + token }, url: HardConfig.getSebURL().replace(/\/$/, '') + path });
const headers: AxiosRequestHeaders = auth.mode === 'bearer' ? { 'Authorization': 'Bearer ' + auth.token ?? "" } : { 'Cookie': auth.cookies ?? "" };
let res = await axios.request({ method: method, headers: headers, url: HardConfig.getSebURL().replace(/\/$/, '') + path });
return {
good: true,
......@@ -54,8 +59,8 @@ class _SebAPI {
}
}
async profile(token: string): Promise<any> {
return await this.request('GET', '/api/profile/me', token);
async profile(auth: Authorization): Promise<any> {
return await this.request('GET', '/api/profile/me', auth);
}
}
......
......@@ -30,18 +30,6 @@ class _HardConfig {
config();
}
getWsIp(): string {
return process.env.WS_IP ?? "127.0.0.1";
}
getWsPort(): number {
return process.env.WS_PORT === undefined ? 8080 : parseInt(process.env.WS_PORT);
}
getWsProxy(): boolean {
return process.env.WS_PROXY === undefined ? false : process.env.WS_PROXY == "true";
}
getHttpIp(): string {
return process.env.HTTP_IP ?? "127.0.0.1";
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment