-rw-r--r-- | backend/node/src/clipperz.js | 2 |
1 files changed, 2 insertions, 0 deletions
diff --git a/backend/node/src/clipperz.js b/backend/node/src/clipperz.js index 72b6c9f..842de31 100644 --- a/backend/node/src/clipperz.js +++ b/backend/node/src/clipperz.js | |||
@@ -80,192 +80,194 @@ var CLIPPERZ = module.exports = function(CONFIG) { | |||
80 | if(e) return D(),cb(e); | 80 | if(e) return D(),cb(e); |
81 | LOGGER.trace('SQL: transaction begun'); | 81 | LOGGER.trace('SQL: transaction begun'); |
82 | cb(null,{ | 82 | cb(null,{ |
83 | Q: function(q,a,cb) { | 83 | Q: function(q,a,cb) { |
84 | LOGGER.trace({query:q,args:a},'SQL: %s',q); | 84 | LOGGER.trace({query:q,args:a},'SQL: %s',q); |
85 | if(this.over) return cb(new Error('game over')); | 85 | if(this.over) return cb(new Error('game over')); |
86 | if('function'===typeof a) cb=a,a=[]; | 86 | if('function'===typeof a) cb=a,a=[]; |
87 | var t0=new Date(); | 87 | var t0=new Date(); |
88 | C.query(q,a,function(e,r) { | 88 | C.query(q,a,function(e,r) { |
89 | var t1=new Date(), dt=t1-t0; | 89 | var t1=new Date(), dt=t1-t0; |
90 | LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount,err:e},"SQL query '%s' took %dms",q,dt); | 90 | LOGGER.trace({query:q,args:a,ms:dt,rows:r&&r.rowCount,err:e},"SQL query '%s' took %dms",q,dt); |
91 | cb(e,r); | 91 | cb(e,r); |
92 | }); | 92 | }); |
93 | }, | 93 | }, |
94 | commit: function(cb) { | 94 | commit: function(cb) { |
95 | LOGGER.trace('SQL: commit'); | 95 | LOGGER.trace('SQL: commit'); |
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('COMMIT',function(e){D();cb&&cb(e)}); | 97 | return (this.over=true),C.query('COMMIT',function(e){D();cb&&cb(e)}); |
98 | }, | 98 | }, |
99 | rollback: function(cb) { | 99 | rollback: function(cb) { |
100 | LOGGER.trace('SQL: rollback'); | 100 | LOGGER.trace('SQL: rollback'); |
101 | if(this.over) return cb(new Error('game over')); | 101 | if(this.over) return cb(new Error('game over')); |
102 | return (this.over=true),C.query('ROLLBACK',function(e){D();cb&&cb(e)}); | 102 | return (this.over=true),C.query('ROLLBACK',function(e){D();cb&&cb(e)}); |
103 | }, | 103 | }, |
104 | end: function(e,cb) { | 104 | end: function(e,cb) { |
105 | if(e) return LOGGER.trace(e,"rolling back transaction due to an error"),this.rollback(cb); | 105 | if(e) return LOGGER.trace(e,"rolling back transaction due to an error"),this.rollback(cb); |
106 | this.commit(cb); | 106 | this.commit(cb); |
107 | } | 107 | } |
108 | }); | 108 | }); |
109 | }); | 109 | }); |
110 | }); | 110 | }); |
111 | } | 111 | } |
112 | }; | 112 | }; |
113 | 113 | ||
114 | 114 | ||
115 | var rv = { | 115 | var rv = { |
116 | 116 | ||
117 | json: function clipperz_json(req,res,cb) { | 117 | json: function clipperz_json(req,res,cb) { |
118 | var method = req.body.method, pp = JSON.parse(req.body.parameters).parameters; | 118 | var method = req.body.method, pp = JSON.parse(req.body.parameters).parameters; |
119 | var message = pp.message; | 119 | var message = pp.message; |
120 | var ppp = pp.parameters; | 120 | var ppp = pp.parameters; |
121 | res.res = function(o) { return res.json({result:o}) }; | 121 | res.res = function(o) { return res.json({result:o}) }; |
122 | LOGGER.trace({method:method,parameters:pp},"JSON request"); | 122 | LOGGER.trace({method:method,parameters:pp},"JSON request"); |
123 | 123 | ||
124 | switch(method) { | 124 | switch(method) { |
125 | case 'registration': | 125 | case 'registration': |
126 | switch(message) { | 126 | switch(message) { |
127 | case 'completeRegistration': return PG.Q( | 127 | case 'completeRegistration': return PG.Q( |
128 | "INSERT INTO clipperz.theuser" | 128 | "INSERT INTO clipperz.theuser" |
129 | +" (u_name, u_srp_s,u_srp_v, u_authversion,u_header,u_statistics,u_version,u_lock)" | 129 | +" (u_name, u_srp_s,u_srp_v, u_authversion,u_header,u_statistics,u_version,u_lock)" |
130 | +" VALUES ($1, $2,$3, $4,$5,$6,$7,$8)", | 130 | +" VALUES ($1, $2,$3, $4,$5,$6,$7,$8)", |
131 | [pp.credentials.C, pp.credentials.s, pp.credentials.v, | 131 | [pp.credentials.C, pp.credentials.s, pp.credentials.v, |
132 | pp.credentials.version,pp.user.header, pp.user.statistics, | 132 | pp.credentials.version,pp.user.header, pp.user.statistics, |
133 | pp.user.version, pp.user.lock], function(e,r) { | 133 | pp.user.version, pp.user.lock], function(e,r) { |
134 | if(e) return cb(e); | 134 | if(e) return cb(e); |
135 | res.res({lock:pp.user.lock,result:'done'}); | 135 | res.res({lock:pp.user.lock,result:'done'}); |
136 | }); | 136 | }); |
137 | } | 137 | } |
138 | break; | 138 | break; |
139 | 139 | ||
140 | case 'handshake': | 140 | case 'handshake': |
141 | switch(message) { | 141 | switch(message) { |
142 | case 'connect': return ASYNC.auto({ | 142 | case 'connect': return ASYNC.auto({ |
143 | u: function(cb) { PG.Q( | 143 | u: function(cb) { PG.Q( |
144 | "SELECT u_id, u_srp_s, u_srp_v FROM clipperz.theuser WHERE u_name=$1", | 144 | "SELECT u_id, u_srp_s, u_srp_v FROM clipperz.theuser WHERE u_name=$1", |
145 | [ppp.C], function(e,r) { | 145 | [ppp.C], function(e,r) { |
146 | if(e) return cb(e); | 146 | if(e) return cb(e); |
147 | if(!r.rowCount) return cb(null,{u_id:null,u_srp_s:n123,u_srp_v:n123}); | 147 | if(!r.rowCount) return cb(null,{u_id:null,u_srp_s:n123,u_srp_v:n123}); |
148 | cb(null,r.rows[0]); | 148 | cb(null,r.rows[0]); |
149 | }) }, | 149 | }) }, |
150 | otp: ['u',function(cb,r) { | 150 | otp: ['u',function(cb,r) { |
151 | if(!req.session.otp) return cb(); | 151 | if(!req.session.otp) return cb(); |
152 | if(req.session.u!=r.u.u_id) return cb(new Error('user/OTP mismatch')); | 152 | if(req.session.u!=r.u.u_id) return cb(new Error('user/OTP mismatch')); |
153 | PG.Q( | 153 | PG.Q( |
154 | "UPDATE clipperz.theotp AS otp" | 154 | "UPDATE clipperz.theotp AS otp" |
155 | +" SET" | 155 | +" SET" |
156 | +" otps_id=CASE WHEN s.otps_code='REQUESTED' THEN (" | 156 | +" otps_id=CASE WHEN s.otps_code='REQUESTED' THEN (" |
157 | +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code='USED'" | 157 | +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code='USED'" |
158 | +" ) ELSE otp.otps_id END," | 158 | +" ) ELSE otp.otps_id END," |
159 | +" otp_utime=current_timestamp" | 159 | +" otp_utime=current_timestamp" |
160 | +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o" | 160 | +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o" |
161 | +" WHERE" | 161 | +" WHERE" |
162 | +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id" | 162 | +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id" |
163 | +" AND otp.otp_id=$1 AND otp.u_id=$2" | 163 | +" AND otp.otp_id=$1 AND otp.u_id=$2" |
164 | +" RETURNING o.otps_id!=otp.otps_id AS yes, o.otp_ref", | 164 | +" RETURNING o.otps_id!=otp.otps_id AS yes, o.otp_ref", |
165 | [ req.session.otp, req.session.u ], | 165 | [ req.session.otp, req.session.u ], |
166 | function(e,r) { | 166 | function(e,r) { |
167 | if(e) return cb(e); | 167 | if(e) return cb(e); |
168 | if(!r.rowCount) return cb(new Error('no OTP found')); | 168 | if(!r.rowCount) return cb(new Error('no OTP found')); |
169 | r=r.rows[0]; | 169 | r=r.rows[0]; |
170 | if(!r.yes) return cb(new Error('OTP is in a sorry state')); | 170 | if(!r.yes) return cb(new Error('OTP is in a sorry state')); |
171 | cb(null,{ref:r.otp_ref}); | 171 | cb(null,{ref:r.otp_ref}); |
172 | }); | 172 | }); |
173 | }] | 173 | }] |
174 | },function(e,r) { | 174 | },function(e,r) { |
175 | if(e) return cb(e); | 175 | if(e) return cb(e); |
176 | var A = BIGNUM(ppp.A,16); | ||
177 | if(A.eq(0)) return cb(new Error('Ground Zero')); | ||
176 | req.session.C = ppp.C; req.session.A = ppp.A; | 178 | req.session.C = ppp.C; req.session.A = ppp.A; |
177 | req.session.s = r.u.u_srp_s; req.session.v = r.u.u_srp_v; | 179 | req.session.s = r.u.u_srp_s; req.session.v = r.u.u_srp_v; |
178 | req.session.u = r.u.u_id; | 180 | req.session.u = r.u.u_id; |
179 | req.session.b = clipperz_random(); | 181 | req.session.b = clipperz_random(); |
180 | req.session.B = srp_k.mul(BIGNUM(req.session.v,16)).add(srp_g.powm(BIGNUM(req.session.b,16),srp_n)).toString(16); | 182 | req.session.B = srp_k.mul(BIGNUM(req.session.v,16)).add(srp_g.powm(BIGNUM(req.session.b,16),srp_n)).toString(16); |
181 | var rv = {s:req.session.s,B:req.session.B} | 183 | var rv = {s:req.session.s,B:req.session.B} |
182 | if(r.otp && r.otp.otp_ref) rv.oneTimePassword=r.otp.otp_ref; | 184 | if(r.otp && r.otp.otp_ref) rv.oneTimePassword=r.otp.otp_ref; |
183 | res.res(rv); | 185 | res.res(rv); |
184 | }); | 186 | }); |
185 | 187 | ||
186 | case 'credentialCheck': | 188 | case 'credentialCheck': |
187 | var u = clipperz_hash(BIGNUM(req.session.A,16).toString(10)+BIGNUM(req.session.B,16).toString(10)); | 189 | var u = clipperz_hash(BIGNUM(req.session.A,16).toString(10)+BIGNUM(req.session.B,16).toString(10)); |
188 | var A = BIGNUM(req.session.A,16); | 190 | var A = BIGNUM(req.session.A,16); |
189 | var S = A.mul(BIGNUM(req.session.v,16).powm(BIGNUM(u,16),srp_n)).powm(BIGNUM(req.session.b,16),srp_n); | 191 | var S = A.mul(BIGNUM(req.session.v,16).powm(BIGNUM(u,16),srp_n)).powm(BIGNUM(req.session.b,16),srp_n); |
190 | var K = clipperz_hash(S.toString(10)); | 192 | var K = clipperz_hash(S.toString(10)); |
191 | var M1 = clipperz_hash( | 193 | var M1 = clipperz_hash( |
192 | srp_hn | 194 | srp_hn |
193 | +clipperz_hash(req.session.C) | 195 | +clipperz_hash(req.session.C) |
194 | +BIGNUM(req.session.s,16).toString(10) | 196 | +BIGNUM(req.session.s,16).toString(10) |
195 | +A.toString(10) | 197 | +A.toString(10) |
196 | +BIGNUM(req.session.B,16).toString(10) | 198 | +BIGNUM(req.session.B,16).toString(10) |
197 | +K ); | 199 | +K ); |
198 | if(M1!=ppp.M1) return res.res({error:'?'}); | 200 | if(M1!=ppp.M1) return res.res({error:'?'}); |
199 | req.session.K = K; | 201 | req.session.K = K; |
200 | var M2 = clipperz_hash(A.toString(10)+M1+K.toString(16)); | 202 | var M2 = clipperz_hash(A.toString(10)+M1+K.toString(16)); |
201 | return res.res({M2:M2,connectionId:'',loginInfo:{latest:{},current:{}},offlineCopyNeeded:false,lock:'----'}); | 203 | return res.res({M2:M2,connectionId:'',loginInfo:{latest:{},current:{}},offlineCopyNeeded:false,lock:'----'}); |
202 | 204 | ||
203 | case 'oneTimePassword': return PG.Q( | 205 | case 'oneTimePassword': return PG.Q( |
204 | "UPDATE clipperz.theotp AS otp" | 206 | "UPDATE clipperz.theotp AS otp" |
205 | +" SET" | 207 | +" SET" |
206 | +" otps_id = CASE WHEN s.otps_code!='ACTIVE' THEN s.otps_id ELSE (" | 208 | +" otps_id = CASE WHEN s.otps_code!='ACTIVE' THEN s.otps_id ELSE (" |
207 | +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code=CASE" | 209 | +" SELECT ss.otps_id FROM clipperz.otpstatus AS ss WHERE ss.otps_code=CASE" |
208 | +" WHEN otp.otp_key_checksum=$2 THEN 'REQUESTED'" | 210 | +" WHEN otp.otp_key_checksum=$2 THEN 'REQUESTED'" |
209 | +" ELSE 'DISABLED' END" | 211 | +" ELSE 'DISABLED' END" |
210 | +" ) END," | 212 | +" ) END," |
211 | +" otp_data = CASE WHEN s.otps_code='ACTIVE' THEN '' ELSE otp.otp_data END," | 213 | +" otp_data = CASE WHEN s.otps_code='ACTIVE' THEN '' ELSE otp.otp_data END," |
212 | +" otp_utime = current_timestamp," | 214 | +" otp_utime = current_timestamp," |
213 | +" otp_rtime = CASE WHEN otp.otp_key_checksum=$2 THEN current_timestamp ELSE otp.otp_rtime END" | 215 | +" otp_rtime = CASE WHEN otp.otp_key_checksum=$2 THEN current_timestamp ELSE otp.otp_rtime END" |
214 | +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o" | 216 | +" FROM clipperz.otpstatus AS s, clipperz.theotp AS o" |
215 | +" WHERE" | 217 | +" WHERE" |
216 | +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id AND otp.otp_key=$1" | 218 | +" o.otp_id=otp.otp_id AND otp.otps_id=s.otps_id AND otp.otp_key=$1" |
217 | +" RETURNING otp.u_id, s.otps_code, otp.otp_id, otp.otp_key_checksum, o.otp_data, otp.otp_version", | 219 | +" RETURNING otp.u_id, s.otps_code, otp.otp_id, otp.otp_key_checksum, o.otp_data, otp.otp_version", |
218 | [ ppp.oneTimePasswordKey, ppp.oneTimePasswordKeyChecksum ], | 220 | [ ppp.oneTimePasswordKey, ppp.oneTimePasswordKeyChecksum ], |
219 | function(e,r) { | 221 | function(e,r) { |
220 | if(e) return cb(e); | 222 | if(e) return cb(e); |
221 | if(!r.rowCount) return cb(new Error('OTP not found')); | 223 | if(!r.rowCount) return cb(new Error('OTP not found')); |
222 | r=r.rows[0]; | 224 | r=r.rows[0]; |
223 | if(r.otp_key_checksum!=ppp.oneTimePasswordKeyChecksum) | 225 | if(r.otp_key_checksum!=ppp.oneTimePasswordKeyChecksum) |
224 | return cb(new Error('OTP was disabled because of checksum mismatch')); | 226 | return cb(new Error('OTP was disabled because of checksum mismatch')); |
225 | if(r.otps_code!='ACTIVE') | 227 | if(r.otps_code!='ACTIVE') |
226 | return cb(new Error("OTP wasn't active, sorry")); | 228 | return cb(new Error("OTP wasn't active, sorry")); |
227 | req.session.u=r.u_id; req.session.otp=r.otp_id; | 229 | req.session.u=r.u_id; req.session.otp=r.otp_id; |
228 | res.res({data:r.otp_data,version:r.otp_version}); | 230 | res.res({data:r.otp_data,version:r.otp_version}); |
229 | }); | 231 | }); |
230 | } | 232 | } |
231 | break; | 233 | break; |
232 | 234 | ||
233 | case 'message': | 235 | case 'message': |
234 | if(!req.session.K) return res.res({result:'EXCEPTION',message:"effectively, we're missing a aconnection"}); | 236 | if(!req.session.K) return res.res({result:'EXCEPTION',message:"effectively, we're missing a aconnection"}); |
235 | if(req.session.K!=pp.srpSharedSecret) return res.res({error:'Wrong shared secret!'}); | 237 | if(req.session.K!=pp.srpSharedSecret) return res.res({error:'Wrong shared secret!'}); |
236 | switch(message) { | 238 | switch(message) { |
237 | case 'getUserDetails': return ASYNC.parallel({ | 239 | case 'getUserDetails': return ASYNC.parallel({ |
238 | u: function(cb) { | 240 | u: function(cb) { |
239 | PG.Q("SELECT u_header,u_statistics,u_version FROM clipperz.theuser WHERE u_id=$1", | 241 | PG.Q("SELECT u_header,u_statistics,u_version FROM clipperz.theuser WHERE u_id=$1", |
240 | [req.session.u],function(e,r) { | 242 | [req.session.u],function(e,r) { |
241 | if(e) return cb(e); | 243 | if(e) return cb(e); |
242 | if(!r.rowCount) return cb(new Error("user's gone AWOL")); | 244 | if(!r.rowCount) return cb(new Error("user's gone AWOL")); |
243 | cb(null,r.rows[0]); | 245 | cb(null,r.rows[0]); |
244 | }); | 246 | }); |
245 | }, | 247 | }, |
246 | stats: function(cb) { | 248 | stats: function(cb) { |
247 | PG.Q("SELECT r_ref,r_mtime FROM clipperz.therecord WHERE u_id=$1", | 249 | PG.Q("SELECT r_ref,r_mtime FROM clipperz.therecord WHERE u_id=$1", |
248 | [req.session.u],function(e,r) { | 250 | [req.session.u],function(e,r) { |
249 | if(e) return cb(e); | 251 | if(e) return cb(e); |
250 | cb(null,r.rows.reduce(function(p,r){p[r.r_ref]={updateDate:r.r_mtime};return p},{})); | 252 | cb(null,r.rows.reduce(function(p,r){p[r.r_ref]={updateDate:r.r_mtime};return p},{})); |
251 | }); | 253 | }); |
252 | } | 254 | } |
253 | },function(e,r) { | 255 | },function(e,r) { |
254 | if(e) return cb(e); | 256 | if(e) return cb(e); |
255 | res.res({header:r.u.u_header,statistics:r.u.u_statistics,version:r.u.u_version,recordsStats:r.stats}); | 257 | res.res({header:r.u.u_header,statistics:r.u.u_statistics,version:r.u.u_version,recordsStats:r.stats}); |
256 | }); | 258 | }); |
257 | 259 | ||
258 | case 'saveChanges': return PG.T(function(e,T) { | 260 | case 'saveChanges': return PG.T(function(e,T) { |
259 | if(e) return cb(e); | 261 | if(e) return cb(e); |
260 | ASYNC.auto({ | 262 | ASYNC.auto({ |
261 | user: function(cb) { | 263 | user: function(cb) { |
262 | T.Q( | 264 | T.Q( |
263 | "UPDATE clipperz.theuser" | 265 | "UPDATE clipperz.theuser" |
264 | +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)" | 266 | +" SET u_header=$1, u_statistics=$2, u_version=$3, u_lock=COALESCE($4,u_lock)" |
265 | +" WHERE u_id=$5" | 267 | +" WHERE u_id=$5" |
266 | +" RETURNING u_lock",[ppp.user.header,ppp.user.statistics,ppp.user.version,ppp.user.lock||null,req.session.u], | 268 | +" RETURNING u_lock",[ppp.user.header,ppp.user.statistics,ppp.user.version,ppp.user.lock||null,req.session.u], |
267 | function(e,r) { | 269 | function(e,r) { |
268 | if(e) return cb(e); | 270 | if(e) return cb(e); |
269 | if(!r.rowCount) return cb(new Error("user's gone AWOL")); | 271 | if(!r.rowCount) return cb(new Error("user's gone AWOL")); |
270 | cb(null,r.rows[0]); | 272 | cb(null,r.rows[0]); |
271 | }); | 273 | }); |