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

Conversation

webplusai
Copy link
Contributor

No description provided.

Copy link

stackblitz bot commented Sep 12, 2024

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

Copy link

Confirmed: webplusai has already signed the Contributor License Agreement (see contributing.md)

@Jermolene
Copy link
Member

Thank you @webplusai this is looking good. I will provide some detailed feedback shortly.

@joshuafontany
Copy link
Contributor

Following along (very busy at work atm, company is reorging... again). This is exciting stuff!

Copy link
Member

@Jermolene Jermolene left a comment

Choose a reason for hiding this comment

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

Thank you @webplusai this is looking good. There is what looks like some work-in-progress areas such as handleLogin. It makes a lot of sense to keep things simple there temporarily, and so I would like to merge this. However I think it may just be worth you adding comments to tag the TODO areas to make explicit the areas that we need to revisit.

plugins/tiddlywiki/authentication/tiddlers/login.tid Outdated Show resolved Hide resolved
plugins/tiddlywiki/authentication/plugin.info Outdated Show resolved Hide resolved
Copy link
Member

@Jermolene Jermolene left a comment

Choose a reason for hiding this comment

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

Thank you @webplusai that's great. I think the best way forwards now is to merge this and discuss further refinements. However at the moment the tests are failing, if you could kindly fix them up then I'll go ahead and merge.

@webplusai
Copy link
Contributor Author

@Jermolene I've fixed the test issue. Also some issues that I had related to the user list and roles were also fixed.

@Jermolene
Copy link
Member

Thank you @webplusai. As discussed I am keen to get this merged. There are some points I would like to return to but we can worry about those later.

I think the blocker now is that we've got users who are testing the multi-wiki-support branch, and it is going to be a bad experience for them if they upgrade and then are presented with a login dialogue, with no obvious way to create or access a user account.

Ideally, we should get to the point where by default the user gets the previous experience of anonymous read/write access, with an obvious route to get to an admin screen so that they can enable password access and try things out.

One possibility would be to add commands like --mws-add-user, --mws-list-users etc. and use them to set up a demo configuration in a new npm command.

Let me know if that makes sense, ideally we should discuss the details before spending too much time on implementation.

@webplusai
Copy link
Contributor Author

@Jermolene I've added the commands as you indicated.

Copy link
Member

@pmario pmario left a comment

Choose a reason for hiding this comment

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

I did only have a closer look at the code, where I did add some comments. I will have a closer look soon.

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.


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 username = this.params[0];
var password = this.params[1];
var email = this.params[2] || username + "@tiddlywiki.com";
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

@pmario
Copy link
Member

pmario commented Oct 2, 2024

@webplusai ... Is there some description which mechanisms are used with the whole authentication system?

@Jermolene ... As I wrote sensible data should not be part of the same database as the content. They should be completely separated. IMO this will also help to use other systems that provide secure vaults in the future.

@pmario
Copy link
Member

pmario commented Oct 2, 2024

  • I did run: npm run mws-add-user
  • Then I did run npm start

Is this order right, or did I do it wrong?

Which does start the server and I do get the user UI. But if I try to create a new bag, I do get a server-runtime error

grafik

syncer-server-filesystem: Dispatching 'save' task: $:/StoryList
Serving on http://127.0.0.1:8080
(press ctrl-C to exit)
$:/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js:31
    var partiallyDecoded = entityName.replace(/%3A/g, ":");
                                      ^

TypeError: Cannot read properties of undefined (reading 'replace')
    at exports.middleware ($:/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js:31:39)
    at exports.handler ($:/plugins/tiddlywiki/multiwikiserver/routes/handlers/post-bag.js:31:2)
    at IncomingMessage.<anonymous> ($:/plugins/tiddlywiki/multiwikiserver/mws-server.js:525:10)
    at IncomingMessage.emit (node:events:519:28)
    at endReadableNT (node:internal/streams/readable:1696:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)

Node.js v20.14.0
PS E:\git\tiddly\tiddlywiki\TiddlyWiki5>

@Jermolene
Copy link
Member

Thanks @webplusai

Great to have your feedback @pmario, this is a great stage to be hammering out some important details.

@@ -0,0 +1,10 @@
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/form
Copy link
Member

Choose a reason for hiding this comment

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

Only 1 empty line is needed after the tiddler-header

Copy link
Member

Choose a reason for hiding this comment

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

please check all you files to use tabs for indentation.

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.

@@ -301,6 +302,22 @@ Server.prototype.addRoute = function(route) {
this.routes.push(route);
};

Server.prototype.verifyPassword = function(inputPassword, storedHash) {
Copy link
Member

Choose a reason for hiding this comment

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

Should authentication be part of this file. IMO it should be it's own file and it should be required. So we get a clear separation of concerns. It should also help to keep the api cleaner.

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 that's a good idea @pmario

"bag-tiddlers": JSON.stringify(bagTiddlers)
(function () {

/*jslint node: true, browser: true */
Copy link
Member

Choose a reason for hiding this comment

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

The first level inside the wrapper function should not be indented. Otherwise the diff does not show, what really changed


\*/
(function() {

Copy link
Member

Choose a reason for hiding this comment

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

indentation

});
} else {
response.writeHead(400);
(function () {
Copy link
Member

Choose a reason for hiding this comment

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

indentation -- please check all files

@@ -0,0 +1,123 @@
title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-roles

\define add-role-actions()
Copy link
Member

Choose a reason for hiding this comment

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

use tabs instead of spaces for indent - please check all files

@webplusai
Copy link
Contributor Author

  • I did run: npm run mws-add-user
  • Then I did run npm start

Is this order right, or did I do it wrong?

Which does start the server and I do get the user UI. But if I try to create a new bag, I do get a server-runtime error

grafik

syncer-server-filesystem: Dispatching 'save' task: $:/StoryList
Serving on http://127.0.0.1:8080
(press ctrl-C to exit)
$:/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js:31
    var partiallyDecoded = entityName.replace(/%3A/g, ":");
                                      ^

TypeError: Cannot read properties of undefined (reading 'replace')
    at exports.middleware ($:/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js:31:39)
    at exports.handler ($:/plugins/tiddlywiki/multiwikiserver/routes/handlers/post-bag.js:31:2)
    at IncomingMessage.<anonymous> ($:/plugins/tiddlywiki/multiwikiserver/mws-server.js:525:10)
    at IncomingMessage.emit (node:events:519:28)
    at endReadableNT (node:internal/streams/readable:1696:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)

Node.js v20.14.0
PS E:\git\tiddly\tiddlywiki\TiddlyWiki5>

@pmario you did it correct. There was an error and I fixed it.

@webplusai
Copy link
Contributor Author

@Jermolene @pmario
All of the major comments are fixed except the ones that @Jermolene mentioned to do later.

@webplusai
Copy link
Contributor Author

Hi @pmario
I've used tabs for indent.

@Jermolene
Copy link
Member

Hi @webplusai I checked out the latest commits, ran npm install and then npm test, but received the following error:

Running Server Test: Check index page
Failed "Check index page" with "Test failed"
Running Server Test: Upload a 1px PNG
$:/plugins/tiddlywiki/multiwikiserver/commands/mws-test-server.js:137
                        return jsonData["imported-tiddlers"] && $tw.utils.isArray(jsonData["imported-tiddlers"]) && jsonData["imported-tiddlers"][0] === "One White Pixel";
                                       ^

TypeError: Cannot read properties of undefined (reading 'imported-tiddlers')
    at Object.expectedResult ($:/plugins/tiddlywiki/multiwikiserver/commands/mws-test-server.js:137:19)
    at IncomingMessage.<anonymous> ($:/plugins/tiddlywiki/multiwikiserver/commands/mws-test-server.js:98:33)
    at IncomingMessage.emit (node:events:529:35)
    at endReadableNT (node:internal/streams/readable:1400:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)

Node.js v18.20.3

Copy link
Member

@Jermolene Jermolene left a comment

Choose a reason for hiding this comment

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

Thank you @webplusai I've included some comments below, but am continuing my review, and will have more to add.

One thing that I do think we need to address is some documentation:

  • Update the PR description at mws authentication #8596 to include a brief description of the changes in this PR, and instructions for trying it out
  • An overview of how roles and authentication are handled (can go into the plugin readme for the moment)
  • Documentation for the end points and commands. We should make two new tabs within the plugin for the commands and the API. Each command and endpoint should be a separate tiddler, transcluded together into an alphabetical list

Let me know if that makes sense.

boot/boot.js Outdated
@@ -336,7 +336,7 @@ Get the browser location.hash. We don't use location.hash because of the way tha
*/
$tw.utils.getLocationHash = function() {
var href = window.location.href;
var idx = href.indexOf('#');
var idx = href.indexOf("#");
Copy link
Member

Choose a reason for hiding this comment

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

There seem to be a few of these corrections to existing core code. These corrections definitely need to be made, but don't belong in this PR and should be submitted to "master" as a separate PR that I can merge first.

@@ -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-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?

} else {
console.log(`Invalid return URL detected: ${returnUrl}. Redirecting to home page.`);
}
response.setHeader('Set-Cookie', `returnUrl=${encodeURIComponent(sanitizedReturnUrl)}; HttpOnly; Path=/`);
Copy link
Member

Choose a reason for hiding this comment

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

Should we include the Secure and SameSite flags?

@@ -0,0 +1,9 @@
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/form

<$macrocall $name="loginForm"/>
Copy link
Member

Choose a reason for hiding this comment

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

What is the purpose of this macro call?

@@ -34,6 +34,7 @@ function Server(options) {
this.authenticators = options.authenticators || [];
this.wiki = options.wiki;
this.boot = options.boot || $tw.boot;
this.sqlTiddlerDatabase = $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.

It would be better to make the database be one of the options properties, and to amend the call sites to explicitly pass the database:

``
this.sqlTiddlerDatabase = options.sqlTiddlerDatabase || $tw.mws.store.sqlTiddlerDatabase;


Server.prototype.redirectToLogin = function(response, returnUrl) {
if (!response.headersSent) {
const validReturnUrlRegex = /^\/(?!.*\.(ico|png|jpg|jpeg|gif|svg|css|js|woff|woff2|ttf|eot|json)$).*$/;
Copy link
Member

Choose a reason for hiding this comment

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

What is the purpose of this sanitisation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The url sanitization was done to prevent the user from being redirected to a file url instead of a page.

@@ -26,6 +28,7 @@ exports.bodyFormat = "www-form-urlencoded";
exports.csrfDisable = true;

exports.handler = function(request,response,state) {
aclMiddleware(request, response, state, "bag", "WRITE");
Copy link
Member

Choose a reason for hiding this comment

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

It looks like the middleware call can fail authentication, and send a response to the client. Presumably we would need some processing here to respond to the outcome of the middleware.

Is it necessary for each route to invoke the middleware explicitly like this? I was hoping the main handler in mws-server.js could handle the middleware more or less invisibly from the point of view of the routes.

@@ -117,6 +214,16 @@ SqlTiddlerDatabase.prototype.createBag = function(bag_name,description,accesscon
$accesscontrol: accesscontrol,
$description: description
});


// update the permissions on ACL records
Copy link
Member

Choose a reason for hiding this comment

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

There are still some minor style issues. Comments should be capitalised, and blank lines should not be used so liberally. Generally, we prefer to use comments to delineate sections of code.


const entityInfo = entityTypeToTableMap[entityType];
if (!entityInfo) {
throw new Error('Invalid entity type: ' + entityType);
Copy link
Member

Choose a reason for hiding this comment

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

I appreciate its a nuisance, but there are still a lot of string constants using single quotes instead of the preferred double quotes.

throw new Error('Invalid entity type: ' + entityType);
}

// if the entityName starts with "$:/", we'll assume its a system tiddler, then grant the user permission
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 this is intended to be special treatment for system bags and recipes, not tiddlers.

@webplusai
Copy link
Contributor Author

@Jermolene I've fixed all of your comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants