зеркало из https://github.com/mozilla/gombot.git
Implement Hawk authorization and status api
This commit is contained in:
Родитель
47a4c690c7
Коммит
03d0100447
30
bin/api
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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(host, port) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
GombotClient = function(path) {
|
||||
var url = URLParse(path);
|
||||
|
||||
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,16 +42,24 @@ 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) {
|
||||
|
@ -49,28 +67,55 @@ GombotClient.prototype = {
|
|||
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);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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 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));
|
||||
//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 + '"';
|
||||
//// now formulate the authorization header.
|
||||
//var val =
|
||||
//'MAC id="' + args.email + '", ' +
|
||||
//'ts="' + args.date + '", ' +
|
||||
//'nonce="' + args.nonce + '", ' +
|
||||
//'mac="' + mac + '"';
|
||||
|
||||
var headers = { "Authorization": val };
|
||||
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
|
||||
|
|
|
@ -21,7 +21,10 @@ var config = module.exports = {
|
|||
},
|
||||
hapi: {
|
||||
name: "Gombot API Server",
|
||||
docs: true
|
||||
docs: true,
|
||||
auth: {
|
||||
scheme: 'hawk'
|
||||
}
|
||||
},
|
||||
db: {
|
||||
hosts: [ 'localhost:8091' ],
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"hapi": "git://github.com/lloyd/hapi#7e6cf0",
|
||||
"hapi": "0.9.2",
|
||||
"walkdir": "0.0.5",
|
||||
"express": "3.0.2",
|
||||
"nunjucks": "0.1.5",
|
||||
"irc": "0.3.3",
|
||||
"awsbox": "0.3.3",
|
||||
"temp": "0.4.0"
|
||||
"temp": "0.4.0",
|
||||
"hawk": "0.0.6"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"couchbase": "0.0.4"
|
||||
|
|
|
@ -12,7 +12,7 @@ describe('the servers', function() {
|
|||
should.not.exist(err);
|
||||
should.exist(r);
|
||||
servers = r;
|
||||
client = new Client(servers.host, servers.port);
|
||||
client = new Client('http://' + servers.host + ':' + servers.port);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -22,7 +22,7 @@ describe('/api/v1/account', function() {
|
|||
it('staging should return success', function(done) {
|
||||
client.account({
|
||||
email: 'foo',
|
||||
password: 'bar'
|
||||
pass: 'bar'
|
||||
}, function(err, r) {
|
||||
should.not.exist(err);
|
||||
should.exist(r);
|
||||
|
|
|
@ -12,7 +12,7 @@ describe('the servers', function() {
|
|||
should.not.exist(err);
|
||||
should.exist(r);
|
||||
servers = r;
|
||||
client = new Client(servers.host, servers.port);
|
||||
client = new Client('http://' + servers.host + ':' + servers.port);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
const
|
||||
should = require('should'),
|
||||
runner = require('./lib/runner.js'),
|
||||
Client = require('../client/client.js');
|
||||
|
||||
var servers;
|
||||
var client;
|
||||
|
||||
var test_user = 'foo';
|
||||
var test_pass = 'bar';
|
||||
|
||||
describe('the servers', function() {
|
||||
it('should start up', function(done) {
|
||||
runner(function(err, r) {
|
||||
should.not.exist(err);
|
||||
should.exist(r);
|
||||
servers = r;
|
||||
client = new Client('http://' + servers.host + ':' + servers.port);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createAccount(email, pass, cb) {
|
||||
client.account({
|
||||
email: email,
|
||||
pass: pass
|
||||
}, function(err, r) {
|
||||
if (err) cb(err);
|
||||
cb(null, client.key);
|
||||
});
|
||||
}
|
||||
|
||||
describe("/api/v1/status", function() {
|
||||
it ('should pass authorization', function(done) {
|
||||
createAccount(test_user, test_pass, function() {
|
||||
try {
|
||||
client.status({
|
||||
email: test_user,
|
||||
key: client.authKey,
|
||||
nonce: 'oh hai',
|
||||
date: new Date()
|
||||
}, function(err, r) {
|
||||
should.not.exist(err);
|
||||
should.exist(r);
|
||||
done();
|
||||
});
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('the servers', function() {
|
||||
it('should stop', function(done) {
|
||||
servers.stop(function(err) {
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -77,7 +77,8 @@ describe('GumbotCrypto.sign', function() {
|
|||
should.not.exist(err);
|
||||
should.exist(rez);
|
||||
(rez.Authorization).should.be.a('string');
|
||||
(rez.Authorization).should.equal('MAC id="bar", ts="1352177818", nonce="one time only please", mac="Zt21WXS7nkwIUdocxbzMBsXKv+0NREsxQ7aBHA9MS4w="');
|
||||
//(rez.Authorization).should.equal('MAC id="bar", ts="1352177818", nonce="one time only please", mac="Zt21WXS7nkwIUdocxbzMBsXKv+0NREsxQ7aBHA9MS4w="');
|
||||
(rez.Authorization).should.equal('Hawk id="bar", ts="1352177818", ext="one time only please", mac="2JSJGewL+/9eoCKgf51mEbhI4cZuEVqNEeZkC3SfXp4="');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче