Authenticates websockets & adds jwt test env

This commit is contained in:
James Deacon 2022-01-10 11:47:50 +00:00
parent 4ba291d86a
commit 1bdcdad3e3
7 changed files with 44 additions and 15 deletions

View file

@ -79,6 +79,12 @@ See instructions on our Wiki about [how to setup a reverse proxy for WBO](https:
WBO is available in multiple languages. The translations are stored in [`server/translations.json`](./server/translations.json).
If you feel like contributing to this collaborative project, you can [translate WBO into your own language](https://github.com/lovasoa/whitebophir/wiki/How-to-translate-WBO-into-your-own-language).
## Authentication
WBO supports authentication with a JWT. This should be passed in as a query with the key `token`, eg, `http://myboard.com/boards/test?token={token}`
The `AUTH_SECRET_KEY` variable in [`configuration.js`](./server/configuration.js) should filled with the secret key for the JWT.
## Configuration
When you start a WBO server, it loads its configuration from several environment variables.

View file

@ -62,8 +62,12 @@ Tools.connect = function () {
self.socket = null;
}
var url = new URL(window.location);
var params = new URLSearchParams(url.search);
var token = params.get("token");
this.socket = io.connect('', {
"query": "token=" + token,
"path": window.location.pathname.split("/boards/")[0] + "/socket.io",
"reconnection": true,
"reconnectionDelay": 100, //Make the xhr connections as fast as possible

View file

@ -24,7 +24,11 @@ module.exports = {
"args": ["-headless"]
}
}
}
},
"jwt": {
"globals": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.5mhBHqs5_DTLdINd9p5m7ZJ6XD0Xc55kIaCRY5r6HRA"
}
}
}

View file

@ -19,7 +19,7 @@
},
"scripts": {
"start": "node ./server/server.js",
"test": "nightwatch tests"
"test": "nightwatch tests && nightwatch tests --env jwt"
},
"main": "./server/server.js",
"repository": {

View file

@ -104,13 +104,9 @@ function validateBoardName(boardName) {
*/
function userHasPermission(url) {
if(config.AUTH_SECRET_KEY != "") {
if(url.searchParams.get("token")) {
var token = url.searchParams.get("token");
try {
jsonwebtoken.verify(token, config.AUTH_SECRET_KEY);
} catch(error) { // Token not valid
throw new Error(error)
}
var token = url.searchParams.get("token");
if(token) {
jsonwebtoken.verify(token, config.AUTH_SECRET_KEY);
} else { // Error out as no token provided
throw new Error("No token provided");
}

View file

@ -1,7 +1,8 @@
var iolib = require("socket.io"),
{ log, gauge, monitorFunction } = require("./log.js"),
BoardData = require("./boardData.js").BoardData,
config = require("./configuration");
config = require("./configuration"),
jsonwebtoken = require("jsonwebtoken");
/** Map from name to *promises* of BoardData
@type {{[boardName: string]: Promise<BoardData>}}
@ -29,6 +30,20 @@ function noFail(fn) {
function startIO(app) {
io = iolib(app);
if (config.AUTH_SECRET_KEY) {
// Middleware to check for valid jwt
io.use(function(socket, next) {
if(socket.handshake.query && socket.handshake.query.token) {
jsonwebtoken.verify(socket.handshake.query.token, config.AUTH_SECRET_KEY, function(err, decoded) {
if(err) return next(new Error("Authentication error: Invalid JWT"));
socket.decoded = decoded;
next();
})
} else {
next(new Error("Authentication error: No jwt provided"));
}
});
}
io.on("connection", noFail(handleSocketConnection));
return io;
}

View file

@ -5,12 +5,16 @@ const path = require("path");
const PORT = 8487
const SERVER = 'http://localhost:' + PORT;
let wbo, data_path;
let wbo, data_path, tokenQuery;
async function beforeEach(browser, done) {
data_path = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'wbo-test-data-'));
process.env["PORT"] = PORT;
process.env["WBO_HISTORY_DIR"] = data_path;
if(browser.globals.token) {
process.env["AUTH_SECRET_KEY"] = "test";
tokenQuery = "token=" + browser.globals.token;
}
console.log("Launching WBO in " + data_path);
wbo = require("../server/server.js");
done();
@ -51,7 +55,7 @@ function testPencil(browser) {
.refresh()
.waitForElementVisible("path[d='M 100 200 L 100 200 C 100 200 300 400 300 400'][stroke='#123456']")
.assert.visible("path[d='M 0 0 L 0 0 C 0 0 40 120 90 120 C 140 120 180 0 180 0'][stroke='#abcdef']")
.url(SERVER + '/preview/anonymous')
.url(SERVER + '/preview/anonymous?' + tokenQuery)
.waitForElementVisible("path[d='M 100 200 L 100 200 C 100 200 300 400 300 400'][stroke='#123456']")
.assert.visible("path[d='M 0 0 L 0 0 C 0 0 40 120 90 120 C 140 120 180 0 180 0'][stroke='#abcdef']")
.back()
@ -92,15 +96,15 @@ function testCursor(browser) {
}
function testBoard(browser) {
var page = browser.url(SERVER + '/boards/anonymous?lang=fr')
var page = browser.url(SERVER + '/boards/anonymous?lang=fr&' + tokenQuery)
.waitForElementVisible('.tool[title ~= Crayon]') // pencil
page = testPencil(page);
page = testCircle(page);
page = testCursor(page);
// test hideMenu
browser.url(SERVER + '/boards/anonymous?lang=fr&hideMenu=true').waitForElementNotVisible('#menu');
browser.url(SERVER + '/boards/anonymous?lang=fr&hideMenu=false').waitForElementVisible('#menu');
browser.url(SERVER + '/boards/anonymous?lang=fr&hideMenu=true&' + tokenQuery).waitForElementNotVisible('#menu');
browser.url(SERVER + '/boards/anonymous?lang=fr&hideMenu=false&' + tokenQuery).waitForElementVisible('#menu');
page.end();
}