issue 451 - MakeDrive auth should be pluggable, configurable, optional #463
Conversation
|
@humphd can you r? |
| } | ||
|
|
||
| req.params.username = username; | ||
| passport.authenticate(env.get('PASSPORT_PROVIDER'), function(err, user, info) { |
humphd
Nov 11, 2014
Member
I think we need to abstract this a bit more. In order to use custom authentication strategies, you have to load other modules, modify the Passport object (e.g., passport.use(...)), and register a provider type. I think we should create a dir server/authentication and have a few files in there:
server/authentication/index.js - this will be a module that will require/initialize passport, figure out which provider to use based on the env settings, then dynamically load (i.e., require) the appropriate module, which will do the provider specific stuff. If you look at how you use Facebook, for example:
http://passportjs.org/guide/facebook/
You need code that can do all that extra stuff for Facebook, which needs to live in a separate module.
server/authentication/passport-webmaker.js - this is the default for webmaker.org, and it will work sort of like the Passport Local provider, but inspect the cookie and make sure we have the username attached.
I'm happy to sketch this out in more detail over video if you want.
I think we need to abstract this a bit more. In order to use custom authentication strategies, you have to load other modules, modify the Passport object (e.g., passport.use(...)), and register a provider type. I think we should create a dir server/authentication and have a few files in there:
server/authentication/index.js- this will be a module that will require/initialize passport, figure out which provider to use based on theenvsettings, then dynamically load (i.e., require) the appropriate module, which will do the provider specific stuff. If you look at how you use Facebook, for example:
http://passportjs.org/guide/facebook/
You need code that can do all that extra stuff for Facebook, which needs to live in a separate module.
server/authentication/passport-webmaker.js- this is the default for webmaker.org, and it will work sort of like the Passport Local provider, but inspect the cookie and make sure we have the username attached.
I'm happy to sketch this out in more detail over video if you want.
| @@ -15,6 +15,30 @@ var middleware = require('./middleware'); | |||
| var routes = require('./routes'); | |||
| var log = require('./lib/logger.js'); | |||
| var nunjucks = require('nunjucks'); | |||
| var passport = require('passport'); | |||
humphd
Nov 11, 2014
Member
All of the changes below are an example of what I am talking about above. This should all live in a file like server/authentication/passport-basic.js or something. We should be able to do all of this in that file, and swap it easily with another provider without having to modify the server code in any way. Right now that's impossible.
All of the changes below are an example of what I am talking about above. This should all live in a file like server/authentication/passport-basic.js or something. We should be able to do all of this in that file, and swap it easily with another provider without having to modify the server code in any way. Right now that's impossible.
| }); | ||
| } | ||
| )); | ||
|
|
||
|
|
||
| var app = express(); | ||
| var webmakerAuth = new WebmakerAuth({ |
humphd
Nov 11, 2014
Member
This code is going to need to move to the server/authentication/passport-webmaker.js file, since we'll only do it for that provider type.
This code is going to need to move to the server/authentication/passport-webmaker.js file, since we'll only do it for that provider type.
| @@ -58,6 +82,7 @@ app.use(express.json()); | |||
| app.use(express.urlencoded()); | |||
| app.use(webmakerAuth.cookieParser()); | |||
| app.use(webmakerAuth.cookieSession()); | |||
humphd
Nov 11, 2014
Member
I wonder if all this cookie parsing should get added by individual passport provider code when needed?
I wonder if all this cookie parsing should get added by individual passport provider code when needed?
| @@ -30,21 +30,6 @@ var app = server.app; | |||
| var serverURL = 'http://127.0.0.1:' + env.get('PORT'), | |||
| socketURL = serverURL.replace( 'http', 'ws' ); | |||
|
|
|||
| // Mock Webmaker auth | |||
humphd
Nov 11, 2014
Member
I don't understand why you've changed all this? Why not have the tests configured to use a passport-webmaker.js provider, and then this fakes authentication for you. Nothing would need to change in this code, or the tests.
I don't understand why you've changed all this? Why not have the tests configured to use a passport-webmaker.js provider, and then this fakes authentication for you. Nothing would need to change in this code, or the tests.
| @@ -952,11 +892,10 @@ function ensureRemoteFilesystem(layout, jar, callback) { | |||
| * FileSystem options on the options object. | |||
| */ | |||
| function setupSyncClient(options, callback) { | |||
| authenticateAndConnect(options, function(err, result) { | |||
| getWebsocketToken(options, function(err, result) { | |||
humphd
Nov 11, 2014
Member
If you'd kept the same name, you wouldn't have needed to update all the tests. Why make work for yourself?
If you'd kept the same name, you wouldn't have needed to update all the tests. Why make work for yourself?
| if(err) throw err; | ||
|
|
||
| // /z/ should come back as export.zip with one dir and file | ||
| // in the archive. | ||
| request.get({ | ||
| url: util.serverURL + '/z/', | ||
| jar: result.jar, | ||
| qs: { | ||
| username: env.get("PASSPORT_USERS"), password: env.get("PASSPORT_PASSWORD") |
humphd
Nov 11, 2014
Member
I don't like how we're causing these tests to have to know things about the environment in order to work. That means we'll get failures with misconfigured env files.
I don't like how we're causing these tests to have to know things about the environment in order to work. That means we'll get failures with misconfigured env files.
|
r-, let's discuss when you're around. |
| @@ -115,23 +102,6 @@ function comparePaths(a, b) { | |||
| return 0; | |||
| } | |||
|
|
|||
| // Ensure that the file is downloadable via /p/ route | |||
alicoding
Nov 13, 2014
Author
Member
I don't see we use this anywhere at all, so I removed this.
I don't see we use this anywhere at all, so I removed this.
| export GITHUB_CLIENTID="clientId" | ||
| export GITHUB_CLIENTSECRET="clientSecret" | ||
| export GITHUB_CALLBACKURL="http://callbackurl" | ||
| export PASSPORT_PROVIDER="passport-github" |
humphd
Nov 13, 2014
Member
Remove, duplicate line.
Remove, duplicate line.
| export PASSPORT_PROVIDER="passport-github" | ||
| ``` | ||
|
|
||
| You can also check other provider listed in the [providers directory](./server/authentication/providers) for more information. |
humphd
Nov 13, 2014
Member
s/other provider/other providers/ (plural)
s/other provider/other providers/ (plural)
| # NOTE: You must include this provider in package.json or manually require it. | ||
| export PASSPORT_PROVIDER="json" | ||
|
|
||
| # PASSPORT Github authentication configurations |
humphd
Nov 13, 2014
Member
I'd remove the Github stuff, since it's not something we'll use. We can just document it in the code/readme.
I'd remove the Github stuff, since it's not something we'll use. We can just document it in the code/readme.
| @@ -0,0 +1,56 @@ | |||
| var env = require('../lib/environment'); | |||
humphd
Nov 13, 2014
Member
Throughout this file, remove the padding spaces around arguments. Do f(a,b) vs. f( a, b )
Throughout this file, remove the padding spaces around arguments. Do f(a,b) vs. f( a, b )
| passport.authenticate(passportProvider.name, function(err, user, info) { | ||
| // If something went wrong with authenticate method | ||
| if(err) { | ||
| return next( 500, err ); |
humphd
Nov 13, 2014
Member
Add a log.error() call here too.
Add a log.error() call here too.
| // Otherwise try to login, so we can have user's information added to the session | ||
| req.login(user, function(err) { | ||
| if(err) { | ||
| return next(err); |
humphd
Nov 13, 2014
Member
Add a log.error() call here too.
Add a log.error() call here too.
| if (!req.user) { | ||
| return next( generateError( 401, "Unauthorized" ) ); | ||
| } | ||
| // at this point put username to params |
humphd
Nov 13, 2014
Member
"Add username to params so it can be accessed elsewhere by MakeDrive"
"Add username to params so it can be accessed elsewhere by MakeDrive"
| return next( generateError( 401, "Unauthorized" ) ); | ||
| } | ||
| // at this point put username to params | ||
| req.params.username = req.user; |
humphd
Nov 13, 2014
Member
Maybe add a log.debug() here to indicate which user just authenticated.
Maybe add a log.debug() here to indicate which user just authenticated.
| var passport = require('passport-strategy'); | ||
| var env = require('../../lib/environment'); | ||
|
|
||
| /** |
humphd
Nov 13, 2014
Member
Move this comment block to the top of the file.
Move this comment block to the top of the file.
| } | ||
|
|
||
| /** | ||
| * Authenticate request based on the environment configurations. |
humphd
Nov 13, 2014
Member
s/configurations/configuration/ (singular)
s/configurations/configuration/ (singular)
| @@ -0,0 +1,86 @@ | |||
| // Module dependencies. | |||
humphd
Nov 13, 2014
Member
I don't think this should be called -json- since there's no JSON involved. You could call it passport-env to indicate it's reading info from the env.
I don't think this should be called -json- since there's no JSON involved. You could call it passport-env to indicate it's reading info from the env.
| */ | ||
| Strategy.prototype.authenticate = function(req, options) { | ||
| // reading username and password from environment configuration. | ||
| var username = env.get("PASSPORT_LOCAL_USER"); |
humphd
Nov 13, 2014
Member
You could read these at startup (i.e., outside this function, near the top of this file) since they won't change after startup.
You could read these at startup (i.e., outside this function, near the top of this file) since they won't change after startup.
| var self = this; | ||
|
|
||
| function verified(err, user, info) { | ||
| if (err) { return self.error(err); } |
humphd
Nov 13, 2014
Member
Prefer newlines after brackes. Also, add logging for the failure cases.
Prefer newlines after brackes. Also, add logging for the failure cases.
| this._verify(username, password, verified); | ||
| } | ||
| } catch (ex) { | ||
| return self.error(ex); |
humphd
Nov 13, 2014
Member
Log this.
Log this.
| }; | ||
|
|
||
|
|
||
| /** |
humphd
Nov 13, 2014
Member
This comment block isn't necessary.
This comment block isn't necessary.
| // Module dependency. | ||
| var passport = require('passport-strategy'); | ||
|
|
||
| /** |
humphd
Nov 13, 2014
Member
Move to the top of the file
Move to the top of the file
| * @api protected | ||
| */ | ||
| Strategy.prototype.authenticate = function(req, options) { | ||
| // Webmaker Auth should already added user object to the cookie session. |
humphd
Nov 13, 2014
Member
"Webmaker Auth should have already added a user object to the cookie session."
"Webmaker Auth should have already added a user object to the cookie session."
| var password = ""; | ||
| // if no username found we can assume that the user is not logged in to Webmaker | ||
| if (!username) { | ||
| return this.fail({ message: 'No user found in Webmaker session' }, 400); |
humphd
Nov 13, 2014
Member
log this?
log this?
alicoding
Nov 13, 2014
Author
Member
This will be return to the authenticate method in the middleware which I'm doing that there.
This will be return to the authenticate method in the middleware which I'm doing that there.
| if(err) { | ||
| log.error(err, 'Unable to authenticate with %s', passportProvider.name); | ||
| return next(500, err); | ||
| // If missing credentials or unknown user |
humphd
Nov 13, 2014
Member
Move this to line 41 (i.e., inside the {...)
Move this to line 41 (i.e., inside the {...)
| * `Strategy` constructor. | ||
| * | ||
| * The local authentication strategy authenticates requests based on the | ||
| * credentials in the environment configuration option. |
humphd
Nov 13, 2014
Member
Get rid of "option" at the end of this.
Get rid of "option" at the end of this.
| // reading username and password from environment configuration. | ||
| var username = env.get("PASSPORT_USER"); | ||
| var password = env.get("PASSPORT_PASSWORD"); | ||
| function Strategy(options, verify) { |
humphd
Nov 13, 2014
Member
Newline above this.
Newline above this.
| return self.error(err); | ||
| } | ||
| if (!user) { | ||
| log.error('failed: ', info); |
humphd
Nov 13, 2014
Member
This looks wrong. What does not having a user here mean? That they failed authentication? If so, you want something like, log.debug('Authentication failed for user %s', ...)
This looks wrong. What does not having a user here mean? That they failed authentication? If so, you want something like, log.debug('Authentication failed for user %s', ...)
| /** | ||
| * `Strategy` constructor. | ||
| * | ||
| * The webmaker authentication strategy authenticates requests based on the Webmaker session. |
humphd
Nov 13, 2014
Member
s/webmaker authentication/Webmaker Authentication/. Also add a link to the Webmaker Auth client repo, which has more docs.
s/webmaker authentication/Webmaker Authentication/. Also add a link to the Webmaker Auth client repo, which has more docs.
|
|
||
| function verified(err, user, info) { | ||
| if (err) { | ||
| return self.error(err); |
humphd
Nov 13, 2014
Member
Same thing here, logging? You did it above for this and the next case.
Same thing here, logging? You did it above for this and the next case.
| @@ -0,0 +1,21 @@ | |||
| /** | |||
| * passport-env: | |||
| * It allows single user to be authenticated for MakeDrive testing. | |||
humphd
Nov 13, 2014
Member
"Allow a single user to be authenticated for MakeDrive testing, using credentials in the env file."
"Allow a single user to be authenticated for MakeDrive testing, using credentials in the env file."
| @@ -0,0 +1,24 @@ | |||
| /** | |||
| * passport-local: | |||
humphd
Nov 13, 2014
Member
passport-query-string ?
passport-query-string ?
| @@ -142,87 +112,30 @@ function toSyncMessage(string) { | |||
| return string; | |||
| } | |||
|
|
|||
| function jar() { | |||
humphd
Nov 13, 2014
Member
Does this get used anywhere now?
Does this get used anywhere now?
alicoding
Nov 13, 2014
Author
Member
I think it's being used in some of the test.
I think it's being used in some of the test.
|
|
||
| options.username = options.username || uniqueUsername(); | ||
| env.set("PASSPORT_USERNAME", options.username); | ||
| env.set("PASSPORT_PASSWORD", 'secret'); |
humphd
Nov 13, 2014
Member
The way you're doing this is wrong--you're forced to change the environment every time you authenticate. That speaks to the fact that we need a list of users. Basically, this is what the existing code was doing, by adding a route to register a new user so they would exist when this part of the code was called. We need a better solution here. In an ideal world, we'd do something for our tests where the value returned by uniqueUsername() would follow a pattern, and we'd allow any username with that pattern and the right password (i.e., the one set in PASSPORT_PASSWORD).
The way you're doing this is wrong--you're forced to change the environment every time you authenticate. That speaks to the fact that we need a list of users. Basically, this is what the existing code was doing, by adding a route to register a new user so they would exist when this part of the code was called. We need a better solution here. In an ideal world, we'd do something for our tests where the value returned by uniqueUsername() would follow a pattern, and we'd allow any username with that pattern and the right password (i.e., the one set in PASSPORT_PASSWORD).
|
|
||
| # PASSPORT query-string and env setup | ||
| export PASSPORT_USERNAME="any_user_name" | ||
| export PASSPORT_PASSWORD="secret" |
humphd
Nov 13, 2014
Member
Remove all this, since zeroconfig shouldn't need them.
Remove all this, since zeroconfig shouldn't need them.
| -------- | -------------- | ||
| `passport-github` | Passport strategy for authenticating with GitHub using the OAuth 2.0 API. | ||
| `passport-env` | Passport strategy for authenticating using environment configuration. | ||
| `passport-zeroconfig` | Passport strategy for always authenticated root user. |
humphd
Nov 13, 2014
Member
"passport-zeroconfig (default)"
"passport-zeroconfig (default)"
| /** | ||
| * `Strategy` constructor. | ||
| * | ||
| * The Webmaker Authentication strategy authenticates requests based on the Webmaker session. |
humphd
Nov 13, 2014
Member
Add link to Webmaker Auth client repo for README.
Add link to Webmaker Auth client repo for README.
| /** | ||
| * `Strategy` constructor. | ||
| * | ||
| * The root strategy authenticates requests based on single username (root). |
humphd
Nov 13, 2014
Member
"The zeroconfig strategy always automatically authenticates requests for a single 'root' user. Only use this for development."
"The zeroconfig strategy always automatically authenticates requests for a single 'root' user. Only use this for development."
| @@ -0,0 +1,24 @@ | |||
| /** | |||
humphd
Nov 13, 2014
Member
Change the filename to match passport-query-string.
Change the filename to match passport-query-string.
| @@ -0,0 +1,42 @@ | |||
| /** | |||
| * passport-webmaker: | |||
| * This provider is meant to be used for either local development or production, and requires a Webmaker Login server to be running. | |||
humphd
Nov 13, 2014
Member
Same thing here, add link to Webmaker Auth client repo.
Same thing here, add link to Webmaker Auth client repo.
| */ | ||
|
|
||
| var passport = require('passport'); | ||
| var ZEROCONFIGStrategy = require('../passport-strategies/passport-env-strategy').Strategy; |
humphd
Nov 13, 2014
Member
passport-env-strategy? Shouldn't that be zeroconfig?
passport-env-strategy? Shouldn't that be zeroconfig?
| var Path = require('path'); | ||
| var http = require('http'); | ||
| var auth = require('./authentication'); |
humphd
Nov 13, 2014
Member
./authentication.js (always use .js to signal it's a file vs. module name).
./authentication.js (always use .js to signal it's a file vs. module name).
| log.error(err, 'Unable to authenticate with %s', passportProvider.name); | ||
| return next(500, err); | ||
| } else if (info) { | ||
| // If missing credentials or unknown user |
humphd
Nov 13, 2014
Member
Indent this 2 spaces.
Indent this 2 spaces.
| * Allow a single user to be authenticated for MakeDrive testing, using credentials in the env file. | ||
| * to use this provider make sure to set these environment configurations in your .env file | ||
| * export PASSPORT_USERNAME="someusername" | ||
| * export PASSPORT_PASSWORD="someusername" |
humphd
Nov 13, 2014
Member
s/someusername/somepassword/
s/someusername/somepassword/
| loginURL: env.get('LOGIN'), | ||
| secretKey: env.get('SESSION_SECRET'), | ||
| forceSSL: env.get('FORCE_SSL'), | ||
| domain: env.get('COOKIE_DOMAIN') |
humphd
Nov 13, 2014
Member
Should we log.error or throw if these env vars are missing? It might be a good idea, so you can figure out why things are broken.
Should we log.error or throw if these env vars are missing? It might be a good idea, so you can figure out why things are broken.
humphd
Nov 13, 2014
Member
Yeah, let's do it, here and in all the other's too.
Yeah, let's do it, here and in all the other's too.
| // reading username and password from environment configuration. | ||
| var username = env.get("PASSPORT_USERNAME"); | ||
| var password = env.get("PASSPORT_PASSWORD"); | ||
|
|
humphd
Nov 13, 2014
Member
Let's check for username/password here, and log.error if they are missing so it's easier to debug.
Let's check for username/password here, and log.error if they are missing so it's easier to debug.
| * to use this provider make sure to set these environment configurations in your .env file | ||
| * export GITHUB_CLIENTID="clientId" | ||
| * export GITHUB_CLIENTSECRET="clientSecret" | ||
| * export GITHUB_CALLBACKURL="http://callbackurl" |
humphd
Nov 13, 2014
Member
Let's add a log.error() if any of these variables are missing from env.
Let's add a log.error() if any of these variables are missing from env.
|
|
||
| function findByUsername(username, fn) { | ||
| if (env.get("PASSPORT_USERNAME") === username) { | ||
| return fn(null, env.get("PASSPORT_USERNAME")); |
humphd
Nov 13, 2014
Member
Instead of reading this from env all the time (here and below), just do it at startup and cache in a variable.
Instead of reading this from env all the time (here and below), just do it at startup and cache in a variable.
|
Fix the logging for missing env stuff, and the other things I just found. r=me with travis. Awesome work Ali! |
1d01574
into
mozilla:master
No description provided.