Automatically augment find() results of SequelizeJS with Auth0 user data

Automatically augment find() results of SequelizeJS with Auth0 user data

For my current side project (aside from TankZ! ;-)) I’m using Auth0 for identity management as well as SequelizeJS with a MS SQL server. That said, I don’t have the user data directly accessible via SQL, since Auth0 hosts the data for me. The side project will have a lot of user generated content, so I need more than just identity management. Users are able to see other users profiles and can interact with each other. Data of users will be visible on almost every page. I’ll only save Auth0’s user_id  in my database wherever I have user specific data. But somehow I need more than just the identifier or do you know who auth0|523b97af5ea82143540812b8 is? Me neither.

Therefore I need a method, which uses Auth0’s user id and and returns user data (like name or email). Doing this with Node.js is quite easy. First install request for easier handling of HTTP(S) requests and responses. Second a little code is needed:

function augment(auth0UserId) {
    return new Promise((resolve, reject) => {
        request({
            url: 'https://myproject.eu.auth0.com/api/v2/users/' + auth0UserId,
            headers: {
                'Authorization': 'Bearer ' + apiToken
            }
        }, (err, response, body) => {
            if (err) {
                return reject(err);
            }

            const jsonBody = JSON.parse(body);
            resolve(new User(jsonBody));
        });
    });
};

function User(auth0Claims) {
    this.id = auth0Claims.user_id;
    this.name = auth0Claims.name;
    this.nickname = auth0Claims.nickname;
    this.picture = auth0Claims.picture;
}

The method augment takes the Auth0 user id and requests the user data from Auth0’s v2 management API. To do that, I need a token, which is added via HTTP Authorization header. Please note, that this token is for backend and trusted parties usage only, since, depending on the scopes, it could do anything with your Auth0 data! When the response comes in, I create a new user with the data I want. The constructor function is only for internal IntelliSense usage. For sure: Caching would be really useful here and is implemented in the project using node-cache.

Nothing spectacular happened yet. I have a function I can call, if I need user data. As mentioned in the intro, the project will have a lot of user generated content, so I need to augment user data a lot. It would be a pitty, calling this method manually over and over again. So why not use a little SequelizeJS magic here? SequelizeJS has the concept of hooks which are triggered, before and after SequelizeJS executes calls to the database. In addition to model hooks, it supports universal hooks and, fortunately, it has a hook called afterFind. It will be executed after every select query. Since the result of a hook can be a promise, it can use the augment  method smoothly. Now that sounds promising! Let’s use it!

function init() {
    sequelize = new Sequelize(database, username, password, {
        host: host,
        dialect: 'mssql',
        define: {
            hooks: {
                afterFind: (result, options) => {
                    return augmentResults(result, options);
                }
            }
        }
    });
};

function augmentResults(results, options) {
    if (options.attributes.indexOf('user') === -1) {
        return Promise.resolve();
    }

    if (Array.isArray(results)) {
        var promises = [];
        results.forEach(result => {
            promises.push(augmentResult(result));
        });

        return Promise.all(promises);
    }

    return augmentResult(results);
}

function augmentResult(result) {
    return augment(result.user)
        .then(user => {
            result.setDataValue('user', user);
        });
 }

The init methods initializes SequelizeJS and creates the afterFind hook, which will call augmentResults. The first parameter results contains the results of the query. The second parameter options  contains the underlying model’s options and a list of the result columns. The method will now check, if a column is named user . By convention, if a column is named user , it will contain an Auth0 user id, which can be augmented. Later on, I’ll use the model options to decide whether to augment a column or not. But for start, the user  convention is good to go. The method continues with checking for an array. If the result is an array (which is only the case, when more than one result is found), it iterates over every item and calls augmentResult . Otherwise it calls augmentResult  directly. augmentResult  just calls our augment  function above and replaces the column user with the actual user data. Instead of getting this result of a select query:

{
    "someOtherColumn": "Hello World!",
    "user": "auth0|523b97af5ea82143540812b8"" 
}

I get this:

{
    "someOtherColumn": "Hello World!",
    "user": {
        "id": "auth0|523b97af5ea82143540812b8",
        "name": "Manuel",
        "nickname": "Manu",
        "picture": ""
    }
}

That’s really nice, now I have user data I can work with! And it works automatically, thanks to the hook.