Differences From
Artifact [17bd63f8c3]:
21 21 key = $1::text
22 22 ]];
23 23 };
24 24
25 25 actor_fetch_uid = {
26 26 params = {uint64}, sql = [[
27 27 select
28 - id, nym, handle, origin,
29 - bio, rank, quota, key
28 + id, nym, handle, origin, bio,
29 + avataruri, rank, quota, key,
30 + extract(epoch from knownsince)::bigint
31 +
30 32 from parsav_actors
31 33 where id = $1::bigint
32 34 ]];
33 35 };
34 36
35 37 actor_fetch_xid = {
36 38 params = {lib.mem.ptr(int8)}, sql = [[
37 - select a.id, a.nym, a.handle, a.origin,
38 - a.bio, a.rank, a.quota, a.key,
39 + select a.id, a.nym, a.handle, a.origin, a.bio,
40 + a.avataruri, a.rank, a.quota, a.key,
41 + extract(epoch from knownsince)::bigint,
39 42 coalesce(a.handle || '@' || s.domain,
40 43 '@' || a.handle) as xid,
41 44
42 45 coalesce(s.domain,
43 46 (select value from parsav_config
44 47 where key='domain' limit 1)) as domain
45 48
................................................................................
50 53 where $1::text = (a.handle || '@' || domain) or
51 54 $1::text = ('@' || a.handle || '@' || domain) or
52 55 (a.origin is null and
53 56 $1::text = a.handle or
54 57 $1::text = ('@' || a.handle))
55 58 ]];
56 59 };
60 +
61 + actor_auth_pw = {
62 + params = {lib.mem.ptr(int8),rawstring,lib.mem.ptr(int8),lib.store.inet}, sql = [[
63 + select a.aid from parsav_auth as a
64 + left join parsav_actors as u on u.id = a.uid
65 + where (a.uid is null or u.handle = $1::text or (
66 + a.uid = 0 and a.name = $1::text
67 + )) and
68 + (a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and
69 + (a.netmask is null or a.netmask >> $4::inet)
70 + order by blacklist desc limit 1
71 + ]];
72 + };
57 73
58 74 actor_enum_local = {
59 75 params = {}, sql = [[
60 - select id, nym, handle, origin,
61 - bio, rank, quota, key,
76 + select id, nym, handle, origin, bio,
77 + null::text, rank, quota, key,
78 + extract(epoch from knownsince)::bigint,
62 79 handle ||'@'||
63 80 (select value from parsav_config
64 81 where key='domain' limit 1) as xid
65 82 from parsav_actors where origin is null
66 83 ]];
67 84 };
68 85
69 86 actor_enum = {
70 87 params = {}, sql = [[
71 - select a.id, a.nym, a.handle, a.origin,
72 - a.bio, a.rank, a.quota, a.key,
88 + select a.id, a.nym, a.handle, a.origin, a.bio,
89 + a.avataruri, a.rank, a.quota, a.key,
90 + extract(epoch from knownsince)::bigint,
73 91 coalesce(a.handle || '@' || s.domain,
74 92 '@' || a.handle) as xid
75 93 from parsav_actors a
76 94 left join parsav_servers s on s.id = a.origin
77 95 ]];
78 96 };
97 +
98 + actor_stats = {
99 + params = {uint64}, sql = ([[
100 + with tweets as (
101 + select from parsav_posts where author = $1::bigint
102 + ),
103 + follows as (
104 + select relatee as user from parsav_rels
105 + where relator = $1::bigint and kind = <follow>
106 + ),
107 + followers as (
108 + select relator as user from parsav_rels
109 + where relatee = $1::bigint and kind = <follow>
110 + ),
111 + mutuals as (select * from follows intersect select * from followers)
112 +
113 + select count(tweets.*)::bigint,
114 + count(follows.*)::bigint,
115 + count(followers.*)::bigint,
116 + count(mutuals.*)::bigint
117 + from tweets, follows, followers, mutuals
118 + ]]):gsub('<(%w+)>',function(r) return tostring(lib.store.relation[r]) end)
119 + };
79 120
80 121 actor_auth_how = {
81 122 params = {rawstring, lib.store.inet}, sql = [[
82 123 with mts as (select a.kind from parsav_auth as a
83 124 left join parsav_actors as u on u.id = a.uid
84 125 where (a.uid is null or u.handle = $1::text or (
85 126 a.uid = 0 and a.name = $1::text
................................................................................
93 134 (select count(*) from mts where kind like 'challenge-%') > 0,
94 135 (select count(*) from mts where kind = 'trust') > 0
95 136 ]]; -- cheat
96 137 };
97 138
98 139 actor_session_fetch = {
99 140 params = {uint64, lib.store.inet}, sql = [[
100 - select a.id, a.nym, a.handle, a.origin,
101 - a.bio, a.rank, a.quota, a.key,
141 + select a.id, a.nym, a.handle, a.origin, a.bio,
142 + a.avataruri, a.rank, a.quota, a.key,
143 + extract(epoch from knownsince)::bigint,
102 144 coalesce(a.handle || '@' || s.domain,
103 145 '@' || a.handle) as xid,
104 146
105 147 au.restrict,
106 148 array['post' ] <@ au.restrict as can_post,
107 149 array['edit' ] <@ au.restrict as can_edit,
108 150 array['acct' ] <@ au.restrict as can_acct,
................................................................................
171 213 for j=0,sz do i.v6[j] = v[4 + j] end -- 😬
172 214 return i
173 215 end
174 216 pqr.methods.int = macro(function(self, ty, row, col)
175 217 return quote
176 218 var i: ty:astype()
177 219 var v = lib.pq.PQgetvalue(self.res, row, col)
220 + --i = @[&uint64](v)
178 221 lib.math.netswap_ip(ty, v, &i)
179 222 in i end
180 223 end)
181 224
182 225 local pqt = {
183 226 [lib.store.inet] = function(cidr)
184 227 local tycode = cidr and 0x01 or 0x00
................................................................................
216 259 end
217 260 lib.bail('could not prepare PGSQL statement ',k,': ',lib.pq.PQresultErrorMessage(res))
218 261 end
219 262 lib.dbg('prepared PGSQL statement ',k)
220 263 end
221 264
222 265 local args, casts, counters, fixers, ft, yield = {}, {}, {}, {}, {}, {}
266 + local dumpers = {}
223 267 for i, ty in ipairs(q.params) do
224 268 args[i] = symbol(ty)
225 269 ft[i] = `1
226 270 if ty == rawstring then
227 271 counters[i] = `lib.trn([args[i]] == nil, 0, lib.str.sz([args[i]]))
228 272 casts[i] = `[&int8]([args[i]])
273 + dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got rawstr %s\n'], [args[i]])
229 274 elseif ty == lib.store.inet then -- assume not CIDR
230 275 counters[i] = `lib.trn([args[i]].pv == 4,4,16)+4
231 276 casts[i] = quote
232 277 var ipbuf: int8[20]
233 278 ;[pqt[lib.store.inet](false)]([args[i]], [&uint8](&ipbuf))
234 279 in &ipbuf[0] end
280 + dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got inet\n'])
235 281 elseif ty.ptr_basetype == int8 or ty.ptr_basetype == uint8 then
236 282 counters[i] = `[args[i]].ct
237 283 casts[i] = `[&int8]([args[i]].ptr)
284 + dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got ptr %llu %.*s\n'], [args[i]].ct, [args[i]].ct, [args[i]].ptr)
238 285 elseif ty:isintegral() then
239 286 counters[i] = ty.bytes
240 287 casts[i] = `[&int8](&[args[i]])
288 + dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got int %llu\n'], [args[i]])
241 289 fixers[#fixers + 1] = quote
242 290 --lib.io.fmt('uid=%llu(%llx)\n',[args[i]],[args[i]])
243 291 [args[i]] = lib.math.netswap(ty, [args[i]])
244 292 end
245 293 end
246 294 end
247 295
248 296 terra q.exec(src: &lib.store.source, [args])
249 297 var params = arrayof([&int8], [casts])
250 298 var params_sz = arrayof(int, [counters])
251 299 var params_ft = arrayof(int, [ft])
252 300 [fixers]
301 + --[dumpers]
253 302 var res = lib.pq.PQexecPrepared([&lib.pq.PGconn](src.handle), stmt,
254 303 [#args], params, params_sz, params_ft, 1)
255 304 if res == nil then
256 305 lib.bail(['grievous error occurred executing '..k..' against database'])
257 306 elseif lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
258 307 lib.bail(['PGSQL database procedure '..k..' failed\n'],
259 308 lib.pq.PQresultErrorMessage(res))
................................................................................
268 317 end
269 318 end
270 319 end
271 320
272 321 local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
273 322 var a: lib.mem.ptr(lib.store.actor)
274 323
275 - if r:cols() >= 8 then
324 + if r:cols() >= 9 then
276 325 a = [ lib.str.encapsulate(lib.store.actor, {
277 326 nym = {`r:string(row,1), `r:len(row,1)+1};
278 327 bio = {`r:string(row,4), `r:len(row,4)+1};
328 + avatar = {`r:string(row,5), `r:len(row,5)+1};
279 329 handle = {`r:string(row, 2); `r:len(row,2) + 1};
280 - xid = {`r:string(row, 8); `r:len(row,8) + 1};
330 + xid = {`r:string(row, 10); `r:len(row,10) + 1};
281 331 }) ]
282 332 else
283 333 a = [ lib.str.encapsulate(lib.store.actor, {
284 334 nym = {`r:string(row,1), `r:len(row,1)+1};
285 335 bio = {`r:string(row,4), `r:len(row,4)+1};
336 + avatar = {`r:string(row,5), `r:len(row,5)+1};
286 337 handle = {`r:string(row, 2); `r:len(row,2) + 1};
287 338 }) ]
288 339 a.ptr.xid = nil
289 340 end
290 341 a.ptr.id = r:int(uint64, row, 0);
291 342 a.ptr.rights = lib.store.rights_default();
292 - a.ptr.rights.rank = r:int(uint16, row, 5);
293 - a.ptr.rights.quota = r:int(uint32, row, 6);
294 - if r:null(row,7) then
343 + a.ptr.rights.rank = r:int(uint16, row, 6);
344 + a.ptr.rights.quota = r:int(uint32, row, 7);
345 + a.ptr.knownsince = r:int(int64,row, 9);
346 + if r:null(row,8) then
295 347 a.ptr.key.ct = 0 a.ptr.key.ptr = nil
296 348 else
297 - a.ptr.key = r:bin(row,7)
349 + a.ptr.key = r:bin(row,8)
298 350 end
299 351 if r:null(row,3) then a.ptr.origin = 0
300 352 else a.ptr.origin = r:int(uint64,row,3) end
301 353 return a
302 354 end
303 355
304 -local checksha = function(hnd, query, hash, origin, username, pw)
305 - local inet_buf = symbol(uint8[4 + 16])
356 +local checksha = function(src, hash, origin, username, pw)
306 357 local validate = function(kind, cred, credlen)
307 358 return quote
308 - var osz: intptr if origin.pv == 4 then osz = 4 else osz = 16 end
309 - var formats = arrayof([int], 1,1,1,1)
310 - var params = arrayof([&int8], username, kind,
311 - [&int8](&cred), [&int8](&inet_buf))
312 - var lens = arrayof(int, lib.str.sz(username), [#kind], credlen, osz + 4)
313 - var res = lib.pq.PQexecParams([&lib.pq.PGconn](hnd), query, 4, nil,
314 - params, lens, formats, 1)
315 - if res == nil then
316 - lib.bail('grievous failure checking pwhash')
317 - elseif lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
318 - lib.warn('pwhash query failed: ', lib.pq.PQresultErrorMessage(res), '\n', query)
319 - else
320 - var r = pqr {
321 - sz = lib.pq.PQntuples(res);
322 - res = res;
323 - }
324 - if r.sz > 0 then -- found a record! stop here
325 - var aid = r:int(uint64, 0,0)
326 - r:free()
327 - return aid
328 - end
359 + var r = queries.actor_auth_pw.exec(
360 + [&lib.store.source](src),
361 + username,
362 + kind,
363 + [lib.mem.ptr(int8)] {ptr=[&int8](cred), ct=credlen},
364 + origin)
365 + if r.sz > 0 then -- found a record! stop here
366 + var aid = r:int(uint64, 0,0)
367 + r:free()
368 + return aid
329 369 end
330 370 end
331 371 end
332 372
333 373 local out = symbol(uint8[64])
334 374 local vdrs = {}
335 375
336 376 local alg = lib.md['MBEDTLS_MD_SHA' .. tostring(hash)]
337 377 vdrs[#vdrs+1] = quote
338 378 if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(alg),
339 - [&uint8](pw), lib.str.sz(pw), out) ~= 0 then
379 + [&uint8](pw.ptr), pw.ct, out) ~= 0 then
340 380 lib.bail('hashing failure!')
341 381 end
342 - [ validate(string.format('pw-sha%u', hash), out, hash / 8) ]
382 + [ validate(string.format('pw-sha%u', hash), `&out[0], hash / 8) ]
343 383 end
344 384
345 385 return quote
346 386 lib.dbg(['searching for hashed password credentials in format SHA' .. tostring(hash)])
347 - var [inet_buf]
348 - [pqt[lib.store.inet](false)](origin, inet_buf)
349 387 var [out]
350 388 [vdrs]
351 389 lib.dbg(['could not find password hash'])
352 390 end
353 391 end
354 392
355 393 local b = `lib.store.backend {
................................................................................
439 477 end
440 478 end];
441 479
442 480 actor_auth_how = [terra(
443 481 src: &lib.store.source,
444 482 ip: lib.store.inet,
445 483 username: rawstring
446 - )
484 + ): {lib.store.credset, bool}
447 485 var cs: lib.store.credset cs:clear();
448 486 var r = queries.actor_auth_how.exec(src, username, ip)
449 - if r.sz == 0 then return cs end -- just in case
487 + if r.sz == 0 then return cs, false end -- just in case
450 488 defer r:free()
451 489 (cs.pw << r:bool(0,0))
452 490 (cs.otp << r:bool(0,1))
453 491 (cs.challenge << r:bool(0,2))
454 492 (cs.trust << r:bool(0,3))
455 - return cs
493 + return cs, true
456 494 end];
457 495
458 496 actor_auth_pw = [terra(
459 497 src: &lib.store.source,
460 498 ip: lib.store.inet,
461 - username: rawstring,
462 - cred: rawstring
463 - )
464 - var q = [[select a.aid from parsav_auth as a
465 - left join parsav_actors as u on u.id = a.uid
466 - where (a.uid is null or u.handle = $1::text or (
467 - a.uid = 0 and a.name = $1::text
468 - )) and
469 - (a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and
470 - (a.netmask is null or a.netmask >> $4::inet)
471 - order by blacklist desc limit 1]]
499 + username: lib.mem.ptr(int8),
500 + cred: lib.mem.ptr(int8)
501 + ): uint64
472 502
473 - [ checksha(`src.handle, q, 256, ip, username, cred) ] -- most common
474 - [ checksha(`src.handle, q, 512, ip, username, cred) ] -- most secure
475 - [ checksha(`src.handle, q, 384, ip, username, cred) ] -- weird
476 - [ checksha(`src.handle, q, 224, ip, username, cred) ] -- weirdest
503 + [ checksha(`src, 256, ip, username, cred) ] -- most common
504 + [ checksha(`src, 512, ip, username, cred) ] -- most secure
505 + [ checksha(`src, 384, ip, username, cred) ] -- weird
506 + [ checksha(`src, 224, ip, username, cred) ] -- weirdest
477 507
478 508 -- TODO: check pbkdf2-hmac
479 509 -- TODO: check OTP
480 510 return 0
481 511 end];
512 +
513 + actor_stats = [terra(src: &lib.store.source, uid: uint64)
514 + var r = queries.actor_stats.exec(src, uid)
515 + if r.sz == 0 then lib.bail('error fetching actor stats!') end
516 + var s: lib.store.actor_stats
517 + s.posts = r:int(uint64, 0, 0)
518 + s.follows = r:int(uint64, 0, 1)
519 + s.followers = r:int(uint64, 0, 2)
520 + s.mutuals = r:int(uint64, 0, 3)
521 + return s
522 + end];
482 523
483 524 actor_session_fetch = [terra(
484 525 src: &lib.store.source,
485 526 aid: uint64,
486 527 ip : lib.store.inet
487 528 ): { lib.stat(lib.store.auth), lib.mem.ptr(lib.store.actor) }
488 529 var r = queries.actor_session_fetch.exec(src, aid, ip)
................................................................................
493 534
494 535 var a = row_to_actor(&r, 0)
495 536 a.ptr.source = src
496 537
497 538 var au = [lib.stat(lib.store.auth)] { ok = true }
498 539 au.val.aid = aid
499 540 au.val.uid = a.ptr.id
500 - if not r:null(0,10) then -- restricted?
541 + if not r:null(0,12) then -- restricted?
501 542 au.val.privs:clear()
502 - (au.val.privs.post << r:bool(0,11))
503 - (au.val.privs.edit << r:bool(0,12))
504 - (au.val.privs.acct << r:bool(0,13))
505 - (au.val.privs.upload << r:bool(0,14))
506 - (au.val.privs.censor << r:bool(0,15))
507 - (au.val.privs.admin << r:bool(0,16))
543 + (au.val.privs.post << r:bool(0,13))
544 + (au.val.privs.edit << r:bool(0,14))
545 + (au.val.privs.acct << r:bool(0,15))
546 + (au.val.privs.upload << r:bool(0,16))
547 + (au.val.privs.censor << r:bool(0,17))
548 + (au.val.privs.admin << r:bool(0,18))
508 549 else au.val.privs:fill() end
509 550
510 551 return au, a
511 552 end
512 553
513 554 ::fail:: return [lib.stat (lib.store.auth) ] { ok = false },
514 555 [lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 }
515 556 end];
516 557 }
517 558
518 559 return b