summaryrefslogtreecommitdiff
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--backend/node/properties/node.properties.json5
-rw-r--r--backend/node/src/app.js51
-rw-r--r--backend/node/src/clipperz.data.sql5
-rw-r--r--backend/node/src/clipperz.js575
-rw-r--r--backend/node/src/clipperz.schema.sql67
-rw-r--r--backend/node/src/conf.json3
-rw-r--r--backend/node/src/package.json15
-rw-r--r--frontend/beta/js/Clipperz/PM/Components/Panels/AccountPanel.js8
-rw-r--r--frontend/beta/js/Clipperz/PM/Components/RecordDetail/FieldValueComponent.js12
-rwxr-xr-xscripts/builder/backends/nodeBuilder.py24
-rw-r--r--scripts/builder/backends/scriptLanguageBuilder.py1
11 files changed, 756 insertions, 10 deletions
diff --git a/backend/node/properties/node.properties.json b/backend/node/properties/node.properties.json
new file mode 100644
index 0000000..b5ecc1e
--- a/dev/null
+++ b/backend/node/properties/node.properties.json
@@ -0,0 +1,5 @@
1{
2 "request.path": "/json",
3 "dump.path": "/dump",
4 "should.pay.toll": "false"
5}
diff --git a/backend/node/src/app.js b/backend/node/src/app.js
new file mode 100644
index 0000000..61c2c72
--- a/dev/null
+++ b/backend/node/src/app.js
@@ -0,0 +1,51 @@
1var BUNYAN = require('bunyan');
2var LOGGER = BUNYAN.createLogger({
3 name: 'clipperz',
4 streams: [
5 { name: "console", stream:process.stderr,level:'trace'}
6 ],
7 serializers: {
8 req: BUNYAN.stdSerializers.req,
9 res: BUNYAN.stdSerializers.res,
10 err: BUNYAN.stdSerializers.err
11 },
12 src: true
13});
14
15
16var EXPRESS = require('express');
17var HTTP = require('http');
18var PATH = require('path');
19
20
21var CLIPPERZ = require('./clipperz');
22var CONF = require('./conf');
23var clipperz = CLIPPERZ({
24 psql: CONF.psql||'postgresql:///clipperz',
25 logger: LOGGER,
26 dump_template: PATH.join(__dirname,'htdocs/beta/index.html')
27});
28
29
30var app = EXPRESS();
31
32app.set('port', process.env.PORT || 3000);
33app.use(EXPRESS.logger('dev'));
34app.use(EXPRESS.urlencoded());
35app.use(EXPRESS.methodOverride());
36app.use(EXPRESS.cookieParser('your secret here'));
37app.use(EXPRESS.session({secret:'99 little bugs in the code', key:'sid', store: clipperz.session_store() }));
38app.use(app.router);
39app.use(EXPRESS.static(PATH.join(__dirname, 'htdocs/')));
40if ('development' == app.get('env')) {
41 app.use(EXPRESS.errorHandler());
42}
43
44
45app.post('/json',clipperz.json);
46app.get('/beta/dump',clipperz.dump);
47
48
49HTTP.createServer(app).listen(app.get('port'), function(){
50 LOGGER.info({port:app.get('port')},"Listener established");
51});
diff --git a/backend/node/src/clipperz.data.sql b/backend/node/src/clipperz.data.sql
new file mode 100644
index 0000000..482b196
--- a/dev/null
+++ b/backend/node/src/clipperz.data.sql
@@ -0,0 +1,5 @@
1INSERT INTO clipperz.otpstatus (otps_code,otps_name,otps_desc) VALUES
2 ('ACTIVE','Active','Active'),
3 ('REQUESTED','Requested','Requested'),
4 ('USED','Used','Used'),
5 ('DISABLED','Disabled','Disabled');
diff --git a/backend/node/src/clipperz.js b/backend/node/src/clipperz.js
new file mode 100644
index 0000000..04b054a
--- a/dev/null
+++ b/backend/node/src/clipperz.js
@@ -0,0 +1,575 @@
1var FS = require('fs');
2var CRYPTO = require('crypto');
3var BIGNUM = require('bignum');
4var ASYNC = require('async');
5
6var express_store = require('express').session.Store;
7
8function clipperz_hash(v) {
9 return CRYPTO.createHash('sha256').update(
10 CRYPTO.createHash('sha256').update(v).digest('binary')
11 ).digest('hex');
12};
13function clipperz_random() {
14 for(var r = '';r.length<64;r+=''+BIGNUM(Math.floor(Math.random()*1e18)).toString(16));
15 return r.substr(0,64);
16};
17function clipperz_store(PG) {
18 var rv = function(o) { express_store.call(this,o); }
19 rv.prototype.get = function(sid,cb) { PG.Q(
20 "SELECT s_data FROM clipperz.thesession WHERE s_id=$1",[sid],
21 function(e,r) { cb(e,(e||!r.rowCount)?null:JSON.parse(r.rows[0].s_data)); }
22 ) };
23 rv.prototype.set = function(sid,data,cb) {
24 var d = JSON.stringify(data);
25 PG.Q(
26 "UPDATE clipperz.thesession SET s_data=$1, s_mtime=current_timestamp"
27 +" WHERE s_id=$2",[d,sid], function(e,r) {
28 if(e) return cb(e);
29 if(r.rowCount) return cb();
30 PG.Q("INSERT INTO clipperz.thesession (s_id,s_data) VALUES ($1,$2)",[sid,d],cb);
31 });
32 };
33 rv.prototype.destroy = function(sid,cb) { PG.Q(
34 "DELETE FROM clipperz.thesession WHERE s_id=$1",[sid],cb
35 ) };
36 rv.prototype.length = function(cb) { PG.Q(
37 "SELECT count(*) AS c FROM clipperz.thesession", function(e,r) {
38 cb(e,e?null:r.rows[0].c);
39 }
40 ) };
41 rv.prototype.length = function(cb) { PQ.Q(
42 "DELETE FROM clipperz.thesession", cb
43 ) };
44 rv.prototype.__proto__ = express_store.prototype;
45 return rv;
46}
47
48var srp_g = BIGNUM(2);
49var srp_n = BIGNUM("115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3",16);
50var n123 = '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00';
51
52
53var CLIPPERZ = module.exports = function(CONFIG) {
54
55 var LOGGER = CONFIG.logger||{trace:function(){}};
56
57 var PG = {
58 url: CONFIG.psql,
59 PG: require('pg').native,
60 Q: function(q,a,cb) {
61 if('function'===typeof a) cb=a,a=[];
62 LOGGER.trace({query:q,args:a},'SQL: %s',q);
63 PG.PG.connect(PG.url,function(e,C,D) {
64 if(e) return cb(e);
65 var t0=new Date();
66 C.query(q,a,function(e,r) {
67 var t1=new Date(), dt=t1-t0;
68 D();
69 LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount,err:e},"SQL query '%s' took %dms",q,dt);
70 cb(e,r);
71 });
72 });
73 },
74 T: function(cb) {
75 PG.PG.connect(PG.url,function(e,C,D) {
76 if(e) return cb(e);
77 C.query('BEGIN',function(e){
78 if(e) return D(),cb(e);
79 LOGGER.trace('SQL: transaction begun');
80 cb(null,{
81 Q: function(q,a,cb) {
82 LOGGER.trace({query:q,args:a},'SQL: %s',q);
83 if(this.over) return cb(new Error('game over'));
84 if('function'===typeof a) cb=a,a=[];
85 var t0=new Date();
86 C.query(q,a,function(e,r) {
87 var t1=new Date(), dt=t1-t0;
88 LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount,err:e},"SQL query '%s' took %dms",q,dt);
89 cb(e,r);
90 });
91 },
92 commit: function(cb) {
93 LOGGER.trace('SQL: commit');
94 if(this.over) return cb(new Error('game over'));
95 return (this.over=true),C.query('COMMIT',function(e){D();cb&&cb(e)});
96 },
97 rollback: function(cb) {
98 LOGGER.trace('SQL: rollback');
99 if(this.over) return cb(new Error('game over'));
100 return (this.over=true),C.query('ROLLBACK',function(e){D();cb&&cb(e)});
101 },
102 end: function(e,cb) {
103 if(e) return LOGGER.trace(e,"rolling back transaction due to an error"),this.rollback(cb);
104 this.commit(cb);
105 }
106 });
107 });
108 });
109 }
110 };
111
112
113 var rv = {
114
115 json: function clipperz_json(req,res,cb) {
116 var method = req.body.method, pp = JSON.parse(req.body.parameters).parameters;
117 var message = pp.message;
118 var ppp = pp.parameters;
119 res.res = function(o) { return res.json({result:o}) };
120 LOGGER.trace({method:method,parameters:pp},"JSON request");
121
122 switch(method) {
123 case 'registration':
124 switch(message) {
125 case 'completeRegistration': return PG.Q(
126 "INSERT INTO clipperz.theuser"
127 +" (u_name, u_srp_s,u_srp_v, u_authversion,u_header,u_statistics,u_version,u_lock)"
128 +" VALUES ($1, $2,$3, $4,$5,$6,$7,$8)",
129 [pp.credentials.C, pp.credentials.s, pp.credentials.v,
130 pp.credentials.version,pp.user.header, pp.user.statistics,
131 pp.user.version, pp.user.lock], function(e,r) {
132 if(e) return cb(e);
133 res.res({lock:pp.user.lock,result:'done'});
134 });
135 }
136 break;
137
138 case 'handshake':
139 switch(message) {
140 case 'connect': return ASYNC.auto({
141 u: function(cb) { PG.Q(
142 "SELECT u_id, u_srp_s, u_srp_v FROM clipperz.theuser WHERE u_name=$1",
143 [ppp.C], function(e,r) {
144 if(e) return cb(e);
145 if(!r.rowCount) return cb(null,{u_id:null,u_srp_s:n123,u_srp_v:n123});
146 cb(null,r.rows[0]);
147 }) },
148 otp: ['u',function(cb,r) {
149 if(!req.session.otp) return cb();
150 if(req.session.u!=r.u.u_id) return cb(new Error('user/OTP mismatch'));
151 PG.Q(
152 "UPDATE clipperz.theotp AS otp"
153 +" SET"
154 +" otps_id=CASE WHEN s.otps_code='REQUESTED' THEN ("
155 +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code='USED'"
156 +" ) ELSE otp.otps_id END,"
157 +" otp_utime=current_timestamp"
158 +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o"
159 +" WHERE"
160 +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id"
161 +" AND otp.otp_id=$1 AND otp.u_id=$2"
162 +" RETURNING o.otps_id!=otp.otps_id AS yes, o.otp_ref",
163 [ req.session.otp, req.session.u ],
164 function(e,r) {
165 if(e) return cb(e);
166 if(!r.rowCount) return cb(new Error('no OTP found'));
167 r=r.rows[0];
168 if(!r.yes) return cb(new Error('OTP is in a sorry state'));
169 cb(null,{ref:r.otp_ref});
170 });
171 }]
172 },function(e,r) {
173 if(e) return cb(e);
174 req.session.C = ppp.C; req.session.A = ppp.A;
175 req.session.s = r.u.u_srp_s; req.session.v = r.u.u_srp_v;
176 req.session.u = r.u.u_id;
177 req.session.b = clipperz_random();
178 req.session.B = BIGNUM(req.session.v,16).add(srp_g.powm(BIGNUM(req.session.b,16),srp_n)).toString(16);
179 var rv = {s:req.session.s,B:req.session.B}
180 if(r.otp && r.otp.otp_ref) rv.oneTimePassword=r.otp.otp_ref;
181 res.res(rv);
182 });
183
184 case 'credentialCheck':
185 var u = clipperz_hash(BIGNUM(req.session.B,16).toString(10));
186 var A = BIGNUM(req.session.A,16);
187 var S = A.mul(BIGNUM(req.session.v,16).powm(BIGNUM(u,16),srp_n)).powm(
188 BIGNUM(req.session.b,16), srp_n);
189 var K = clipperz_hash(S.toString(10));
190 var M1 = clipperz_hash(A.toString(10)+BIGNUM(req.session.B,16).toString(10)+K.toString(16));
191 if(M1!=ppp.M1) return res.res({error:'?'});
192 req.session.K = K;
193 var M2 = clipperz_hash(A.toString(10)+M1+K.toString(16));
194 return res.res({M2:M2,connectionId:'',loginInfo:{latest:{},current:{}},offlineCopyNeeded:false,lock:'----'});
195
196 case 'oneTimePassword': return PG.Q(
197 "UPDATE clipperz.theotp AS otp"
198 +" SET"
199 +" otps_id = CASE WHEN s.otps_code!='ACTIVE' THEN s.otps_id ELSE ("
200 +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code=CASE"
201 +" WHEN otp.otp_key_checksum=$2 THEN 'REQUESTED'"
202 +" ELSE 'DISABLED' END"
203 +" ) END,"
204 +" otp_data = CASE WHEN s.otps_code='ACTIVE' THEN '' ELSE otp.otp_data END,"
205 +" otp_utime = current_timestamp,"
206 +" otp_rtime = CASE WHEN otp.otp_key_checksum=$2 THEN current_timestamp ELSE otp.otp_rtime END"
207 +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o"
208 +" WHERE"
209 +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id AND otp.otp_key=$1"
210 +" RETURNING otp.u_id, s.otps_code, otp.otp_id, otp.otp_key_checksum, o.otp_data, otp.otp_version",
211 [ ppp.oneTimePasswordKey, ppp.oneTimePasswordKeyChecksum ],
212 function(e,r) {
213 if(e) return cb(e);
214 if(!r.rowCount) return cb(new Error('OTP not found'));
215 r=r.rows[0];
216 if(r.otp_key_checksum!=ppp.oneTimePasswordKeyChecksum)
217 return cb(new Error('OTP was disabled because of checksum mismatch'));
218 if(r.otps_code!='ACTIVE')
219 return cb(new Error("OTP wasn't active, sorry"));
220 req.session.u=r.u_id; req.session.otp=r.otp_id;
221 res.res({data:r.otp_data,version:r.otp_version});
222 });
223 }
224 break;
225
226 case 'message':
227 if(!req.session.K) return res.res({result:'EXCEPTION',message:"effectively, we're missing a aconnection"});
228 if(req.session.K!=pp.srpSharedSecret) return res.res({error:'Wrong shared secret!'});
229 switch(message) {
230 case 'getUserDetails': return ASYNC.parallel({
231 u: function(cb) {
232 PG.Q("SELECT u_header,u_statistics,u_version FROM clipperz.theuser WHERE u_id=$1",
233 [req.session.u],function(e,r) {
234 if(e) return cb(e);
235 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
236 cb(null,r.rows[0]);
237 });
238 },
239 stats: function(cb) {
240 PG.Q("SELECT r_ref,r_mtime FROM clipperz.therecord WHERE u_id=$1",
241 [req.session.u],function(e,r) {
242 if(e) return cb(e);
243 cb(null,r.rows.reduce(function(p,r){p[r.r_ref]={updateDate:r.r_mtime};return p},{}));
244 });
245 }
246 },function(e,r) {
247 if(e) return cb(e);
248 res.res({header:r.u.u_header,statistics:r.u.u_statistics,version:r.u.u_version,recordsStats:r.stats});
249 });
250
251 case 'saveChanges': return PG.T(function(e,T) {
252 if(e) return cb(e);
253 ASYNC.auto({
254 user: function(cb) {
255 T.Q(
256 "UPDATE clipperz.theuser"
257 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)"
258 +" WHERE u_id=$5"
259 +" RETURNING u_lock",[ppp.user.header,ppp.user.statistics,ppp.user.version,ppp.user.lock||null,req.session.u],
260 function(e,r) {
261 if(e) return cb(e);
262 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
263 cb(null,r.rows[0]);
264 });
265 },
266 updaterecords: function(cb) {
267 if(!(ppp.records && ppp.records.updated && ppp.records.updated.length)) return cb();
268 ASYNC.each(ppp.records.updated,function(r,cb) {
269 ASYNC.auto({
270 updater: function(cb) {
271 T.Q(
272 "UPDATE clipperz.therecord"
273 +" SET r_data=$2, r_version=$3, r_mtime=current_timestamp"
274 +" WHERE r_ref=$1 AND u_id=$4 RETURNING r_id",
275 [r.record.reference,r.record.data,r.record.version,req.session.u], function(e,r) {
276 if(e) return cb(e);
277 return cb(null,r.rows.length?r.rows[0]:null);
278 });
279 },
280 insertr: ['updater',function(cb,rr) {
281 if(rr.updater) return cb();
282 T.Q(
283 "INSERT INTO clipperz.therecord"
284 +" (u_id,r_ref,r_data,r_version)"
285 +" VALUES ($1,$2,$3,$4) RETURNING r_id",[req.session.u,r.record.reference,r.record.data,r.record.version],
286 function(e,r) {
287 if(e) return cb(e);
288 return cb(null,r.rows[0]);
289 });
290 }],
291 updatev: ['updater','insertr',function(cb,rr) {
292 var crv=r.currentRecordVersion;
293 T.Q(
294 "UPDATE clipperz.therecordversion"
295 +" SET rv_ref=$1, rv_data=$2, rv_version=$3,"
296 +" rv_previous_id=COALESCE($4,rv_previous_id),"
297 +" rv_previous_key=$5, r_id=$6, rv_mtime=current_timestamp"
298 +" WHERE"
299 +" rv_id=(SELECT rv_id FROM clipperz.therecordversion WHERE r_id=$6 ORDER BY r_id ASC LIMIT 1)"
300 +" RETURNING rv_id",
301 [crv.reference,crv.data,crv.version,
302 crv.previousVersion||null,crv.previousVersionKey,
303 (rr.updater||rr.insertr).r_id],
304 function(e,r) {
305 if(e) return cb(e);
306 return cb(null,r.rows.length?r.rows[0]:null);
307 });
308 }],
309 insertv: ['updatev',function(cb,rr) {
310 if(rr.updatev) return cb();
311 var crv=r.currentRecordVersion;
312 T.Q(
313 "INSERT INTO clipperz.therecordversion"
314 +" (r_id,rv_ref,rv_data,rv_version,rv_previous_id,rv_previous_key)"
315 +" VALUES ($1,$2,$3,$4,$5,$6) RETURNING rv_id",
316 [(rr.updater||rr.insertr).r_id,
317 crv.reference, crv.data, crv.version,
318 crv.previousVersion||null,crv.previousVersionKey],
319 function(e,r) {
320 if(e) return cb(e);
321 return cb(null,r.rows[0]);
322 });
323 }]
324 },cb);
325 },cb);
326 },
327 deleterecords: function(cb) {
328 if(!(ppp.records && ppp.records.deleted && ppp.records.deleted.length)) return cb();
329 T.Q(
330 "DELETE FROM clipperz.therecord"
331 +" WHERE r_ref = ANY($1::text[]) AND u_id=$2",
332 [ '{'+ppp.records.deleted.join(',')+'}', req.session.u ], cb);
333 }
334 },function(e,r) {
335 T.end(e, function(e) {
336 if(e) return cb(e);
337 res.res({result:'done',lock:r.user.u_lock});
338 });
339 });
340 });
341
342 case 'getRecordDetail': return ASYNC.auto({ // TODO: could be done in one query instead
343 record: function(cb) {
344 PG.Q(
345 "SELECT r_id,r_ref,r_data,r_version, r_ctime, r_mtime, r_atime FROM clipperz.therecord WHERE r_ref=$1",
346 [ppp.reference],function(e,r) {
347 if(e) return cb(e);
348 if(!r.rowCount) return cb(new Error('no record found'));
349 return cb(null,r.rows[0]);
350 });
351 },
352 version: ['record',function(cb,r) {
353 PG.Q(
354 "SELECT rv_ref, rv_data, rv_header, rv_version, rv_ctime, rv_mtime, rv_atime"
355 +" FROM clipperz.therecordversion WHERE r_id=$1 ORDER BY rv_id ASC LIMIT 1",
356 [r.record.r_id],function(e,r) {
357 if(e) return cb(e);
358 if(!r.rowCount) return cb(new Error('no record version found'));
359 return cb(null,r.rows[0]);
360 });
361 }]
362 },function(e,r) {
363 if(e) return cb(e);
364 var v = {};
365 v[r.version.rv_ref] = {
366 reference: r.version.rv_ref,
367 data: r.version.rv_data, header: r.version.rv_header,
368 version: r.version.rv_version,
369 creationDate: r.version.rv_ctime, updateDate: r.version.rv_mtime, accessDate: r.version.rv_atime
370 };
371 res.res({ versions: v, currentVersion: r.version.rv_ref, reference: r.record.r_ref,
372 data: r.record.r_data, version: r.record.r_version,
373 creationDate: r.record.r_ctime, updateDate: r.record.r_mtime, accessDate: r.record.r_atime,
374 oldestUsedEncryptedVersion: '---' });
375 });
376
377 case 'addNewOneTimePassword': return PG.T(function(e,T) {
378 if(e) return cb(e);
379 ASYNC.parallel({
380 otp: function(cb) {
381 var otp = ppp.oneTimePassword;
382 T.Q(
383 "INSERT INTO clipperz.theotp"
384 +" (u_id,otp_ref,otp_key,otp_key_checksum,otp_data,otp_version,otps_id)"
385 +" SELECT $1,$2,$3,$4,$5,$6,otps_id FROM clipperz.otpstatus"
386 +" WHERE otps_code='ACTIVE'",
387 [ req.session.u, otp.reference, otp.key, otp.keyChecksum,
388 otp.data, otp.version], function(e,r) {
389 if(e) return cb(e);
390 if(!r.rowCount) return cb(new Error('no user or status'));
391 cb();
392 });
393 },
394 user: function(cb) {
395 var u = ppp.user;
396 T.Q(
397 "UPDATE clipperz.theuser"
398 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)"
399 +" WHERE u_id=$5",
400 [ u.header, u.statistics, u.version, u.lock||null, req.session.u],
401 function(e,r) {
402 if(e) return cb(e);
403 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
404 cb();
405 });
406 }
407 },function(e,r) {
408 T.end(e, function(e) {
409 if(e) return cb(e);
410 res.res({result:'done',lock:ppp.user.lock});
411 });
412 });
413 });
414
415 case 'updateOneTimePasswords': return PG.T(function(e,T) {
416 if(e) return cb(e);
417 ASYNC.parallel({
418 otp: function(cb) {
419 T.Q(
420 "DELETE FROM clipperz.theotp"
421 +" WHERE u_id=$1"
422 +" AND NOT otp_ref = ANY($2::text[])",
423 [ req.session.u,'{'+ppp.oneTimePasswords.join(',')+'}' ],cb);
424 },
425 user: function(cb) {
426 var u = ppp.user;
427 T.Q(
428 "UPDATE clipperz.theuser"
429 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)"
430 +" WHERE u_id=$5",
431 [ u.header, u.statistics, u.version, u.lock||null, req.session.u],
432 function(e,r) {
433 if(e) return cb(e);
434 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
435 cb();
436 });
437 }
438 },function(e,r) {
439 T.end(e, function(e) {
440 if(e) return cb(e);
441 res.res({result:ppp.user.lock});
442 });
443 });
444 });
445
446 case 'upgradeUserCredentials': return PG.T(function(e,T) {
447 if(e) return cb(e);
448 ASYNC.parallel({
449 user: function(cb) {
450 var u = ppp.user, c = ppp.credentials;
451 T.Q(
452 "UPDATE clipperz.theuser"
453 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock),"
454 +" u_name=$5, u_srp_s=$6, u_srp_v=$7, u_authversion=$8"
455 +" WHERE u_id=$9 RETURNING u_lock",
456 [ u.header,u.statistics,u.version,u.lock||null,
457 c.C,c.s,c.v,c.version, req.session.u ],function(e,r) {
458 if(e) return cb(e);
459 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
460 cb(e,r.rows[0]);
461 });
462 },
463 otp: function(cb) {
464 var otps=ppp.oneTimePasswords;
465 if(!otps) return cb();
466 ASYNC.each(Object.keys(otps),function(r,cb) {
467 T.Q(
468 "UPDATE clipperz.theotp"
469 +" SET otp_data=$1, otp_utime=current_timestamp WHERE otp_ref=$2 AND u_id=$3",
470 [ otps[r], r, req.session.u ], function(e,r) {
471 if(e) return cb(e);
472 if(!r.rowCount) return cb(new Error("OTP's gone AWOL"));
473 cb();
474 });
475 },cb);
476 }
477 },function(e,r) {
478 T.end(e, function(e) {
479 if(e) return cb(e);
480 res.res({result:'done',lock:r.user.u_lock});
481 });
482 });
483 });
484
485 case 'deleteUser': return PG.Q(
486 "DELETE FROM clipperz.theuser WHERE u_id=$1",
487 [req.session.u],function(e,r) {
488 if(e) return cb(e);
489 res.res({result:'ok'});
490 });
491
492 case 'echo': return res.res({result:ppp});
493 case 'getOneTimePasswordsDetails': return res.res({});
494 case 'getLoginHistory': return res.res({result:[]});
495 }
496 break;
497 case 'logout': return req.session.destroy(function(e){res.res({})});
498 }
499 cb();
500 },
501
502 dump: function(req,res,cb) {
503 if(!req.session.u) return cb(new Error('logging in helps'));
504 return ASYNC.parallel({
505 u: function(cb) {
506 PG.Q(
507 "SELECT"
508 +" u_name, u_srp_s, u_srp_v, u_authversion, u_header, u_statistics, u_version"
509 +" FROM clipperz.theuser WHERE u_id=$1",[req.session.u],function(e,r) {
510 if(e) return cb(e);
511 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
512 r = r.rows[0];
513 return cb(null,{u:r.u_name,d:{s:r.u_srp_s,v:r.u_srp_v, version:r.u_authversion,
514 maxNumberOfRecords: '100', userDetails: r.u_header,
515 statistics: r.u_statistics, userDetailsVersion: r.u_version
516 }});
517 });
518 },
519 records: function(cb) {
520 PG.Q(
521 "SELECT"
522 +" r.r_id, r.r_ref, r_data, r_version, r_ctime, r_mtime, r_atime,"
523 +" rv.rv_id, rv.rv_ref AS rv_ref, rv_header, rv_data, rv_version, rv_ctime, rv_mtime, rv_atime"
524 +" FROM"
525 +" clipperz.therecord AS r"
526 +" LEFT JOIN clipperz.therecordversion AS rv USING (r_id)"
527 +" WHERE r.u_id=$1"
528 +" ORDER BY r.r_id ASC, rv.rv_id ASC", [req.session.u],function(e,r) {
529 if(e) return cb(e);
530 var rv = {};
531 r.rows.forEach(function(r) {
532 if(!rv[r.r_ref]) rv[r.r_ref] = {
533 data: r.r_data, version: r.r_version,
534 creationDate: r.r_ctime.toString(),
535 updateDate: r.r_mtime.toString(),
536 accessDate: r.r_atime.toString(),
537 versions: {}
538 };
539 if(!r.rv_id) return;
540 rv[r.r_ref].versions[rv[r.r_ref].currentVersion=r.rv_ref] = {
541 header: r.rv_header, data: r.rv_data, version: r.rv_version,
542 creationDate: r.rv_ctime.toString(),
543 updateDate: r.rv_mtime.toString(),
544 accessDate: r.rv_atime.toString()
545 };
546 });
547 cb(null,rv);
548 });
549 },
550 html: function(cb) {
551 FS.readFile(CONFIG.dump_template,{encoding:'utf-8'},cb);
552 }
553 },function(e,r) {
554 if(e) return cb(e);
555 var d = new Date();
556 res.attachment('Clipperz_'+d.getFullYear()+'_'+(d.getMonth()+1)+'_'+d.getDate()+'.html');
557 var ojs = { users: {
558 catchAllUser: { __masterkey_test_value__: 'masterkey', s: n123, v: n123 }
559 } };
560 r.u.d.records = r.records;
561 ojs.users[r.u.u] = r.u.d;
562 res.send(r.html.replace('/*offline_data_placeholder*/',
563 "_clipperz_dump_data_="+JSON.stringify(ojs)
564 +";"
565 +"Clipperz.PM.Proxy.defaultProxy = new Clipperz.PM.Proxy.Offline();"
566 +"Clipperz.Crypto.PRNG.defaultRandomGenerator().fastEntropyAccumulationForTestingPurpose();"));
567 });
568 }
569
570 };
571 rv.__defineGetter__('session_store',function(){ return function(o) { return new (clipperz_store(PG))(o) } });
572
573 return rv;
574
575};
diff --git a/backend/node/src/clipperz.schema.sql b/backend/node/src/clipperz.schema.sql
new file mode 100644
index 0000000..591828a
--- a/dev/null
+++ b/backend/node/src/clipperz.schema.sql
@@ -0,0 +1,67 @@
1CREATE SCHEMA clipperz;
2
3CREATE TABLE clipperz.theuser (
4 u_id serial PRIMARY KEY,
5 u_name varchar NOT NULL UNIQUE,
6 u_srp_s varchar NOT NULL,
7 u_srp_v varchar NOT NULL,
8 u_header varchar NOT NULL,
9 u_statistics varchar NOT NULL,
10 u_authversion varchar NOT NULL,
11 u_version varchar NOT NULL,
12 u_lock varchar NOT NULL
13);
14
15CREATE TABLE clipperz.therecord (
16 r_id serial PRIMARY KEY,
17 u_id integer NOT NULL REFERENCES clipperz.theuser(u_id) ON UPDATE CASCADE ON DELETE CASCADE,
18 r_ref varchar NOT NULL UNIQUE,
19 r_data varchar NOT NULL,
20 r_version varchar NOT NULL,
21 r_ctime timestamp NOT NULL DEFAULT current_timestamp,
22 r_mtime timestamp NOT NULL DEFAULT current_timestamp,
23 r_atime timestamp NOT NULL DEFAULT current_timestamp
24);
25CREATE INDEX therecord_u_id_key ON clipperz.therecord (u_id);
26
27CREATE TABLE clipperz.therecordversion (
28 rv_id serial PRIMARY KEY,
29 r_id integer NOT NULL REFERENCES clipperz.therecord (r_id) ON UPDATE CASCADE ON DELETE CASCADE,
30 rv_ref varchar NOT NULL UNIQUE,
31 rv_header varchar,
32 rv_data varchar NOT NULL,
33 rv_version varchar NOT NULL,
34 rv_previous_key varchar NOT NULL,
35 rv_previous_id varchar,
36 rv_ctime timestamp NOT NULL DEFAULT current_timestamp,
37 rv_mtime timestamp NOT NULL DEFAULT current_timestamp,
38 rv_atime timestamp NOT NULL DEFAULT current_timestamp
39);
40
41CREATE TABLE clipperz.otpstatus (
42 otps_id serial PRIMARY KEY,
43 otps_code varchar NOT NULL,
44 otps_name varchar NOT NULL,
45 otps_desc varchar NOT NULL
46);
47
48CREATE TABLE clipperz.theotp (
49 otp_id serial PRIMARY KEY,
50 u_id integer REFERENCES clipperz.theuser (u_id) ON UPDATE CASCADE ON DELETE CASCADE,
51 otps_id integer REFERENCES clipperz.otpstatus (otps_id) ON UPDATE CASCADE ON DELETE CASCADE,
52 otp_ref varchar NOT NULL UNIQUE,
53 otp_key varchar NOT NULL UNIQUE,
54 otp_key_checksum varchar NOT NULL,
55 otp_data varchar NOT NULL,
56 otp_version varchar NOT NULL,
57 otp_ctime timestamp NOT NULL DEFAULT current_timestamp,
58 otp_rtime timestamp NOT NULL DEFAULT current_timestamp,
59 otp_utime timestamp NOT NULL DEFAULT current_timestamp
60);
61
62CREATE TABLE clipperz.thesession (
63 s_id varchar PRIMARY KEY,
64 s_data varchar,
65 s_ctime timestamp DEFAULT current_timestamp,
66 s_mtime timestamp DEFAULT current_timestamp
67);
diff --git a/backend/node/src/conf.json b/backend/node/src/conf.json
new file mode 100644
index 0000000..bc1f65e
--- a/dev/null
+++ b/backend/node/src/conf.json
@@ -0,0 +1,3 @@
1{
2 "psql": "postgresql:///clipperz"
3}
diff --git a/backend/node/src/package.json b/backend/node/src/package.json
new file mode 100644
index 0000000..825f756
--- a/dev/null
+++ b/backend/node/src/package.json
@@ -0,0 +1,15 @@
1{
2 "name": "clipperz",
3 "version": "0.0.0",
4 "private": true,
5 "scripts": {
6 "start": "node app 2>&1 | ./node_modules/.bin/bunyan"
7 },
8 "dependencies": {
9 "express": "3.4.4",
10 "async": "~0.2.9",
11 "bignum": "~0.6.2",
12 "pg": "~2.8.3",
13 "bunyan": "~0.22.0"
14 }
15}
diff --git a/frontend/beta/js/Clipperz/PM/Components/Panels/AccountPanel.js b/frontend/beta/js/Clipperz/PM/Components/Panels/AccountPanel.js
index defce91..a627adc 100644
--- a/frontend/beta/js/Clipperz/PM/Components/Panels/AccountPanel.js
+++ b/frontend/beta/js/Clipperz/PM/Components/Panels/AccountPanel.js
@@ -1,215 +1,215 @@
1/* 1/*
2 2
3Copyright 2008-2013 Clipperz Srl 3Copyright 2008-2013 Clipperz Srl
4 4
5This file is part of Clipperz, the online password manager. 5This file is part of Clipperz, the online password manager.
6For further information about its features and functionalities please 6For further information about its features and functionalities please
7refer to http://www.clipperz.com. 7refer to http://www.clipperz.com.
8 8
9* Clipperz is free software: you can redistribute it and/or modify it 9* Clipperz is free software: you can redistribute it and/or modify it
10 under the terms of the GNU Affero General Public License as published 10 under the terms of the GNU Affero General Public License as published
11 by the Free Software Foundation, either version 3 of the License, or 11 by the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version. 12 (at your option) any later version.
13 13
14* Clipperz is distributed in the hope that it will be useful, but 14* Clipperz is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of 15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 See the GNU Affero General Public License for more details. 17 See the GNU Affero General Public License for more details.
18 18
19* You should have received a copy of the GNU Affero General Public 19* You should have received a copy of the GNU Affero General Public
20 License along with Clipperz. If not, see http://www.gnu.org/licenses/. 20 License along with Clipperz. If not, see http://www.gnu.org/licenses/.
21 21
22*/ 22*/
23 23
24if (typeof(Clipperz) == 'undefined') { Clipperz = {}; } 24if (typeof(Clipperz) == 'undefined') { Clipperz = {}; }
25if (typeof(Clipperz.PM) == 'undefined') { Clipperz.PM = {}; } 25if (typeof(Clipperz.PM) == 'undefined') { Clipperz.PM = {}; }
26if (typeof(Clipperz.PM.Components) == 'undefined') { Clipperz.PM.Components = {}; } 26if (typeof(Clipperz.PM.Components) == 'undefined') { Clipperz.PM.Components = {}; }
27if (typeof(Clipperz.PM.Components.Panels) == 'undefined') { Clipperz.PM.Components.Panels = {}; } 27if (typeof(Clipperz.PM.Components.Panels) == 'undefined') { Clipperz.PM.Components.Panels = {}; }
28 28
29//############################################################################# 29//#############################################################################
30 30
31Clipperz.PM.Components.Panels.AccountPanel = function(anElement, args) { 31Clipperz.PM.Components.Panels.AccountPanel = function(anElement, args) {
32//MochiKit.Logging.logDebug(">>> new AccountPanel"); 32//MochiKit.Logging.logDebug(">>> new AccountPanel");
33 args = args || {}; 33 args = args || {};
34 34
35 Clipperz.PM.Components.Panels.AccountPanel.superclass.constructor.call(this, anElement, args); 35 Clipperz.PM.Components.Panels.AccountPanel.superclass.constructor.call(this, anElement, args);
36 36
37 Clipperz.NotificationCenter.register(null, 'setupDone', this, 'render'); 37 Clipperz.NotificationCenter.register(null, 'setupDone', this, 'render');
38 38
39 this._shouldLoadLoginHistory = true; 39 this._shouldLoadLoginHistory = true;
40 40
41 //this.render(); 41 //this.render();
42//MochiKit.Logging.logDebug("<<< new AccountPanel"); 42//MochiKit.Logging.logDebug("<<< new AccountPanel");
43 43
44 return this; 44 return this;
45} 45}
46 46
47//============================================================================= 47//=============================================================================
48 48
49YAHOO.extendX(Clipperz.PM.Components.Panels.AccountPanel, Clipperz.PM.Components.Panels.BasePanel, { 49YAHOO.extendX(Clipperz.PM.Components.Panels.AccountPanel, Clipperz.PM.Components.Panels.BasePanel, {
50 50
51 'toString': function() { 51 'toString': function() {
52 return "Clipperz.PM.Components.AccountPanel component"; 52 return "Clipperz.PM.Components.AccountPanel component";
53 }, 53 },
54 54
55 //------------------------------------------------------------------------- 55 //-------------------------------------------------------------------------
56 56
57 'render': function() { 57 'render': function() {
58 var errorMessageActor; 58 var errorMessageActor;
59 varchangePasswordButton; 59 varchangePasswordButton;
60 var deleteAccountButton; 60 var deleteAccountButton;
61 61
62try { 62try {
63//MochiKit.Logging.logDebug(">>> AccountPanel.render"); 63//MochiKit.Logging.logDebug(">>> AccountPanel.render");
64 Clipperz.NotificationCenter.unregister(this); 64 Clipperz.NotificationCenter.unregister(this);
65 MochiKit.Signal.disconnectAllTo(this); 65 MochiKit.Signal.disconnectAllTo(this);
66 66
67 this.element().update(""); 67 this.element().update("");
68 Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'table', border:'0', cellspacing:'0', cellpadding:'0', children:[ 68 Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'table', border:'0', cellspacing:'0', cellpadding:'0', children:[
69 {tag:'tbody', children:[ 69 {tag:'tbody', children:[
70 {tag:'tr', children:[ 70 {tag:'tr', children:[
71 {tag:'td', valign:'top', width:'200', children:[ 71 {tag:'td', valign:'top', width:'200', children:[
72 {tag:'ul', id:"accountSubMenu", cls:'subMenu', children:[ 72 {tag:'ul', id:"accountSubMenu", cls:'subMenu', children:[
73 {tag:'li', id:'changePassphraseTab', htmlString:Clipperz.PM.Strings['changePasswordTabLabel']}, 73 {tag:'li', id:'changePassphraseTab', htmlString:Clipperz.PM.Strings['changePasswordTabLabel']},
74 {tag:'li', id:'manageOTPTab', htmlString:Clipperz.PM.Strings['manageOTPTabLabel']}, 74 {tag:'li', id:'manageOTPTab', htmlString:Clipperz.PM.Strings['manageOTPTabLabel']},
75 {tag:'li', id:'accountPreferencesTab', htmlString:Clipperz.PM.Strings['accountPreferencesLabel']}, 75 {tag:'li', id:'accountPreferencesTab', htmlString:Clipperz.PM.Strings['accountPreferencesLabel']},
76 {tag:'li', id:'loginHistoryTab', htmlString:Clipperz.PM.Strings['accountLoginHistoryLabel']}, 76 {tag:'li', id:'loginHistoryTab', htmlString:Clipperz.PM.Strings['accountLoginHistoryLabel']},
77 {tag:'li', id:'deleteAccountTab', htmlString:Clipperz.PM.Strings['deleteAccountTabLabel']} 77 {tag:'li', id:'deleteAccountTab', htmlString:Clipperz.PM.Strings['deleteAccountTabLabel']}
78 // {tag:'li', id:'paidAccountTab'), htmlString:Clipperz.PM.Strings['paidAccountTabLabel']} 78 // {tag:'li', id:'paidAccountTab'), htmlString:Clipperz.PM.Strings['paidAccountTabLabel']}
79 ]} 79 ]}
80 ]}, 80 ]},
81 {tag:'td', valign:'top', children:[ 81 {tag:'td', valign:'top', children:[
82 {tag:'ul', cls:'clipperzTabPanels', children:[ 82 {tag:'ul', cls:'clipperzTabPanels', children:[
83 {tag:'li', id:this.getId('changePassphrasePanel'), children:[ 83 {tag:'li', id:this.getId('changePassphrasePanel'), children:[
84 {tag:'div', cls:'clipperzSubPanel', children:[ 84 {tag:'div', cls:'clipperzSubPanel', children:[
85 {tag:'h5', htmlString:Clipperz.PM.Strings['changePasswordTabTitle']}, 85 {tag:'h5', htmlString:Clipperz.PM.Strings['changePasswordTabTitle']},
86 {tag:'div', cls:'panelBody', id:'changePassphraseBlock', children:[ 86 {tag:'div', cls:'panelBody', id:'changePassphraseBlock', children:[
87 {tag:'form', id:this.getId('changePassphraseForm'), children:[ 87 {tag:'form', id:this.getId('changePassphraseForm'), children:[
88 {tag:'h5', cls:'errorMessage', id:this.getId('changePassphrase_errorMessage')}, 88 {tag:'h5', cls:'errorMessage', id:this.getId('changePassphrase_errorMessage')},
89 {tag:'table', cls:'panelBody', children:[ 89 {tag:'table', cls:'panelBody', children:[
90 {tag:'tr', children:[ 90 {tag:'tr', children:[
91 {tag:'td', children:[ 91 {tag:'td', children:[
92 {tag:'span', cls:'formLabel', htmlString:Clipperz.PM.Strings['changePasswordFormUsernameLabel']} 92 {tag:'span', cls:'formLabel', htmlString:Clipperz.PM.Strings['changePasswordFormUsernameLabel']}
93 ]}, 93 ]},
94 {tag:'td', children:[ 94 {tag:'td', children:[
95 {tag:'input', type:'text', name:'username', id:this.getId('changePassphrase_username')} 95 {tag:'input', type:'text', name:'username', id:this.getId('changePassphrase_username'), autocomplete:'off'}
96 ]} 96 ]}
97 ]}, 97 ]},
98 {tag:'tr', children:[ 98 {tag:'tr', children:[
99 {tag:'td', children:[ 99 {tag:'td', children:[
100 {tag:'span', cls:'formLabel', htmlString:Clipperz.PM.Strings['changePasswordFormOldPassphraseLabel']} 100 {tag:'span', cls:'formLabel', htmlString:Clipperz.PM.Strings['changePasswordFormOldPassphraseLabel']}
101 ]}, 101 ]},
102 {tag:'td', children:[ 102 {tag:'td', children:[
103 {tag:'input', type:'password', name:'oldPassphrase', id:this.getId('changePassphrase_oldPassphrase')} 103 {tag:'input', type:'password', name:'oldPassphrase', id:this.getId('changePassphrase_oldPassphrase'), autocomplete:'off'}
104 ]} 104 ]}
105 ]}, 105 ]},
106 {tag:'tr', children:[ 106 {tag:'tr', children:[
107 {tag:'td', children:[ 107 {tag:'td', children:[
108 {tag:'span', cls:'formLabel', htmlString:Clipperz.PM.Strings['changePasswordFormNewPassphraseLabel']} 108 {tag:'span', cls:'formLabel', htmlString:Clipperz.PM.Strings['changePasswordFormNewPassphraseLabel']}
109 ]}, 109 ]},
110 {tag:'td', children:[ 110 {tag:'td', children:[
111 {tag:'input', type:'password', name:'newPassphrase', id:this.getId('changePassphrase_newPassphrase')} 111 {tag:'input', type:'password', name:'newPassphrase', id:this.getId('changePassphrase_newPassphrase'), autocomplete:'off'}
112 ]} 112 ]}
113 ]}, 113 ]},
114 {tag:'tr', children:[ 114 {tag:'tr', children:[
115 {tag:'td', children:[ 115 {tag:'td', children:[
116 {tag:'span', cls:'formLabel', htmlString:Clipperz.PM.Strings['changePasswordFormRetypePassphraseLabel']} 116 {tag:'span', cls:'formLabel', htmlString:Clipperz.PM.Strings['changePasswordFormRetypePassphraseLabel']}
117 ]}, 117 ]},
118 {tag:'td', children:[ 118 {tag:'td', children:[
119 {tag:'input', type:'password', name:'renewPassphrase', id:this.getId('changePassphrase_renewPassphrase')} 119 {tag:'input', type:'password', name:'renewPassphrase', id:this.getId('changePassphrase_renewPassphrase'), autocomplete:'off'}
120 ]} 120 ]}
121 ]}, 121 ]},
122 {tag:'tr', children:[ 122 {tag:'tr', children:[
123 {tag:'td', align:'right', children:[ 123 {tag:'td', align:'right', children:[
124 {tag:'input', type:'checkbox', id:this.getId('changePassphrase_safetyCheck')} 124 {tag:'input', type:'checkbox', id:this.getId('changePassphrase_safetyCheck')}
125 ]}, 125 ]},
126 {tag:'td', children:[ 126 {tag:'td', children:[
127 {tag:'span', htmlString:Clipperz.PM.Strings['changePasswordFormSafetyCheckboxLabel']} 127 {tag:'span', htmlString:Clipperz.PM.Strings['changePasswordFormSafetyCheckboxLabel']}
128 ]} 128 ]}
129 ]} 129 ]}
130 ]}, 130 ]},
131 {tag:'div', cls:'clipperzSubPanelButtonBox', children:[ 131 {tag:'div', cls:'clipperzSubPanelButtonBox', children:[
132 {tag:'div', id:this.getId('changePassphraseButton')} 132 {tag:'div', id:this.getId('changePassphraseButton')}
133 ]} 133 ]}
134 ]} 134 ]}
135 ]} 135 ]}
136 ]} 136 ]}
137 ]}, 137 ]},
138 {tag:'li', id:this.getId('manageOTPPanel'), children:[ 138 {tag:'li', id:this.getId('manageOTPPanel'), children:[
139 {tag:'div', cls:'clipperzSubPanel', children:[ 139 {tag:'div', cls:'clipperzSubPanel', children:[
140 {tag:'h5', htmlString:Clipperz.PM.Strings['manageOTPTabTitle']}, 140 {tag:'h5', htmlString:Clipperz.PM.Strings['manageOTPTabTitle']},
141 {tag:'div', cls:'panelDescription', htmlString:Clipperz.PM.Strings['manageOTPTabDescription']}, 141 {tag:'div', cls:'panelDescription', htmlString:Clipperz.PM.Strings['manageOTPTabDescription']},
142 {tag:'div', id:'OTPComponent'} 142 {tag:'div', id:'OTPComponent'}
143 ]} 143 ]}
144 ]}, 144 ]},
145 {tag:'li', id:this.getId('accountPreferencesPanel'), children:[ 145 {tag:'li', id:this.getId('accountPreferencesPanel'), children:[
146 {tag:'div', cls:'clipperzSubPanel', children:[ 146 {tag:'div', cls:'clipperzSubPanel', children:[
147 {tag:'h5', htmlString:Clipperz.PM.Strings['accountPreferencesTabTitle']}, 147 {tag:'h5', htmlString:Clipperz.PM.Strings['accountPreferencesTabTitle']},
148 {tag:'div', cls:'panelBody', id:this.getId('preferencesPanelBody')} 148 {tag:'div', cls:'panelBody', id:this.getId('preferencesPanelBody')}
149 ]} 149 ]}
150 ]}, 150 ]},
151 {tag:'li', id:this.getId('loginHistoryAccountPanel'), children:[ 151 {tag:'li', id:this.getId('loginHistoryAccountPanel'), children:[
152 {tag:'div', cls:'clipperzSubPanel', children:[ 152 {tag:'div', cls:'clipperzSubPanel', children:[
153 {tag:'h5', htmlString:Clipperz.PM.Strings['loginHistoryTabTitle']}, 153 {tag:'h5', htmlString:Clipperz.PM.Strings['loginHistoryTabTitle']},
154 {tag:'div', cls:'panelBody', id:'loginHistoryAccountBlock'} 154 {tag:'div', cls:'panelBody', id:'loginHistoryAccountBlock'}
155 ]} 155 ]}
156 ]}, 156 ]},
157 {tag:'li', id:this.getId('deleteAccountPanel'), children:[ 157 {tag:'li', id:this.getId('deleteAccountPanel'), children:[
158 {tag:'div', cls:'clipperzSubPanel', children:[ 158 {tag:'div', cls:'clipperzSubPanel', children:[
159 {tag:'h5', htmlString:Clipperz.PM.Strings['deleteAccountTabTitle']}, 159 {tag:'h5', htmlString:Clipperz.PM.Strings['deleteAccountTabTitle']},
160 160
161 {tag:'div', cls:'panelBody', id:'deleteAccountBlock', children:[ 161 {tag:'div', cls:'panelBody', id:'deleteAccountBlock', children:[
162 {tag:'form', id:this.getId('deleteAccountForm'), children:[ 162 {tag:'form', id:this.getId('deleteAccountForm'), children:[
163 {tag:'h5', cls:'errorMessage', id:this.getId('deleteAccount_errorMessage')}, 163 {tag:'h5', cls:'errorMessage', id:this.getId('deleteAccount_errorMessage')},
164 {tag:'table', cls:'panelBody', children:[ 164 {tag:'table', cls:'panelBody', children:[
165 {tag:'tr', children:[ 165 {tag:'tr', children:[
166 {tag:'td', children:[ 166 {tag:'td', children:[
167 {tag:'span', cls:'formLabel', htmlString:Clipperz.PM.Strings['deleteAccountFormUsernameLabel']} 167 {tag:'span', cls:'formLabel', htmlString:Clipperz.PM.Strings['deleteAccountFormUsernameLabel']}
168 ]}, 168 ]},
169 {tag:'td', children:[ 169 {tag:'td', children:[
170 {tag:'input', type:'text', name:'username', id:this.getId('deleteAccount_username')} 170 {tag:'input', type:'text', name:'username', id:this.getId('deleteAccount_username')}
171 ]} 171 ]}
172 ]}, 172 ]},
173 {tag:'tr', children:[ 173 {tag:'tr', children:[
174 {tag:'td', children:[ 174 {tag:'td', children:[
175 {tag:'span', cls:'formLabel', htmlString:Clipperz.PM.Strings['deleteAccountFormPassphraseLabel']} 175 {tag:'span', cls:'formLabel', htmlString:Clipperz.PM.Strings['deleteAccountFormPassphraseLabel']}
176 ]}, 176 ]},
177 {tag:'td', children:[ 177 {tag:'td', children:[
178 {tag:'input', type:'password', name:'passphrase', id:this.getId('deleteAccount_passphrase')} 178 {tag:'input', type:'password', name:'passphrase', id:this.getId('deleteAccount_passphrase')}
179 ]} 179 ]}
180 ]}, 180 ]},
181 {tag:'tr', children:[ 181 {tag:'tr', children:[
182 {tag:'td', align:'right', children:[ 182 {tag:'td', align:'right', children:[
183 {tag:'input', type:'checkbox', id:this.getId('deleteAccount_safetyCheck')} 183 {tag:'input', type:'checkbox', id:this.getId('deleteAccount_safetyCheck')}
184 ]}, 184 ]},
185 {tag:'td', children:[ 185 {tag:'td', children:[
186 {tag:'span', htmlString:Clipperz.PM.Strings['deleteAccountFormSafetyCheckboxLabel']} 186 {tag:'span', htmlString:Clipperz.PM.Strings['deleteAccountFormSafetyCheckboxLabel']}
187 ]} 187 ]}
188 ]} 188 ]}
189 ]}, 189 ]},
190 {tag:'div', cls:'clipperzSubPanelButtonBox', children:[ 190 {tag:'div', cls:'clipperzSubPanelButtonBox', children:[
191 {tag:'div', id:this.getId('deleteAccountButton')} 191 {tag:'div', id:this.getId('deleteAccountButton')}
192 ]} 192 ]}
193 ]} 193 ]}
194 ]} 194 ]}
195 ]} 195 ]}
196 ]} 196 ]}
197 /* 197 /*
198 {tag:'li', id:this.getId('paidAccountPanel'), children:[ 198 {tag:'li', id:this.getId('paidAccountPanel'), children:[
199 {tag:'div', cls:'clipperzSubPanel', children:[ 199 {tag:'div', cls:'clipperzSubPanel', children:[
200 {tag:'h5', htmlString:Clipperz.PM.Strings['upgradeAccountTabTitle']}, 200 {tag:'h5', htmlString:Clipperz.PM.Strings['upgradeAccountTabTitle']},
201 {tag:'div', htmlString:Clipperz.PM.Strings['comingSoon']} 201 {tag:'div', htmlString:Clipperz.PM.Strings['comingSoon']}
202 ]} 202 ]}
203 ]} 203 ]}
204*/ 204*/
205 ]} 205 ]}
206 ]} 206 ]}
207 ]} 207 ]}
208 ]} 208 ]}
209 ]}); 209 ]});
210 210
211//MochiKit.Logging.logDebug("--- AccountPanel.render - 1"); 211//MochiKit.Logging.logDebug("--- AccountPanel.render - 1");
212 MochiKit.Signal.connect(this.getId('changePassphraseForm'), 'onkeydown', this, 'onkeydown'); 212 MochiKit.Signal.connect(this.getId('changePassphraseForm'), 'onkeydown', this, 'onkeydown');
213 errorMessageActor = this.getActor('changePassphrase_errorMessage'); 213 errorMessageActor = this.getActor('changePassphrase_errorMessage');
214 errorMessageActor.setVisibilityMode(YAHOO.ext.Element.DISPLAY); 214 errorMessageActor.setVisibilityMode(YAHOO.ext.Element.DISPLAY);
215 errorMessageActor.update("---"); 215 errorMessageActor.update("---");
diff --git a/frontend/beta/js/Clipperz/PM/Components/RecordDetail/FieldValueComponent.js b/frontend/beta/js/Clipperz/PM/Components/RecordDetail/FieldValueComponent.js
index f2c70aa..a8117d7 100644
--- a/frontend/beta/js/Clipperz/PM/Components/RecordDetail/FieldValueComponent.js
+++ b/frontend/beta/js/Clipperz/PM/Components/RecordDetail/FieldValueComponent.js
@@ -44,227 +44,227 @@ Clipperz.PM.Components.RecordDetail.FieldValueComponent = function(anElement, ar
44//============================================================================= 44//=============================================================================
45 45
46YAHOO.extendX(Clipperz.PM.Components.RecordDetail.FieldValueComponent, Clipperz.PM.Components.RecordDetail.AbstractFieldSubComponent, { 46YAHOO.extendX(Clipperz.PM.Components.RecordDetail.FieldValueComponent, Clipperz.PM.Components.RecordDetail.AbstractFieldSubComponent, {
47 47
48 'toString': function() { 48 'toString': function() {
49 return "Clipperz.PM.Components.RecordDetail.FieldValueComponent component"; 49 return "Clipperz.PM.Components.RecordDetail.FieldValueComponent component";
50 }, 50 },
51 51
52 //------------------------------------------------------------------------- 52 //-------------------------------------------------------------------------
53 53
54 'value': function() { 54 'value': function() {
55 return this.recordField().value(); 55 return this.recordField().value();
56 }, 56 },
57 57
58 'setValue': function(aValue) { 58 'setValue': function(aValue) {
59 this.recordField().setValue(aValue); 59 this.recordField().setValue(aValue);
60 }, 60 },
61 61
62 //------------------------------------------------------------------------- 62 //-------------------------------------------------------------------------
63 63
64 'inputElement': function() { 64 'inputElement': function() {
65 return this._inputElement; 65 return this._inputElement;
66 }, 66 },
67 67
68 'setInputElement': function(aValue) { 68 'setInputElement': function(aValue) {
69 this._inputElement = aValue; 69 this._inputElement = aValue;
70 }, 70 },
71 71
72 //------------------------------------------------------------------------- 72 //-------------------------------------------------------------------------
73 73
74 'scrambledStatus': function() { 74 'scrambledStatus': function() {
75 return this._scrambledStatus; 75 return this._scrambledStatus;
76 }, 76 },
77 77
78 'setScrambledStatus': function(aValue) { 78 'setScrambledStatus': function(aValue) {
79 this._scrambledStatus = aValue; 79 this._scrambledStatus = aValue;
80 }, 80 },
81 81
82 //------------------------------------------------------------------------- 82 //-------------------------------------------------------------------------
83 83
84 'handleTypeChange': function() { 84 'handleTypeChange': function() {
85//MochiKit.Logging.logDebug(">>> handling type change - " + this.recordField().type()); 85//MochiKit.Logging.logDebug(">>> handling type change - " + this.recordField().type());
86 this.synchronizeComponentValues(); 86 this.synchronizeComponentValues();
87 this.update(); 87 this.update();
88 }, 88 },
89 89
90 //------------------------------------------------------------------------- 90 //-------------------------------------------------------------------------
91 91
92 'addrUrl': function() { 92 'addrUrl': function() {
93 var result; 93 var result;
94 94
95 result = "http://maps.google.com/maps?q=" + this.value().split(' ').join('+'); 95 result = "http://maps.google.com/maps?q=" + this.value().split(' ').join('+');
96 96
97 return result; 97 return result;
98 }, 98 },
99 99
100 //------------------------------------------------------------------------- 100 //-------------------------------------------------------------------------
101 101
102 'updateViewMode': function() { 102 'updateViewMode': function() {
103 var scarmbledStatus; 103 var scarmbledStatus;
104 104
105 scrambledStatus = this.scrambledStatus() || 'SCRAMBLED'; 105 scrambledStatus = this.scrambledStatus() || 'SCRAMBLED';
106 106
107 this.element().update(""); 107 this.element().update("");
108 if (this.recordField().hidden() == false) { 108 if (this.recordField().hidden() == false) {
109 switch(this.recordField().type()) { 109 switch(this.recordField().type()) {
110 case 'TXT': 110 case 'TXT':
111 case 'PWD': 111 case 'PWD':
112 Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'span', html:this.value()}); 112 Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'span', html:this.value()});
113 break; 113 break;
114 case 'URL': 114 case 'URL':
115 varurlLocation; 115 varurlLocation;
116 116
117 urlLocation = Clipperz.Base.sanitizeString(this.value()); 117 urlLocation = Clipperz.Base.sanitizeString(this.value());
118 if (! (/^(https?|ftp|svn):\/\//.test(urlLocation))) { 118 if (! (/^(https?|ftp|svn):\/\//.test(urlLocation))) {
119 urlLocation = 'http://' + urlLocation; 119 urlLocation = 'http://' + urlLocation;
120 } 120 }
121 Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'a', href:urlLocation, html:this.value(), target:'_blank'}); 121 Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'a', href:urlLocation, html:this.value(), target:'_blank'});
122 break; 122 break;
123 case 'DATE': 123 case 'DATE':
124 Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'span', html:this.value()}); 124 Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'span', html:this.value()});
125 break; 125 break;
126 case 'ADDR': 126 case 'ADDR':
127 Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'a', href:this.addrUrl(), html:this.value(), target:'_blank'}); 127 Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'a', href:this.addrUrl(), html:this.value(), target:'_blank'});
128 break; 128 break;
129 } 129 }
130 } else { 130 } else {
131 var tableElement; 131 var tableElement;
132 var tdElement; 132 var tdElement;
133 var inputElement; 133 var inputElement;
134 var passwordElementConfiguration; 134 var passwordElementConfiguration;
135 135
136 if (scrambledStatus == 'SCRAMBLED') { 136 if (scrambledStatus == 'SCRAMBLED') {
137 varscrambledInputElement; 137 varscrambledInputElement;
138 138
139 if ((Clipperz_IEisBroken === true) && (Clipperz.PM.Proxy.defaultProxy.isReadOnly())) { 139 if ((Clipperz_IEisBroken === true) && (Clipperz.PM.Proxy.defaultProxy.isReadOnly())) {
140 scrambledInputElement = {tag:'input', type:'password', value:"this.value()"}; 140 scrambledInputElement = {tag:'input', type:'password', value:"this.value()", autocomplete:"off"};
141 } else { 141 } else {
142 scrambledInputElement = {tag:'input', type:'text', cls:'scrambledField', title:Clipperz.PM.Strings['recordDetailPasswordFieldTooltipLabel'], value:"this.value()"}; 142 scrambledInputElement = {tag:'input', type:'text', cls:'scrambledField', title:Clipperz.PM.Strings['recordDetailPasswordFieldTooltipLabel'], value:"this.value()", autocomplete:"off"};
143 } 143 }
144 144
145 passwordElementConfiguration = 145 passwordElementConfiguration =
146 {tag:'table', border:'0', cellspacing:'2', cellpadding:'0', children:[ 146 {tag:'table', border:'0', cellspacing:'2', cellpadding:'0', children:[
147 {tag:'tbody', children:[ 147 {tag:'tbody', children:[
148 {tag:'tr', children:[ 148 {tag:'tr', children:[
149 {tag:'td', valign:'top', children:[ 149 {tag:'td', valign:'top', children:[
150 scrambledInputElement, 150 scrambledInputElement,
151 {tag:'a', cls:'scrambleLink', id:this.getId('scrambleLink'), href:'#', htmlString:Clipperz.PM.Strings['recordDetailPasswordFieldUnscrambleLabel']} 151 {tag:'a', cls:'scrambleLink', id:this.getId('scrambleLink'), href:'#', htmlString:Clipperz.PM.Strings['recordDetailPasswordFieldUnscrambleLabel']}
152 ]}, 152 ]},
153 {tag:'td', valign:'top', children:[ 153 {tag:'td', valign:'top', children:[
154 {tag:'span', cls:'scrambledFieldLabel', htmlString:Clipperz.PM.Strings['recordDetailPasswordFieldHelpLabel']} 154 {tag:'span', cls:'scrambledFieldLabel', htmlString:Clipperz.PM.Strings['recordDetailPasswordFieldHelpLabel']}
155 ]} 155 ]}
156 ]} 156 ]}
157 ]} 157 ]}
158 ]}; 158 ]};
159 } else { 159 } else {
160 passwordElementConfiguration = 160 passwordElementConfiguration =
161 {tag:'div', children:[ 161 {tag:'div', children:[
162 {tag:'input', type:'text', cls:'unscrambledField', value:"this.value()"}, 162 {tag:'input', type:'text', cls:'unscrambledField', value:"this.value()", autocomplete:"off"},
163 {tag:'a', cls:'scrambleLink', id:this.getId('scrambleLink'), href:'#', htmlString:Clipperz.PM.Strings['recordDetailPasswordFieldScrambleLabel']} 163 {tag:'a', cls:'scrambleLink', id:this.getId('scrambleLink'), href:'#', htmlString:Clipperz.PM.Strings['recordDetailPasswordFieldScrambleLabel']}
164 ]}; 164 ]};
165 } 165 }
166 166
167 tableElement = Clipperz.YUI.DomHelper.append(this.element().dom, passwordElementConfiguration, true); 167 tableElement = Clipperz.YUI.DomHelper.append(this.element().dom, passwordElementConfiguration, true);
168 168
169 inputElement = tableElement.getChildrenByTagName('input')[0]; 169 inputElement = tableElement.getChildrenByTagName('input')[0];
170 inputElement.dom.value = this.value(); 170 inputElement.dom.value = this.value();
171 inputElement.wrap({tag:'div', cls:'passwordBackground'}).setStyle('background-position', "0px -" + Math.min(128, Clipperz.PM.Crypto.passwordEntropy(this.value())) + "px"); 171 inputElement.wrap({tag:'div', cls:'passwordBackground'}).setStyle('background-position', "0px -" + Math.min(128, Clipperz.PM.Crypto.passwordEntropy(this.value())) + "px");
172 172
173 MochiKit.Signal.connect(inputElement.dom, 'onfocus', this, 'selectHiddenFieldOnFocus'); 173 MochiKit.Signal.connect(inputElement.dom, 'onfocus', this, 'selectHiddenFieldOnFocus');
174 MochiKit.Signal.connect(this.getDom('scrambleLink'), 'onclick', this, 'toggleScramble'); 174 MochiKit.Signal.connect(this.getDom('scrambleLink'), 'onclick', this, 'toggleScramble');
175 } 175 }
176 }, 176 },
177 177
178 //------------------------------------------------------------------------- 178 //-------------------------------------------------------------------------
179 179
180 'updateEditMode': function() { 180 'updateEditMode': function() {
181 var inputElement; 181 var inputElement;
182 var scarmbledStatus; 182 var scarmbledStatus;
183 183
184 scrambledStatus = this.scrambledStatus() || 'SCRAMBLED'; 184 scrambledStatus = this.scrambledStatus() || 'SCRAMBLED';
185 185
186 this.element().update(""); 186 this.element().update("");
187 switch(this.recordField().type()) { 187 switch(this.recordField().type()) {
188 case 'TXT': 188 case 'TXT':
189 case 'URL': 189 case 'URL':
190 case 'ADDR': 190 case 'ADDR':
191 inputElement = Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'input', type:'text', value:"this.value()"}, true); 191 inputElement = Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'input', type:'text', value:"this.value()", autocomplete:"off"}, true);
192 inputElement.dom.value = this.value(); 192 inputElement.dom.value = this.value();
193 break; 193 break;
194 case 'PWD': 194 case 'PWD':
195 Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'table', width:'100%', cellpadding:'0', cellspacing:'0', children:[ 195 Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'table', width:'100%', cellpadding:'0', cellspacing:'0', children:[
196 {tag:'tbody', children:[ 196 {tag:'tbody', children:[
197 {tag:'tr', children:[ 197 {tag:'tr', children:[
198 {tag:'td', valign:'top', children:[ 198 {tag:'td', valign:'top', children:[
199 {tag:'input', type:((scrambledStatus == 'SCRAMBLED') ? 'password' : 'text'), id:this.getId('passwordInputElement'), value:"this.value()"}, 199 {tag:'input', type:((scrambledStatus == 'SCRAMBLED') ? 'password' : 'text'), id:this.getId('passwordInputElement'), value:"this.value()", autocomplete:"off"},
200 {tag:'a', cls:'scrambleLink', id:this.getId('scrambleLink'), href:'#', html:(scrambledStatus == 'SCRAMBLED' ? Clipperz.PM.Strings['recordDetailPasswordFieldUnscrambleLabel'] : Clipperz.PM.Strings['recordDetailPasswordFieldScrambleLabel'])} 200 {tag:'a', cls:'scrambleLink', id:this.getId('scrambleLink'), href:'#', html:(scrambledStatus == 'SCRAMBLED' ? Clipperz.PM.Strings['recordDetailPasswordFieldUnscrambleLabel'] : Clipperz.PM.Strings['recordDetailPasswordFieldScrambleLabel'])}
201 ]}, 201 ]},
202 {tag:'td', valign:'top', children:[ 202 {tag:'td', valign:'top', children:[
203 {tag:'div', id:this.getId('passwordGenerator'), cls:'Clipperz_PasswordGenerator_button', html:'&nbsp;'} 203 {tag:'div', id:this.getId('passwordGenerator'), cls:'Clipperz_PasswordGenerator_button', html:'&nbsp;'}
204 ]} 204 ]}
205 ]} 205 ]}
206 ]} 206 ]}
207 ]}) 207 ]})
208 inputElement = this.getElement('passwordInputElement'); 208 inputElement = this.getElement('passwordInputElement');
209 inputElement.dom.value = this.value(); 209 inputElement.dom.value = this.value();
210 new Clipperz.PM.Components.PasswordEntropyDisplay(this.getElement('passwordInputElement')); 210 new Clipperz.PM.Components.PasswordEntropyDisplay(this.getElement('passwordInputElement'));
211 new Clipperz.PM.Components.PasswordGenerator(this.getElement('passwordGenerator'), this); 211 new Clipperz.PM.Components.PasswordGenerator(this.getElement('passwordGenerator'), this);
212 MochiKit.Signal.connect(this.getDom('scrambleLink'), 'onclick', this, 'toggleScramble'); 212 MochiKit.Signal.connect(this.getDom('scrambleLink'), 'onclick', this, 'toggleScramble');
213 break; 213 break;
214 // case 'NOTE': 214 // case 'NOTE':
215 // inputElement = Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'textarea', rows:'5', html:this.value()}, true); 215 // inputElement = Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'textarea', rows:'5', html:this.value()}, true);
216 // break 216 // break
217 case 'DATE': 217 case 'DATE':
218 inputElement = Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'input', type:'text', value:"this.value()"}, true); 218 inputElement = Clipperz.YUI.DomHelper.append(this.element().dom, {tag:'input', type:'text', value:"this.value()", autocomplete:"off"}, true);
219 inputElement.dom.value = this.value(); 219 inputElement.dom.value = this.value();
220 break; 220 break;
221 } 221 }
222 222
223 this.setInputElement(inputElement); 223 this.setInputElement(inputElement);
224 }, 224 },
225 225
226 //------------------------------------------------------------------------- 226 //-------------------------------------------------------------------------
227 227
228 'synchronizeComponentValues': function() { 228 'synchronizeComponentValues': function() {
229//MochiKit.Logging.logDebug(">>> FieldValueComponent.synchronizeComponentValues"); 229//MochiKit.Logging.logDebug(">>> FieldValueComponent.synchronizeComponentValues");
230 if (this.inputElement() != null) { 230 if (this.inputElement() != null) {
231 var value; 231 var value;
232 232
233 switch(this.recordField().type()) { 233 switch(this.recordField().type()) {
234 case 'TXT': 234 case 'TXT':
235 case 'URL': 235 case 'URL':
236 case 'ADDR': 236 case 'ADDR':
237 case 'PWD': 237 case 'PWD':
238 case 'DATE': 238 case 'DATE':
239 value = this.inputElement().dom.value; 239 value = this.inputElement().dom.value;
240 break; 240 break;
241 } 241 }
242 this.setValue(value); 242 this.setValue(value);
243 } 243 }
244//MochiKit.Logging.logDebug("<<< FieldValueComponent.synchronizeComponentValues"); 244//MochiKit.Logging.logDebug("<<< FieldValueComponent.synchronizeComponentValues");
245 }, 245 },
246 246
247 //------------------------------------------------------------------------- 247 //-------------------------------------------------------------------------
248 248
249 'selectHiddenFieldOnFocus': function(anEvent) { 249 'selectHiddenFieldOnFocus': function(anEvent) {
250 anEvent.src().select(); 250 anEvent.src().select();
251 }, 251 },
252 252
253 //------------------------------------------------------------------------- 253 //-------------------------------------------------------------------------
254 254
255 'toggleScramble': function(anEvent) { 255 'toggleScramble': function(anEvent) {
256 this.synchronizeComponentValues(); 256 this.synchronizeComponentValues();
257 257
258 if (this.scrambledStatus() == 'SCRAMBLED') { 258 if (this.scrambledStatus() == 'SCRAMBLED') {
259 this.setScrambledStatus('UNSCRAMBLED'); 259 this.setScrambledStatus('UNSCRAMBLED');
260 } else { 260 } else {
261 this.setScrambledStatus('SCRAMBLED'); 261 this.setScrambledStatus('SCRAMBLED');
262 }; 262 };
263 263
264 this.update(); 264 this.update();
265 }, 265 },
266 266
267 //------------------------------------------------------------------------- 267 //-------------------------------------------------------------------------
268 __syntaxFix__: "syntax fix" 268 __syntaxFix__: "syntax fix"
269}); 269});
270 270
diff --git a/scripts/builder/backends/nodeBuilder.py b/scripts/builder/backends/nodeBuilder.py
new file mode 100755
index 0000000..ab43144
--- a/dev/null
+++ b/scripts/builder/backends/nodeBuilder.py
@@ -0,0 +1,24 @@
1#!/usr/bin/env python
2# -*- coding: UTF-8 -*-
3
4import os
5import shutil
6from scriptLanguageBuilder import ScriptLanguageBuilder
7
8class NodeBuilder(ScriptLanguageBuilder):
9
10 def name(self):
11 return "Node builder"
12
13 def relativePath(self):
14 return 'node'
15
16 def frontEndTempFolder (self):
17 return os.path.join(self.tempFolder(),'htdocs')
18
19 def compileCode (self):
20 src = self.sourceFolder()
21 dst = self.tempFolder()
22
23 shutil.copytree(src, dst, ignore = shutil.ignore_patterns('htdocs','node_modules'))
24
diff --git a/scripts/builder/backends/scriptLanguageBuilder.py b/scripts/builder/backends/scriptLanguageBuilder.py
index 7d5b31c..fb8e609 100644
--- a/scripts/builder/backends/scriptLanguageBuilder.py
+++ b/scripts/builder/backends/scriptLanguageBuilder.py
@@ -1,20 +1,21 @@
1#!/usr/bin/env python 1#!/usr/bin/env python
2# -*- coding: UTF-8 -*- 2# -*- coding: UTF-8 -*-
3 3
4import os
4import shutil 5import shutil
5from backendBuilder import BackendBuilder 6from backendBuilder import BackendBuilder
6 7
7class ScriptLanguageBuilder(BackendBuilder): 8class ScriptLanguageBuilder(BackendBuilder):
8 9
9 def compileCode (self): 10 def compileCode (self):
10 src = self.sourceFolder() 11 src = self.sourceFolder()
11 dst = self.tempFolder() 12 dst = self.tempFolder()
12 13
13 shutil.copytree(src, dst) 14 shutil.copytree(src, dst)
14 15
15 16
16 def createPackage (self): 17 def createPackage (self):
17 src = self.tempFolder() 18 src = self.tempFolder()
18 dst = self.targetFolder() 19 dst = self.targetFolder()
19 20
20 shutil.copytree(src, dst) 21 shutil.copytree(src, dst)