mirror of
https://github.com/lovasoa/whitebophir
synced 2024-11-10 06:24:17 +00:00
Merge pull request #254 from H2-invent/master
add JWT board name verification
This commit is contained in:
commit
27c79074ed
6 changed files with 157 additions and 23 deletions
27
README.md
27
README.md
|
@ -94,9 +94,34 @@ Within the payload, you can declare the user's roles as an array. Currently the
|
|||
"roles": ["moderator"]
|
||||
}
|
||||
```
|
||||
|
||||
Moderators have access to the Clear tool, which will wipe all content from the board.
|
||||
|
||||
## Board name verification in the JWT
|
||||
|
||||
WBO supports verification of the board with a JWT.
|
||||
|
||||
The `AUTH_SECRET_KEY` variable in [`configuration.js`](./server/configuration.js) should be filled with the secret key for the JWT.
|
||||
|
||||
To check for a valid board name just add the board name to the role with a ":". With this you can set a moderator for a specific board.
|
||||
|
||||
```
|
||||
{
|
||||
....
|
||||
"roles": ["moderator:<boardName1>","moderator:<boardName2>","editor:<boardName3>","editor:<boardName4>"] }
|
||||
}
|
||||
```
|
||||
eg, `http://myboard.com/boards/mySecretBoardName?token={token}`
|
||||
```
|
||||
{
|
||||
"iat": 1516239022,
|
||||
"exp": 1516298489,
|
||||
"roles": ["moderator:mySecretBoardName"]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
You can now be sure that only users who have the correct token have access to the board with the specific name.
|
||||
|
||||
## Configuration
|
||||
|
||||
When you start a WBO server, it loads its configuration from several environment variables.
|
||||
|
|
|
@ -57,4 +57,5 @@ module.exports = {
|
|||
|
||||
/** Secret key for jwt */
|
||||
AUTH_SECRET_KEY: (process.env["AUTH_SECRET_KEY"] || ""),
|
||||
|
||||
};
|
||||
|
|
99
server/jwtBoardnameAuth.js
Normal file
99
server/jwtBoardnameAuth.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* WHITEBOPHIR
|
||||
*********************************************************
|
||||
* @licstart The following is the entire license notice for the
|
||||
* JavaScript code in this page.
|
||||
*
|
||||
* Copyright (C) 2013 Ophir LOJKINE
|
||||
*
|
||||
*
|
||||
* The JavaScript code in this page is free software: you can
|
||||
* redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License (GNU GPL) as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version. The code is distributed WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
|
||||
*
|
||||
* As additional permission under GNU GPL version 3 section 7, you
|
||||
* may distribute non-source (e.g., minimized or compacted) forms of
|
||||
* that code without the copy of the GNU GPL normally required by
|
||||
* section 4, provided you include this license notice and a URL
|
||||
* through which recipients can access the Corresponding Source.
|
||||
*
|
||||
* @licend
|
||||
*/
|
||||
|
||||
config = require("./configuration.js"),
|
||||
jsonwebtoken = require("jsonwebtoken");
|
||||
|
||||
/**
|
||||
* This function checks if a board name is set in the roles claim.
|
||||
* Returns true of the board name is set in the JWT and the board name matches the board name in the URL
|
||||
* @param {string} url
|
||||
* @param {string} boardNameIn
|
||||
@returns {boolean} - True if user does not have the role forbidden false if the user hase the role forbidden
|
||||
@throws {Error} - If no boardname match
|
||||
*/
|
||||
|
||||
function checkBoardnameInToken(url, boardNameIn) {
|
||||
var token = url.searchParams.get("token");
|
||||
if (roleInBoard(token, boardNameIn) === 'forbidden') {
|
||||
throw new Error("Acess Forbidden");
|
||||
}
|
||||
}
|
||||
|
||||
function parse_role(role) {
|
||||
let [_, role_name, board_name] = role.match(/^([^:]*):?(.*)$/);
|
||||
return {role_name, board_name}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function checks if a oard name is set in the roles claim.
|
||||
* Returns string depending on the role in the board
|
||||
* @param {string} token
|
||||
* @param {string} board
|
||||
@returns {string} "moderator"|"editor"|"forbidden"
|
||||
*/
|
||||
function roleInBoard(token, board = null) {
|
||||
if (config.AUTH_SECRET_KEY != "") {
|
||||
if (!token) {
|
||||
throw new Error("No token provided");
|
||||
}
|
||||
var payload = jsonwebtoken.verify(token, config.AUTH_SECRET_KEY);
|
||||
|
||||
var roles = payload.roles;
|
||||
var oneHasBoardName = false;
|
||||
var oneHasModerator = false;
|
||||
|
||||
if (roles) {
|
||||
for (var line of roles) {
|
||||
var role = parse_role(line);
|
||||
|
||||
if (role.board_name !== '') {
|
||||
oneHasBoardName = true;
|
||||
}
|
||||
if (role.role_name === "moderator") {
|
||||
oneHasModerator = true;
|
||||
}
|
||||
if (role.board_name === board) {
|
||||
return role.role_name;
|
||||
}
|
||||
}
|
||||
if ((!board && oneHasModerator) || !oneHasBoardName) {
|
||||
if (oneHasModerator) {
|
||||
return "moderator";
|
||||
} else {
|
||||
return "editor";
|
||||
}
|
||||
}
|
||||
return "forbidden";
|
||||
} else {
|
||||
return "editor";
|
||||
}
|
||||
} else {
|
||||
return "editor";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {checkBoardnameInToken, roleInBoard};
|
|
@ -24,9 +24,10 @@
|
|||
* @licend
|
||||
*/
|
||||
|
||||
|
||||
config = require("./configuration.js"),
|
||||
jsonwebtoken = require("jsonwebtoken");
|
||||
|
||||
const {roleInBoard} = require("./jwtBoardnameAuth");
|
||||
/**
|
||||
* Validates jwt and returns whether user is a moderator
|
||||
* @param {URL} url
|
||||
|
@ -38,7 +39,7 @@ function checkUserPermission(url) {
|
|||
if (config.AUTH_SECRET_KEY != "") {
|
||||
var token = url.searchParams.get("token");
|
||||
if (token) {
|
||||
isModerator = checkIfModerator(token);
|
||||
isModerator = roleInBoard(token) === "moderator";
|
||||
} else {
|
||||
// Error out as no token provided
|
||||
throw new Error("No token provided");
|
||||
|
@ -47,22 +48,5 @@ function checkUserPermission(url) {
|
|||
return isModerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is a moderator
|
||||
* @param {string} token
|
||||
*/
|
||||
function checkIfModerator(token) {
|
||||
if(config.AUTH_SECRET_KEY != "") {
|
||||
var payload = jsonwebtoken.verify(token, config.AUTH_SECRET_KEY);
|
||||
var roles = payload.roles;
|
||||
if(roles) {
|
||||
return roles.includes("moderator");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { checkUserPermission, checkIfModerator };
|
||||
module.exports = { checkUserPermission };
|
||||
|
|
|
@ -11,6 +11,7 @@ var app = require("http").createServer(handler),
|
|||
polyfillLibrary = require("polyfill-library"),
|
||||
check_output_directory = require("./check_output_directory.js"),
|
||||
jwtauth = require("./jwtauth.js");
|
||||
jwtBoardName = require("./jwtBoardnameAuth.js");
|
||||
|
||||
var MIN_NODE_VERSION = 10.0;
|
||||
|
||||
|
@ -120,11 +121,13 @@ function handleRequest(request, response) {
|
|||
if (parts.length === 1) {
|
||||
// '/boards?board=...' This allows html forms to point to boards
|
||||
var boardName = parsedUrl.searchParams.get("board") || "anonymous";
|
||||
jwtBoardName.checkBoardnameInToken(parsedUrl, boardName);
|
||||
var headers = { Location: "boards/" + encodeURIComponent(boardName) };
|
||||
response.writeHead(301, headers);
|
||||
response.end();
|
||||
} else if (parts.length === 2 && parsedUrl.pathname.indexOf(".") === -1) {
|
||||
validateBoardName(parts[1]);
|
||||
var boardName = validateBoardName(parts[1]);
|
||||
jwtBoardName.checkBoardnameInToken(parsedUrl, boardName);
|
||||
boardTemplate.serve(request, response, isModerator);
|
||||
// If there is no dot and no directory, parts[1] is the board name
|
||||
} else {
|
||||
|
@ -139,6 +142,7 @@ function handleRequest(request, response) {
|
|||
config.HISTORY_DIR,
|
||||
"board-" + boardName + ".json"
|
||||
);
|
||||
jwtBoardName.checkBoardnameInToken(parsedUrl, boardName);
|
||||
if (parts.length > 2 && /^[0-9A-Za-z.\-]+$/.test(parts[2])) {
|
||||
history_file += "." + parts[2] + ".bak";
|
||||
}
|
||||
|
@ -161,6 +165,7 @@ function handleRequest(request, response) {
|
|||
config.HISTORY_DIR,
|
||||
"board-" + boardName + ".json"
|
||||
);
|
||||
jwtBoardName.checkBoardnameInToken(parsedUrl, boardName);
|
||||
response.writeHead(200, {
|
||||
"Content-Type": "image/svg+xml",
|
||||
"Content-Security-Policy": CSP,
|
||||
|
|
|
@ -105,7 +105,27 @@ function testBoard(browser) {
|
|||
// test hideMenu
|
||||
browser.url(SERVER + '/boards/anonymous?lang=fr&hideMenu=true&' + tokenQuery).waitForElementNotVisible('#menu');
|
||||
browser.url(SERVER + '/boards/anonymous?lang=fr&hideMenu=false&' + tokenQuery).waitForElementVisible('#menu');
|
||||
|
||||
if(browser.globals.token) {
|
||||
//has moderator jwt and no board name
|
||||
browser.url(SERVER + '/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3IiXX0.PqYHmV0loeKwyLLYZ1a1eIXBCCaa3t5lYUTu_P_-i14').waitForElementVisible('#toolID-Clear');
|
||||
//has moderator JWT and other board name
|
||||
browser.url(SERVER + '/boards/testboard123?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3IiXX0.PqYHmV0loeKwyLLYZ1a1eIXBCCaa3t5lYUTu_P_-i14').waitForElementVisible('#toolID-Clear');
|
||||
//has moderator JWT and board name match board name in url
|
||||
browser.url(SERVER + '/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3I6dGVzdGJvYXJkIl19.UVf6awGEChVxcWBbt6dYoNH0Scq7cVD_xfQn-U8A1lw').waitForElementVisible('#toolID-Clear');
|
||||
//has moderator JWT and board name NOT match board name in url
|
||||
browser.url(SERVER + '/boards/testboard123?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3I6dGVzdGJvYXJkIl19.UVf6awGEChVxcWBbt6dYoNH0Scq7cVD_xfQn-U8A1lw').waitForElementNotPresent('#menu');
|
||||
//has editor JWT and no boardname provided
|
||||
browser.url(SERVER + '/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0b3IiXX0.IJehwM8tPVQFzJ2fZMBHveii1DRChVtzo7PEnSmmFt8').waitForElementNotPresent('#toolID-Clear');
|
||||
browser.url(SERVER + '/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0b3IiXX0.IJehwM8tPVQFzJ2fZMBHveii1DRChVtzo7PEnSmmFt8').waitForElementVisible('#menu')
|
||||
//has editor JWT and boardname provided and match to the board in the url
|
||||
browser.url(SERVER + '/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0bzp0ZXN0Ym9hcmQiXX0.-P6gjYlPP5I2zgSoVTlADdesVPfSXV-JXZQK5uh3Xwo').waitForElementVisible('#menu');
|
||||
browser.url(SERVER + '/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0bzp0ZXN0Ym9hcmQiXX0.-P6gjYlPP5I2zgSoVTlADdesVPfSXV-JXZQK5uh3Xwo').waitForElementNotPresent('#toolID-Clear');
|
||||
//has editor JWT and boardname provided and and not match to the board in the url
|
||||
browser.url(SERVER + '/boards/testboard123?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0bzp0ZXN0Ym9hcmQiXX0.-P6gjYlPP5I2zgSoVTlADdesVPfSXV-JXZQK5uh3Xwo').waitForElementNotPresent('#menu');
|
||||
//is moderator and boardname contains ":"
|
||||
browser.url(SERVER + '/boards/test:board?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3I6dGVzdDpib2FyZCJdfQ.LKYcDccheD2oXAMAemxSekDeowGsMl29CFkgJgwbkGE').waitForElementNotPresent('#menu');
|
||||
browser.url(SERVER + '/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3I6dGVzdDpib2FyZCJdfQ.LKYcDccheD2oXAMAemxSekDeowGsMl29CFkgJgwbkGE').waitForElementNotPresent('#menu');
|
||||
}
|
||||
page.end();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue