summaryrefslogtreecommitdiff
path: root/backend
authorMichael Krelin <hacker@klever.net>2013-11-27 17:11:49 (UTC)
committer Michael Krelin <hacker@klever.net>2013-11-27 17:11:49 (UTC)
commit6cd9e34b5b49473923190a6a70c436908c98505e (patch) (unidiff)
treed6182625f7406f8b8facb71166c5e4a607dd2c65 /backend
parent0f1cc2ac41835ee8fa5dded1593fa95092b54bbe (diff)
downloadclipperz-6cd9e34b5b49473923190a6a70c436908c98505e.zip
clipperz-6cd9e34b5b49473923190a6a70c436908c98505e.tar.gz
clipperz-6cd9e34b5b49473923190a6a70c436908c98505e.tar.bz2
more tracelogging
Diffstat (limited to 'backend') (more/less context) (ignore whitespace changes)
-rw-r--r--backend/node/src/clipperz.js1
1 files changed, 1 insertions, 0 deletions
diff --git a/backend/node/src/clipperz.js b/backend/node/src/clipperz.js
index b8b4d3e..04b054a 100644
--- a/backend/node/src/clipperz.js
+++ b/backend/node/src/clipperz.js
@@ -1,462 +1,463 @@
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:JSON.parse(r.rows[0].s_data)); } 21 function(e,r) { cb(e,(e||!r.rowCount)?null:JSON.parse(r.rows[0].s_data)); }
22 ) }; 22 ) };
23 rv.prototype.set = function(sid,data,cb) { 23 rv.prototype.set = function(sid,data,cb) {
24 var d = JSON.stringify(data); 24 var d = JSON.stringify(data);
25 PG.Q( 25 PG.Q(
26 "UPDATE clipperz.thesession SET s_data=$1, s_mtime=current_timestamp" 26 "UPDATE clipperz.thesession SET s_data=$1, s_mtime=current_timestamp"
27 +" WHERE s_id=$2",[d,sid], function(e,r) { 27 +" WHERE s_id=$2",[d,sid], function(e,r) {
28 if(e) return cb(e); 28 if(e) return cb(e);
29 if(r.rowCount) return cb(); 29 if(r.rowCount) return cb();
30 PG.Q("INSERT INTO clipperz.thesession (s_id,s_data) VALUES ($1,$2)",[sid,d],cb); 30 PG.Q("INSERT INTO clipperz.thesession (s_id,s_data) VALUES ($1,$2)",[sid,d],cb);
31 }); 31 });
32 }; 32 };
33 rv.prototype.destroy = function(sid,cb) { PG.Q( 33 rv.prototype.destroy = function(sid,cb) { PG.Q(
34 "DELETE FROM clipperz.thesession WHERE s_id=$1",[sid],cb 34 "DELETE FROM clipperz.thesession WHERE s_id=$1",[sid],cb
35 ) }; 35 ) };
36 rv.prototype.length = function(cb) { PG.Q( 36 rv.prototype.length = function(cb) { PG.Q(
37 "SELECT count(*) AS c FROM clipperz.thesession", function(e,r) { 37 "SELECT count(*) AS c FROM clipperz.thesession", function(e,r) {
38 cb(e,e?null:r.rows[0].c); 38 cb(e,e?null:r.rows[0].c);
39 } 39 }
40 ) }; 40 ) };
41 rv.prototype.length = function(cb) { PQ.Q( 41 rv.prototype.length = function(cb) { PQ.Q(
42 "DELETE FROM clipperz.thesession", cb 42 "DELETE FROM clipperz.thesession", cb
43 ) }; 43 ) };
44 rv.prototype.__proto__ = express_store.prototype; 44 rv.prototype.__proto__ = express_store.prototype;
45 return rv; 45 return rv;
46} 46}
47 47
48var srp_g = BIGNUM(2); 48var srp_g = BIGNUM(2);
49var srp_n = BIGNUM("115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3",16); 49var srp_n = BIGNUM("115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3",16);
50var n123 = '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00'; 50var n123 = '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00';
51 51
52 52
53var CLIPPERZ = module.exports = function(CONFIG) { 53var CLIPPERZ = module.exports = function(CONFIG) {
54 54
55 var LOGGER = CONFIG.logger||{trace:function(){}}; 55 var LOGGER = CONFIG.logger||{trace:function(){}};
56 56
57 var PG = { 57 var PG = {
58 url: CONFIG.psql, 58 url: CONFIG.psql,
59 PG: require('pg').native, 59 PG: require('pg').native,
60 Q: function(q,a,cb) { 60 Q: function(q,a,cb) {
61 if('function'===typeof a) cb=a,a=[]; 61 if('function'===typeof a) cb=a,a=[];
62 LOGGER.trace({query:q,args:a},'SQL: %s',q); 62 LOGGER.trace({query:q,args:a},'SQL: %s',q);
63 PG.PG.connect(PG.url,function(e,C,D) { 63 PG.PG.connect(PG.url,function(e,C,D) {
64 if(e) return cb(e); 64 if(e) return cb(e);
65 var t0=new Date(); 65 var t0=new Date();
66 C.query(q,a,function(e,r) { 66 C.query(q,a,function(e,r) {
67 var t1=new Date(), dt=t1-t0; 67 var t1=new Date(), dt=t1-t0;
68 D(); 68 D();
69 LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount,err:e},"SQL query '%s' took %dms",q,dt); 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); 70 cb(e,r);
71 }); 71 });
72 }); 72 });
73 }, 73 },
74 T: function(cb) { 74 T: function(cb) {
75 PG.PG.connect(PG.url,function(e,C,D) { 75 PG.PG.connect(PG.url,function(e,C,D) {
76 if(e) return cb(e); 76 if(e) return cb(e);
77 C.query('BEGIN',function(e){ 77 C.query('BEGIN',function(e){
78 if(e) return D(),cb(e); 78 if(e) return D(),cb(e);
79 LOGGER.trace('SQL: transaction begun');
79 cb(null,{ 80 cb(null,{
80 Q: function(q,a,cb) { 81 Q: function(q,a,cb) {
81 LOGGER.trace({query:q,args:a},'SQL: %s',q); 82 LOGGER.trace({query:q,args:a},'SQL: %s',q);
82 if(this.over) return cb(new Error('game over')); 83 if(this.over) return cb(new Error('game over'));
83 if('function'===typeof a) cb=a,a=[]; 84 if('function'===typeof a) cb=a,a=[];
84 var t0=new Date(); 85 var t0=new Date();
85 C.query(q,a,function(e,r) { 86 C.query(q,a,function(e,r) {
86 var t1=new Date(), dt=t1-t0; 87 var t1=new Date(), dt=t1-t0;
87 LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount,err:e},"SQL query '%s' took %dms",q,dt); 88 LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount,err:e},"SQL query '%s' took %dms",q,dt);
88 cb(e,r); 89 cb(e,r);
89 }); 90 });
90 }, 91 },
91 commit: function(cb) { 92 commit: function(cb) {
92 LOGGER.trace('SQL: commit'); 93 LOGGER.trace('SQL: commit');
93 if(this.over) return cb(new Error('game over')); 94 if(this.over) return cb(new Error('game over'));
94 return (this.over=true),C.query('COMMIT',function(e){D();cb&&cb(e)}); 95 return (this.over=true),C.query('COMMIT',function(e){D();cb&&cb(e)});
95 }, 96 },
96 rollback: function(cb) { 97 rollback: function(cb) {
97 LOGGER.trace('SQL: rollback'); 98 LOGGER.trace('SQL: rollback');
98 if(this.over) return cb(new Error('game over')); 99 if(this.over) return cb(new Error('game over'));
99 return (this.over=true),C.query('ROLLBACK',function(e){D();cb&&cb(e)}); 100 return (this.over=true),C.query('ROLLBACK',function(e){D();cb&&cb(e)});
100 }, 101 },
101 end: function(e,cb) { 102 end: function(e,cb) {
102 if(e) return LOGGER.trace(e,"rolling back transaction due to an error"),this.rollback(cb); 103 if(e) return LOGGER.trace(e,"rolling back transaction due to an error"),this.rollback(cb);
103 this.commit(cb); 104 this.commit(cb);
104 } 105 }
105 }); 106 });
106 }); 107 });
107 }); 108 });
108 } 109 }
109 }; 110 };
110 111
111 112
112 var rv = { 113 var rv = {
113 114
114 json: function clipperz_json(req,res,cb) { 115 json: function clipperz_json(req,res,cb) {
115 var method = req.body.method, pp = JSON.parse(req.body.parameters).parameters; 116 var method = req.body.method, pp = JSON.parse(req.body.parameters).parameters;
116 var message = pp.message; 117 var message = pp.message;
117 var ppp = pp.parameters; 118 var ppp = pp.parameters;
118 res.res = function(o) { return res.json({result:o}) }; 119 res.res = function(o) { return res.json({result:o}) };
119 LOGGER.trace({method:method,parameters:pp},"JSON request"); 120 LOGGER.trace({method:method,parameters:pp},"JSON request");
120 121
121 switch(method) { 122 switch(method) {
122 case 'registration': 123 case 'registration':
123 switch(message) { 124 switch(message) {
124 case 'completeRegistration': return PG.Q( 125 case 'completeRegistration': return PG.Q(
125 "INSERT INTO clipperz.theuser" 126 "INSERT INTO clipperz.theuser"
126 +" (u_name, u_srp_s,u_srp_v, u_authversion,u_header,u_statistics,u_version,u_lock)" 127 +" (u_name, u_srp_s,u_srp_v, u_authversion,u_header,u_statistics,u_version,u_lock)"
127 +" VALUES ($1, $2,$3, $4,$5,$6,$7,$8)", 128 +" VALUES ($1, $2,$3, $4,$5,$6,$7,$8)",
128 [pp.credentials.C, pp.credentials.s, pp.credentials.v, 129 [pp.credentials.C, pp.credentials.s, pp.credentials.v,
129 pp.credentials.version,pp.user.header, pp.user.statistics, 130 pp.credentials.version,pp.user.header, pp.user.statistics,
130 pp.user.version, pp.user.lock], function(e,r) { 131 pp.user.version, pp.user.lock], function(e,r) {
131 if(e) return cb(e); 132 if(e) return cb(e);
132 res.res({lock:pp.user.lock,result:'done'}); 133 res.res({lock:pp.user.lock,result:'done'});
133 }); 134 });
134 } 135 }
135 break; 136 break;
136 137
137 case 'handshake': 138 case 'handshake':
138 switch(message) { 139 switch(message) {
139 case 'connect': return ASYNC.auto({ 140 case 'connect': return ASYNC.auto({
140 u: function(cb) { PG.Q( 141 u: function(cb) { PG.Q(
141 "SELECT u_id, u_srp_s, u_srp_v FROM clipperz.theuser WHERE u_name=$1", 142 "SELECT u_id, u_srp_s, u_srp_v FROM clipperz.theuser WHERE u_name=$1",
142 [ppp.C], function(e,r) { 143 [ppp.C], function(e,r) {
143 if(e) return cb(e); 144 if(e) return cb(e);
144 if(!r.rowCount) return cb(null,{u_id:null,u_srp_s:n123,u_srp_v:n123}); 145 if(!r.rowCount) return cb(null,{u_id:null,u_srp_s:n123,u_srp_v:n123});
145 cb(null,r.rows[0]); 146 cb(null,r.rows[0]);
146 }) }, 147 }) },
147 otp: ['u',function(cb,r) { 148 otp: ['u',function(cb,r) {
148 if(!req.session.otp) return cb(); 149 if(!req.session.otp) return cb();
149 if(req.session.u!=r.u.u_id) return cb(new Error('user/OTP mismatch')); 150 if(req.session.u!=r.u.u_id) return cb(new Error('user/OTP mismatch'));
150 PG.Q( 151 PG.Q(
151 "UPDATE clipperz.theotp AS otp" 152 "UPDATE clipperz.theotp AS otp"
152 +" SET" 153 +" SET"
153 +" otps_id=CASE WHEN s.otps_code='REQUESTED' THEN (" 154 +" otps_id=CASE WHEN s.otps_code='REQUESTED' THEN ("
154 +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code='USED'" 155 +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code='USED'"
155 +" ) ELSE otp.otps_id END," 156 +" ) ELSE otp.otps_id END,"
156 +" otp_utime=current_timestamp" 157 +" otp_utime=current_timestamp"
157 +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o" 158 +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o"
158 +" WHERE" 159 +" WHERE"
159 +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id" 160 +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id"
160 +" AND otp.otp_id=$1 AND otp.u_id=$2" 161 +" AND otp.otp_id=$1 AND otp.u_id=$2"
161 +" RETURNING o.otps_id!=otp.otps_id AS yes, o.otp_ref", 162 +" RETURNING o.otps_id!=otp.otps_id AS yes, o.otp_ref",
162 [ req.session.otp, req.session.u ], 163 [ req.session.otp, req.session.u ],
163 function(e,r) { 164 function(e,r) {
164 if(e) return cb(e); 165 if(e) return cb(e);
165 if(!r.rowCount) return cb(new Error('no OTP found')); 166 if(!r.rowCount) return cb(new Error('no OTP found'));
166 r=r.rows[0]; 167 r=r.rows[0];
167 if(!r.yes) return cb(new Error('OTP is in a sorry state')); 168 if(!r.yes) return cb(new Error('OTP is in a sorry state'));
168 cb(null,{ref:r.otp_ref}); 169 cb(null,{ref:r.otp_ref});
169 }); 170 });
170 }] 171 }]
171 },function(e,r) { 172 },function(e,r) {
172 if(e) return cb(e); 173 if(e) return cb(e);
173 req.session.C = ppp.C; req.session.A = ppp.A; 174 req.session.C = ppp.C; req.session.A = ppp.A;
174 req.session.s = r.u.u_srp_s; req.session.v = r.u.u_srp_v; 175 req.session.s = r.u.u_srp_s; req.session.v = r.u.u_srp_v;
175 req.session.u = r.u.u_id; 176 req.session.u = r.u.u_id;
176 req.session.b = clipperz_random(); 177 req.session.b = clipperz_random();
177 req.session.B = BIGNUM(req.session.v,16).add(srp_g.powm(BIGNUM(req.session.b,16),srp_n)).toString(16); 178 req.session.B = BIGNUM(req.session.v,16).add(srp_g.powm(BIGNUM(req.session.b,16),srp_n)).toString(16);
178 var rv = {s:req.session.s,B:req.session.B} 179 var rv = {s:req.session.s,B:req.session.B}
179 if(r.otp && r.otp.otp_ref) rv.oneTimePassword=r.otp.otp_ref; 180 if(r.otp && r.otp.otp_ref) rv.oneTimePassword=r.otp.otp_ref;
180 res.res(rv); 181 res.res(rv);
181 }); 182 });
182 183
183 case 'credentialCheck': 184 case 'credentialCheck':
184 var u = clipperz_hash(BIGNUM(req.session.B,16).toString(10)); 185 var u = clipperz_hash(BIGNUM(req.session.B,16).toString(10));
185 var A = BIGNUM(req.session.A,16); 186 var A = BIGNUM(req.session.A,16);
186 var S = A.mul(BIGNUM(req.session.v,16).powm(BIGNUM(u,16),srp_n)).powm( 187 var S = A.mul(BIGNUM(req.session.v,16).powm(BIGNUM(u,16),srp_n)).powm(
187 BIGNUM(req.session.b,16), srp_n); 188 BIGNUM(req.session.b,16), srp_n);
188 var K = clipperz_hash(S.toString(10)); 189 var K = clipperz_hash(S.toString(10));
189 var M1 = clipperz_hash(A.toString(10)+BIGNUM(req.session.B,16).toString(10)+K.toString(16)); 190 var M1 = clipperz_hash(A.toString(10)+BIGNUM(req.session.B,16).toString(10)+K.toString(16));
190 if(M1!=ppp.M1) return res.res({error:'?'}); 191 if(M1!=ppp.M1) return res.res({error:'?'});
191 req.session.K = K; 192 req.session.K = K;
192 var M2 = clipperz_hash(A.toString(10)+M1+K.toString(16)); 193 var M2 = clipperz_hash(A.toString(10)+M1+K.toString(16));
193 return res.res({M2:M2,connectionId:'',loginInfo:{latest:{},current:{}},offlineCopyNeeded:false,lock:'----'}); 194 return res.res({M2:M2,connectionId:'',loginInfo:{latest:{},current:{}},offlineCopyNeeded:false,lock:'----'});
194 195
195 case 'oneTimePassword': return PG.Q( 196 case 'oneTimePassword': return PG.Q(
196 "UPDATE clipperz.theotp AS otp" 197 "UPDATE clipperz.theotp AS otp"
197 +" SET" 198 +" SET"
198 +" otps_id = CASE WHEN s.otps_code!='ACTIVE' THEN s.otps_id ELSE (" 199 +" otps_id = CASE WHEN s.otps_code!='ACTIVE' THEN s.otps_id ELSE ("
199 +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code=CASE" 200 +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code=CASE"
200 +" WHEN otp.otp_key_checksum=$2 THEN 'REQUESTED'" 201 +" WHEN otp.otp_key_checksum=$2 THEN 'REQUESTED'"
201 +" ELSE 'DISABLED' END" 202 +" ELSE 'DISABLED' END"
202 +" ) END," 203 +" ) END,"
203 +" otp_data = CASE WHEN s.otps_code='ACTIVE' THEN '' ELSE otp.otp_data END," 204 +" otp_data = CASE WHEN s.otps_code='ACTIVE' THEN '' ELSE otp.otp_data END,"
204 +" otp_utime = current_timestamp," 205 +" otp_utime = current_timestamp,"
205 +" otp_rtime = CASE WHEN otp.otp_key_checksum=$2 THEN current_timestamp ELSE otp.otp_rtime END" 206 +" otp_rtime = CASE WHEN otp.otp_key_checksum=$2 THEN current_timestamp ELSE otp.otp_rtime END"
206 +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o" 207 +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o"
207 +" WHERE" 208 +" WHERE"
208 +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id AND otp.otp_key=$1" 209 +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id AND otp.otp_key=$1"
209 +" RETURNING otp.u_id, s.otps_code, otp.otp_id, otp.otp_key_checksum, o.otp_data, otp.otp_version", 210 +" RETURNING otp.u_id, s.otps_code, otp.otp_id, otp.otp_key_checksum, o.otp_data, otp.otp_version",
210 [ ppp.oneTimePasswordKey, ppp.oneTimePasswordKeyChecksum ], 211 [ ppp.oneTimePasswordKey, ppp.oneTimePasswordKeyChecksum ],
211 function(e,r) { 212 function(e,r) {
212 if(e) return cb(e); 213 if(e) return cb(e);
213 if(!r.rowCount) return cb(new Error('OTP not found')); 214 if(!r.rowCount) return cb(new Error('OTP not found'));
214 r=r.rows[0]; 215 r=r.rows[0];
215 if(r.otp_key_checksum!=ppp.oneTimePasswordKeyChecksum) 216 if(r.otp_key_checksum!=ppp.oneTimePasswordKeyChecksum)
216 return cb(new Error('OTP was disabled because of checksum mismatch')); 217 return cb(new Error('OTP was disabled because of checksum mismatch'));
217 if(r.otps_code!='ACTIVE') 218 if(r.otps_code!='ACTIVE')
218 return cb(new Error("OTP wasn't active, sorry")); 219 return cb(new Error("OTP wasn't active, sorry"));
219 req.session.u=r.u_id; req.session.otp=r.otp_id; 220 req.session.u=r.u_id; req.session.otp=r.otp_id;
220 res.res({data:r.otp_data,version:r.otp_version}); 221 res.res({data:r.otp_data,version:r.otp_version});
221 }); 222 });
222 } 223 }
223 break; 224 break;
224 225
225 case 'message': 226 case 'message':
226 if(!req.session.K) return res.res({result:'EXCEPTION',message:"effectively, we're missing a aconnection"}); 227 if(!req.session.K) return res.res({result:'EXCEPTION',message:"effectively, we're missing a aconnection"});
227 if(req.session.K!=pp.srpSharedSecret) return res.res({error:'Wrong shared secret!'}); 228 if(req.session.K!=pp.srpSharedSecret) return res.res({error:'Wrong shared secret!'});
228 switch(message) { 229 switch(message) {
229 case 'getUserDetails': return ASYNC.parallel({ 230 case 'getUserDetails': return ASYNC.parallel({
230 u: function(cb) { 231 u: function(cb) {
231 PG.Q("SELECT u_header,u_statistics,u_version FROM clipperz.theuser WHERE u_id=$1", 232 PG.Q("SELECT u_header,u_statistics,u_version FROM clipperz.theuser WHERE u_id=$1",
232 [req.session.u],function(e,r) { 233 [req.session.u],function(e,r) {
233 if(e) return cb(e); 234 if(e) return cb(e);
234 if(!r.rowCount) return cb(new Error("user's gone AWOL")); 235 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
235 cb(null,r.rows[0]); 236 cb(null,r.rows[0]);
236 }); 237 });
237 }, 238 },
238 stats: function(cb) { 239 stats: function(cb) {
239 PG.Q("SELECT r_ref,r_mtime FROM clipperz.therecord WHERE u_id=$1", 240 PG.Q("SELECT r_ref,r_mtime FROM clipperz.therecord WHERE u_id=$1",
240 [req.session.u],function(e,r) { 241 [req.session.u],function(e,r) {
241 if(e) return cb(e); 242 if(e) return cb(e);
242 cb(null,r.rows.reduce(function(p,r){p[r.r_ref]={updateDate:r.r_mtime};return p},{})); 243 cb(null,r.rows.reduce(function(p,r){p[r.r_ref]={updateDate:r.r_mtime};return p},{}));
243 }); 244 });
244 } 245 }
245 },function(e,r) { 246 },function(e,r) {
246 if(e) return cb(e); 247 if(e) return cb(e);
247 res.res({header:r.u.u_header,statistics:r.u.u_statistics,version:r.u.u_version,recordsStats:r.stats}); 248 res.res({header:r.u.u_header,statistics:r.u.u_statistics,version:r.u.u_version,recordsStats:r.stats});
248 }); 249 });
249 250
250 case 'saveChanges': return PG.T(function(e,T) { 251 case 'saveChanges': return PG.T(function(e,T) {
251 if(e) return cb(e); 252 if(e) return cb(e);
252 ASYNC.auto({ 253 ASYNC.auto({
253 user: function(cb) { 254 user: function(cb) {
254 T.Q( 255 T.Q(
255 "UPDATE clipperz.theuser" 256 "UPDATE clipperz.theuser"
256 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)" 257 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)"
257 +" WHERE u_id=$5" 258 +" WHERE u_id=$5"
258 +" RETURNING u_lock",[ppp.user.header,ppp.user.statistics,ppp.user.version,ppp.user.lock||null,req.session.u], 259 +" RETURNING u_lock",[ppp.user.header,ppp.user.statistics,ppp.user.version,ppp.user.lock||null,req.session.u],
259 function(e,r) { 260 function(e,r) {
260 if(e) return cb(e); 261 if(e) return cb(e);
261 if(!r.rowCount) return cb(new Error("user's gone AWOL")); 262 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
262 cb(null,r.rows[0]); 263 cb(null,r.rows[0]);
263 }); 264 });
264 }, 265 },
265 updaterecords: function(cb) { 266 updaterecords: function(cb) {
266 if(!(ppp.records && ppp.records.updated && ppp.records.updated.length)) return cb(); 267 if(!(ppp.records && ppp.records.updated && ppp.records.updated.length)) return cb();
267 ASYNC.each(ppp.records.updated,function(r,cb) { 268 ASYNC.each(ppp.records.updated,function(r,cb) {
268 ASYNC.auto({ 269 ASYNC.auto({
269 updater: function(cb) { 270 updater: function(cb) {
270 T.Q( 271 T.Q(
271 "UPDATE clipperz.therecord" 272 "UPDATE clipperz.therecord"
272 +" SET r_data=$2, r_version=$3, r_mtime=current_timestamp" 273 +" SET r_data=$2, r_version=$3, r_mtime=current_timestamp"
273 +" WHERE r_ref=$1 AND u_id=$4 RETURNING r_id", 274 +" WHERE r_ref=$1 AND u_id=$4 RETURNING r_id",
274 [r.record.reference,r.record.data,r.record.version,req.session.u], function(e,r) { 275 [r.record.reference,r.record.data,r.record.version,req.session.u], function(e,r) {
275 if(e) return cb(e); 276 if(e) return cb(e);
276 return cb(null,r.rows.length?r.rows[0]:null); 277 return cb(null,r.rows.length?r.rows[0]:null);
277 }); 278 });
278 }, 279 },
279 insertr: ['updater',function(cb,rr) { 280 insertr: ['updater',function(cb,rr) {
280 if(rr.updater) return cb(); 281 if(rr.updater) return cb();
281 T.Q( 282 T.Q(
282 "INSERT INTO clipperz.therecord" 283 "INSERT INTO clipperz.therecord"
283 +" (u_id,r_ref,r_data,r_version)" 284 +" (u_id,r_ref,r_data,r_version)"
284 +" VALUES ($1,$2,$3,$4) RETURNING r_id",[req.session.u,r.record.reference,r.record.data,r.record.version], 285 +" VALUES ($1,$2,$3,$4) RETURNING r_id",[req.session.u,r.record.reference,r.record.data,r.record.version],
285 function(e,r) { 286 function(e,r) {
286 if(e) return cb(e); 287 if(e) return cb(e);
287 return cb(null,r.rows[0]); 288 return cb(null,r.rows[0]);
288 }); 289 });
289 }], 290 }],
290 updatev: ['updater','insertr',function(cb,rr) { 291 updatev: ['updater','insertr',function(cb,rr) {
291 var crv=r.currentRecordVersion; 292 var crv=r.currentRecordVersion;
292 T.Q( 293 T.Q(
293 "UPDATE clipperz.therecordversion" 294 "UPDATE clipperz.therecordversion"
294 +" SET rv_ref=$1, rv_data=$2, rv_version=$3," 295 +" SET rv_ref=$1, rv_data=$2, rv_version=$3,"
295 +" rv_previous_id=COALESCE($4,rv_previous_id)," 296 +" rv_previous_id=COALESCE($4,rv_previous_id),"
296 +" rv_previous_key=$5, r_id=$6, rv_mtime=current_timestamp" 297 +" rv_previous_key=$5, r_id=$6, rv_mtime=current_timestamp"
297 +" WHERE" 298 +" WHERE"
298 +" rv_id=(SELECT rv_id FROM clipperz.therecordversion WHERE r_id=$6 ORDER BY r_id ASC LIMIT 1)" 299 +" rv_id=(SELECT rv_id FROM clipperz.therecordversion WHERE r_id=$6 ORDER BY r_id ASC LIMIT 1)"
299 +" RETURNING rv_id", 300 +" RETURNING rv_id",
300 [crv.reference,crv.data,crv.version, 301 [crv.reference,crv.data,crv.version,
301 crv.previousVersion||null,crv.previousVersionKey, 302 crv.previousVersion||null,crv.previousVersionKey,
302 (rr.updater||rr.insertr).r_id], 303 (rr.updater||rr.insertr).r_id],
303 function(e,r) { 304 function(e,r) {
304 if(e) return cb(e); 305 if(e) return cb(e);
305 return cb(null,r.rows.length?r.rows[0]:null); 306 return cb(null,r.rows.length?r.rows[0]:null);
306 }); 307 });
307 }], 308 }],
308 insertv: ['updatev',function(cb,rr) { 309 insertv: ['updatev',function(cb,rr) {
309 if(rr.updatev) return cb(); 310 if(rr.updatev) return cb();
310 var crv=r.currentRecordVersion; 311 var crv=r.currentRecordVersion;
311 T.Q( 312 T.Q(
312 "INSERT INTO clipperz.therecordversion" 313 "INSERT INTO clipperz.therecordversion"
313 +" (r_id,rv_ref,rv_data,rv_version,rv_previous_id,rv_previous_key)" 314 +" (r_id,rv_ref,rv_data,rv_version,rv_previous_id,rv_previous_key)"
314 +" VALUES ($1,$2,$3,$4,$5,$6) RETURNING rv_id", 315 +" VALUES ($1,$2,$3,$4,$5,$6) RETURNING rv_id",
315 [(rr.updater||rr.insertr).r_id, 316 [(rr.updater||rr.insertr).r_id,
316 crv.reference, crv.data, crv.version, 317 crv.reference, crv.data, crv.version,
317 crv.previousVersion||null,crv.previousVersionKey], 318 crv.previousVersion||null,crv.previousVersionKey],
318 function(e,r) { 319 function(e,r) {
319 if(e) return cb(e); 320 if(e) return cb(e);
320 return cb(null,r.rows[0]); 321 return cb(null,r.rows[0]);
321 }); 322 });
322 }] 323 }]
323 },cb); 324 },cb);
324 },cb); 325 },cb);
325 }, 326 },
326 deleterecords: function(cb) { 327 deleterecords: function(cb) {
327 if(!(ppp.records && ppp.records.deleted && ppp.records.deleted.length)) return cb(); 328 if(!(ppp.records && ppp.records.deleted && ppp.records.deleted.length)) return cb();
328 T.Q( 329 T.Q(
329 "DELETE FROM clipperz.therecord" 330 "DELETE FROM clipperz.therecord"
330 +" WHERE r_ref = ANY($1::text[]) AND u_id=$2", 331 +" WHERE r_ref = ANY($1::text[]) AND u_id=$2",
331 [ '{'+ppp.records.deleted.join(',')+'}', req.session.u ], cb); 332 [ '{'+ppp.records.deleted.join(',')+'}', req.session.u ], cb);
332 } 333 }
333 },function(e,r) { 334 },function(e,r) {
334 T.end(e, function(e) { 335 T.end(e, function(e) {
335 if(e) return cb(e); 336 if(e) return cb(e);
336 res.res({result:'done',lock:r.user.u_lock}); 337 res.res({result:'done',lock:r.user.u_lock});
337 }); 338 });
338 }); 339 });
339 }); 340 });
340 341
341 case 'getRecordDetail': return ASYNC.auto({ // TODO: could be done in one query instead 342 case 'getRecordDetail': return ASYNC.auto({ // TODO: could be done in one query instead
342 record: function(cb) { 343 record: function(cb) {
343 PG.Q( 344 PG.Q(
344 "SELECT r_id,r_ref,r_data,r_version, r_ctime, r_mtime, r_atime FROM clipperz.therecord WHERE r_ref=$1", 345 "SELECT r_id,r_ref,r_data,r_version, r_ctime, r_mtime, r_atime FROM clipperz.therecord WHERE r_ref=$1",
345 [ppp.reference],function(e,r) { 346 [ppp.reference],function(e,r) {
346 if(e) return cb(e); 347 if(e) return cb(e);
347 if(!r.rowCount) return cb(new Error('no record found')); 348 if(!r.rowCount) return cb(new Error('no record found'));
348 return cb(null,r.rows[0]); 349 return cb(null,r.rows[0]);
349 }); 350 });
350 }, 351 },
351 version: ['record',function(cb,r) { 352 version: ['record',function(cb,r) {
352 PG.Q( 353 PG.Q(
353 "SELECT rv_ref, rv_data, rv_header, rv_version, rv_ctime, rv_mtime, rv_atime" 354 "SELECT rv_ref, rv_data, rv_header, rv_version, rv_ctime, rv_mtime, rv_atime"
354 +" FROM clipperz.therecordversion WHERE r_id=$1 ORDER BY rv_id ASC LIMIT 1", 355 +" FROM clipperz.therecordversion WHERE r_id=$1 ORDER BY rv_id ASC LIMIT 1",
355 [r.record.r_id],function(e,r) { 356 [r.record.r_id],function(e,r) {
356 if(e) return cb(e); 357 if(e) return cb(e);
357 if(!r.rowCount) return cb(new Error('no record version found')); 358 if(!r.rowCount) return cb(new Error('no record version found'));
358 return cb(null,r.rows[0]); 359 return cb(null,r.rows[0]);
359 }); 360 });
360 }] 361 }]
361 },function(e,r) { 362 },function(e,r) {
362 if(e) return cb(e); 363 if(e) return cb(e);
363 var v = {}; 364 var v = {};
364 v[r.version.rv_ref] = { 365 v[r.version.rv_ref] = {
365 reference: r.version.rv_ref, 366 reference: r.version.rv_ref,
366 data: r.version.rv_data, header: r.version.rv_header, 367 data: r.version.rv_data, header: r.version.rv_header,
367 version: r.version.rv_version, 368 version: r.version.rv_version,
368 creationDate: r.version.rv_ctime, updateDate: r.version.rv_mtime, accessDate: r.version.rv_atime 369 creationDate: r.version.rv_ctime, updateDate: r.version.rv_mtime, accessDate: r.version.rv_atime
369 }; 370 };
370 res.res({ versions: v, currentVersion: r.version.rv_ref, reference: r.record.r_ref, 371 res.res({ versions: v, currentVersion: r.version.rv_ref, reference: r.record.r_ref,
371 data: r.record.r_data, version: r.record.r_version, 372 data: r.record.r_data, version: r.record.r_version,
372 creationDate: r.record.r_ctime, updateDate: r.record.r_mtime, accessDate: r.record.r_atime, 373 creationDate: r.record.r_ctime, updateDate: r.record.r_mtime, accessDate: r.record.r_atime,
373 oldestUsedEncryptedVersion: '---' }); 374 oldestUsedEncryptedVersion: '---' });
374 }); 375 });
375 376
376 case 'addNewOneTimePassword': return PG.T(function(e,T) { 377 case 'addNewOneTimePassword': return PG.T(function(e,T) {
377 if(e) return cb(e); 378 if(e) return cb(e);
378 ASYNC.parallel({ 379 ASYNC.parallel({
379 otp: function(cb) { 380 otp: function(cb) {
380 var otp = ppp.oneTimePassword; 381 var otp = ppp.oneTimePassword;
381 T.Q( 382 T.Q(
382 "INSERT INTO clipperz.theotp" 383 "INSERT INTO clipperz.theotp"
383 +" (u_id,otp_ref,otp_key,otp_key_checksum,otp_data,otp_version,otps_id)" 384 +" (u_id,otp_ref,otp_key,otp_key_checksum,otp_data,otp_version,otps_id)"
384 +" SELECT $1,$2,$3,$4,$5,$6,otps_id FROM clipperz.otpstatus" 385 +" SELECT $1,$2,$3,$4,$5,$6,otps_id FROM clipperz.otpstatus"
385 +" WHERE otps_code='ACTIVE'", 386 +" WHERE otps_code='ACTIVE'",
386 [ req.session.u, otp.reference, otp.key, otp.keyChecksum, 387 [ req.session.u, otp.reference, otp.key, otp.keyChecksum,
387 otp.data, otp.version], function(e,r) { 388 otp.data, otp.version], function(e,r) {
388 if(e) return cb(e); 389 if(e) return cb(e);
389 if(!r.rowCount) return cb(new Error('no user or status')); 390 if(!r.rowCount) return cb(new Error('no user or status'));
390 cb(); 391 cb();
391 }); 392 });
392 }, 393 },
393 user: function(cb) { 394 user: function(cb) {
394 var u = ppp.user; 395 var u = ppp.user;
395 T.Q( 396 T.Q(
396 "UPDATE clipperz.theuser" 397 "UPDATE clipperz.theuser"
397 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)" 398 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)"
398 +" WHERE u_id=$5", 399 +" WHERE u_id=$5",
399 [ u.header, u.statistics, u.version, u.lock||null, req.session.u], 400 [ u.header, u.statistics, u.version, u.lock||null, req.session.u],
400 function(e,r) { 401 function(e,r) {
401 if(e) return cb(e); 402 if(e) return cb(e);
402 if(!r.rowCount) return cb(new Error("user's gone AWOL")); 403 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
403 cb(); 404 cb();
404 }); 405 });
405 } 406 }
406 },function(e,r) { 407 },function(e,r) {
407 T.end(e, function(e) { 408 T.end(e, function(e) {
408 if(e) return cb(e); 409 if(e) return cb(e);
409 res.res({result:'done',lock:ppp.user.lock}); 410 res.res({result:'done',lock:ppp.user.lock});
410 }); 411 });
411 }); 412 });
412 }); 413 });
413 414
414 case 'updateOneTimePasswords': return PG.T(function(e,T) { 415 case 'updateOneTimePasswords': return PG.T(function(e,T) {
415 if(e) return cb(e); 416 if(e) return cb(e);
416 ASYNC.parallel({ 417 ASYNC.parallel({
417 otp: function(cb) { 418 otp: function(cb) {
418 T.Q( 419 T.Q(
419 "DELETE FROM clipperz.theotp" 420 "DELETE FROM clipperz.theotp"
420 +" WHERE u_id=$1" 421 +" WHERE u_id=$1"
421 +" AND NOT otp_ref = ANY($2::text[])", 422 +" AND NOT otp_ref = ANY($2::text[])",
422 [ req.session.u,'{'+ppp.oneTimePasswords.join(',')+'}' ],cb); 423 [ req.session.u,'{'+ppp.oneTimePasswords.join(',')+'}' ],cb);
423 }, 424 },
424 user: function(cb) { 425 user: function(cb) {
425 var u = ppp.user; 426 var u = ppp.user;
426 T.Q( 427 T.Q(
427 "UPDATE clipperz.theuser" 428 "UPDATE clipperz.theuser"
428 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)" 429 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)"
429 +" WHERE u_id=$5", 430 +" WHERE u_id=$5",
430 [ u.header, u.statistics, u.version, u.lock||null, req.session.u], 431 [ u.header, u.statistics, u.version, u.lock||null, req.session.u],
431 function(e,r) { 432 function(e,r) {
432 if(e) return cb(e); 433 if(e) return cb(e);
433 if(!r.rowCount) return cb(new Error("user's gone AWOL")); 434 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
434 cb(); 435 cb();
435 }); 436 });
436 } 437 }
437 },function(e,r) { 438 },function(e,r) {
438 T.end(e, function(e) { 439 T.end(e, function(e) {
439 if(e) return cb(e); 440 if(e) return cb(e);
440 res.res({result:ppp.user.lock}); 441 res.res({result:ppp.user.lock});
441 }); 442 });
442 }); 443 });
443 }); 444 });
444 445
445 case 'upgradeUserCredentials': return PG.T(function(e,T) { 446 case 'upgradeUserCredentials': return PG.T(function(e,T) {
446 if(e) return cb(e); 447 if(e) return cb(e);
447 ASYNC.parallel({ 448 ASYNC.parallel({
448 user: function(cb) { 449 user: function(cb) {
449 var u = ppp.user, c = ppp.credentials; 450 var u = ppp.user, c = ppp.credentials;
450 T.Q( 451 T.Q(
451 "UPDATE clipperz.theuser" 452 "UPDATE clipperz.theuser"
452 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)," 453 +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock),"
453 +" u_name=$5, u_srp_s=$6, u_srp_v=$7, u_authversion=$8" 454 +" u_name=$5, u_srp_s=$6, u_srp_v=$7, u_authversion=$8"
454 +" WHERE u_id=$9 RETURNING u_lock", 455 +" WHERE u_id=$9 RETURNING u_lock",
455 [ u.header,u.statistics,u.version,u.lock||null, 456 [ u.header,u.statistics,u.version,u.lock||null,
456 c.C,c.s,c.v,c.version, req.session.u ],function(e,r) { 457 c.C,c.s,c.v,c.version, req.session.u ],function(e,r) {
457 if(e) return cb(e); 458 if(e) return cb(e);
458 if(!r.rowCount) return cb(new Error("user's gone AWOL")); 459 if(!r.rowCount) return cb(new Error("user's gone AWOL"));
459 cb(e,r.rows[0]); 460 cb(e,r.rows[0]);
460 }); 461 });
461 }, 462 },
462 otp: function(cb) { 463 otp: function(cb) {