Bunch of changes

This commit is contained in:
Chris Blanchard 2015-08-18 10:56:35 +01:00
parent 805b942c5e
commit 9151212c78
35 changed files with 346 additions and 130 deletions

View file

@ -5,7 +5,9 @@ var config = {
mongo: {
uri: "mongodb://localhost/swsgather_development"
},
secret_token: ""
secret_token: "",
session_store_name: "_ENSL_session_key_staging",
ensl_url: "http://staging.ensl.org/"
};
module.exports = config;

View file

@ -5,7 +5,9 @@ var config = {
mongo: {
uri: "" // Set using MONGOLAB_URI
},
secret_token: ""
secret_token: "",
session_store_name: "_ENSL_session_key_staging",
ensl_url: "http://www.ensl.org/"
};
module.exports = config;

View file

@ -5,7 +5,9 @@ var config = {
mongo: {
uri: "" // Set using MONGOLAB_URI
},
secret_token: ""
secret_token: "",
session_store_name: "_ENSL_session_key_staging",
ensl_url: "http://staging.ensl.org/"
};
module.exports = config;

View file

@ -5,7 +5,9 @@ var config = {
mongo: {
uri: "mongodb://localhost/swsgather_test"
},
secret_token: "SUPERSECRETFOO"
secret_token: "SUPERSECRETFOO",
session_store_name: "_ENSL_session_key_staging",
ensl_url: "http://staging.ensl.org/"
};
module.exports = config;

View file

@ -8,11 +8,13 @@ var winston = require("winston");
var config = require("./config.js");
var favicon = require("serve-favicon");
var exphbs = require("express-handlebars");
var cookieParser = require("cookie-parser");
var env = process.env.NODE_ENV || "development";
var pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../package.json")));
module.exports = app => {
app.use(express.static(path.join(__dirname, '../public')));
app.use(cookieParser());
app.use(favicon(path.join(__dirname, '../public/images/favicon.ico')));
// Use winston on production

View file

@ -1,13 +1,26 @@
"use strict";
var path = require("path");
var winston = require("winston");
var config = require("./config.js");
module.exports = app => {
app.get("/", (request, response) => {
app.get("/", (request, response, next) => {
response.render("index.hbs");
});
app.get("/redirect", (request, response, next) => {
response.render("redirect.hbs", {
redirect: config.ensl_url
});
});
app.get("*", (request, response) => {
response.status(404).render("404.hbs");
});
app.use(function (error, request, response, next) {
winston.error(error);
return response.status(500).render("500.hbs");
});
};

View file

@ -2,13 +2,15 @@
var winston = require("winston");
var User = require("../lib/user/user");
var client = require("../lib/ensl/client")();
var config = require("./config");
var EnslClient = require("../lib/ensl/client");
var client = EnslClient();
var chatController = require("../lib/chat/controller");
var gatherController = require("../lib/gather/controller");
var userController = require("../lib/user/controller");
var getRandomUser = callback => {
var id = Math.floor(Math.random() * 5000) + 1;
let id = Math.floor(Math.random() * 5000) + 1;
client.getUserById({
id: id
}, (error, response, body) => {
@ -17,20 +19,41 @@ var getRandomUser = callback => {
});
};
var parseCookies = (socket) => {
let cookieString = socket.request.headers.cookie;
if (typeof cookieString !== 'string') return null;
let cookies = socket.request.headers.cookie.split(";")
.map(cookie => cookie.trim())
.reduce((acc, cookie) => {
let values = cookie.split("=");
let attr = values[0];
let val = values[1];
if (attr && val) acc[attr] = val;
return acc;
}, {})
return cookies;
};
module.exports = io => {
var rootNamespace = io.of('/')
// Authorisation
// Authentication
io.use((socket, next) => {
getRandomUser((error, _, body) => {
let session = EnslClient.decodeSession(parseCookies(socket)[config.session_store_name]);
if (!session || typeof session.user !== 'number') return next(new Error("Authentication Failed"));
client.getUserById({
id: session.user
}, (error, response, body) => {
if (error) {
winston.error(error);
return next(error)
};
socket._user = new User(body);
console.log("You:", body.username, body.id);
next();
})
winston.info("Logged in:", body.username, body.id);
return next();
});
});
userController(rootNamespace);

View file

@ -8,5 +8,6 @@ mongoose.connect(config.mongo.uri);
// Load models
require(path.join(__dirname, "/models/message"));
require(path.join(__dirname, "/models/session"));
module.exports = mongoose;

View file

@ -37,4 +37,4 @@ messageSchema.statics.list = (options, callback) => {
});
};
module.exports = mongoose.model('message', messageSchema);
module.exports = mongoose.model('Message', messageSchema);

18
db/models/session.js Normal file
View file

@ -0,0 +1,18 @@
"use strict";
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var crypto = require('crypto');
var keyGenerator = () => {
return crypto.randomBytes(20).toString('hex');
};
var sessionSchema = new Schema({
userId: { type: Number, required: true },
key: { type: String, required: true, default: keyGenerator }
});
sessionSchema.index({ userId: 1 });
module.exports = mongoose.model("Session", sessionSchema);

View file

@ -15,7 +15,7 @@
*/
var mongoose = require("mongoose");
var Message = mongoose.model("message");
var Message = mongoose.model("Message");
module.exports = namespace => {

View file

@ -78,15 +78,13 @@ EnslClient.decodeSession = sessionCookie => {
if (crypto.createHmac("sha1", SECRET_TOKEN).update(text).digest('hex') !== signature) return null;
var parsedSession;
try {
var parsedSession = new Marshal((new Buffer(text, "base64")).toString("ascii"));
parsedSession = (new Marshal((new Buffer(text, "base64")).toString("ascii"))).parsed;
} catch (e) {
logger.error(e);
parsedSession = null;
}
return parsedSession;
return parsedSession || null;
};
module.exports = EnslClient;

View file

@ -55,10 +55,8 @@ module.exports = function (namespace) {
// ***** Generate Test Users *****
var helper = require("./helper");
helper.createTestUsers({ gather: gather });
helper.createTestUsers({ gather: gather }, refreshGather);
namespace.on("connection", function (socket) {
socket.on("gather:join", function (data) {
if (gather.can("addGatherer")) gather.addGatherer(socket._user);

View file

@ -4,7 +4,7 @@ var User = require("../user/user");
var client = require("../ensl/client")();
var async = require("async");
var createTestUsers = options => {
var createTestUsers = (options, callback) => {
var gather = options.gather;
var getRandomUser = callback => {
@ -40,10 +40,11 @@ var createTestUsers = options => {
array[index].leaderVote = array[candidate].id;
});
console.log("Assigned vote for each gatherer");
if (typeof callback === 'function') return callback(gather);
}
});
};
module.exports = {
createTestUsers: createTestUsers
}
};

View file

@ -584,8 +584,8 @@ var Gatherers = React.createClass({
}
return (
<tr key={gatherer.user.id}>
<td className="col-md-5">{country} {gatherer.user.username}&nbsp; ({gatherer.user.id})</td>
<tr key={gatherer.user.id} data-userid={gatherer.user.id}>
<td className="col-md-5">{country} {gatherer.user.username}&nbsp;</td>
<td className="col-md-5">
{lifeform} {division} {team}&nbsp;
</td>

View file

@ -27,11 +27,37 @@ var initialiseVisibilityMonitoring = (socket) => {
}, false);
}
var removeAuthWidget = () => {
$("#authenticating").remove();
};
var showAuthenticationNotice = () => {
$("#auth-required").show();
};
var renderPage = (socket) => {
initialiseVisibilityMonitoring(socket);
React.render(<UserMenu />, document.getElementById('side-menu'));
React.render(<Chatroom />, document.getElementById('chatroom'));
React.render(<Gather />, document.getElementById('gathers'));
React.render(<CurrentUser />, document.getElementById('currentuser'));
React.render(<AdminPanel />, document.getElementById('admin-menu'));
};
var initialiseComponents = () => {
let socketUrl = window.location.protocol + "//" + window.location.host;
socket = io(socketUrl)
.on("connect", () => {
console.log("Connected");
removeAuthWidget();
renderPage(socket);
})
.on("error", (error, foo) => {
console.log(error);
if (error === "Authentication Failed") {
removeAuthWidget();
showAuthenticationNotice();
}
})
.on("reconnect", () => {
console.log("Reconnected");
@ -39,13 +65,4 @@ var initialiseComponents = () => {
.on("disconnect", () => {
console.log("Disconnected")
});
initialiseVisibilityMonitoring(socket);
// Render Page
React.render(<UserMenu />, document.getElementById('side-menu'));
React.render(<Chatroom />, document.getElementById('chatroom'));
React.render(<Gather />, document.getElementById('gathers'));
React.render(<CurrentUser />, document.getElementById('currentuser'));
React.render(<AdminPanel />, document.getElementById('admin-menu'));
};

View file

@ -27,18 +27,15 @@ var UserLogin = React.createClass({
type="text"
className="form-control"
ref="authorize_id"
placeholder="Choose an ID..." />
placeholder="Change user" />
<span className="input-group-btn">
<input
type="submit"
className="btn btn-primary"
id="btn-chat"
value="Login" />
value="Assume ID" />
</span>
</div>
<div className="signin">
<p className="text-center"><small>Just a temporary measure until genuine authentication is implemented</small></p>
</div>
</form>
);
}
@ -71,7 +68,6 @@ var UserMenu = React.createClass({
</a>
</li>
{users}
<li><br /><UserLogin /><br /></li>
</ul>
);
}
@ -88,6 +84,7 @@ var AdminPanel = React.createClass({
<li>
<div className="admin-panel">
<h5>Admin</h5>
<UserLogin />
<button
className="btn btn-danger max-width"
onClick={this.handleGatherReset}>
@ -130,9 +127,6 @@ var CurrentUser = React.createClass({
<li>
<a href="#" data-toggle="modal" data-target="#designmodal">Design Goals</a>
</li>
<li className="divider"></li>
<li><a href="login.html"><i className="fa fa-sign-out fa-fw"></i> Logout</a>
</li>
</ul>
</li>

View file

@ -9,7 +9,7 @@
* Client API
* users:online - User is on the page
* users:away - User is away from page
* users:authorize - Authorize user
* users:authorize - Sign on with arbitary user ID (to be enabled for admin only)
* users:refresh - Request new user list
*
*/
@ -17,15 +17,17 @@
var userCache = {};
var User = require("./user");
var winston = require("winston");
var mongoose = require("mongoose");
var Session = mongoose.model("Session");
var enslClient = require("../ensl/client")();
var _ = require("lodash");
module.exports = function (namespace) {
var refreshUsers = function (socket) {
module.exports = namespace => {
var refreshUsers = socket => {
var receivers = (socket !== undefined) ? [socket] : namespace.sockets;
var newCache = {};
namespace.sockets.forEach(function (socket) {
namespace.sockets.forEach(socket => {
var user = socket._user;
newCache[user.id] = user;
});
@ -39,7 +41,7 @@ module.exports = function (namespace) {
}
}
receivers.forEach(function (socket) {
receivers.forEach(socket => {
socket.emit('users:update', {
count: users.length,
users: users,
@ -48,25 +50,26 @@ module.exports = function (namespace) {
});
};
namespace.on('connection', function (socket) {
namespace.on('connection', socket => {
refreshUsers();
socket.on('users:refresh', refreshUsers.bind(null, socket));
socket.on('users:online', function () {
socket.on('users:online', () => {
socket._user.online = true;
});
socket.on('users:away', function () {
socket.on('users:away', () => {
socket._user.online = false;
});
socket.on("users:authorize", function (data) {
socket.on("users:authorize", data => {
if (!socket._user.admin) return;
var id = parseInt(data.id, 10);
if (isNaN(id)) return;
enslClient.getUserById({
id: id
}, function (error, response, body) {
}, (error, response, body) => {
if (error || response.statusCode !== 200) {
winston.error("An error occurred in authorising id", id);
winston.error(error);
@ -78,8 +81,6 @@ module.exports = function (namespace) {
});
});
socket.on('disconnect', function (socket) {
refreshUsers();
});
socket.on('disconnect', socket => { refreshUsers(); });
});
};

17
npm-shrinkwrap.json generated
View file

@ -1049,6 +1049,23 @@
}
}
},
"cookie-parser": {
"version": "1.3.5",
"from": "cookie-parser@*",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.5.tgz",
"dependencies": {
"cookie": {
"version": "0.1.3",
"from": "cookie@0.1.3",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz"
},
"cookie-signature": {
"version": "1.0.6",
"from": "cookie-signature@1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz"
}
}
},
"express": {
"version": "4.13.3",
"from": "express@~4.13.1",

View file

@ -27,6 +27,7 @@
"dependencies": {
"async": "~1.4.0",
"babel": "~5.8.21",
"cookie-parser": "^1.3.5",
"express": "~4.13.1",
"express-handlebars": "~2.0.1",
"extend": "~3.0.0",

View file

@ -65,4 +65,16 @@
.add-top {
margin-top: 10px;
}
.jumbo-auth {
margin-top: 30px;
}
.jumbo-img {
max-height: 150px;
}
.spinner {
max-height: 30px !important;
}

BIN
public/images/ensl_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

56
public/images/spinner.svg Normal file
View file

@ -0,0 +1,56 @@
<svg width="105" height="105" viewBox="0 0 105 105" xmlns="http://www.w3.org/2000/svg" fill="#fff">
<circle cx="12.5" cy="12.5" r="12.5">
<animate attributeName="fill-opacity"
begin="0s" dur="1s"
values="1;.2;1" calcMode="linear"
repeatCount="indefinite" />
</circle>
<circle cx="12.5" cy="52.5" r="12.5" fill-opacity=".5">
<animate attributeName="fill-opacity"
begin="100ms" dur="1s"
values="1;.2;1" calcMode="linear"
repeatCount="indefinite" />
</circle>
<circle cx="52.5" cy="12.5" r="12.5">
<animate attributeName="fill-opacity"
begin="300ms" dur="1s"
values="1;.2;1" calcMode="linear"
repeatCount="indefinite" />
</circle>
<circle cx="52.5" cy="52.5" r="12.5">
<animate attributeName="fill-opacity"
begin="600ms" dur="1s"
values="1;.2;1" calcMode="linear"
repeatCount="indefinite" />
</circle>
<circle cx="92.5" cy="12.5" r="12.5">
<animate attributeName="fill-opacity"
begin="800ms" dur="1s"
values="1;.2;1" calcMode="linear"
repeatCount="indefinite" />
</circle>
<circle cx="92.5" cy="52.5" r="12.5">
<animate attributeName="fill-opacity"
begin="400ms" dur="1s"
values="1;.2;1" calcMode="linear"
repeatCount="indefinite" />
</circle>
<circle cx="12.5" cy="92.5" r="12.5">
<animate attributeName="fill-opacity"
begin="700ms" dur="1s"
values="1;.2;1" calcMode="linear"
repeatCount="indefinite" />
</circle>
<circle cx="52.5" cy="92.5" r="12.5">
<animate attributeName="fill-opacity"
begin="500ms" dur="1s"
values="1;.2;1" calcMode="linear"
repeatCount="indefinite" />
</circle>
<circle cx="92.5" cy="92.5" r="12.5">
<animate attributeName="fill-opacity"
begin="200ms" dur="1s"
values="1;.2;1" calcMode="linear"
repeatCount="indefinite" />
</circle>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -756,16 +756,14 @@ var Gatherers = React.createClass({
return React.createElement(
"tr",
{ key: gatherer.user.id },
{ key: gatherer.user.id, "data-userid": gatherer.user.id },
React.createElement(
"td",
{ className: "col-md-5" },
country,
" ",
gatherer.user.username,
"  (",
gatherer.user.id,
")"
" "
),
React.createElement(
"td",
@ -956,19 +954,16 @@ var initialiseVisibilityMonitoring = function initialiseVisibilityMonitoring(soc
}, false);
};
var initialiseComponents = function initialiseComponents() {
var socketUrl = window.location.protocol + "//" + window.location.host;
socket = io(socketUrl).on("connect", function () {
console.log("Connected");
}).on("reconnect", function () {
console.log("Reconnected");
}).on("disconnect", function () {
console.log("Disconnected");
});
var removeAuthWidget = function removeAuthWidget() {
$("#authenticating").remove();
};
var showAuthenticationNotice = function showAuthenticationNotice() {
$("#auth-required").show();
};
var renderPage = function renderPage(socket) {
initialiseVisibilityMonitoring(socket);
// Render Page
React.render(React.createElement(UserMenu, null), document.getElementById('side-menu'));
React.render(React.createElement(Chatroom, null), document.getElementById('chatroom'));
React.render(React.createElement(Gather, null), document.getElementById('gathers'));
@ -976,6 +971,25 @@ var initialiseComponents = function initialiseComponents() {
React.render(React.createElement(AdminPanel, null), document.getElementById('admin-menu'));
};
var initialiseComponents = function initialiseComponents() {
var socketUrl = window.location.protocol + "//" + window.location.host;
socket = io(socketUrl).on("connect", function () {
console.log("Connected");
removeAuthWidget();
renderPage(socket);
}).on("error", function (error, foo) {
console.log(error);
if (error === "Authentication Failed") {
removeAuthWidget();
showAuthenticationNotice();
}
}).on("reconnect", function () {
console.log("Reconnected");
}).on("disconnect", function () {
console.log("Disconnected");
});
};
"use strict";
var Chatroom = React.createClass({
@ -1189,7 +1203,7 @@ var UserLogin = React.createClass({
type: "text",
className: "form-control",
ref: "authorize_id",
placeholder: "Choose an ID..." }),
placeholder: "Change user" }),
React.createElement(
"span",
{ className: "input-group-btn" },
@ -1197,20 +1211,7 @@ var UserLogin = React.createClass({
type: "submit",
className: "btn btn-primary",
id: "btn-chat",
value: "Login" })
)
),
React.createElement(
"div",
{ className: "signin" },
React.createElement(
"p",
{ className: "text-center" },
React.createElement(
"small",
null,
"Just a temporary measure until genuine authentication is implemented"
)
value: "Assume ID" })
)
)
);
@ -1265,14 +1266,7 @@ var UserMenu = React.createClass({
)
)
),
users,
React.createElement(
"li",
null,
React.createElement("br", null),
React.createElement(UserLogin, null),
React.createElement("br", null)
)
users
);
}
});
@ -1299,6 +1293,7 @@ var AdminPanel = React.createClass({
null,
"Admin"
),
React.createElement(UserLogin, null),
React.createElement(
"button",
{
@ -1390,17 +1385,6 @@ var CurrentUser = React.createClass({
{ href: "#", "data-toggle": "modal", "data-target": "#designmodal" },
"Design Goals"
)
),
React.createElement("li", { className: "divider" }),
React.createElement(
"li",
null,
React.createElement(
"a",
{ href: "login.html" },
React.createElement("i", { className: "fa fa-sign-out fa-fw" }),
" Logout"
)
)
)
);

View file

@ -1,3 +0,0 @@
$(function () {
initialiseComponents();
});

View file

@ -11,7 +11,8 @@ describe("ENSL Client", function () {
describe (".decodeSession", function () {
it ("decodes an ENSL session", function () {
var output = EnslClient.decodeSession(sessionString);
assert.isDefined(output.parsed);
assert.isNotNull(output);
assert.isDefined(output.session_id);
});
it ("returns null if invalid cookie format", function () {
assert.isNull(EnslClient.decodeSession("foo"));

View file

@ -21,7 +21,8 @@ var EnslClient = helpers.EnslClient = require(path.join(__dirname, "../../lib/en
// Mongo & Associated Models
var db = require(path.join(__dirname, "../../db/index"));
var mongoose = require("mongoose");
var Message = helpers.Message = mongoose.model('message');
var Message = helpers.Message = mongoose.model("Message");
var Session = helpers.Session = mongoose.model("Session");
var async = require("async");
helpers.clearDb = function (callback) {
@ -50,7 +51,7 @@ var createUser = helpers.createUser = (function () {
nickname: "SteamUser" + counter
}
};
if (o && typeof o === 'object') {
if (o && typeof o === "object") {
defaultUser = extend(defaultUser, o);
}
return new User(defaultUser);

View file

@ -5,10 +5,10 @@ var request = require("supertest");
var app = helper.app;
describe("Basic Spec", function () {
it ("returns 200", function (done) {
it ("redirects if user is not authenticated", function (done) {
request(app)
.get("/")
.expect(200)
.expect(302)
.end(done);
});

23
spec/session.js Normal file
View file

@ -0,0 +1,23 @@
"use strict";
var helper = require("./helpers/index.js");
var Session = helper.Session;
var assert = require("chai").assert;
var async = require("async");
describe("Session model", function () {
describe(".create", function () {
var session;
beforeEach(function () {
session = { userId: Math.floor(Math.random() * 10000) };
});
it ("creates a new session", function (done) {
Session.create(session, function (error, result) {
if (error) return done(error);
assert.equal(result.userId, session.userId);
assert.isString(result.key);
done();
});
});
});
});

10
views/500.hbs Normal file
View file

@ -0,0 +1,10 @@
<div id="page-wrapper" style="min-height: 750px;">
<div class="container-fluid">
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">Something went wrong )-:</h1>
<p>Find an ENSL admin and get them to fix it!</p>
</div>
</div>
</div>
</div>

View file

@ -1,12 +1,41 @@
<div id="page-wrapper" style="min-height: 750px;">
<div class="container-fluid">
<div class="row">
<div class="col-lg-3 col-md-6" id="gatherCounter">
<div id="wrapper">
{{>menu}}
<div id="page-wrapper" style="min-height: 750px;">
<div class="container-fluid">
<div class="row" id="authenticating">
<div class="col-lg-6 col-lg-offset-3">
<div class="jumbotron jumbo-auth text-center">
<div>
<img src="/images/ensl_logo.png" class="jumbo-img" alt="ENSL Logo" />
</div>
<br />
<h3>Authenticating your ENSL account</h3>
<br />
<div>
<img src="/images/spinner.svg" class="spinner" alt="Loading" />
</div>
</div>
</div>
</div>
<div class="row" id="auth-required" style="display:none;">
<div class="col-lg-6 col-lg-offset-3">
<div class="jumbotron jumbo-auth text-center">
<div>
<img src="/images/ensl_logo.png" alt="ENSL Logo" />
</div>
<h3>You need to be logged to the ENSL website to access gathers</h3>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-6" id="gatherCounter">
</div>
</div>
<div class="row">
<div class="col-lg-6" id="gathers"></div>
<div class="col-lg-6" id="chatroom"></div>
</div>
</div>
<div class="row">
<div class="col-lg-6" id="gathers"></div>
<div class="col-lg-6" id="chatroom"></div>
</div>
</div>
</div>
</div>
</div>
{{>foot}}

View file

@ -1,10 +1,6 @@
<html>
{{>head}}
<body>
<div id="wrapper">
{{>menu}}
{{{ body }}}
</div>
{{>foot}}
{{{ body }}}
</body>
</html>

View file

@ -1,2 +1,4 @@
<script src="/js/app.js"></script>
<script src="/js/client.js"></script>
<script>
$(function () { initialiseComponents(); });
</script>

View file

@ -6,7 +6,6 @@
<h4 class="modal-title">Design Goals</h4>
</div>
<div class="modal-body">
<p>This app is made to promote 3 things</p>
<ul>
<li>Create a nice place where the NS2 community can assemble and have fun</li>
<li>Create an efficient, easy-to-use and flexible system to create NS2 gathers</li>

14
views/redirect.hbs Normal file
View file

@ -0,0 +1,14 @@
<div class="container-fluid">
<div class="row">
<div class="col-lg-6 col-lg-offset-3">
<div class="jumbotron text-center">
<div>
<img src="/images/ensl_logo.png" alt="ENSL Logo" />
</div>
<h2>You need to be logged to the ENSL website to access gathers</h2>
<br />
<p><a class="btn btn-primary btn-lg" href="{{ redirect }}" role="button">Go to website</a></p>
</div>
</div>
</div>
</div>