Differences From
Artifact [2c2a215381]:
6 6 params = {rawstring}, sql = [[
7 7 select value from parsav_config
8 8 where key = $1::text limit 1
9 9 ]];
10 10 };
11 11
12 12 conf_set = {
13 - params = {rawstring,rawstring}, sql = [[
13 + params = {rawstring,rawstring}, cmd=true, sql = [[
14 14 insert into parsav_config (key, value)
15 15 values ($1::text, $2::text)
16 16 on conflict (key) do update set value = $2::text
17 17 ]];
18 18 };
19 19
20 20 conf_reset = {
21 - params = {rawstring}, sql = [[
21 + params = {rawstring}, cmd=true, sql = [[
22 22 delete from parsav_config where
23 23 key = $1::text
24 24 ]];
25 25 };
26 26
27 27 actor_fetch_uid = {
28 28 params = {uint64}, sql = [[
29 29 select a.id, a.nym, a.handle, a.origin, a.bio,
30 - a.avataruri, a.rank, a.quota, a.key,
30 + a.avataruri, a.rank, a.quota, a.key, a.epithet,
31 31 extract(epoch from a.knownsince)::bigint,
32 32 coalesce(a.handle || '@' || s.domain,
33 33 '@' || a.handle) as xid
34 34
35 35 from parsav_actors as a
36 36 left join parsav_servers as s
37 37 on a.origin = s.id
................................................................................
38 38 where a.id = $1::bigint
39 39 ]];
40 40 };
41 41
42 42 actor_fetch_xid = {
43 43 params = {pstring}, sql = [[
44 44 select a.id, a.nym, a.handle, a.origin, a.bio,
45 - a.avataruri, a.rank, a.quota, a.key,
45 + a.avataruri, a.rank, a.quota, a.key, a.epithet,
46 46 extract(epoch from a.knownsince)::bigint,
47 47 coalesce(a.handle || '@' || s.domain,
48 48 '@' || a.handle) as xid,
49 49
50 50 coalesce(s.domain,
51 51 (select value from parsav_config
52 52 where key='domain' limit 1)) as domain
................................................................................
70 70 rawstring, uint16, uint32
71 71 };
72 72 sql = [[
73 73 insert into parsav_actors (
74 74 nym,handle,
75 75 origin,knownsince,
76 76 bio,avataruri,key,
77 - title,rank,quota
77 + epithet,rank,quota
78 78 ) values ($1::text, $2::text,
79 79 case when $3::bigint = 0 then null
80 80 else $3::bigint end,
81 81 to_timestamp($4::bigint),
82 82 $5::bigint, $6::bigint, $7::bytea,
83 83 $8::text, $9::smallint, $10::integer
84 84 ) returning id
................................................................................
98 98 order by blacklist desc limit 1
99 99 ]];
100 100 };
101 101
102 102 actor_enum_local = {
103 103 params = {}, sql = [[
104 104 select id, nym, handle, origin, bio,
105 - null::text, rank, quota, key,
105 + null::text, rank, quota, key, epithet,
106 106 extract(epoch from knownsince)::bigint,
107 107 handle ||'@'||
108 108 (select value from parsav_config
109 109 where key='domain' limit 1) as xid
110 110 from parsav_actors where origin is null
111 111 ]];
112 112 };
113 113
114 114 actor_enum = {
115 115 params = {}, sql = [[
116 116 select a.id, a.nym, a.handle, a.origin, a.bio,
117 - a.avataruri, a.rank, a.quota, a.key,
117 + a.avataruri, a.rank, a.quota, a.key, a.epithet,
118 118 extract(epoch from a.knownsince)::bigint,
119 119 coalesce(a.handle || '@' || s.domain,
120 120 '@' || a.handle) as xid
121 121 from parsav_actors a
122 122 left join parsav_servers s on s.id = a.origin
123 123 ]];
124 124 };
................................................................................
163 163 (select count(*) from mts where kind = 'trust') > 0
164 164 ]]; -- cheat
165 165 };
166 166
167 167 actor_session_fetch = {
168 168 params = {uint64, lib.store.inet}, sql = [[
169 169 select a.id, a.nym, a.handle, a.origin, a.bio,
170 - a.avataruri, a.rank, a.quota, a.key,
170 + a.avataruri, a.rank, a.quota, a.key, a.epithet,
171 171 extract(epoch from a.knownsince)::bigint,
172 172 coalesce(a.handle || '@' || s.domain,
173 173 '@' || a.handle) as xid,
174 174
175 175 au.restrict,
176 176 array['post' ] <@ au.restrict as can_post,
177 177 array['edit' ] <@ au.restrict as can_edit,
................................................................................
190 190 };
191 191
192 192 actor_powers_fetch = {
193 193 params = {uint64}, sql = [[
194 194 select key, allow from parsav_rights where actor = $1::bigint
195 195 ]]
196 196 };
197 +
198 + actor_power_insert = {
199 + params = {uint64,lib.mem.ptr(int8),uint16}, cmd = true, sql = [[
200 + insert into parsav_rights (actor, key, allow) values (
201 + $1::bigint, $2::text, ($3::smallint)::integer::bool
202 + )
203 + ]]
204 + };
205 +
206 + auth_create_pw = {
207 + params = {uint64, lib.mem.ptr(uint8)}, cmd = true, sql = [[
208 + insert into parsav_auth (uid, name, kind, cred) values (
209 + $1::bigint,
210 + (select handle from parsav_actors where id = $1::bigint),
211 + 'pw-sha256', $2::bytea
212 + )
213 + ]]
214 + };
197 215
198 216 post_create = {
199 217 params = {uint64, rawstring, rawstring, rawstring}, sql = [[
200 218 insert into parsav_posts (
201 219 author, subject, acl, body,
202 220 posted, discovered,
203 221 circles, mentions
................................................................................
339 357 return buf
340 358 end
341 359 end;
342 360 }
343 361
344 362 local con = symbol(&lib.pq.PGconn)
345 363 local prep = {}
346 -local sqlsquash = function(s) return s:gsub('%s+',' '):gsub('^%s*(.-)%s*$','%1') end
364 +local function sqlsquash(s) return s
365 + :gsub('%%include (.-)%%',function(f)
366 + return sqlsquash(lib.util.ingest('backend/schema/' .. f))
367 + end) -- include dependencies
368 + :gsub('%-%-.-\n','') -- remove disruptive line comments
369 + :gsub('%-%-.-$','') -- remove unnecessary terminal comments
370 + :gsub('%s+',' ') -- remove whitespace
371 + :gsub('^%s*(.-)%s*$','%1') -- chomp
372 +end
373 +
347 374 for k,q in pairs(queries) do
348 375 local qt = sqlsquash(q.sql)
349 376 local stmt = 'parsavpg_' .. k
350 - prep[#prep + 1] = quote
377 + terra q.prep([con])
351 378 var res = lib.pq.PQprepare([con], stmt, qt, [#q.params], nil)
352 379 defer lib.pq.PQclear(res)
353 380 if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_COMMAND_OK then
354 381 if res == nil then
355 382 lib.bail('grievous error occurred preparing ',k,' statement')
356 383 end
357 384 lib.bail('could not prepare PGSQL statement ',k,': ',lib.pq.PQresultErrorMessage(res))
358 385 end
359 386 lib.dbg('prepared PGSQL statement ',k)
360 387 end
388 + prep[#prep + 1] = quote q.prep([con]) end
361 389
362 390 local args, casts, counters, fixers, ft, yield = {}, {}, {}, {}, {}, {}
363 391 local dumpers = {}
364 392 for i, ty in ipairs(q.params) do
365 393 args[i] = symbol(ty)
366 394 ft[i] = `1
367 395 if ty == rawstring then
................................................................................
389 417 dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got int %llu\n'], [args[i]])
390 418 fixers[#fixers + 1] = quote
391 419 [args[i]] = lib.math.netswap(ty, [args[i]])
392 420 end
393 421 end
394 422 end
395 423
424 + local okconst = lib.pq.PGRES_TUPLES_OK
425 + if q.cmd then okconst = lib.pq.PGRES_COMMAND_OK end
396 426 terra q.exec(src: &lib.store.source, [args])
397 427 var params = arrayof([&int8], [casts])
398 428 var params_sz = arrayof(int, [counters])
399 429 var params_ft = arrayof(int, [ft])
400 430 [fixers]
401 431 --[dumpers]
402 432 var res = lib.pq.PQexecPrepared([&lib.pq.PGconn](src.handle), stmt,
403 433 [#args], params, params_sz, params_ft, 1)
404 434 if res == nil then
405 435 lib.bail(['grievous error occurred executing '..k..' against database'])
406 - elseif lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
436 + elseif lib.pq.PQresultStatus(res) ~= okconst then
407 437 lib.bail(['PGSQL database procedure '..k..' failed\n'],
408 438 lib.pq.PQresultErrorMessage(res))
409 439 end
410 440
411 441 var ct = lib.pq.PQntuples(res)
412 442 if ct == 0 then
413 443 lib.pq.PQclear(res)
................................................................................
448 478 return p
449 479 end
450 480 local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
451 481 var a: lib.mem.ptr(lib.store.actor)
452 482 var av: rawstring, avlen: intptr
453 483 var nym: rawstring, nymlen: intptr
454 484 var bio: rawstring, biolen: intptr
485 + var epi: rawstring, epilen: intptr
455 486 if r:null(row,5) then avlen = 0 av = nil else
456 487 av = r:string(row,5)
457 488 avlen = r:len(row,5)+1
458 489 end
459 490 if r:null(row,1) then nymlen = 0 nym = nil else
460 491 nym = r:string(row,1)
461 492 nymlen = r:len(row,1)+1
462 493 end
463 494 if r:null(row,4) then biolen = 0 bio = nil else
464 495 bio = r:string(row,4)
465 496 biolen = r:len(row,4)+1
497 + end
498 + if r:null(row,9) then epilen = 0 epi = nil else
499 + epi = r:string(row,9)
500 + epilen = r:len(row,9)+1
466 501 end
467 502 a = [ lib.str.encapsulate(lib.store.actor, {
468 503 nym = {`nym, `nymlen};
469 504 bio = {`bio, `biolen};
505 + epithet = {`epi, `epilen};
470 506 avatar = {`av,`avlen};
471 507 handle = {`r:string(row, 2); `r:len(row,2) + 1};
472 - xid = {`r:string(row, 10); `r:len(row,10) + 1};
508 + xid = {`r:string(row, 11); `r:len(row,11) + 1};
473 509 }) ]
474 510 a.ptr.id = r:int(uint64, row, 0);
475 511 a.ptr.rights = lib.store.rights_default();
476 512 a.ptr.rights.rank = r:int(uint16, row, 6);
477 513 a.ptr.rights.quota = r:int(uint32, row, 7);
478 - a.ptr.knownsince = r:int(int64,row, 9);
514 + a.ptr.knownsince = r:int(int64,row, 10);
479 515 if r:null(row,8) then
480 516 a.ptr.key.ct = 0 a.ptr.key.ptr = nil
481 517 else
482 518 a.ptr.key = r:bin(row,8)
483 519 end
484 520 if r:null(row,3) then a.ptr.origin = 0
485 521 else a.ptr.origin = r:int(uint64,row,3) end
................................................................................
530 566 lib.dbg(['searching for hashed password credentials in format SHA' .. tostring(hash)])
531 567 var [out]
532 568 [vdrs]
533 569 lib.dbg(['could not find password hash'])
534 570 end
535 571 end
536 572
573 +local schema = sqlsquash(lib.util.ingest('backend/schema/pgsql.sql'))
574 +local obliterator = sqlsquash(lib.util.ingest('backend/schema/pgsql-drop.sql'))
575 +
537 576 local b = `lib.store.backend {
538 577 id = "pgsql";
539 578 open = [terra(src: &lib.store.source): &opaque
540 579 lib.report('connecting to postgres database: ', src.string.ptr)
541 580 var [con] = lib.pq.PQconnectdb(src.string.ptr)
542 581 if lib.pq.PQstatus(con) ~= lib.pq.CONNECTION_OK then
543 582 lib.warn('postgres backend connection failed')
................................................................................
556 595 defer lib.pq.PQclear(res)
557 596 if lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
558 597 lib.warn('failed to secure postgres connection')
559 598 lib.pq.PQfinish(con)
560 599 return nil
561 600 end
562 601
563 - [prep]
564 602 return con
565 603 end];
604 +
566 605 close = [terra(src: &lib.store.source) lib.pq.PQfinish([&lib.pq.PGconn](src.handle)) end];
606 +
607 + conprep = [terra(src: &lib.store.source, mode: lib.store.prepmode.t)
608 + var [con] = [&lib.pq.PGconn](src.handle)
609 + if mode == lib.store.prepmode.full then [prep]
610 + elseif mode == lib.store.prepmode.conf or
611 + mode == lib.store.prepmode.admin then
612 + queries.conf_get.prep(con)
613 + queries.conf_set.prep(con)
614 + queries.conf_reset.prep(con)
615 + if mode == lib.store.prepmode.admin then
616 + end
617 + else lib.bail('unsupported connection preparation mode') end
618 + end];
619 +
620 + dbsetup = [terra(src: &lib.store.source)
621 + var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), schema)
622 + if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
623 + lib.report('successfully instantiated schema in database')
624 + return true
625 + else
626 + lib.warn('backend pgsql - failed to initialize database: \n', lib.pq.PQresultErrorMessage(res))
627 + return false
628 + end
629 + end];
630 +
631 + obliterate_everything = [terra(src: &lib.store.source)
632 + var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), obliterator)
633 + if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
634 + lib.report('successfully wiped out everything parsav-related in database')
635 + return true
636 + else
637 + lib.warn('backend pgsql - failed to obliterate database: \n', lib.pq.PQresultErrorMessage(res))
638 + return false
639 + end
640 + end];
567 641
568 642 conf_get = [terra(src: &lib.store.source, key: rawstring)
569 643 var r = queries.conf_get.exec(src, key)
570 644 if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else
571 645 defer r:free()
572 646 return r:String(0,0)
573 647 end
................................................................................
678 752
679 753 var a = row_to_actor(&r, 0)
680 754 a.ptr.source = src
681 755
682 756 var au = [lib.stat(lib.store.auth)] { ok = true }
683 757 au.val.aid = aid
684 758 au.val.uid = a.ptr.id
685 - if not r:null(0,12) then -- restricted?
759 + if not r:null(0,13) then -- restricted?
686 760 au.val.privs:clear()
687 - (au.val.privs.post << r:bool(0,13))
688 - (au.val.privs.edit << r:bool(0,14))
689 - (au.val.privs.acct << r:bool(0,15))
690 - (au.val.privs.upload << r:bool(0,16))
691 - (au.val.privs.censor << r:bool(0,17))
692 - (au.val.privs.admin << r:bool(0,18))
761 + (au.val.privs.post << r:bool(0,14))
762 + (au.val.privs.edit << r:bool(0,15))
763 + (au.val.privs.acct << r:bool(0,16))
764 + (au.val.privs.upload << r:bool(0,17))
765 + (au.val.privs.censor << r:bool(0,18))
766 + (au.val.privs.admin << r:bool(0,19))
693 767 else au.val.privs:fill() end
694 768
695 769 return au, a
696 770 end
697 771
698 772 ::fail:: return [lib.stat (lib.store.auth) ] { ok = false },
699 773 [lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 }
................................................................................
759 833 return powers
760 834 end];
761 835
762 836 actor_create = [terra(
763 837 src: &lib.store.source,
764 838 ac: &lib.store.actor
765 839 ): uint64
766 - var r = queries.actor_create.exec(src,ac.nym, ac.handle, ac.origin, ac.knownsince, ac.bio, ac.avatar, ac.key, ac.title, ac.rights.rank, ac.rights.quota)
840 + var r = queries.actor_create.exec(src,ac.nym, ac.handle, ac.origin, ac.knownsince, ac.bio, ac.avatar, ac.key, ac.epithet, ac.rights.rank, ac.rights.quota)
767 841 if r.sz == 0 then lib.bail('failed to create actor!') end
768 - return r:int(uint64,0,0)
842 + var uid = r:int(uint64,0,0)
843 +
844 + -- check against default rights, insert records for wherever powers differ
845 + lib.dbg('created new actor, establishing powers')
846 + var pdef = lib.store.rights_default().powers
847 + var map = array([privmap])
848 + for i=0, [map.type.N] do
849 + var d = pdef and map[i].priv
850 + var u = ac.rights.powers and map[i].priv
851 + if d:sz() > 0 and u:sz() == 0 then
852 + lib.dbg('blocking power ', {map[i].name.ptr, map[i].name.ct})
853 + queries.actor_power_insert.exec(src, uid, map[i].name, 0)
854 + elseif d:sz() == 0 and u:sz() > 0 then
855 + lib.dbg('granting power ', {map[i].name.ptr, map[i].name.ct})
856 + queries.actor_power_insert.exec(src, uid, map[i].name, 1)
857 + end
858 + end
859 +
860 + lib.dbg('powers established')
861 + return uid
862 + end];
863 +
864 + auth_create_pw = [terra(
865 + src: &lib.store.source,
866 + uid: uint64,
867 + reset: bool,
868 + pw: lib.mem.ptr(int8)
869 + ): {}
870 + -- TODO impl reset support
871 + var hash: uint8[lib.crypt.algsz.sha256]
872 + if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(lib.crypt.alg.sha256.id),
873 + [&uint8](pw.ptr), pw.ct, &hash[0]) ~= 0 then
874 + lib.bail('cannot hash password')
875 + end
876 + queries.auth_create_pw.exec(src, uid, [lib.mem.ptr(uint8)] {ptr = &hash[0], ct = [hash.type.N]})
769 877 end];
770 878
771 879 actor_auth_register_uid = nil; -- not necessary for view-based auth
880 +
772 881 }
773 882
774 883 return b