Skip to content
This repository has been archived by the owner on Jan 8, 2025. It is now read-only.

Commit

Permalink
Adding a solid attempt at the rule
Browse files Browse the repository at this point in the history
Still needs further testing, but it makes it to the extension at this point
  • Loading branch information
machuga committed Aug 18, 2017
1 parent dbeb363 commit 72a5aed
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
node_modules
.DS_Store
.nyc_output
config/server.json
server/config.json
181 changes: 181 additions & 0 deletions rules/link.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/* global configuration, auth0, jwt */
// This is the rule provided with the account linking PoC. It needs some revamping.
/**
* Do NOT execute this code from anywhere within this extension.
* It is the rule that gets pushed up to the Auth0 Server.
* I want this code to be analyzable and readable, so putting
* it into a string early is a show-stopper. Since JavaScript
* is more than happy to let me grab the string representation
* of the function, we'll export it as the string to push up to
* the server.
*/

module.export = function (user, context, callback) {
var request = require('[email protected]');
var Promise = require('[email protected]');

var CONTINUE_PROTOCOL = 'redirect-callback';

var config = {
endpoints: {
linking: 'http://localhost:3001/api/link',
userApi: auth0.baseUrl + '/users'
},
token: {
clientId: configuration.AUTH0_CLIENT_ID,
clientSecret: configuration.AUTH0_CLIENT_SECRET,
issuer: configuration.ISSUER || "https://machuga-auth0.auth0.com/"
}
};

// If any of these above conditions are met, fire off the callback, we don't need to proceed
if (shouldNotProceed(user, context)) {
console.log("Will not proceed");
return callback(null, user, context);
}

// Need to make sure they want to link for this to make sense
// This function should work because it excludes the user_id
// which should still be the old user at this point
var strategy = shouldProceedToContinue() ? continueAuth() : linkVerifiedUsers();

strategy.then(callbackWithSuccess).catch(callbackWithFailure);

/**
*
* Functions
*
*/

function continueAuth() {
console.log('moving to continue');
return searchUsersWithSameEmail().then(function(users) {
return users[0];
}).then(function(user) {
if (user) {
context.primaryUser = user.user_id;
}
});
}

function linkVerifiedUsers() {
console.log('linking verified users');
return searchUsersWithSameEmail()
.then(onlyVerifiedUsers)
.then(function(users) {
// Okay we now have verified identities.
// If not empty, we need attempt to auth them to ensure they're who they say they are

// Will we only ever have one? Skip iteration if so
users.forEach(function(targetUser) {
var token = createToken(config.token, targetUser);

// Overwriting every time...why?
context.redirect = { url: buildRedirectUrl(token) };
});
});
}


function callbackWithSuccess(_) {
callback(null, user, context);

return _;
}

function callbackWithFailure(err) {
callback(err, user, context);

throw err;
}

function onlyVerifiedUsers(users) {
return users.filter(function(u) {
return u.email_verified;
});
}

function createToken(tokenInfo, targetUser) {
var options = {
expiresInMinutes: 5,
audience: tokenInfo.clientId,
issuer: tokenInfo.issuer
};

var userSub = {
sub: user.user_id,
email: user.email,
targetUser: targetUser,
base: auth0.baseUrl
};

return jwt.sign(userSub, tokenInfo.clientSecret, options);
}

function searchUsersWithSameEmail() {
return new Promise(function(resolve, reject) {
request({
url: config.endpoints.userApi,
headers: { Authorization: 'Bearer ' + auth0.accessToken },
qs: {
search_engine: 'v2',
q: 'email:"' + user.email + '" -user_id:"' + user.user_id + '"'
}
}, function(err, response, body) {
if (err) {
reject(err);
} else if (response.statusCode !== 200) {
reject(new Error(body));
} else {
resolve(response);
}
});
}).then(function(response) {
return JSON.parse(response.body);
});
}

// Consider moving this logic out of the rule and into the extension
function buildRedirectUrl(token) {
return config.endpoints.linking + '?' +
[
'child_token=' + token,
'client=' + context.request.query.client_id,
'redirect_uri=' + context.request.query.redirect_uri,
'scope=' + context.request.query.scope,
'response_type=' + context.request.query.response_type,
'auth0Client=' + context.request.query.auth0Client
].join('&');
}

function shouldNotProceed(user, context) {
// Check if email is verified, we shouldn't
// merge accounts if this is not the case.
var emailNotVerified = function() { return !user.email_verified; };

// Check if we're inside a redirect
// in order to avoid a redirect loop
var insideRedirect = function() {
return context.request.query.redirect_uri &&
context.request.query.redirect_uri.indexOf(config.endpoints.linking) !== -1;
};

// Check if this is not the first login of the user
// since merging already active accounts can be a
// destructive action
var notFirstLogin = function() {
return context.stats.loginsCount > 0;
};

return /*emailNotVerified() || */ insideRedirect(); //|| notFirstLogin();
}

function shouldProceedToContinue() {
// Check if we're coming back from a redirect
// in order to avoid a redirect loop. User will
// be sent to /continue at this point. We need
// to assign them to their primary user if so.
// TODO: This part needs to do work, may break out from this area.
return context.protocol === CONTINUE_PROTOCOL;
}
}.toString();

0 comments on commit 72a5aed

Please sign in to comment.