Adding back authentication to uws websocket

This commit is contained in:
David Négrier 2020-09-30 10:12:40 +02:00
parent e9b538e43c
commit 5de2f61231
4 changed files with 98 additions and 42 deletions

View File

@ -39,6 +39,7 @@ export class AuthenticateController extends BaseController {
res.onAborted(() => { res.onAborted(() => {
console.warn('Login request was aborted'); console.warn('Login request was aborted');
}) })
const host = req.getHeader('host');
const param = await res.json(); 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?
@ -63,7 +64,7 @@ export class AuthenticateController extends BaseController {
newUrl = this.getNewUrlOnAdminAuth(data) newUrl = this.getNewUrlOnAdminAuth(data)
} else { } else {
userUuid = uuid(); userUuid = uuid();
mapUrlStart = req.getHeader('host').replace('api.', 'maps.') + URL_ROOM_STARTED; mapUrlStart = host.replace('api.', 'maps.') + URL_ROOM_STARTED;
newUrl = null; newUrl = null;
} }
@ -76,7 +77,7 @@ export class AuthenticateController extends BaseController {
})); }));
} catch (e) { } catch (e) {
console.log(e.message) console.log("An error happened", e)
res.writeStatus(e.status || "500 Internal Server Error").end('An error happened'); res.writeStatus(e.status || "500 Internal Server Error").end('An error happened');
} }

View File

@ -47,7 +47,8 @@ import {
import {UserMovesMessage} from "../Messages/generated/messages_pb"; import {UserMovesMessage} from "../Messages/generated/messages_pb";
import Direction = PositionMessage.Direction; import Direction = PositionMessage.Direction;
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils"; import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import {App, TemplatedApp, WebSocket} from "uWebSockets.js" import {App, HttpRequest, TemplatedApp, WebSocket} from "uWebSockets.js"
import {parse} from "query-string";
enum SocketIoEvent { enum SocketIoEvent {
CONNECTION = "connection", CONNECTION = "connection",
@ -135,67 +136,122 @@ export class IoSocketController {
return null; return null;
}*/ }*/
private authenticate(ws: WebSocket) { private async authenticate(req: HttpRequest): Promise<{ token: string, userUuid: string }> {
//console.log(socket.handshake.query.token); //console.log(socket.handshake.query.token);
/*if (!socket.handshake.query || !socket.handshake.query.token) { const query = parse(req.getQuery());
if (!query.token) {
console.error('An authentication error happened, a user tried to connect without a token.'); console.error('An authentication error happened, a user tried to connect without a token.');
return next(new Error('Authentication error')); throw new Error('An authentication error happened, a user tried to connect without a token.');
} }
if(socket.handshake.query.token === 'test'){
const token = query.token;
if (typeof(token) !== "string") {
throw new Error('Token is expected to be a string');
}
if(token === 'test'){
if (ALLOW_ARTILLERY) { if (ALLOW_ARTILLERY) {
(socket as ExSocketInterface).token = socket.handshake.query.token; return {
(socket as ExSocketInterface).userId = this.nextUserId; token,
(socket as ExSocketInterface).userUuid = uuid(); userUuid: uuid()
this.nextUserId++; }
(socket as ExSocketInterface).isArtillery = true;
console.log((socket as ExSocketInterface).userId);
next();
return;
} else { } else {
console.warn("In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'"); throw new Error("In order to perform a load-testing test on this environment, you must set the ALLOW_ARTILLERY environment variable to 'true'");
next();
} }
} }
(socket as ExSocketInterface).isArtillery = false;
if(this.searchClientByToken(socket.handshake.query.token)){ /*if(this.searchClientByToken(socket.handshake.query.token)){
console.error('An authentication error happened, a user tried to connect while its token is already connected.'); console.error('An authentication error happened, a user tried to connect while its token is already connected.');
return next(new Error('Authentication error')); return next(new Error('Authentication error'));
} }*/
Jwt.verify(socket.handshake.query.token, SECRET_KEY, (err: JsonWebTokenError, tokenDecoded: object) => {
const promise = new Promise<{ token: string, userUuid: string }>((resolve, reject) => {
Jwt.verify(token, SECRET_KEY, (err: JsonWebTokenError, tokenDecoded: object) => {
const tokenInterface = tokenDecoded as TokenInterface; const tokenInterface = tokenDecoded as TokenInterface;
if (err) { if (err) {
console.error('An authentication error happened, invalid JsonWebToken.', err); console.error('An authentication error happened, invalid JsonWebToken.', err);
return next(new Error('Authentication error')); reject(new Error('An authentication error happened, invalid JsonWebToken. '+err.message));
return;
} }
if (!this.isValidToken(tokenInterface)) { if (!this.isValidToken(tokenInterface)) {
return next(new Error('Authentication error, invalid token structure')); reject(new Error('Authentication error, invalid token structure.'));
return;
} }
(socket as ExSocketInterface).token = socket.handshake.query.token; resolve({
(socket as ExSocketInterface).userId = this.nextUserId; token,
(socket as ExSocketInterface).userUuid = tokenInterface.userUuid; userUuid: tokenInterface.userUuid
this.nextUserId++; });
next(); });
});*/ });
const socket = ws as ExSocketInterface;
socket.userId = this.nextUserId; return promise;
this.nextUserId++;
} }
ioConnection() { ioConnection() {
this.app.ws('/*', { this.app.ws('/*', {
/* Options */ /* Options */
//compression: uWS.SHARED_COMPRESSOR, //compression: uWS.SHARED_COMPRESSOR,
maxPayloadLength: 16 * 1024 * 1024, maxPayloadLength: 16 * 1024 * 1024,
//idleTimeout: 10, //idleTimeout: 10,
upgrade: (res, req, context) => {
console.log('An Http connection wants to become WebSocket, URL: ' + req.getUrl() + '!');
(async () => {
/* Keep track of abortions */
const upgradeAborted = {aborted: false};
res.onAborted(() => {
/* We can simply signal that we were aborted */
upgradeAborted.aborted = true;
});
try {
const result = await this.authenticate(req);
if (upgradeAborted.aborted) {
console.log("Ouch! Client disconnected before we could upgrade it!");
/* You must not upgrade now */
return;
}
/* This immediately calls open handler, you must not use res after this call */
res.upgrade({
// Data passed here is accessible on the "websocket" socket object.
url: req.getUrl(),
token: result.token,
userUuid: result.userUuid
},
/* Spell these correctly */
req.getHeader('sec-websocket-key'),
req.getHeader('sec-websocket-protocol'),
req.getHeader('sec-websocket-extensions'),
context);
} catch (e: unknown) {
if (e instanceof Error) {
console.warn(e.message);
res.writeStatus("401 Unauthorized").end(e.message);
} else {
console.warn(e);
res.writeStatus("500 Internal Server Error").end('An error occurred');
}
return;
}
})();
},
/* Handlers */ /* Handlers */
open: (ws) => { open: (ws) => {
this.authenticate(ws);
// TODO: close if authenticate is ko
const client : ExSocketInterface = ws as ExSocketInterface; const client : ExSocketInterface = ws as ExSocketInterface;
client.userId = this.nextUserId;
this.nextUserId++;
client.userUuid = ws.userUuid;
client.token = ws.token;
client.batchedMessages = new BatchMessage(); client.batchedMessages = new BatchMessage();
client.batchTimeout = null; client.batchTimeout = null;
client.emitInBatch = (payload: SubMessage): void => { client.emitInBatch = (payload: SubMessage): void => {

View File

@ -13,7 +13,6 @@ export interface ExSocketInterface extends WebSocket, Identificable {
characterLayers: string[]; characterLayers: string[];
position: PointInterface; position: PointInterface;
viewport: ViewportInterface; viewport: ViewportInterface;
isArtillery: boolean; // Whether this socket is opened by Artillery for load testing (hack)
/** /**
* Pushes an event that will be sent in the next batch of events * Pushes an event that will be sent in the next batch of events
*/ */