BORAX By Example - Part II

Friday, January 27th 2012

As you may recall from the last BORAX post, I had set up the application to transition to the start state which, for the purposes of our application, means someone needs to login. The application loads its first state as indicated by a linked relation in the header of the launchpad. Now, I sure think we would find it cool if we could handle the authentication.

Following along

This post will also cover some server-side development. If you want to follow along, install node.js and play with it. If you and others end up liking BORAX and its ability to make REST easy, then we can build bindings for non-node.js platforms.

If you want to start from the last post, clone the BORAX.js repository from GitHub and switch to the “post-1” tag.

Some server-side lovin’

BORIS

If you look at the current “example/server.js” in the repository, you’ll see a mess. Just a mess. Blech. Bad Curtis! But, every mess means a refactoring (or rewriting) opportunity!

Since this portion of the application deals with authentication, I think it would rock if I include the protection of resources in a server-side library that I will start writing in this post. The “lib/borax-in-server.js” will contain, obviously, the server-side components for BORAX on node.js.

It seems that the normal method for hooking in middleware to things like express.js and flatiron.js takes the form of a function with three parameters: the http.ServerRequest, the http.ClientResponse, and some next function to call if the registered handler did not fully handle the request. I can do the same here, I think.

Following the KISS with the perspective that “simple” refers to the use of the API, I think I can get away with the following.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var http = require('http')
, boris = require('borax-in-server')
, credential_function = require('my-auth-lib').authenticate
;

var auth = boris.auth(credential_function, content_function);
auth.addTree('/');

http.createServer(function(req, res) {
auth.protect(req, res, function() {
// Normal request processing.
});
}).listen(8181);

/* Or, if you use flatiron or the like, something like:
app.http.before.push(auth.protect);
*/

This would protect the path represented by ‘/‘ (the root) and all paths that would fall beneath it. That matches the requirements for the example application pretty nicely.

I’ll list the stories that describe boris. You can check out the tests and what not in the project.

For the borax-server library:

  • It has an auth function that requires:
    • a function that checks the credentials sent to the server; and,
    • a function that provides the content of the template to the response object
  • The auth method returns a non-null object called the protector.
  • The protector has an addTree function that requires a string.
  • The protector has a protect function that requires the following three arguments:
    • an object that represents the client’s request with “url” and “headers” attributes;
    • an object that represents the server’s response; and,
    • a function that the protector can call if the resource needs no protection
  • When the protector receives a request that matches a string passed to addTree, then it:
    • sets the responses status code to 401;
    • writes a header with the key ‘WWW-Authenticate’ and a value ‘Borax-Basic’; and,
    • writes the return value of the content_function to the body of the response.
  • When the protector receives a request that starts with the string passed to addTree, then it performs the same steps as in the last section
  • When the protector receives a request that does not start with nor equals a value passed to addTree, then it calls the next function.
  • When the protector receives a request that contains an ‘Authorization’ header, then it decomposes the data in the header and passes it to the credentials function with the scheme name.
  • If the credentials function returns true, then the protector calls the next callback; otherwise,
  • If the credentials function returns false, then the protector returns the challenge, again.

I changed the “server.js” file to use this new BORAX-provided asset protection. Now, things seem a little more reasonable. If I used flatiron or express, then I would have even less SLOCs in the server file. The following code abridges the actual server file and shows only the BORIS-related code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var creds = function(scheme, params) {
return scheme == 'Borax-Basic' &&
params['name'] == 'curtis' &&
params['password'] == 'password';
};

var challenge = function(res) {
var _401 = path.join('example', path.join('assets', '401.html'));
fs.readFile(_401, function(err, data) {
if(err) {
res.writeHead(404, {'Content-Type': 'text/html'});
res.end('File Not Found');
return;
}
res.writeHead(401, {'Content-Type': 'text/html'});
res.end(data);
});
}

var protector = boris.auth(creds, challenge);
.addTree('/dashboard.html');

var server = http.createServer(function(req, res) {
var url = urlparse(req.url);
var asset_path = path.join('./example', url.pathname);
fs.stat(asset_path, function(err, stat) {

/* Use the protector to guard the configured path from above. */
protector.protect(req, res, function() {
if(stat && stat.isDirectory()) {
asset_path = path.join(asset_path, 'index.html');
}
var ext = path.extname(asset_path);
fs.readFile(asset_path, function(err, data) {
if(err) {
res.writeHead(404, {'Content-Type': 'text/html'});
res.end('File Not Found');
return;
}
res.writeHead(200, {'Content-Type': mediaTypeExtensions[ext]});
res.end(data);
});
});
});
});