Skip to content
This repository has been archived by the owner. It is now read-only.

issue 451 - MakeDrive auth should be pluggable, configurable, optional #463

Merged
merged 1 commit into from Nov 13, 2014

Conversation

@alicoding
Copy link
Member

@alicoding alicoding commented Nov 10, 2014

No description provided.

@alicoding alicoding force-pushed the alicoding:passport branch from fac9c3c to 05ba065 Nov 11, 2014
@alicoding alicoding changed the title WIP issue 451 - MakeDrive auth should be pluggable, configurable, optional Nov 11, 2014
@alicoding
Copy link
Member Author

@alicoding alicoding commented Nov 11, 2014

@humphd can you r?

}

req.params.username = username;
passport.authenticate(env.get('PASSPORT_PROVIDER'), function(err, user, info) {

This comment has been minimized.

@humphd

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.

@@ -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');

This comment has been minimized.

@humphd

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.

});
}
));


var app = express();
var webmakerAuth = new WebmakerAuth({

This comment has been minimized.

@humphd

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.

@@ -58,6 +82,7 @@ app.use(express.json());
app.use(express.urlencoded());
app.use(webmakerAuth.cookieParser());
app.use(webmakerAuth.cookieSession());

This comment has been minimized.

@humphd

humphd Nov 11, 2014
Member

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

This comment has been minimized.

@humphd

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.

@@ -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) {

This comment has been minimized.

@humphd

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(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")

This comment has been minimized.

@humphd

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.

@humphd
Copy link
Member

@humphd humphd commented Nov 11, 2014

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

This comment has been minimized.

@alicoding

alicoding Nov 13, 2014
Author Member

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"

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

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.

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

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

This comment has been minimized.

@humphd

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.

@@ -0,0 +1,56 @@
var env = require('../lib/environment');

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

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 );

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

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);

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

Add a log.error() call here too.

if (!req.user) {
return next( generateError( 401, "Unauthorized" ) );
}
// at this point put username to params

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

"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;

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

Maybe add a log.debug() here to indicate which user just authenticated.

var passport = require('passport-strategy');
var env = require('../../lib/environment');

/**

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

Move this comment block to the top of the file.

}

/**
* Authenticate request based on the environment configurations.

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

s/configurations/configuration/ (singular)

@@ -0,0 +1,86 @@
// Module dependencies.

This comment has been minimized.

@humphd

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.

*/
Strategy.prototype.authenticate = function(req, options) {
// reading username and password from environment configuration.
var username = env.get("PASSPORT_LOCAL_USER");

This comment has been minimized.

@humphd

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.

var self = this;

function verified(err, user, info) {
if (err) { return self.error(err); }

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

Prefer newlines after brackes. Also, add logging for the failure cases.

this._verify(username, password, verified);
}
} catch (ex) {
return self.error(ex);

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

Log this.

};


/**

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

This comment block isn't necessary.

// Module dependency.
var passport = require('passport-strategy');

/**

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

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.

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

"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);

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

log this?

This comment has been minimized.

@alicoding

alicoding Nov 13, 2014
Author Member

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

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

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.

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

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) {

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

Newline above this.

return self.error(err);
}
if (!user) {
log.error('failed: ', info);

This comment has been minimized.

@humphd

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', ...)

/**
* `Strategy` constructor.
*
* The webmaker authentication strategy authenticates requests based on the Webmaker session.

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

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);

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

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.

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

"Allow a single user to be authenticated for MakeDrive testing, using credentials in the env file."

@@ -0,0 +1,24 @@
/**
* passport-local:

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

passport-query-string ?

@alicoding alicoding force-pushed the alicoding:passport branch from 63ef8b4 to 71ff397 Nov 13, 2014
@@ -142,87 +112,30 @@ function toSyncMessage(string) {
return string;
}

function jar() {

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

Does this get used anywhere now?

This comment has been minimized.

@alicoding

alicoding Nov 13, 2014
Author Member

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');

This comment has been minimized.

@humphd

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).

@alicoding alicoding force-pushed the alicoding:passport branch from 7049316 to 4d0d3dd Nov 13, 2014

# PASSPORT query-string and env setup
export PASSPORT_USERNAME="any_user_name"
export PASSPORT_PASSWORD="secret"

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

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.

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

"passport-zeroconfig (default)"

/**
* `Strategy` constructor.
*
* The Webmaker Authentication strategy authenticates requests based on the Webmaker session.

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

Add link to Webmaker Auth client repo for README.

/**
* `Strategy` constructor.
*
* The root strategy authenticates requests based on single username (root).

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

"The zeroconfig strategy always automatically authenticates requests for a single 'root' user. Only use this for development."

@@ -0,0 +1,24 @@
/**

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

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.

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

Same thing here, add link to Webmaker Auth client repo.

*/

var passport = require('passport');
var ZEROCONFIGStrategy = require('../passport-strategies/passport-env-strategy').Strategy;

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

passport-env-strategy? Shouldn't that be zeroconfig?

var Path = require('path');
var http = require('http');
var auth = require('./authentication');

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

./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

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

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"

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

s/someusername/somepassword/

loginURL: env.get('LOGIN'),
secretKey: env.get('SESSION_SECRET'),
forceSSL: env.get('FORCE_SSL'),
domain: env.get('COOKIE_DOMAIN')

This comment has been minimized.

@humphd

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.

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

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");

This comment has been minimized.

@humphd

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.

* 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"

This comment has been minimized.

@humphd

humphd Nov 13, 2014
Member

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"));

This comment has been minimized.

@humphd

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.

@humphd
Copy link
Member

@humphd humphd commented Nov 13, 2014

Fix the logging for missing env stuff, and the other things I just found. r=me with travis. Awesome work Ali!

@alicoding alicoding force-pushed the alicoding:passport branch from e2d639f to 1d01574 Nov 13, 2014
@alicoding alicoding merged commit 1d01574 into mozilla:master Nov 13, 2014
1 check was pending
1 check was pending
@alicoding
continuous-integration/travis-ci The Travis CI build is in progress
Details
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

2 participants