Skip to content

Commit

Permalink
* Removed ClientEvent in favour of ClientInstruction & ServerInstruction
Browse files Browse the repository at this point in the history
* Client Connections now have a boolean to say if they are authorized. To be authorized means to be given special access. The socket request must provide a 'users-api-key' header with a valid key matching that in the config socketApiKey
* Security Bug Fix: Socket Meta instructions are now secured and only allowed by authorized clients
* Removed EventType in favour of ClientInstructionType & ServerInstructionType
* Updated tests
* Updated references, moved folders/files & updated definition
  • Loading branch information
MKHenson committed Aug 19, 2016
1 parent 8862f53 commit 508cc8e
Show file tree
Hide file tree
Showing 15 changed files with 375 additions and 407 deletions.
15 changes: 8 additions & 7 deletions src/bucket-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import * as zlib from "zlib"
import * as compressible from "compressible"
import express = require("express");
import {CommsController} from "./socket-api/comms-controller";
import {EventType} from "./socket-api/socket-event-types";
import {ClientInstructionType} from "./socket-api/socket-event-types";
import {ClientInstruction} from "./socket-api/client-instruction";
import * as def from "webinate-users";

/**
Expand Down Expand Up @@ -270,8 +271,8 @@ export class BucketManager
var updateResult = await stats.updateOne(<users.IStorageStats>{ user: user }, { $inc: <users.IStorageStats>{ apiCallsUsed: 1 } });

// Send bucket added events to sockets
var fEvent: def.SocketEvents.IBucketAddedEvent = { eventType: EventType.BucketUploaded, bucket: bucketEntry, username: user, error : undefined };
await CommsController.singleton.broadcastEventToAll(fEvent);
var token: def.SocketEvents.IBucketToken = { type: ClientInstructionType[ClientInstructionType.BucketUploaded], bucket: bucketEntry, username: user };
await CommsController.singleton.processClientInstruction( new ClientInstruction(token, null, user));
return gBucket;
}

Expand Down Expand Up @@ -379,8 +380,8 @@ export class BucketManager
var result = await stats.updateOne(<users.IStorageStats>{ user: bucketEntry.user }, { $inc: <users.IStorageStats>{ apiCallsUsed : 1 } });

// Send events to sockets
var fEvent: def.SocketEvents.IBucketRemovedEvent = { eventType: EventType.BucketRemoved, bucket: bucketEntry, error : undefined };
await CommsController.singleton.broadcastEventToAll(fEvent);
var token: def.SocketEvents.IBucketToken = { type: ClientInstructionType[ClientInstructionType.BucketRemoved], bucket: bucketEntry, username : bucketEntry.user };
await CommsController.singleton.processClientInstruction(new ClientInstruction(token, null, bucketEntry.user ));

return bucketEntry;
}
Expand Down Expand Up @@ -433,8 +434,8 @@ export class BucketManager
await stats.updateOne(<users.IStorageStats>{ user: bucketEntry.user }, { $inc: <users.IStorageStats>{ memoryUsed: -fileEntry.size, apiCallsUsed: 1 } });

// Update any listeners on the sockets
var fEvent: def.SocketEvents.IFileRemovedEvent = { eventType: EventType.FileRemoved, file: fileEntry, error : undefined };
await CommsController.singleton.broadcastEventToAll(fEvent);
var token: def.SocketEvents.IFileToken = { type: ClientInstructionType[ClientInstructionType.FileRemoved], file: fileEntry, username : fileEntry.user };
await CommsController.singleton.processClientInstruction(new ClientInstruction(token, null, fileEntry.user));

return fileEntry;
}
Expand Down
7 changes: 4 additions & 3 deletions src/controllers/bucket-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import * as compression from "compression";
import * as winston from "winston";
import * as gcloud from "gcloud";
import {CommsController} from "../socket-api/comms-controller";
import {EventType} from "../socket-api/socket-event-types";
import {ClientInstruction} from "../socket-api/client-instruction";
import {ClientInstructionType} from "../socket-api/socket-event-types";
import * as def from "webinate-users";
import {okJson, errJson} from "../serializers";

Expand Down Expand Up @@ -810,8 +811,8 @@ export class BucketController extends Controller
for (var i = 0, l = files.length; i < l; i++)
{
// Send file added events to sockets
var fEvent: def.SocketEvents.IFileAddedEvent = { username: user, eventType: EventType.FileUploaded, file: files[i], error : undefined };
await CommsController.singleton.broadcastEventToAll(fEvent)
var token: def.SocketEvents.IFileToken = { username: user, type: ClientInstructionType[ClientInstructionType.FileUploaded], file: files[i] };
await CommsController.singleton.processClientInstruction(new ClientInstruction(token, null, user))
}


Expand Down
77 changes: 35 additions & 42 deletions src/definitions/custom/definitions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,82 +10,69 @@
*/
export module SocketEvents
{
/*
* The base interface for all socket events
*/
export interface IEvent
{
eventType: number;

/*
* Will be null if no error, or a string if there is
*/
error: string;
}

/*
* A very simple echo event. This simply pings the server with a message, which then returns with the same message
* either to the client or, if broadcast is true, to all clients.
*/
export interface IEchoEvent extends IEvent
export type ClientInstructionType = (
'Login' |
'Logout' |
'Activated' |
'Removed' |
'FileUploaded' |
'FileRemoved' |
'BucketUploaded' |
'BucketRemoved' |
'MetaRequest'
);

export type ServerInstructionType = (
'MetaRequest'
);

/**
* The base interface for all data that is serialized & sent to clients or server.
* The type property describes to the reciever what kind of data to expect.
*/
export interface IToken
{
message: string;
broadcast?: boolean;
error? : string;
type: ClientInstructionType | ServerInstructionType | string;
}

/*
* Describes a get/set Meta request, which can fetch or set meta data for a given user
* if you provide a property value, then only that specific meta property is edited.
* If not provided, then the entire meta data is set.
*/
export interface IMetaEvent extends IEvent
export interface IMetaToken extends IToken
{
username?: string;
property: string;
val: any;
property?: string;
val?: any;
}

/*
* The socket user event
*/
export interface IUserEvent extends IEvent
export interface IUserToken extends IToken
{
username: string;
}

/*
* Interface for file added events
*/
export interface IFileAddedEvent extends IEvent
export interface IFileToken extends IToken
{
username: string;
file: IFileEntry;
}

/*
* Interface for file removed events
*/
export interface IFileRemovedEvent extends IEvent
{
file: IFileEntry;
}

/*
* Interface for a bucket being added
*/
export interface IBucketAddedEvent extends IEvent
export interface IBucketToken extends IToken
{
username: string;
bucket: IBucketEntry
}

/*
* Interface for a bucket being removed
*/
export interface IBucketRemovedEvent extends IEvent
{
bucket: IBucketEntry
}
}

/*
Expand Down Expand Up @@ -181,6 +168,12 @@
*/
export interface IWebsocket
{
/**
* A key that must be provided in the headers of socket client connections. If the connection headers
* contain 'users-api-key', and it matches this key, then the connection is considered an authorized connection.
*/
socketApiKey: string;

/**
* The port number to use for web socket communication. You can use this port to send and receive events or messages
* to the server.
Expand Down
1 change: 1 addition & 0 deletions src/dist-files/example-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
},
"websocket": {
"port": 8063,
"socketApiKey" : "my-secret-key",
"approvedSocketDomains": [
"localhost",
"^ws://www.webinate.net:123$",
Expand Down
15 changes: 10 additions & 5 deletions src/socket-api/client-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,27 @@ import * as fs from "fs";
import * as winston from "winston";
import {UserManager, User} from "../users";
import {CommsController} from "./comms-controller";
import {ClientEvent} from "./client-event";
import {EventResponseType, EventType} from "./socket-event-types";
import {ServerInstruction} from "./server-instruction";
import {SocketAPI} from "./socket-api";

/**
* A wrapper class for client connections made to the CommsController
*/
export class ClientConnection
{
public onDisconnected: (connection: ClientConnection) => void;
public ws: ws;
public user: User;
public domain: string;
public authorizedThirdParty: boolean;
private _controller: CommsController;

constructor(ws: ws, domain: string, controller : CommsController)
constructor(ws: ws, domain: string, controller : CommsController, authorizedThirdParty: boolean)
{
var that = this;
this.domain = domain;
this._controller = controller;
this.authorizedThirdParty = authorizedThirdParty;

UserManager.get.loggedIn(ws.upgradeReq, null).then(function (user)
{
Expand All @@ -49,8 +51,8 @@ export class ClientConnection
{
winston.info(`Received message from client: '${message}'`, { process: process.pid } );
try {
var event : def.SocketEvents.IEvent = JSON.parse(message);
this._controller.alertMessage(new ClientEvent(event, this));
var token : def.SocketEvents.IToken = JSON.parse(message);
this._controller.processServerInstruction(new ServerInstruction(token, this));
}
catch(err) {
winston.error(`Could not parse socket message: '${err}'`, { process: process.pid } );
Expand All @@ -62,6 +64,9 @@ export class ClientConnection
*/
private onClose()
{
if (this.onDisconnected)
this.onDisconnected(this);

winston.info(`Websocket disconnected: ${this.domain}`, {process : process.pid})

this.ws.removeAllListeners("message");
Expand Down
41 changes: 0 additions & 41 deletions src/socket-api/client-event.ts

This file was deleted.

33 changes: 33 additions & 0 deletions src/socket-api/client-instruction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use strict";

import * as def from "webinate-users";
import {ClientConnection} from "./client-connection";

/**
* An instruction that is generated by the server and sent to relevant clients.
*/
export class ClientInstruction<T extends def.SocketEvents.IToken>
{
/**
* Specify a username that if set, will only send this instruction to authorized clients
* and/or the spefic user who may be connected
*/
username: string;

/**
* An array of clients to send the instruction to. If null, then all clients will be considered
*/
recipients: ClientConnection[];

/**
* The event sent from the client
*/
token: T;

constructor(event: T, client: ClientConnection[] = null, username : string = null )
{
this.recipients = client;
this.token = event;
this.username = username;
}
}
Loading

0 comments on commit 508cc8e

Please sign in to comment.