Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mws authentication #8596

Open
wants to merge 20 commits into
base: multi-wiki-support
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions boot/boot.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions editions/multiwikiserver/tiddlywiki.info
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@
"tiddlywiki/snowwhite"
],
"build": {
"--mws-list-users": [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be confusing to have a named build that matches a built-in command. Perhaps this one isn't necessary in any case?

"--mws-list-users"
],
"mws-add-user": [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment there is no parameterisation of build commands, so I can't see how this one could be used in practice. Is it intended to be part of the documentation?

"--mws-add-permission", "READ", "Allows user to create tiddlers",
"--mws-add-permission", "WRITE", "Gives the user the permission to edit and delete tiddlers",
"--mws-add-role", "ADMIN", "System Administrator",
"--mws-assign-role-permission", "ADMIN", "READ",
"--mws-assign-role-permission", "ADMIN", "WRITE",
"--mws-add-user", "user", "pass123",
"--mws-assign-user-role", "user", "ADMIN"
],
"load-mws-demo-data": [
"--mws-load-wiki-folder","./editions/tw5.com","docs", "TiddlyWiki Documentation from https://tiddlywiki.com","docs","TiddlyWiki Documentation from https://tiddlywiki.com",
"--mws-load-wiki-folder","./editions/dev","dev","TiddlyWiki Developer Documentation from https://tiddlywiki.com/dev","dev-docs", "TiddlyWiki Developer Documentation from https://tiddlywiki.com/dev",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"start": "node ./tiddlywiki.js ./editions/multiwikiserver --mws-load-plugin-bags --build load-mws-demo-data --mws-listen",
"build:test-edition": "node ./tiddlywiki.js ./editions/test --verbose --version --build index",
"test:multiwikiserver-edition": "node ./tiddlywiki.js ./editions/multiwikiserver/ --build load-mws-demo-data --mws-listen --mws-test-server http://127.0.0.1:8080/ --quit",
"mws-add-user": "node ./tiddlywiki.js ./editions/multiwikiserver --build load-mws-demo-data --mws-listen --build mws-add-user --quit",
"test": "npm run build:test-edition && npm run test:multiwikiserver-edition",
"lint:fix": "eslint . --fix",
"lint": "eslint ."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-add-permission.js
type: application/javascript
module-type: command

Command to create a permission

\*/
(function(){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiddlywiki code uses tabs for indentation. Tab width = 4

The first level inside the (function(){ wrapper is not indented. So eg: export.info = ... does not need a leading tab.


/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";

exports.info = {
name: "mws-add-permission",
synchronous: false
};

var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};

Command.prototype.execute = function() {
var self = this;

if(this.params.length < 2) {
return "Usage: --mws-add-permission <permission_name> <description>";
}

if(!$tw.mws || !$tw.mws.store || !$tw.mws.store.sqlTiddlerDatabase) {
return "Error: MultiWikiServer or SQL database not initialized.";
}

var permission_name = this.params[0];
var description = this.params[1];

$tw.mws.store.sqlTiddlerDatabase.createPermission(permission_name, description);

console.log(permission_name+" Permission Created Successfully!")
self.callback();
return null;
};

exports.Command = Command;

})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-add-role.js
type: application/javascript
module-type: command

Command to create a role

\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";

exports.info = {
name: "mws-add-role",
synchronous: false
};

var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};

Command.prototype.execute = function() {
var self = this;

if(this.params.length < 2) {
return "Usage: --mws-add-role <role_name> <description>";
}

if(!$tw.mws || !$tw.mws.store || !$tw.mws.store.sqlTiddlerDatabase) {
return "Error: MultiWikiServer or SQL database not initialized.";
}

var role_name = this.params[0];
var description = this.params[1];

$tw.mws.store.sqlTiddlerDatabase.createRole(role_name, description);

console.log(role_name+" Role Created Successfully!")
self.callback(null, "Role Created Successfully!");
return null;
};

exports.Command = Command;

})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-add-user.js
type: application/javascript
module-type: command

Command to create users and grant permission

\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";

var crypto = require("crypto");

exports.info = {
name: "mws-add-user",
synchronous: false
};

var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};

Command.prototype.execute = function() {
var self = this;

if(this.params.length < 2) {
return "Usage: --mws-add-user <username> <password> [email]";
}

if(!$tw.mws || !$tw.mws.store || !$tw.mws.store.sqlTiddlerDatabase) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the usernames and passwords should never be stored with the content. So for me !$tw.mws.store.sqlTiddlerDatabase looks like it is in the same database file?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @pmario I think it's perfectly OK for this data to be in the same database. There's no more obvious way for queries of the tiddler database to accidentally start returning results from the user database than there is for any data corruption within a program.

What I do want to pay attention to is the best practice of making the admin UI run off of a different HTTP host/port, so that administrators can use external tools to lock down admin access.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this will prevent us from using different authentication and authorisation services / mechanisms in the future.

Using Usernames and passwords is notoriously unsecure and imo outdated already today. The minimal requirement I would consider somewhat save is username/password in a combination with a time based 2-factor-code generated with an authenticator app.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plan is to have pluggable authentication schemes. Form-based authentication is still pretty standard for website logins and so I don't think we can avoid offering it, but I definitely want to support things like logging in with Google or GitHub credentials.

return "Error: MultiWikiServer or SQL database not initialized.";
}

var username = this.params[0];
var password = this.params[1];
var email = this.params[2] || username + "@tiddlywiki.com";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If username + @tiddlywiki.com is a placeholder, imo it should be example.com -- Otherwise it means, that tiddlywiki.com also provided an email-service. Which is not the case

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps use example.com for the moment?

var hashedPassword = crypto.createHash("sha256").update(password).digest("hex");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simple sha256 hashing the password is not enough. The password needs to be salted and then encrypted at least 10000 times. Key-stretching makes it harder to use rainbow-tables and brute force password attacks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm comfortable with having a placeholder implementation to begin with. At this point, I think clarity is the most important consideration, and with adequate encapsulation it will be straightforward for us to revisit the implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is, that it should be done right from the beginning, because the salt has to be stored with the user data. It also plays into the login form. So there are at least 3 different positions, where a change here causes changes up to the UI password handling.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pmario the challenge is that we need to merge this PR to get feedback. If we wait until we've got a production ready authentication system then this PR would have to stay open for a long time, making all other work on MWS much more complicated. At this point we have the freedom that if required MWS can nuke the user database on an upgrade. We will lose that freedom as MWS moves beyond the experimental stage, so we should take advantage of it now to improve our overall velocity.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK


var user = $tw.mws.store.sqlTiddlerDatabase.getUserByUsername(username);

if(user) {
self.callback("WARNING: An account with the username (" + username + ") already exists");
} else {
$tw.mws.store.sqlTiddlerDatabase.createUser(username, email, hashedPassword);
console.log("User Account Created Successfully!")
self.callback();
}
return null;
};

exports.Command = Command;

})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-assign-role-permission.js
type: application/javascript
module-type: command

Command to assign permission to a role

\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";

exports.info = {
name: "mws-assign-role-permission",
synchronous: false
};

var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};

Command.prototype.execute = function() {
var self = this;

if(this.params.length < 2) {
return "Usage: --mws-assign-role-permission <role_name> <permission_name>";
}

if(!$tw.mws || !$tw.mws.store || !$tw.mws.store.sqlTiddlerDatabase) {
return "Error: MultiWikiServer or SQL database not initialized.";
}

var role_name = this.params[0];
var permission_name = this.params[1];
var role = $tw.mws.store.sqlTiddlerDatabase.getRoleByName(role_name);
var permission = $tw.mws.store.sqlTiddlerDatabase.getPermissionByName(permission_name);

if(!role) {
return "Error: Unable to find Role: "+role_name;
}

if(!permission) {
return "Error: Unable to find Permission: "+permission_name;
}

var permission = $tw.mws.store.sqlTiddlerDatabase.getPermissionByName(permission_name);


$tw.mws.store.sqlTiddlerDatabase.addPermissionToRole(role.role_id, permission.permission_id);

console.log(permission_name+" permission assigned to "+role_name+" role successfully!")
self.callback();
return null;
};

exports.Command = Command;

})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-assign-user-role.js
type: application/javascript
module-type: command

Command to assign a role to a user

\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";

exports.info = {
name: "mws-assign-user-role",
synchronous: false
};

var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};

Command.prototype.execute = function() {
var self = this;

if(this.params.length < 2) {
return "Usage: --mws-assign-user-role <username> <role_name>";
}

if(!$tw.mws || !$tw.mws.store || !$tw.mws.store.sqlTiddlerDatabase) {
return "Error: MultiWikiServer or SQL database not initialized.";
}

var username = this.params[0];
var role_name = this.params[1];
var role = $tw.mws.store.sqlTiddlerDatabase.getRoleByName(role_name);
var user = $tw.mws.store.sqlTiddlerDatabase.getUserByUsername(username);

if(!role) {
return "Error: Unable to find Role: "+role_name;
}

if(!user) {
return "Error: Unable to find user with the username "+username;
}

$tw.mws.store.sqlTiddlerDatabase.addRoleToUser(user.user_id, role.role_id);

console.log(role_name+" role has been assigned to user with username "+username)
self.callback();
return null;
};

exports.Command = Command;

})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-list-users.js
type: application/javascript
module-type: command

Command to list users

\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";

exports.info = {
name: "mws-list-users",
synchronous: false
};

var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};

Command.prototype.execute = function() {
var self = this;

if(!$tw.mws || !$tw.mws.store || !$tw.mws.store.sqlTiddlerDatabase) {
return "Error: MultiWikiServer or SQL database not initialized.";
}

var users = $tw.mws.store.sqlTiddlerDatabase.listUsers().map(function(user){
return ({
username: user.username,
email: user.email,
created_at: user.created_at,
})
});
console.log("Users:", users);
self.callback(null, "Users retrieved successfully:\n" + JSON.stringify(users, null, 2));
};

exports.Command = Command;

})();
Loading
Loading