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,469 +1,469 @@
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"));