Adding a codeplex repo to strider

After my last post, I can now get an authenticated user's repositores. However, I am experiencing a couple of issues.

First, the codeplex oauth tokens expire after an hour. Because of this, I am seeing a number of authentication errors. In order to solve this, I need to get the refresh token from codeplex. Thankfully, it seems this is a feature built into passport. All I had to do was update the validation methods to take one more parameter:

function validateAuth(req, accessToken, refreshToken, parms, profile, done) {  
  if (!req.user) {
    console.warn('Codeplex OAuth but no logged-in user')
    req.flash('account', "Cannot link a codeplex account if you aren't logged in")
    return done()
  } 
  var account = req.user.account('codeplex', profile.UserName)
  if (account) {
    console.warn("Trying to attach a codeplex account that's already attached...")
    req.flash('account', 'That codeplex account is already linked. <a href="https://codeplex.com/site/signout/" target="_blank">Sign out of codeplex</a> before you click "Add Account".')
    return done(null, req.user)
  }

  req.user.accounts.push(makeAccount(accessToken, refreshToken, profile))
  req.user.save(function (err) {
    done(err, req.user);
  })
}

function makeAccount(accessToken, refreshToken, profile) {  
  var username = profile.UserName;
  return {
    provider: 'codeplex',
    id: username,
    display_url: 'https://www.codeplex.com/site/users/view/' + username,
    title: username,
    config: {
      accessToken: accessToken,
      refreshToken: refreshToken,
      name: username,
      avatar: profile.Avatar
    }
  }
}

With the refresh token on hand, I was able to use the refresh-token package to get a valid oauth token. The way I see it, there are 3 options for handling token refreshes:

  • Always get a fresh token before making an API call
  • Store the expiration of the token, and only refresh when needed
  • Assume a valid token; on error, refresh and retry

I am unsure if there is a best practice for this, but I decided on option 3. The first option seemed like too much overhead for my taste. The second option is appealing to me, but I was too lazy to go look up how to translate the token's expiration timeout into an absolute expiration date. Horrible, I know. But, there you have it. I went with #3 because lazy.

Now that I might have to refresh the token, my API method will need the clientId and clientSecret. I modified getCodeplex and getRepos to take the appConfig, as both are properties there. The most important bit, getCodeplex:

function getCodeplex(account, appConfig, resourcePath, callback) {

    var options = {
      hostname: 'www.codeplex.com',
      path: resourcePath,
      method: 'GET',
      headers: {'x-ms-version': '2012-09-01' }
    };

    if (account && account.accessToken) {
        options.headers.Authorization = 'Bearer ' + account.accessToken;
    }

    var req = https.request(options, function(res) {
        var json = '';
        res.on('data', function(d) {
            json += d;
        });
        res.on('error', function(err) {
            callback(err, null);
        });
        res.on('end', function() {
            var fin = JSON.parse(json);

            // this is an error message
            if (fin.Message) {
                if (appConfig.clientId && appConfig.clientSecret && fin.Message === 'Authentication required.') {
                    var tokenProvider = new TokenProvider('https://www.codeplex.com/oauth/token', {
                    refresh_token: account.refreshToken, 
                    client_id:     appConfig.clientId, 
                    client_secret: appConfig.clientSecret
                    });

                    tokenProvider.getToken(function (err, token) {
                        if (err) { callback(err, null); }
                        else {
                            account.accessToken = token;
                            getCodeplex(account, resourcePath, callback);
                        }
                    });
                }
                else {
                    callback(fin.Message, null); 
                }
            }
            else { callback(null, fin); }
        });
    });

    req.on('error', function(err) {
        callback(err, null);
    });
    req.end();
}

The second bug: when I try to add a repo to strider, I get an error message in the UI:

Error creating project for repo aheidebrecht/codeonlystoredprocedures:  
{
  "results": [], 
  "status": "error", 
  "errors": [ { 
    "code": 400, 
    "reason": "provider.repo_id is required" 
  } ]
}

It seems that I left out a required id property in the parseRepo method:

function parseRepo(account, repo) {  
  return {
    id: account.name + '/' + repo.Name,
    name: account.name + '/' + repo.Name,
    display_name: repo.Title,
    display_url: repo.Url,
    group: account.name,
    'private': !repo.IsPublished,
    config: {
      sourceType: repo.SourceControl.ServerType,
      sourceUrl: repo.SourceControl.Url,
      role: repo.Role
    }
  }
}

After fixing these issues, I was finally able to start the process of setting up a repo. I decided to hold off on implementing the webhook part mostly because, as of this blog post, there is no generic webhook support (it supports these and these commercial websites... oh Microsoft).

Instead, I just let strider think I have done something special. Since I don't have to setup or teardown a webhook, those two required api methods are quite simple:

setupRepo: function (account, config, project, done) {  
  if (!account.accessToken) return done(new Error('Codeplex account not configured'));
  done(null, config);
},

teardownRepo: function (account, config, project, done) {  
  if (!account.accessToken) return done(new Error('Codeplex account not configured'));
  done(null, config);
}

With those methods in place, we can add and remove a repo to strider! Woot!

You can grab this version at github.

-AH

Making a strider plugin

When I last left off, my codeplex plugin for strider was able to pull in repos from codeplex by passing in a valid codeplex username. While it was a great proof of concept, it isn't the way that strider wants (and in fact needs to) to interract with source control... Read More

My first module

In my previous post, I started a project to create a codeplex plugin for strider. Today, I will take another baby step. Because baby steps are the easiest. Unless you're a baby. A node baby. Ahem. In order to make it useful, I did a little bit of refactoring. I... Read More

Hello codeplex!

Now that I've done the (admittedly easy) work of getting ghost setup, I can dive into learning node. This will essentially be hello, world!, but that isn't particularly interesting to anybody (and it is trivial to find yourself), so I am going to challenge myself a little bit. I've recently... Read More

Pointing my old blog to the new one

Even though my old blog had literally 5 posts, because I am either: A good internet citizen A narcissist Both I have decided to do the good thing and do 302 redirects from the old posts to their new locations. The first thing I needed to do was point my... Read More

Moving to ghost

As is obvious to the most casual of observers, I've never been the most prolific blogger. But, I have recently realized that I need to learn javascript (I can't stay a WPF developer my whole career if MS decides to stop updating it...). At least if I want to have... Read More