Permalink
Browse files

Implement Hawk authorization and status api

  • Loading branch information...
1 parent 47a4c69 commit 03d0100447126bfd851bffea83221a12a45ee86d @zaach zaach committed Dec 6, 2012
Showing with 245 additions and 70 deletions.
  1. +23 −7 bin/api
  2. +60 −15 client/client.js
  3. +47 −35 client/crypto.js
  4. +4 −1 etc/config.js
  5. +4 −3 lib/api/v1/account.js
  6. +3 −1 lib/api/v1/context.js
  7. +23 −0 lib/api/v1/status.js
  8. +9 −1 lib/db/json.js
  9. +2 −1 lib/views.js
  10. +3 −2 package.json
  11. +2 −2 test/api.v1.account.js
  12. +1 −1 test/api.v1.context.js
  13. +62 −0 test/api.v1.status.js
  14. +2 −1 test/crypto.sign.js
View
30 bin/api
@@ -11,22 +11,38 @@ function fatal(msg) {
process.exit(1);
}
-var bindTo = config.process.api;
+function credentialsFunc(id, callback) {
+ db.getAuthKey(id, function (err, key){
+ if (err) return callback(err);
+
+ var credentials = {
+ id: id,
+ key: key,
+ algorithm: 'hmac-sha-256',
+ user: id
+ };
+
+ return callback(null, credentials);
+ });
+}
+
+var apiOptions = config.hapi;
+apiOptions.auth.getCredentialsFunc = credentialsFunc;
+
// Create a server with a host and port
-var server = new Hapi.Server(bindTo.host, bindTo.port, config.hapi);
+var bindTo = config.process.api;
+var server = new Hapi.Server(bindTo.host, bindTo.port, apiOptions);
console.log("api starting up");
-server.on('bound', function(host, port) {
- console.log("running on http://" + host + ":" + port);
-});
-
// now load up api handlers
apiLoader(server, function(err) {
if (err) fatal(err);
db.connect(config.db, function() {
// Start the server
- server.start();
+ server.start(function() {
+ console.log("running on http://" + server.settings.host + ":" + server.settings.port);
+ });
});
});
View
@@ -1,12 +1,19 @@
if (typeof GombotCrypto === 'undefined') {
var GombotCrypto = require('./crypto.js');
}
+if (typeof URLParse === 'undefined') {
+ var URLParse = require('./urlparse.js');
+}
+
+(function() {
-;(function() {
+GombotClient = function(path) {
+ var url = URLParse(path);
-GombotClient = function(host, port) {
- this.host = host;
- this.port = port;
+ this.scheme = url.scheme;
+ this.host = url.host;
+ this.port = url.port;
+ this.path = url.path || '';
};
var xhr = typeof jQuery !== 'undefined' ? jQuery.ajax : require('xhrequest');
@@ -21,8 +28,11 @@ function request(args, cb) {
var req = {
url: url,
method: method,
+ type: method,
data: args.data,
- headers: {},
+ //dataType: 'json',
+ //accepts: {json: 'application/json'},
+ headers: args.headers || {},
success: function(data, res, status) {
try {
var body = JSON.parse(data);
@@ -32,45 +42,80 @@ function request(args, cb) {
body.session_context = {};
cb(null, body);
},
+ processData: false,
error: function(data, res, status) {
cb('Error: ' + data + '\nStatus: ' + status);
}
};
if (method == 'PUT' || method == 'POST') {
- req.headers['Content-Type'] = 'application/json';
+ req.contentType = req.headers['Content-Type'] = 'application/json';
}
xhr(url, req);
}
+function mergeArgs(args, def) {
+ args.scheme = def.scheme;
+ args.host = args.host || def.host;
+ args.port = args.port || def.port;
+ return args;
+}
+
GombotClient.prototype = {
// get "session context" from the server
context: function(args, cb) {
if (typeof args === 'function') {
cb = args;
args = {};
}
- args.host = args.host || this.host;
- args.port = args.port || this.port;
+ args = mergeArgs(args, this);
args.method = 'get';
- args.path = '/v1/context';
+ args.path = this.path + '/v1/context';
request(args, cb);
},
account: function(args, cb) {
- args.host = args.host || this.host;
- args.port = args.port || this.port;
- args.method = 'put';
- args.path = '/v1/account';
+ var self = this;
+ args = mergeArgs(args, this);
+ args.method = 'post';
+ args.path = this.path + '/v1/account';
// compute the authKey
- var keys = GombotCrypto.derive({
+ var headers = GombotCrypto.derive({
email: args.email,
- password: args.password
+ password: args.pass
}, function(err, r) {
+ self.authKey = r.authKey;
args.data = JSON.stringify({email: args.email, pass: r.authKey});
// send request with authKey as the password
request(args, cb);
});
+ },
+ status: function(args, cb) {
+ args = mergeArgs(args, this);
+ args.method = 'get';
+ args.path = this.path + '/v1/status';
+
+ var url = args.scheme ? args.scheme : 'http';
+ url += '://' + args.host;
+ if (args.port) url += ':' + args.port;
+ url += args.path;
+
+ // compute the authKey
+ GombotCrypto.sign({
+ email: args.email,
+ key: args.key,
+ url: url,
+ host: args.host,
+ port: args.port,
+ method: args.method,
+ nonce: args.nonce,
+ date: args.date
+ }, function(err, r) {
+ if (err) return cb(err);
+ args.headers = r;
+ // send request with authKey as the password
+ request(args, cb);
+ });
}
};
View
@@ -7,6 +7,10 @@ if (typeof URLParse === 'undefined') {
var URLParse = require('./urlparse.js');
}
+if (typeof Hawk === 'undefined') {
+ var Hawk = require('hawk');
+}
+
var GombotCrypto = (function() {
// the number of rounds used in PBKDF2 to generate a stretched derived
// key from a user password.
@@ -115,48 +119,56 @@ var GombotCrypto = (function() {
if (typeof cb !== 'function')
throw new Error("missing required callback argument");
- // how about if the key is poorly formated?
- var keyBits = sjcl.codec.base64.toBits(args.key);
-
// normalize method
args.method = args.method.toUpperCase();
- args.url = URLParse(args.url);
- // add a port if default is in use
- if (!args.url.port) {
- args.url.port = (args.url.scheme === 'https' ? '443' : '80');
- }
+ // how about if the key is poorly formated?
+ //var keyBits = sjcl.codec.base64.toBits(args.key);
+
+ var url = URLParse(args.url);
+ //// add a port if default is in use
+ //if (!args.url.port) {
+ //args.url.port = (args.url.scheme === 'https' ? '443' : '80');
+ //}
setTimeout(function() {
// see https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
// for normalization procedure
- var hmac = new sjcl.misc.hmac(keyBits);
- var body =
- // string representation of seconds since epoch
- args.date.toString() + "\n" +
- // random nonce
- args.nonce + '\n' +
- // normalized method
- args.method + '\n' +
- // path
- args.path + '\n' +
- // hostname
- args.host + '\n' +
- // port
- args.port + '\n' +
- // payload
- args.payload + '\n';
-
- var mac = sjcl.codec.base64.fromBits(hmac.mac(body));
-
- // now formulate the authorization header.
- var val =
- 'MAC id="' + args.email + '", ' +
- 'ts="' + args.date + '", ' +
- 'nonce="' + args.nonce + '", ' +
- 'mac="' + mac + '"';
-
- var headers = { "Authorization": val };
+ //var hmac = new sjcl.misc.hmac(keyBits);
+ //var body =
+ //// string representation of seconds since epoch
+ //args.date.toString() + "\n" +
+ //// random nonce
+ //args.nonce + '\n' +
+ //// normalized method
+ //args.method + '\n' +
+ //// path
+ //args.path + '\n' +
+ //// hostname
+ //args.host + '\n' +
+ //// port
+ //args.port + '\n' +
+ //// payload
+ //args.payload + '\n';
+
+ //var mac = sjcl.codec.base64.fromBits(hmac.mac(body));
+
+ //// now formulate the authorization header.
+ //var val =
+ //'MAC id="' + args.email + '", ' +
+ //'ts="' + args.date + '", ' +
+ //'nonce="' + args.nonce + '", ' +
+ //'mac="' + mac + '"';
+
+ var credentials = {
+ id: args.email,
+ key: args.key,
+ algorithm: 'hmac-sha-256'
+ };
+
+ var headers = {
+ Authorization: Hawk.getAuthorizationHeader(credentials, args.method, args.url, url.host, url.port, args.nonce, args.date)
+ };
// and pass a bag of calculated authorization headers (only one)
// back to the client
View
@@ -21,7 +21,10 @@ var config = module.exports = {
},
hapi: {
name: "Gombot API Server",
- docs: true
+ docs: true,
+ auth: {
+ scheme: 'hawk'
+ }
},
db: {
hosts: [ 'localhost:8091' ],
View
@@ -6,10 +6,12 @@ var B = Hapi.Types.Boolean,
S = Hapi.Types.String;
module.exports = {
- method: 'PUT',
+ method: 'POST',
handler: handler,
config: {
- auth: false,
+ auth: {
+ mode: 'none'
+ },
description: 'Stage a new account',
schema: {
email: B(),
@@ -22,7 +24,6 @@ module.exports = {
};
function handler(request) {
- console.log('$$$$$$$$$$');
db.stageAccount(request.payload, function(err) {
if (err) request.reply(Hapi.Error.internal("error staging account"));
request.reply({
View
@@ -8,7 +8,9 @@ module.exports = {
method: 'GET',
handler: handler,
config: {
- auth: false,
+ auth: {
+ mode: 'none'
+ },
description: 'Get "context" for subsequent operations',
response: {
server_time: N(),
View
@@ -0,0 +1,23 @@
+var Hapi = require('hapi');
+
+var B = Hapi.Types.Boolean;
+
+module.exports = {
+ method: 'GET',
+ handler: handler,
+ auth: {
+ mode: 'hawk'
+ },
+ config: {
+ description: 'Check authorization status',
+ response: {
+ success: B()
+ }
+ }
+};
+
+function handler(request) {
+ request.reply({
+ success: true
+ });
+}
View
@@ -7,17 +7,25 @@ module.exports = {
db = {};
cb(null);
}, 0);
+ return this;
},
stageAccount: function(data, cb) {
var account = {
pass: data.pass,
email: data.email,
- staged: true
+ staged: false
+ //staged: true
};
setTimeout(function() {
db[data.email] = account;
cb(null);
}, 0);
return this;
+ },
+ getAuthKey: function(email, cb) {
+ setTimeout(function() {
+ cb(null, db[email].pass);
+ }, 0);
+ return this;
}
};
View
@@ -1,5 +1,6 @@
const fs = require('fs');
const path = require('path');
+const db = require('./db');
// generated from public key during packaging
var appid = 'gbmmgmjoeplelogofbnjpmkmpodpfaif';
@@ -19,7 +20,7 @@ function setup(app) {
});
app.post('/join_alpha', function(req, res) {
- console.log('got new alpha user email:', req.param('email'));
+ console.log('got new alpha user email:', req.params.email);
res.redirect('/download');
});
Oops, something went wrong.

0 comments on commit 03d0100

Please sign in to comment.