summaryrefslogtreecommitdiff
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--backend/node/src/clipperz.js4
1 files changed, 2 insertions, 2 deletions
diff --git a/backend/node/src/clipperz.js b/backend/node/src/clipperz.js
index 73af0a0..b98c00e 100644
--- a/backend/node/src/clipperz.js
+++ b/backend/node/src/clipperz.js
@@ -1,572 +1,572 @@
1var FS = require('fs'); 1var FS = require('fs');
2var CRYPTO = require('crypto'); 2var CRYPTO = require('crypto');
3var BIGNUM = require('bignum'); 3var BIGNUM = require('bignum');
4var ASYNC = require('async'); 4var ASYNC = require('async');
5 5
6var express_store = require('express').session.Store; 6var express_store = require('express').session.Store;
7 7
8function clipperz_hash(v) { 8function clipperz_hash(v) {
9 return CRYPTO.createHash('sha256').update( 9 return CRYPTO.createHash('sha256').update(
10 CRYPTO.createHash('sha256').update(v).digest('binary') 10 CRYPTO.createHash('sha256').update(v).digest('binary')
11 ).digest('hex'); 11 ).digest('hex');
12}; 12};
13function clipperz_random() { 13function clipperz_random() {
14 for(var r = '';r.length<64;r+=''+BIGNUM(Math.floor(Math.random()*1e18)).toString(16)); 14 for(var r = '';r.length<64;r+=''+BIGNUM(Math.floor(Math.random()*1e18)).toString(16));
15 return r.substr(0,64); 15 return r.substr(0,64);
16}; 16};
17function clipperz_store(PG) { 17function clipperz_store(PG) {
18 var rv = function(o) { express_store.call(this,o); } 18 var rv = function(o) { express_store.call(this,o); }
19 rv.prototype.get = function(sid,cb) { PG.Q( 19 rv.prototype.get = function(sid,cb) { PG.Q(
20 "SELECT s_data FROM clipperz.thesession WHERE s_id=$1",[sid], 20 "SELECT s_data FROM clipperz.thesession WHERE s_id=$1",[sid],
21 function(e,r) { cb(e,(e||!r.rowCount)?null:r.rows[0].s_data); } 21 function(e,r) { cb(e,(e||!r.rowCount)?null:r.rows[0].s_data); }
22 ) }; 22 ) };
23 rv.prototype.set = function(sid,data,cb) { PG.Q( 23 rv.prototype.set = function(sid,data,cb) { PG.Q(
24 "UPDATE clipperz.thesession SET s_data=$1, s_mtime=current_timestamp" 24 "UPDATE clipperz.thesession SET s_data=$1, s_mtime=current_timestamp"
25 +" WHERE s_id=$2",[data,sid], function(e,r) { 25 +" WHERE s_id=$2",[data,sid], function(e,r) {
26 if(e) return cb(e); 26 if(e) return cb(e);
27 if(r.rowCount) return cb(); 27 if(r.rowCount) return cb();
28 PG.Q("INSERT INTO clipperz.thesession (s_id,s_data) VALUES ($1,$2)",[sid,data],cb); 28 PG.Q("INSERT INTO clipperz.thesession (s_id,s_data) VALUES ($1,$2)",[sid,data],cb);
29 } 29 }
30 ) }; 30 ) };
31 rv.prototype.destroy = function(sid,cb) { PG.Q( 31 rv.prototype.destroy = function(sid,cb) { PG.Q(
32 "DELETE FROM clipperz.thesession WHERE s_id=$1",[sid],cb 32 "DELETE FROM clipperz.thesession WHERE s_id=$1",[sid],cb
33 ) }; 33 ) };
34 rv.prototype.length = function(cb) { PG.Q( 34 rv.prototype.length = function(cb) { PG.Q(
35 "SELECT count(*) AS c FROM clipperz.thesession", function(e,r) { 35 "SELECT count(*) AS c FROM clipperz.thesession", function(e,r) {
36 cb(e,e?null:r.rows[0].c); 36 cb(e,e?null:r.rows[0].c);
37 } 37 }
38 ) }; 38 ) };
39 rv.prototype.length = function(cb) { PQ.Q( 39 rv.prototype.length = function(cb) { PQ.Q(
40 "DELETE FROM clipperz.thesession", cb 40 "DELETE FROM clipperz.thesession", cb
41 ) }; 41 ) };
42 rv.prototype.__proto__ = express_store.prototype; 42 rv.prototype.__proto__ = express_store.prototype;
43 return rv; 43 return rv;
44} 44}
45 45
46var srp_g = BIGNUM(2); 46var srp_g = BIGNUM(2);
47var srp_n = BIGNUM("115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3",16); 47var srp_n = BIGNUM("115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3",16);
48var n123 = '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00'; 48var n123 = '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00';
49 49
50 50
51var CLIPPERZ = module.exports = function(CONFIG) { 51var CLIPPERZ = module.exports = function(CONFIG) {
52 52
53 var LOGGER = CONFIG.logger||{trace:function(){}}; 53 var LOGGER = CONFIG.logger||{trace:function(){}};
54 54
55 var PG = { 55 var PG = {
56 url: CONFIG.psql, 56 url: CONFIG.psql,
57 PG: require('pg').native, 57 PG: require('pg').native,
58 Q: function(q,a,cb) { 58 Q: function(q,a,cb) {
59 if('function'===typeof a) cb=a,a=[]; 59 if('function'===typeof a) cb=a,a=[];
60 LOGGER.trace({query:q,args:a},'SQL: %s',q); 60 LOGGER.trace({query:q,args:a},'SQL: %s',q);
61 PG.PG.connect(PG.url,function(e,C,D) { 61 PG.PG.connect(PG.url,function(e,C,D) {
62 if(e) return cb(e); 62 if(e) return cb(e);
63 var t0=new Date(); 63 var t0=new Date();
64 C.query(q,a,function(e,r) { 64 C.query(q,a,function(e,r) {
65 var t1=new Date(), dt=t1-t0; 65 var t1=new Date(), dt=t1-t0;
66 D(); 66 D();
67 LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount},"SQL query '%s' took %dms",q,dt); 67 LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount,err:e},"SQL query '%s' took %dms",q,dt);
68 cb(e,r); 68 cb(e,r);
69 }); 69 });
70 }); 70 });
71 }, 71 },
72 T: function(cb) { 72 T: function(cb) {
73 PG.PG.connect(PG.url,function(e,C,D) { 73 PG.PG.connect(PG.url,function(e,C,D) {
74 if(e) return cb(e); 74 if(e) return cb(e);
75 C.query('BEGIN',function(e){ 75 C.query('BEGIN',function(e){
76 if(e) return D(),cb(e); 76 if(e) return D(),cb(e);
77 cb(null,{ 77 cb(null,{
78 Q: function(q,a,cb) { 78 Q: function(q,a,cb) {
79 LOGGER.trace({query:q,args:a},'SQL: %s',q); 79 LOGGER.trace({query:q,args:a},'SQL: %s',q);
80 if(this.over) return cb(new Error('game over')); 80 if(this.over) return cb(new Error('game over'));
81 if('function'===typeof a) cb=a,a=[]; 81 if('function'===typeof a) cb=a,a=[];
82 var t0=new Date(); 82 var t0=new Date();
83 C.query(q,a,function(e,r) { 83 C.query(q,a,function(e,r) {
84 var t1=new Date(), dt=t1-t0; 84 var t1=new Date(), dt=t1-t0;
85 LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount},"SQL query '%s' took %dms",q,dt); 85 LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount,err:e},"SQL query '%s' took %dms",q,dt);
86 cb(e,r); 86 cb(e,r);
87 }); 87 });
88 }, 88 },
89 commit: function(cb) { 89 commit: function(cb) {
90 LOGGER.trace('SQL: commit'); 90 LOGGER.trace('SQL: commit');
91 if(this.over) return cb(new Error('game over')); 91 if(this.over) return cb(new Error('game over'));
92 return (this.over=true),C.query('COMMIT',function(e){D();cb&&cb(e)}); 92 return (this.over=true),C.query('COMMIT',function(e){D();cb&&cb(e)});
93 }, 93 },
94 rollback: function(cb) { 94 rollback: function(cb) {
95 LOGGER.trace('SQL: rollback'); 95 LOGGER.trace('SQL: rollback');
96 if(this.over) return cb(new Error('game over')); 96 if(this.over) return cb(new Error('game over'));
97 return (this.over=true),C.query('ROLLBACK',function(e){D();cb&&cb(e)}); 97 return (this.over=true),C.query('ROLLBACK',function(e){D();cb&&cb(e)});
98 }, 98 },
99 end: function(e,cb) { 99 end: function(e,cb) {
100 if(e) return LOGGER.trace(e,"rolling back transaction due to an error"),this.rollback(cb); 100 if(e) return LOGGER.trace(e,"rolling back transaction due to an error"),this.rollback(cb);
101 this.commit(cb); 101 this.commit(cb);
102 } 102 }
103 }); 103 });
104 }); 104 });
105 }); 105 });
106 } 106 }
107 }; 107 };
108 108
109 109
110 var rv = { 110 var rv = {
111 111
112 json: function clipperz_json(req,res,cb) { 112 json: function clipperz_json(req,res,cb) {
113 var method = req.body.method, pp = JSON.parse(req.body.parameters).parameters; 113 var method = req.body.method, pp = JSON.parse(req.body.parameters).parameters;
114 var message = pp.message; 114 var message = pp.message;
115 var ppp = pp.parameters; 115 var ppp = pp.parameters;
116 res.res = function(o) { return res.json({result:o}) }; 116 res.res = function(o) { return res.json({result:o}) };
117 LOGGER.trace({method:method,parameters:pp},"JSON request"); 117 LOGGER.trace({method:method,parameters:pp},"JSON request");
118 118
119 switch(method) { 119 switch(method) {
120 case 'registration': 120 case 'registration':
121 switch(message) { 121 switch(message) {
122 case 'completeRegistration': return PG.Q( 122 case 'completeRegistration': return PG.Q(
123 "INSERT INTO clipperz.theuser" 123 "INSERT INTO clipperz.theuser"
124 +" (u_name, u_srp_s,u_srp_v, u_authversion,u_header,u_statistics,u_version,u_lock)" 124 +" (u_name, u_srp_s,u_srp_v, u_authversion,u_header,u_statistics,u_version,u_lock)"
125 +" VALUES ($1, $2,$3, $4,$5,$6,$7,$8)", 125 +" VALUES ($1, $2,$3, $4,$5,$6,$7,$8)",
126 [pp.credentials.C, pp.credentials.s, pp.credentials.v, 126 [pp.credentials.C, pp.credentials.s, pp.credentials.v,
127 pp.credentials.version,pp.user.header, pp.user.statistics, 127 pp.credentials.version,pp.user.header, pp.user.statistics,
128 pp.user.version, pp.user.lock], function(e,r) { 128 pp.user.version, pp.user.lock], function(e,r) {
129 if(e) return cb(e); 129 if(e) return cb(e);
130 res.res({lock:pp.user.lock,result:'done'}); 130 res.res({lock:pp.user.lock,result:'done'});
131 }); 131 });
132 } 132 }
133 break; 133 break;
134 134
135 case 'handshake': 135 case 'handshake':
136 switch(message) { 136 switch(message) {
137 case 'connect': return ASYNC.auto({ 137 case 'connect': return ASYNC.auto({
138 u: function(cb) { PG.Q( 138 u: function(cb) { PG.Q(
139 "SELECT u_id, u_srp_s, u_srp_v FROM clipperz.theuser WHERE u_name=$1", 139 "SELECT u_id, u_srp_s, u_srp_v FROM clipperz.theuser WHERE u_name=$1",
140 [ppp.C], function(e,r) { 140 [ppp.C], function(e,r) {
141 if(e) return cb(e); 141 if(e) return cb(e);
142 if(!r.rowCount) return cb(null,{u_id:null,u_srp_s:n123,u_srp_v:n123}); 142 if(!r.rowCount) return cb(null,{u_id:null,u_srp_s:n123,u_srp_v:n123});
143 cb(null,r.rows[0]); 143 cb(null,r.rows[0]);
144 }) }, 144 }) },
145 otp: ['u',function(cb,r) { 145 otp: ['u',function(cb,r) {
146 if(!req.session.otp) return cb(); 146 if(!req.session.otp) return cb();
147 if(req.session.u!=r.u.u_id) return cb(new Error('user/OTP mismatch')); 147 if(req.session.u!=r.u.u_id) return cb(new Error('user/OTP mismatch'));
148 PG.Q( 148 PG.Q(
149 "UPDATE clipperz.theotp AS otp" 149 "UPDATE clipperz.theotp AS otp"
150 +" SET" 150 +" SET"
151 +" otps_id=CASE WHEN s.otps_code='REQUESTED' THEN (" 151 +" otps_id=CASE WHEN s.otps_code='REQUESTED' THEN ("
152 +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code='USED'" 152 +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code='USED'"
153 +" ) ELSE otp.otps_id END," 153 +" ) ELSE otp.otps_id END,"
154 +" otp_utime=current_timestamp" 154 +" otp_utime=current_timestamp"
155 +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o" 155 +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o"
156 +" WHERE" 156 +" WHERE"
157 +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id" 157 +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id"
158 +" AND otp.otp_id=$1 AND otp.u_id=$2" 158 +" AND otp.otp_id=$1 AND otp.u_id=$2"
159 +" RETURNING o.otps_id!=otp.otps_id AS yes, o.otp_ref", 159 +" RETURNING o.otps_id!=otp.otps_id AS yes, o.otp_ref",
160 [ req.session.otp, req.session.u ], 160 [ req.session.otp, req.session.u ],
161 function(e,r) { 161 function(e,r) {
162 if(e) return cb(e); 162 if(e) return cb(e);
163 if(!r.rowCount) return cb(new Error('no OTP found')); 163 if(!r.rowCount) return cb(new Error('no OTP found'));
164 r=r.rows[0]; 164 r=r.rows[0];
165 if(!r.yes) return cb(new Error('OTP is in a sorry state')); 165 if(!r.yes) return cb(new Error('OTP is in a sorry state'));
166 cb(null,{ref:r.otp_ref}); 166 cb(null,{ref:r.otp_ref});
167 }); 167 });
168 }] 168 }]
169 },function(e,r) { 169 },function(e,r) {
170 if(e) return cb(e); 170 if(e) return cb(e);
171 req.session.C = ppp.C; req.session.A = ppp.A; 171 req.session.C = ppp.C; req.session.A = ppp.A;
172 req.session.s = r.u.u_srp_s; req.session.v = r.u.u_srp_v; 172 req.session.s = r.u.u_srp_s; req.session.v = r.u.u_srp_v;
173 req.session.u = r.u.u_id; 173 req.session.u = r.u.u_id;
174 req.session.b = clipperz_random(); 174 req.session.b = clipperz_random();
175 req.session.B = BIGNUM(req.session.v,16).add(srp_g.powm(BIGNUM(req.session.b,16),srp_n)).toString(16); 175 req.session.B = BIGNUM(req.session.v,16).add(srp_g.powm(BIGNUM(req.session.b,16),srp_n)).toString(16);
176 var rv = {s:req.session.s,B:req.session.B} 176 var rv = {s:req.session.s,B:req.session.B}
177 if(r.otp && r.otp.otp_ref) rv.oneTimePassword=r.otp.otp_ref; 177 if(r.otp && r.otp.otp_ref) rv.oneTimePassword=r.otp.otp_ref;
178 res.res(rv); 178 res.res(rv);
179 }); 179 });
180 180
181 case 'credentialCheck': 181 case 'credentialCheck':
182 var u = clipperz_hash(BIGNUM(req.session.B,16).toString(10)); 182 var u = clipperz_hash(BIGNUM(req.session.B,16).toString(10));
183 var A = BIGNUM(req.session.A,16); 183 var A = BIGNUM(req.session.A,16);
184 var S = A.mul(BIGNUM(req.session.v,16).powm(BIGNUM(u,16),srp_n)).powm( 184 var S = A.mul(BIGNUM(req.session.v,16).powm(BIGNUM(u,16),srp_n)).powm(
185 BIGNUM(req.session.b,16), srp_n); 185 BIGNUM(req.session.b,16), srp_n);
186 var K = clipperz_hash(S.toString(10)); 186 var K = clipperz_hash(S.toString(10));
187 var M1 = clipperz_hash(A.toString(10)+BIGNUM(req.session.B,16).toString(10)+K.toString(16)); 187 var M1 = clipperz_hash(A.toString(10)+BIGNUM(req.session.B,16).toString(10)+K.toString(16));
188 if(M1!=ppp.M1) return res.res({error:'?'}); 188 if(M1!=ppp.M1) return res.res({error:'?'});
189 req.session.K = K; 189 req.session.K = K;
190 var M2 = clipperz_hash(A.toString(10)+M1+K.toString(16)); 190 var M2 = clipperz_hash(A.toString(10)+M1+K.toString(16));
191 return res.res({M2:M2,connectionId:'',loginInfo:{latest:{},current:{}},offlineCopyNeeded:false,lock:'----'}); 191 return res.res({M2:M2,connectionId:'',loginInfo:{latest:{},current:{}},offlineCopyNeeded:false,lock:'----'});
192 192
193 case 'oneTimePassword': return PG.Q( 193 case 'oneTimePassword': return PG.Q(
194 "UPDATE clipperz.theotp AS otp" 194 "UPDATE clipperz.theotp AS otp"
195 +" SET" 195 +" SET"
196 +" otps_id = CASE WHEN s.otps_code!='ACTIVE' THEN s.otps_id ELSE (" 196 +" otps_id = CASE WHEN s.otps_code!='ACTIVE' THEN s.otps_id ELSE ("
197 +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code=CASE" 197 +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code=CASE"
198 +" WHEN otp.otp_key_checksum=$2 THEN 'REQUESTED'" 198 +" WHEN otp.otp_key_checksum=$2 THEN 'REQUESTED'"
199 +" ELSE 'DISABLED' END" 199 +" ELSE 'DISABLED' END"
200 +" ) END," 200 +" ) END,"
201 +" otp_data = CASE WHEN s.otps_code='ACTIVE' THEN '' ELSE otp.otp_data END," 201 +" otp_data = CASE WHEN s.otps_code='ACTIVE' THEN '' ELSE otp.otp_data END,"
202 +" otp_utime = current_timestamp," 202 +" otp_utime = current_timestamp,"
203 +" otp_rtime = CASE WHEN otp.otp_key_checksum=$2 THEN current_timestamp ELSE otp.otp_rtime END" 203 +" otp_rtime = CASE WHEN otp.otp_key_checksum=$2 THEN current_timestamp ELSE otp.otp_rtime END"
204 +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o" 204 +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o"
205 +" WHERE" 205 +" WHERE"
206 +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id AND otp.otp_key=$1" 206 +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id AND otp.otp_key=$1"
207 +" RETURNING otp.u_id, s.otps_code, otp.otp_id, otp.otp_key_checksum, o.otp_data, otp.otp_version", 207 +" RETURNING otp.u_id, s.otps_code, otp.otp_id, otp.otp_key_checksum, o.otp_data, otp.otp_version",
208 [ ppp.oneTimePasswordKey, ppp.oneTimePasswordKeyChecksum ], 208 [ ppp.oneTimePasswordKey, ppp.oneTimePasswordKeyChecksum ],
209 function(e,r) { 209 function(e,r) {
210 if(e) return cb(e); 210 if(e) return cb(e);
211 if(!r.rowCount) return cb(new Error('OTP not found')); 211 if(!r.rowCount) return cb(new Error('OTP not found'));
212 r=r.rows[0]; 212 r=r.rows[0];
213 if(r.otp_key_checksum!=ppp.oneTimePasswordKeyChecksum) 213 if(r.otp_key_checksum!=ppp.oneTimePasswordKeyChecksum)
214 return cb(new Error('OTP was disabled because of checksum mismatch')); 214 return cb(new Error('OTP was disabled because of checksum mismatch'));
215 if(r.otps_code!='ACTIVE') 215 if(r.otps_code!='ACTIVE')
216 return cb(new Error("OTP wasn't active, sorry")); 216 return cb(new Error("OTP wasn't active, sorry"));
217 req.session.u=r.u_id; req.session.otp=r.otp_id; 217 req.session.u=r.u_id; req.session.otp=r.otp_id;
218 res.res({data:r.otp_data,version:r.otp_version}); 218 res.res({data:r.otp_data,version:r.otp_version});
219 }); 219 });
220 } 220 }
221 break; 221 break;
222 222
223 case 'message': 223 case 'message':
224 if(!req.session.K) return res.res({result:'EXCEPTION',message:"effectively, we're missing a aconnection"}); 224 if(!req.session.K) return res.res({result:'EXCEPTION',message:"effectively, we're missing a aconnection"});
225 if(req.session.K!=pp.srpSharedSecret) return res.res({error:'Wrong shared secret!'}); 225 if(req.session.K!=pp.srpSharedSecret) return res.res({error:'Wrong shared secret!'});
226 switch(message) { 226 switch(message) {
227 case 'getUserDetails': return ASYNC.parallel({ 227 case 'getUserDetails': return ASYNC.parallel({
228 u: function(cb) { 228 u: function(cb) {
229 PG.Q("SELECT u_header::varchar,u_statistics,u_version FROM clipperz.theuser WHERE u_id=$1", 229 PG.Q("SELECT u_header::varchar,u_statistics,u_version FROM clipperz.theuser WHERE u_id=$1",
230 [req.session.u],function(e,r) { 230 [req.session.u],function(e,r) {
231 if(e) return cb(e); 231 if(e) return cb(e);
232 if(!r.rowCount) return cb(new Error("user's gone AWOL")); 232 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
233 cb(null,r.rows[0]); 233 cb(null,r.rows[0]);
234 }); 234 });
235 }, 235 },
236 stats: function(cb) { 236 stats: function(cb) {
237 PG.Q("SELECT r_ref,r_mtime FROM clipperz.therecord WHERE u_id=$1", 237 PG.Q("SELECT r_ref,r_mtime FROM clipperz.therecord WHERE u_id=$1",
238 [req.session.u],function(e,r) { 238 [req.session.u],function(e,r) {
239 if(e) return cb(e); 239 if(e) return cb(e);
240 cb(null,r.rows.reduce(function(p,r){p[r.r_ref]={updateDate:r.r_mtime};return p},{})); 240 cb(null,r.rows.reduce(function(p,r){p[r.r_ref]={updateDate:r.r_mtime};return p},{}));
241 }); 241 });
242 } 242 }
243 },function(e,r) { 243 },function(e,r) {
244 if(e) return cb(e); 244 if(e) return cb(e);
245 res.res({header:r.u.u_header,statistics:r.u.u_statistics,version:r.u.u_version,recordsStats:r.stats}); 245 res.res({header:r.u.u_header,statistics:r.u.u_statistics,version:r.u.u_version,recordsStats:r.stats});
246 }); 246 });
247 247
248 case 'saveChanges': return PG.T(function(e,T) { 248 case 'saveChanges': return PG.T(function(e,T) {
249 if(e) return cb(e); 249 if(e) return cb(e);
250 ASYNC.auto({ 250 ASYNC.auto({
251 user: function(cb) { 251 user: function(cb) {
252 T.Q( 252 T.Q(
253 "UPDATE clipperz.theuser" 253 "UPDATE clipperz.theuser"
254 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)" 254 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)"
255 +" WHERE u_id=$5" 255 +" WHERE u_id=$5"
256 +" RETURNING u_lock",[ppp.user.header,ppp.user.statistics,ppp.user.version,ppp.user.lock||null,req.session.u], 256 +" RETURNING u_lock",[ppp.user.header,ppp.user.statistics,ppp.user.version,ppp.user.lock||null,req.session.u],
257 function(e,r) { 257 function(e,r) {
258 if(e) return cb(e); 258 if(e) return cb(e);
259 if(!r.rowCount) return cb(new Error("user's gone AWOL")); 259 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
260 cb(null,r.rows[0]); 260 cb(null,r.rows[0]);
261 }); 261 });
262 }, 262 },
263 updaterecords: function(cb) { 263 updaterecords: function(cb) {
264 if(!(ppp.records && ppp.records.updated && ppp.records.updated.length)) return cb(); 264 if(!(ppp.records && ppp.records.updated && ppp.records.updated.length)) return cb();
265 ASYNC.each(ppp.records.updated,function(r,cb) { 265 ASYNC.each(ppp.records.updated,function(r,cb) {
266 ASYNC.auto({ 266 ASYNC.auto({
267 updater: function(cb) { 267 updater: function(cb) {
268 T.Q( 268 T.Q(
269 "UPDATE clipperz.therecord" 269 "UPDATE clipperz.therecord"
270 +" SET r_data=$2, r_version=$3, r_mtime=current_timestamp" 270 +" SET r_data=$2, r_version=$3, r_mtime=current_timestamp"
271 +" WHERE r_ref=$1 AND u_id=$4 RETURNING r_id", 271 +" WHERE r_ref=$1 AND u_id=$4 RETURNING r_id",
272 [r.record.reference,r.record.data,r.record.version,req.session.u], function(e,r) { 272 [r.record.reference,r.record.data,r.record.version,req.session.u], function(e,r) {
273 if(e) return cb(e); 273 if(e) return cb(e);
274 return cb(null,r.rows.length?r.rows[0]:null); 274 return cb(null,r.rows.length?r.rows[0]:null);
275 }); 275 });
276 }, 276 },
277 insertr: ['updater',function(cb,rr) { 277 insertr: ['updater',function(cb,rr) {
278 if(rr.updater) return cb(); 278 if(rr.updater) return cb();
279 T.Q( 279 T.Q(
280 "INSERT INTO clipperz.therecord" 280 "INSERT INTO clipperz.therecord"
281 +" (u_id,r_ref,r_data,r_version)" 281 +" (u_id,r_ref,r_data,r_version)"
282 +" VALUES ($1,$2,$3,$4) RETURNING r_id",[req.session.u,r.record.reference,r.record.data,r.record.version], 282 +" VALUES ($1,$2,$3,$4) RETURNING r_id",[req.session.u,r.record.reference,r.record.data,r.record.version],
283 function(e,r) { 283 function(e,r) {
284 if(e) return cb(e); 284 if(e) return cb(e);
285 return cb(null,r.rows[0]); 285 return cb(null,r.rows[0]);
286 }); 286 });
287 }], 287 }],
288 updatev: ['updater','insertr',function(cb,rr) { 288 updatev: ['updater','insertr',function(cb,rr) {
289 var crv=r.currentRecordVersion; 289 var crv=r.currentRecordVersion;
290 T.Q( 290 T.Q(
291 "UPDATE clipperz.therecordversion" 291 "UPDATE clipperz.therecordversion"
292 +" SET rv_ref=$1, rv_data=$2, rv_version=$3," 292 +" SET rv_ref=$1, rv_data=$2, rv_version=$3,"
293 +" rv_previous_id=COALESCE($4,rv_previous_id)," 293 +" rv_previous_id=COALESCE($4,rv_previous_id),"
294 +" rv_previous_key=$5, r_id=$6, rv_mtime=current_timestamp" 294 +" rv_previous_key=$5, r_id=$6, rv_mtime=current_timestamp"
295 +" WHERE" 295 +" WHERE"
296 +" rv_id=(SELECT rv_id FROM clipperz.therecordversion WHERE r_id=$6 ORDER BY r_id ASC LIMIT 1)" 296 +" rv_id=(SELECT rv_id FROM clipperz.therecordversion WHERE r_id=$6 ORDER BY r_id ASC LIMIT 1)"
297 +" RETURNING rv_id", 297 +" RETURNING rv_id",
298 [crv.reference,crv.data,crv.version, 298 [crv.reference,crv.data,crv.version,
299 crv.previousVersion||null,crv.previousVersionKey, 299 crv.previousVersion||null,crv.previousVersionKey,
300 (rr.updater||rr.insertr).r_id], 300 (rr.updater||rr.insertr).r_id],
301 function(e,r) { 301 function(e,r) {
302 if(e) return cb(e); 302 if(e) return cb(e);
303 return cb(null,r.rows.length?r.rows[0]:null); 303 return cb(null,r.rows.length?r.rows[0]:null);
304 }); 304 });
305 }], 305 }],
306 insertv: ['updatev',function(cb,rr) { 306 insertv: ['updatev',function(cb,rr) {
307 if(rr.updatev) return cb(); 307 if(rr.updatev) return cb();
308 var crv=r.currentRecordVersion; 308 var crv=r.currentRecordVersion;
309 T.Q( 309 T.Q(
310 "INSERT INTO clipperz.therecordversion" 310 "INSERT INTO clipperz.therecordversion"
311 +" (r_id,rv_ref,rv_data,rv_version,rv_previous_id,rv_previous_key)" 311 +" (r_id,rv_ref,rv_data,rv_version,rv_previous_id,rv_previous_key)"
312 +" VALUES ($1,$2,$3,$4,$5,$6) RETURNING rv_id", 312 +" VALUES ($1,$2,$3,$4,$5,$6) RETURNING rv_id",
313 [(rr.updater||rr.insertr).r_id, 313 [(rr.updater||rr.insertr).r_id,
314 crv.reference, crv.data, crv.version, 314 crv.reference, crv.data, crv.version,
315 crv.previousVersion||null,crv.previousVersionKey], 315 crv.previousVersion||null,crv.previousVersionKey],
316 function(e,r) { 316 function(e,r) {
317 if(e) return cb(e); 317 if(e) return cb(e);
318 return cb(null,r.rows[0]); 318 return cb(null,r.rows[0]);
319 }); 319 });
320 }] 320 }]
321 },cb); 321 },cb);
322 },cb); 322 },cb);
323 }, 323 },
324 deleterecords: function(cb) { 324 deleterecords: function(cb) {
325 if(!(ppp.records && ppp.records.deleted && ppp.records.deleted.length)) return cb(); 325 if(!(ppp.records && ppp.records.deleted && ppp.records.deleted.length)) return cb();
326 T.Q( 326 T.Q(
327 "DELETE FROM clipperz.therecord" 327 "DELETE FROM clipperz.therecord"
328 +" WHERE r_ref = ANY($1::text[]) AND u_id=$2", 328 +" WHERE r_ref = ANY($1::text[]) AND u_id=$2",
329 [ '{'+ppp.records.deleted.join(',')+'}', req.session.u ], cb); 329 [ '{'+ppp.records.deleted.join(',')+'}', req.session.u ], cb);
330 } 330 }
331 },function(e,r) { 331 },function(e,r) {
332 T.end(e, function(e) { 332 T.end(e, function(e) {
333 if(e) return cb(e); 333 if(e) return cb(e);
334 res.res({result:'done',lock:r.user.u_lock}); 334 res.res({result:'done',lock:r.user.u_lock});
335 }); 335 });
336 }); 336 });
337 }); 337 });
338 338
339 case 'getRecordDetail': return ASYNC.auto({ // TODO: could be done in one query instead 339 case 'getRecordDetail': return ASYNC.auto({ // TODO: could be done in one query instead
340 record: function(cb) { 340 record: function(cb) {
341 PG.Q( 341 PG.Q(
342 "SELECT r_id,r_ref,r_data,r_version, r_ctime, r_mtime, r_atime FROM clipperz.therecord WHERE r_ref=$1", 342 "SELECT r_id,r_ref,r_data,r_version, r_ctime, r_mtime, r_atime FROM clipperz.therecord WHERE r_ref=$1",
343 [ppp.reference],function(e,r) { 343 [ppp.reference],function(e,r) {
344 if(e) return cb(e); 344 if(e) return cb(e);
345 if(!r.rowCount) return cb(new Error('no record found')); 345 if(!r.rowCount) return cb(new Error('no record found'));
346 return cb(null,r.rows[0]); 346 return cb(null,r.rows[0]);
347 }); 347 });
348 }, 348 },
349 version: ['record',function(cb,r) { 349 version: ['record',function(cb,r) {
350 PG.Q( 350 PG.Q(
351 "SELECT rv_ref, rv_data, rv_header, rv_version, rv_ctime, rv_mtime, rv_atime" 351 "SELECT rv_ref, rv_data, rv_header, rv_version, rv_ctime, rv_mtime, rv_atime"
352 +" FROM clipperz.therecordversion WHERE r_id=$1 ORDER BY rv_id ASC LIMIT 1", 352 +" FROM clipperz.therecordversion WHERE r_id=$1 ORDER BY rv_id ASC LIMIT 1",
353 [r.record.r_id],function(e,r) { 353 [r.record.r_id],function(e,r) {
354 if(e) return cb(e); 354 if(e) return cb(e);
355 if(!r.rowCount) return cb(new Error('no record version found')); 355 if(!r.rowCount) return cb(new Error('no record version found'));
356 return cb(null,r.rows[0]); 356 return cb(null,r.rows[0]);
357 }); 357 });
358 }] 358 }]
359 },function(e,r) { 359 },function(e,r) {
360 if(e) return cb(e); 360 if(e) return cb(e);
361 var v = {}; 361 var v = {};
362 v[r.version.rv_ref] = { 362 v[r.version.rv_ref] = {
363 reference: r.version.rv_ref, 363 reference: r.version.rv_ref,
364 data: r.version.rv_data, header: r.version.rv_header, 364 data: r.version.rv_data, header: r.version.rv_header,
365 version: r.version.rv_version, 365 version: r.version.rv_version,
366 creationDate: r.version.rv_ctime, updateDate: r.version.rv_mtime, accessDate: r.version.rv_atime 366 creationDate: r.version.rv_ctime, updateDate: r.version.rv_mtime, accessDate: r.version.rv_atime
367 }; 367 };
368 res.res({ versions: v, currentVersion: r.version.rv_ref, reference: r.record.r_ref, 368 res.res({ versions: v, currentVersion: r.version.rv_ref, reference: r.record.r_ref,
369 data: r.record.r_data, version: r.record.r_version, 369 data: r.record.r_data, version: r.record.r_version,
370 creationDate: r.record.r_ctime, updateDate: r.record.r_mtime, accessDate: r.record.r_atime, 370 creationDate: r.record.r_ctime, updateDate: r.record.r_mtime, accessDate: r.record.r_atime,
371 oldestUsedEncryptedVersion: '---' }); 371 oldestUsedEncryptedVersion: '---' });
372 }); 372 });
373 373
374 case 'addNewOneTimePassword': return PG.T(function(e,T) { 374 case 'addNewOneTimePassword': return PG.T(function(e,T) {
375 if(e) return cb(e); 375 if(e) return cb(e);
376 ASYNC.parallel({ 376 ASYNC.parallel({
377 otp: function(cb) { 377 otp: function(cb) {
378 var otp = ppp.oneTimePassword; 378 var otp = ppp.oneTimePassword;
379 T.Q( 379 T.Q(
380 "INSERT INTO clipperz.theotp" 380 "INSERT INTO clipperz.theotp"
381 +" (u_id,otp_ref,otp_key,otp_key_checksum,otp_data,otp_version,otps_id)" 381 +" (u_id,otp_ref,otp_key,otp_key_checksum,otp_data,otp_version,otps_id)"
382 +" SELECT $1,$2,$3,$4,$5,$6,otps_id FROM clipperz.otpstatus" 382 +" SELECT $1,$2,$3,$4,$5,$6,otps_id FROM clipperz.otpstatus"
383 +" WHERE otps_code='ACTIVE'", 383 +" WHERE otps_code='ACTIVE'",
384 [ req.session.u, otp.reference, otp.key, otp.keyChecksum, 384 [ req.session.u, otp.reference, otp.key, otp.keyChecksum,
385 otp.data, otp.version], function(e,r) { 385 otp.data, otp.version], function(e,r) {
386 if(e) return cb(e); 386 if(e) return cb(e);
387 if(!r.rowCount) return cb(new Error('no user or status')); 387 if(!r.rowCount) return cb(new Error('no user or status'));
388 cb(); 388 cb();
389 }); 389 });
390 }, 390 },
391 user: function(cb) { 391 user: function(cb) {
392 var u = ppp.user; 392 var u = ppp.user;
393 T.Q( 393 T.Q(
394 "UPDATE clipperz.theuser" 394 "UPDATE clipperz.theuser"
395 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)" 395 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)"
396 +" WHERE u_id=$5", 396 +" WHERE u_id=$5",
397 [ u.header, u.statistics, u.version, u.lock||null, req.session.u], 397 [ u.header, u.statistics, u.version, u.lock||null, req.session.u],
398 function(e,r) { 398 function(e,r) {
399 if(e) return cb(e); 399 if(e) return cb(e);
400 if(!r.rowCount) return cb(new Error("user's gone AWOL")); 400 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
401 cb(); 401 cb();
402 }); 402 });
403 } 403 }
404 },function(e,r) { 404 },function(e,r) {
405 T.end(e, function(e) { 405 T.end(e, function(e) {
406 if(e) return cb(e); 406 if(e) return cb(e);
407 res.res({result:'done',lock:ppp.user.lock}); 407 res.res({result:'done',lock:ppp.user.lock});
408 }); 408 });
409 }); 409 });
410 }); 410 });
411 411
412 case 'updateOneTimePasswords': return PG.T(function(e,T) { 412 case 'updateOneTimePasswords': return PG.T(function(e,T) {
413 if(e) return cb(e); 413 if(e) return cb(e);
414 ASYNC.parallel({ 414 ASYNC.parallel({
415 otp: function(cb) { 415 otp: function(cb) {
416 T.Q( 416 T.Q(
417 "DELETE FROM clipperz.theotp" 417 "DELETE FROM clipperz.theotp"
418 +" WHERE u_id=$1" 418 +" WHERE u_id=$1"
419 +" AND NOT otp_ref = ANY($2::text[])", 419 +" AND NOT otp_ref = ANY($2::text[])",
420 [ req.session.u,'{'+ppp.oneTimePasswords.join(',')+'}' ],cb); 420 [ req.session.u,'{'+ppp.oneTimePasswords.join(',')+'}' ],cb);
421 }, 421 },
422 user: function(cb) { 422 user: function(cb) {
423 var u = ppp.user; 423 var u = ppp.user;
424 T.Q( 424 T.Q(
425 "UPDATE clipperz.theuser" 425 "UPDATE clipperz.theuser"
426 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)" 426 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)"
427 +" WHERE u_id=$5", 427 +" WHERE u_id=$5",
428 [ u.header, u.statistics, u.version, u.lock||null, req.session.u], 428 [ u.header, u.statistics, u.version, u.lock||null, req.session.u],
429 function(e,r) { 429 function(e,r) {
430 if(e) return cb(e); 430 if(e) return cb(e);
431 if(!r.rowCount) return cb(new Error("user's gone AWOL")); 431 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
432 cb(); 432 cb();
433 }); 433 });
434 } 434 }
435 },function(e,r) { 435 },function(e,r) {
436 T.end(e, function(e) { 436 T.end(e, function(e) {
437 if(e) return cb(e); 437 if(e) return cb(e);
438 res.res({result:ppp.user.lock}); 438 res.res({result:ppp.user.lock});
439 }); 439 });
440 }); 440 });
441 }); 441 });
442 442
443 case 'upgradeUserCredentials': return PG.T(function(e,T) { 443 case 'upgradeUserCredentials': return PG.T(function(e,T) {
444 if(e) return cb(e); 444 if(e) return cb(e);
445 ASYNC.parallel({ 445 ASYNC.parallel({
446 user: function(cb) { 446 user: function(cb) {
447 var u = ppp.user, c = ppp.credentials; 447 var u = ppp.user, c = ppp.credentials;
448 T.Q( 448 T.Q(
449 "UPDATE clipperz.theuser" 449 "UPDATE clipperz.theuser"
450 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)," 450 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock),"
451 +" u_name=$5, u_srp_s=$6, u_srp_v=$7, u_authversion=$8" 451 +" u_name=$5, u_srp_s=$6, u_srp_v=$7, u_authversion=$8"
452 +" WHERE u_id=$9 RETURNING u_lock", 452 +" WHERE u_id=$9 RETURNING u_lock",
453 [ u.header,u.statistics,u.version,u.lock||null, 453 [ u.header,u.statistics,u.version,u.lock||null,
454 c.C,c.s,c.v,c.version, req.session.u ],function(e,r) { 454 c.C,c.s,c.v,c.version, req.session.u ],function(e,r) {
455 if(e) return cb(e); 455 if(e) return cb(e);
456 if(!r.rowCount) return cb(new Error("user's gone AWOL")); 456 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
457 cb(e,r.rows[0]); 457 cb(e,r.rows[0]);
458 }); 458 });
459 }, 459 },
460 otp: function(cb) { 460 otp: function(cb) {
461 var otps=ppp.oneTimePasswords; 461 var otps=ppp.oneTimePasswords;
462 if(!otps) return cb(); 462 if(!otps) return cb();
463 ASYNC.each(Object.keys(otps),function(r,cb) { 463 ASYNC.each(Object.keys(otps),function(r,cb) {
464 T.Q( 464 T.Q(
465 "UPDATE clipperz.theotp" 465 "UPDATE clipperz.theotp"
466 +" SET otp_data=$1, otp_utime=current_timestamp WHERE otp_ref=$2 AND u_id=$3", 466 +" SET otp_data=$1, otp_utime=current_timestamp WHERE otp_ref=$2 AND u_id=$3",
467 [ otps[r], r, req.session.u ], function(e,r) { 467 [ otps[r], r, req.session.u ], function(e,r) {
468 if(e) return cb(e); 468 if(e) return cb(e);
469 if(!r.rowCount) return cb(new Error("OTP's gone AWOL")); 469 if(!r.rowCount) return cb(new Error("OTP's gone AWOL"));
470 cb(); 470 cb();
471 }); 471 });
472 },cb); 472 },cb);
473 } 473 }
474 },function(e,r) { 474 },function(e,r) {
475 T.end(e, function(e) { 475 T.end(e, function(e) {
476 if(e) return cb(e); 476 if(e) return cb(e);
477 res.res({result:'done',lock:r.user.u_lock}); 477 res.res({result:'done',lock:r.user.u_lock});
478 }); 478 });
479 }); 479 });
480 }); 480 });
481 481
482 case 'deleteUser': return PG.Q( 482 case 'deleteUser': return PG.Q(
483 "DELETE FROM clipperz.theuser WHERE u_id=$1", 483 "DELETE FROM clipperz.theuser WHERE u_id=$1",
484 [req.session.u],function(e,r) { 484 [req.session.u],function(e,r) {
485 if(e) return cb(e); 485 if(e) return cb(e);
486 res.res({result:'ok'}); 486 res.res({result:'ok'});
487 }); 487 });
488 488
489 case 'echo': return res.res({result:ppp}); 489 case 'echo': return res.res({result:ppp});
490 case 'getOneTimePasswordsDetails': return res.res({}); 490 case 'getOneTimePasswordsDetails': return res.res({});
491 case 'getLoginHistory': return res.res({result:[]}); 491 case 'getLoginHistory': return res.res({result:[]});
492 } 492 }
493 break; 493 break;
494 case 'logout': return req.session.destroy(function(e){res.res({})}); 494 case 'logout': return req.session.destroy(function(e){res.res({})});
495 } 495 }
496 cb(); 496 cb();
497 }, 497 },
498 498
499 dump: function(req,res,cb) { 499 dump: function(req,res,cb) {
500 if(!req.session.u) return cb(new Error('logging in helps')); 500 if(!req.session.u) return cb(new Error('logging in helps'));
501 return ASYNC.parallel({ 501 return ASYNC.parallel({
502 u: function(cb) { 502 u: function(cb) {
503 PG.Q( 503 PG.Q(
504 "SELECT" 504 "SELECT"
505 +" u_name, u_srp_s, u_srp_v, u_authversion, u_header::varchar, u_statistics, u_version" 505 +" u_name, u_srp_s, u_srp_v, u_authversion, u_header::varchar, u_statistics, u_version"
506 +" FROM clipperz.theuser WHERE u_id=$1",[req.session.u],function(e,r) { 506 +" FROM clipperz.theuser WHERE u_id=$1",[req.session.u],function(e,r) {
507 if(e) return cb(e); 507 if(e) return cb(e);
508 if(!r.rowCount) return cb(new Error("user's gone AWOL")); 508 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
509 r = r.rows[0]; 509 r = r.rows[0];
510 return cb(null,{u:r.u_name,d:{s:r.u_srp_s,v:r.u_srp_v, version:r.u_authversion, 510 return cb(null,{u:r.u_name,d:{s:r.u_srp_s,v:r.u_srp_v, version:r.u_authversion,
511 maxNumberOfRecords: '100', userDetails: r.u_header, 511 maxNumberOfRecords: '100', userDetails: r.u_header,
512 statistics: r.u_statistics, userDetailsVersion: r.u_version 512 statistics: r.u_statistics, userDetailsVersion: r.u_version
513 }}); 513 }});
514 }); 514 });
515 }, 515 },
516 records: function(cb) { 516 records: function(cb) {
517 PG.Q( 517 PG.Q(
518 "SELECT" 518 "SELECT"
519 +" r.r_id, r.r_ref, r_data, r_version, r_ctime, r_mtime, r_atime," 519 +" r.r_id, r.r_ref, r_data, r_version, r_ctime, r_mtime, r_atime,"
520 +" rv.rv_id, rv.rv_ref AS rv_ref, rv_header, rv_data, rv_version, rv_ctime, rv_mtime, rv_atime" 520 +" rv.rv_id, rv.rv_ref AS rv_ref, rv_header, rv_data, rv_version, rv_ctime, rv_mtime, rv_atime"
521 +" FROM" 521 +" FROM"
522 +" clipperz.therecord AS r" 522 +" clipperz.therecord AS r"
523 +" LEFT JOIN clipperz.therecordversion AS rv USING (r_id)" 523 +" LEFT JOIN clipperz.therecordversion AS rv USING (r_id)"
524 +" WHERE r.u_id=$1" 524 +" WHERE r.u_id=$1"
525 +" ORDER BY r.r_id ASC, rv.rv_id ASC", [req.session.u],function(e,r) { 525 +" ORDER BY r.r_id ASC, rv.rv_id ASC", [req.session.u],function(e,r) {
526 if(e) return cb(e); 526 if(e) return cb(e);
527 var rv = {}; 527 var rv = {};
528 r.rows.forEach(function(r) { 528 r.rows.forEach(function(r) {
529 if(!rv[r.r_ref]) rv[r.r_ref] = { 529 if(!rv[r.r_ref]) rv[r.r_ref] = {
530 data: r.r_data, version: r.r_version, 530 data: r.r_data, version: r.r_version,
531 creationDate: r.r_ctime.toString(), 531 creationDate: r.r_ctime.toString(),
532 updateDate: r.r_mtime.toString(), 532 updateDate: r.r_mtime.toString(),
533 accessDate: r.r_atime.toString(), 533 accessDate: r.r_atime.toString(),
534 versions: {} 534 versions: {}
535 }; 535 };
536 if(!r.rv_id) return; 536 if(!r.rv_id) return;
537 rv[r.r_ref].versions[rv[r.r_ref].currentVersion=r.rv_ref] = { 537 rv[r.r_ref].versions[rv[r.r_ref].currentVersion=r.rv_ref] = {
538 header: r.rv_header, data: r.rv_data, version: r.rv_version, 538 header: r.rv_header, data: r.rv_data, version: r.rv_version,
539 creationDate: r.rv_ctime.toString(), 539 creationDate: r.rv_ctime.toString(),
540 updateDate: r.rv_mtime.toString(), 540 updateDate: r.rv_mtime.toString(),
541 accessDate: r.rv_atime.toString() 541 accessDate: r.rv_atime.toString()
542 }; 542 };
543 }); 543 });
544 cb(null,rv); 544 cb(null,rv);
545 }); 545 });
546 }, 546 },
547 html: function(cb) { 547 html: function(cb) {
548 FS.readFile(CONFIG.dump_template,{encoding:'utf-8'},cb); 548 FS.readFile(CONFIG.dump_template,{encoding:'utf-8'},cb);
549 } 549 }
550 },function(e,r) { 550 },function(e,r) {
551 if(e) return cb(e); 551 if(e) return cb(e);
552 var d = new Date(); 552 var d = new Date();
553 res.attachment('Clipperz_'+d.getFullYear()+'_'+(d.getMonth()+1)+'_'+d.getDate()+'.html'); 553 res.attachment('Clipperz_'+d.getFullYear()+'_'+(d.getMonth()+1)+'_'+d.getDate()+'.html');
554 var ojs = { users: { 554 var ojs = { users: {
555 catchAllUser: { __masterkey_test_value__: 'masterkey', s: n123, v: n123 } 555 catchAllUser: { __masterkey_test_value__: 'masterkey', s: n123, v: n123 }
556 } }; 556 } };
557 r.u.d.records = r.records; 557 r.u.d.records = r.records;
558 ojs.users[r.u.u] = r.u.d; 558 ojs.users[r.u.u] = r.u.d;
559 res.send(r.html.replace('/*offline_data_placeholder*/', 559 res.send(r.html.replace('/*offline_data_placeholder*/',
560 "_clipperz_dump_data_="+JSON.stringify(ojs) 560 "_clipperz_dump_data_="+JSON.stringify(ojs)
561 +";" 561 +";"
562 +"Clipperz.PM.Proxy.defaultProxy = new Clipperz.PM.Proxy.Offline();" 562 +"Clipperz.PM.Proxy.defaultProxy = new Clipperz.PM.Proxy.Offline();"
563 +"Clipperz.Crypto.PRNG.defaultRandomGenerator().fastEntropyAccumulationForTestingPurpose();")); 563 +"Clipperz.Crypto.PRNG.defaultRandomGenerator().fastEntropyAccumulationForTestingPurpose();"));
564 }); 564 });
565 } 565 }
566 566
567 }; 567 };
568 rv.__defineGetter__('session_store',function(){ return function(o) { return new (clipperz_store(PG))(o) } }); 568 rv.__defineGetter__('session_store',function(){ return function(o) { return new (clipperz_store(PG))(o) } });
569 569
570 return rv; 570 return rv;
571 571
572}; 572};