-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
base: multi-wiki-support
Are you sure you want to change the base?
mws authentication #8596
Conversation
Run & review this pull request in StackBlitz Codeflow. |
Confirmed: webplusai has already signed the Contributor License Agreement (see contributing.md) |
Thank you @webplusai this is looking good. I will provide some detailed feedback shortly. |
Following along (very busy at work atm, company is reorging... again). This is exciting stuff! |
There was a problem hiding this 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.
There was a problem hiding this 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.
@Jermolene I've fixed the test issue. Also some issues that I had related to the user list and roles were also fixed. |
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 Let me know if that makes sense, ideally we should discuss the details before spending too much time on implementation. |
@Jermolene I've added the commands as you indicated. |
There was a problem hiding this 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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
@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. |
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
|
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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(){ |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 */ |
There was a problem hiding this comment.
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() { | ||
|
There was a problem hiding this comment.
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 () { |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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
@pmario you did it correct. There was an error and I fixed it. |
@Jermolene @pmario |
Hi @pmario |
Hi @webplusai I checked out the latest commits, ran
|
There was a problem hiding this 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("#"); |
There was a problem hiding this comment.
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": [ |
There was a problem hiding this comment.
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": [ |
There was a problem hiding this comment.
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=/`); |
There was a problem hiding this comment.
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"/> |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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)$).*$/; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
@Jermolene I've fixed all of your comments. |
No description provided.