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

[Gateway] Changed authentication

Authentication is now done with an `Authorisation` header and
an `intents` query parameter.
parent 5dd6f60c
......@@ -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,7 +68,7 @@ class Client {
* @param {*} data
*/
send(data: any): void {
this.ws.send(data);
this.ws?.send(data);
}
sendEvent(name: string, type: string, data: any) {
......@@ -75,22 +79,6 @@ class Client {
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();
}
/**
......
......@@ -18,10 +18,11 @@
*/
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';
/**
......@@ -43,11 +44,9 @@ class _Gateway {
* @param {WebSocket} ws
* @param {http.IncomingMessage} req
*/
private connect(ws: WebSocket, req: IncomingMessage): void {
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}.`);
const client = new Client(ws);
private connect(ws: WebSocket, req: IncomingMessage, client: Client): void {
this.clients[client.getId()] = client;
client.setWS(ws, req);
}
/**
......@@ -55,11 +54,64 @@ class _Gateway {
*/
start(): void {
this.logger.info(`Starting gateway...`);
this.ws = new WebSocketServer({ server: API.getServer() });
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> {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
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();
}
const token = req.headers.authorization.split(' ')[1];
const profile: any = await SebAPI.profile(token);
if (profile.good) {
let intents: string[] = this.getEffectiveIntents(intents_str.split(",") ?? [], profile?.data?.data?.permissions ?? []);
return new Client(intents);
} else {
throw new Error();
}
} else {
// TODO: Check for session cookies
}
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
*/
......@@ -104,7 +156,7 @@ class _Gateway {
this.clients[k].sendEvent(res, typ, data);
}
this.logger.info(`Broadcasted event ${name}`);
// this.logger.info(`Broadcasted event ${name}`);
return true;
}
}
......
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