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