/
index.js
248 lines (196 loc) · 7 KB
/
index.js
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/** index.js
*
* @author Victor Petrov <victor.petrov@gmail.com>
* @copyright (c) 2012, The Neuroinformatics Research Group at Harvard University.
* @copyright (c) 2012, The President and Fellows of Harvard College.
* @license New BSD License (see LICENSE file for details).
*/
var express=require('express');
var DB=require('./db');
var sconfig=require("./config");
var log=require('logule').init(module);
var path=require('path');
var ejs=require('ejs');
var ursa=require('ursa');
var fs=require('fs');
var util=require('./util');
ejs.open='{{';
ejs.close='}}';
global.obj=require('./lib/obj');
global.arrays=require('./lib/arrays');
global.ROOT=path.dirname(process.mainModule.filename);
function addModule(app,name,mconf)
{
var mname=sconfig.module_prefix+'-'+name;
var module=require(mname);
mconf=mergeConfig(sconfig,mconf);
//merge app config with module config
module.config=mergeConfig(module.config,mconf);
//set the brand name (allows for easier change later on)
module.config.brand=sconfig.brand;
app.log.info('Mounting '+mname+' on '+module.config.prefix)
var mserver=module.server(app,express); //get access to the module's 'app' instance
mserver.use(globalErrorHandler); //register global error handler
mserver.publicKey=app.publicKey; //transfer server public key
mserver.privateKey=app.privateKey; //transfer server private key
mserver.keyID=app.keyID;
mserver.randomId=util.randomId;
//mount module
app.use(module.config.prefix,mserver);
return module;
}
function mergeConfig(source,config)
{
//iterate over each key in config
for (var p in config)
{
//if both keys are objects, merge objects recursively
if ((typeof(source[p])==='object') && (typeof(config[p])==='object'))
source[p]=mergeConfig(source[p],config[p]);
else
//override value in source
source[p]=config[p];
}
return source;
}
function routing(app,mroutes)
{
//path to <module>/routes/
var route_dir=path.normalize(path.join(app.dirname,'/routes'));
//loop over all methods: GET/POST/PUT/DELETE
for (var m in mroutes)
{
var routes=mroutes[m];
//loop over all routes defined for each method
for (var r in routes)
{
var route=routes[r];
var cname='index';
var action='index';
//objects have the form: controller:action
if (typeof(route)==='object')
{
for (var c in route)
{
cname=c;
action=route[c];
//it doesn't make sense to have more than 1 controller/route
break;
}
}
else
//string routes are synonyms for <controller>:'index'
cname=route;
//load the controller
var controller=require(path.join(route_dir,cname));
if (typeof(controller[action])!=='function')
{
app.log.error("route ["+m+" '"+r+"']:","no such action:",cname+'::'+action);
continue;
}
var method=m.toLowerCase();
//link the route to the action
app[method](r,controller[action]);
app.log.debug('['+m+" '"+r+"'] ->",cname+'::'+action);
}
}
}
function globalErrorHandler(err,req,res,next)
{
var app=req.app;
var log=app.log; //use app-specific logger
log.error(err.message,err.stack);
res.send({
success:0,
message:err.message
},500);
}
function getServerKey(encryption)
{
if (!encryption.key)
throw Error('Configuration: No encryption key defined (encryption.key)');
var key=null; //an instance of an 'ursa' private key
var keyPath=path.join(ROOT,encryption.key).split('.'); //join application root dir and 'key' config value
//remove file extension by deleting the last string after a dot
if (keyPath.length>1)
keyPath.pop();
//join all strings
keyPath=keyPath.join('.');
var privateKeyPath=keyPath+'.private'; //append ".private" to private keys
var publicKeyPath =keyPath+'.public'; //append ".public" to public keys
//if the private key has already been generated, use it - the public key can be regenerated from it
if (fs.existsSync(privateKeyPath))
key=ursa.coercePrivateKey(fs.readFileSync(privateKeyPath)); //load private key from disk
else
{
//if not - generate a new key
key=ursa.generatePrivateKey(encryption.bits); //generate new key with specified # of bits
fs.writeFileSync(privateKeyPath,key.toPrivatePem()); //save key to disk
fs.writeFileSync(publicKeyPath,key.toPublicPem()); //save public key in PEM format
/* Note: The public key is saved so that it could be shared with other modules for signing purposes. The idea is
to let the user copy 'local.public' to 'user@server:/www/private/harvard.public' (i.e. some other
survana component that needs to interact with this instance of survana) and then the user can point
a specific component (such as 'survana-study') to this public key, which would then allow this instance
to send signed requests to the other instance.
TODO: Use a database to store the keys. Provide an UI for key exchanges and remote instance registration
*/
}
return key;
}
function readKeys(items)
{
//load all keys
for (var i in items)
{
//skip elements without a 'key' property
if (!items[i]['key'])
continue;
var keypath=items[i].key;
if (!fs.existsSync(keypath))
throw Error("'"+i+"': no key could be found at location '"+keypath+"'");
//read the key and store it instead of the 'key' property
items[i].key=ursa.coercePublicKey(fs.readFileSync(keypath));
items[i].keyID=items[i].key.toPublicSshFingerprint('hex');
}
return items;
}
exports.run=function(config)
{
var app=module.app=express.createServer();
module.config=config;
var key=getServerKey(config.encryption);
log.info('Waking up');
app.configure(function(){
app.use(express.methodOverride());
app.use(express.bodyParser());
app.use(app.router);
app.use(globalErrorHandler);
app.log=log;
});
//expose utility methods
app.mergeConfig=mergeConfig;
app.addModule=addModule;
app.routing=routing;
app.readKeys=readKeys;
app.db=DB;
app.publicKey=ursa.coercePublicKey(key.toPublicPem());
app.privateKey=ursa.coercePrivateKey(key);
app.keyID=app.publicKey.toPublicSshFingerprint('hex');
//root module must be added last, to prevent regex paths
//from conflicting
var last=null;
//load modules
for (var m in config.modules)
{
var mconf=config.modules[m]; //module config
if (mconf.prefix==='/') //check mount point
last=m; //if /, leave for last
else
addModule(app,m,mconf); //add module
}
//load last module
if (last)
addModule(app,last,config.modules[last]);
module.app.log.info('HTTP Server listening on '+module.config.host+':'+module.config.port);
module.app.listen(module.config.port,module.config.host);
}