Overview
| Comment: | user mgmt and rt improvements |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
05af79b909dfb0c5ac50472976eed863 |
| User & Date: | lexi on 2021-01-09 07:15:21 |
| Other Links: | manifest | tags |
Context
|
2021-01-10
| ||
| 03:54 | add memory pool impl, handle various little details, add beginnings of mimelib check-in: 8d35307a7f user: lexi tags: trunk | |
|
2021-01-09
| ||
| 07:15 | user mgmt and rt improvements check-in: 05af79b909 user: lexi tags: trunk | |
|
2021-01-08
| ||
| 05:58 | enable passwords check-in: d6024624c6 user: lexi tags: trunk | |
Changes
Modified backend/pgsql.t from [9c53eed84d] to [175b022aff].
187 187 select (pg_temp.parsavpg_translate_actor(a)).*, 188 188 189 189 au.restrict, 190 190 array['post' ] <@ au.restrict, 191 191 array['edit' ] <@ au.restrict, 192 192 array['account' ] <@ au.restrict, 193 193 array['upload' ] <@ au.restrict, 194 + array['artifact'] <@ au.restrict, 194 195 array['moderate'] <@ au.restrict, 195 - array['admin' ] <@ au.restrict 196 + array['admin' ] <@ au.restrict, 197 + array['invite' ] <@ au.restrict 196 198 197 199 from parsav_auth au 198 200 left join parsav_actors a on au.uid = a.id 199 201 200 202 where au.aid = $1::bigint and au.blacklist = false and 201 203 (au.netmask is null or au.netmask >> $2::inet) and 202 204 ($3::bigint = 0 or --slightly abusing the epoch time fmt here, but ................................................................................ 271 273 update parsav_actors set 272 274 authtime = $2::bigint 273 275 where id = $1::bigint 274 276 ]]; 275 277 }; 276 278 277 279 auth_create_pw = { 278 - params = {uint64, binblob, int64, pstring}, cmd = true, sql = [[ 280 + params = {uint64, binblob, int64, pstring}, sql = [[ 279 281 insert into parsav_auth (uid, name, kind, cred, valperiod, comment) values ( 280 282 $1::bigint, 281 283 (select handle from parsav_actors where id = $1::bigint), 282 284 'pw-sha256', $2::bytea, 283 285 $3::bigint, $4::text 284 - ) 286 + ) on conflict (name,kind,cred) do update set comment = $4::text returning aid 285 287 ]] 286 288 }; 289 + 290 + auth_privs_clear = { 291 + params = {uint64}, cmd = true, sql = [[ 292 + update parsav_auth set restrict = array[]::text[] where aid = $1::bigint 293 + ]]; 294 + }; 295 + 296 + auth_priv_install = { 297 + params = {uint64,pstring}, cmd = true, sql = [[ 298 + update parsav_auth set restrict = restrict || $2::text where aid = $1::bigint 299 + ]]; 300 + }; 287 301 288 302 auth_purge_type = { 289 303 params = {rawstring, uint64, rawstring}, cmd = true, sql = [[ 290 304 delete from parsav_auth where 291 305 ((uid = 0 and name = $1::text) or uid = $2::bigint) and 292 306 kind like $3::text 293 307 ]] 294 308 }; 309 + 310 + auth_purge_aid = { 311 + params = {uint64}, cmd = true, sql = [[ 312 + delete from parsav_auth where aid = $1::bigint 313 + ]] 314 + }; 295 315 296 316 auth_enum_uid = { 297 317 params = {uint64}, sql = [[ 298 318 select aid, kind, comment, netmask, blacklist from parsav_auth where uid = $1::bigint 299 319 ]]; 300 320 }; 301 321 ................................................................................ 401 421 params = {uint64, uint64, pstring}, cmd = true, sql = [[ 402 422 delete from parsav_acts where 403 423 actor = $1::bigint and 404 424 subject = $2::bigint and 405 425 kind = $3::text 406 426 ]]; 407 427 }; 428 + 429 + post_react_delete = { 430 + params = {uint64}, cmd = true, sql = [[ 431 + delete from parsav_acts where id = $1::bigint 432 + ]]; 433 + }; 408 434 409 435 post_reacts_fetch_uid = { 410 436 params = {uint64, uint64, pstring}, sql = [[ 411 437 select id, actor, subject, kind, body, time from parsav_acts where 412 438 ($1::bigint = 0 or actor = $1::bigint) and 413 439 ($2::bigint = 0 or subject = $2::bigint) and 414 440 ($3::text is null or kind = $3::text ) 415 - ]] 441 + ]]; 442 + }; 443 + 444 + post_act_fetch_notice = { 445 + params = {uint64}, sql = [[ 446 + select (pg_temp.parsavpg_translate_act(a)).* 447 + from parsav_acts as a 448 + where id = $1::bigint 449 + ]]; 416 450 }; 417 451 418 452 post_enum_author_uid = { 419 453 params = {uint64,uint64,uint64,uint64, uint64}, sql = [[ 420 454 select (c.post).* 421 455 from pg_temp.parsavpg_known_content as c 422 456 ................................................................................ 964 998 a.ptr.key = r:bin(row,8) 965 999 end 966 1000 a.ptr.origin = origin 967 1001 if avia.buf ~= nil then avia:free() end 968 1002 return a 969 1003 end 970 1004 971 -local privmap = lib.store.privmap 1005 +local privmap = lib.store.powmap 972 1006 973 1007 local checksha = function(src, hash, origin, username, pw) 974 1008 local validate = function(kind, cred, credlen) 975 1009 return quote 976 1010 var r = queries.actor_auth_pw.exec( 977 1011 [&lib.store.source](src), 978 1012 username, ................................................................................ 1015 1049 local privupdate = terra( 1016 1050 src: &lib.store.source, 1017 1051 ac: &lib.store.actor 1018 1052 ): {} 1019 1053 var pdef: lib.store.powerset pdef:clear() 1020 1054 var map = array([privmap]) 1021 1055 for i=0, [map.type.N] do 1022 - var d = pdef and map[i].priv 1023 - var u = ac.rights.powers and map[i].priv 1056 + var d = pdef and map[i].val 1057 + var u = ac.rights.powers and map[i].val 1024 1058 queries.actor_power_delete.exec(src, ac.id, map[i].name) 1025 1059 if d:sz() > 0 and u:sz() == 0 then 1026 1060 lib.dbg('blocking power ', {map[i].name.ptr, map[i].name.ct}) 1027 1061 queries.actor_power_insert.exec(src, ac.id, map[i].name, 0) 1028 1062 elseif d:sz() == 0 and u:sz() > 0 then 1029 1063 lib.dbg('granting power ', {map[i].name.ptr, map[i].name.ct}) 1030 1064 queries.actor_power_insert.exec(src, ac.id, map[i].name, 1) ................................................................................ 1041 1075 var r = queries.actor_powers_fetch.exec(src, uid) 1042 1076 1043 1077 for i=0, r.sz do 1044 1078 for j=0, [map.type.N] do 1045 1079 var pn = r:_string(i,0) 1046 1080 if map[j].name:cmp(pn) then 1047 1081 if r:bool(i,1) 1048 - then powers = powers + map[j].priv 1049 - else powers = powers - map[j].priv 1082 + then powers = powers + map[j].val 1083 + else powers = powers - map[j].val 1050 1084 end 1051 1085 end 1052 1086 end 1053 1087 end 1054 1088 1055 1089 return powers 1056 1090 end ................................................................................ 1268 1302 1269 1303 var a = row_to_actor(&r, 0) 1270 1304 a.ptr.source = src 1271 1305 1272 1306 var au = [lib.stat(lib.store.auth)] { ok = true } 1273 1307 au.val.aid = aid 1274 1308 au.val.uid = a.ptr.id 1275 - if not r:null(0,13) then -- restricted? 1309 + if not r:null(0,14) then -- restricted? 1276 1310 au.val.privs:clear() 1277 - (au.val.privs.post << r:bool(0,14)) 1278 - (au.val.privs.edit << r:bool(0,15)) 1279 - (au.val.privs.account << r:bool(0,16)) 1280 - (au.val.privs.upload << r:bool(0,17)) 1281 - (au.val.privs.moderate<< r:bool(0,18)) 1282 - (au.val.privs.admin << r:bool(0,19)) 1311 + (au.val.privs.post << r:bool(0,15)) 1312 + (au.val.privs.edit << r:bool(0,16)) 1313 + (au.val.privs.account << r:bool(0,17)) 1314 + (au.val.privs.upload << r:bool(0,18)) 1315 + (au.val.privs.artifact<< r:bool(0,19)) 1316 + (au.val.privs.moderate<< r:bool(0,20)) 1317 + (au.val.privs.admin << r:bool(0,21)) 1318 + (au.val.privs.invite << r:bool(0,22)) 1283 1319 else au.val.privs:fill() end 1284 1320 1285 1321 return au, a 1286 1322 end 1287 1323 1288 1324 ::fail:: return [lib.stat (lib.store.auth) ] { ok = false }, 1289 1325 [lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 } ................................................................................ 1321 1357 var r = queries.post_fetch.exec(src, post) 1322 1358 if r.sz == 0 then return [lib.mem.ptr(lib.store.post)].null() end 1323 1359 var p = row_to_post(&r, 0) 1324 1360 p.ptr.source = src 1325 1361 return p 1326 1362 end]; 1327 1363 1364 + post_act_cancel = [terra( 1365 + src: &lib.store.source, 1366 + act: uint64 1367 + ): {} queries.post_react_delete.exec(src, act) end]; 1368 + 1328 1369 post_retweet = [terra( 1329 1370 src: &lib.store.source, 1330 1371 uid: uint64, 1331 1372 post: uint64, 1332 1373 undo: bool 1333 1374 ): {} 1334 - var time = lib.osclock.time(nil) 1335 1375 if not undo then 1376 + var time = lib.osclock.time(nil) 1336 1377 queries.post_react_simple.exec(src,uid,post,"rt",time) 1337 1378 else 1338 1379 queries.post_react_cancel.exec(src,uid,post,"rt") 1339 1380 end 1340 1381 end]; 1341 1382 post_like = [terra( 1342 1383 src: &lib.store.source, ................................................................................ 1528 1569 1529 1570 auth_attach_pw = [terra( 1530 1571 src: &lib.store.source, 1531 1572 uid: uint64, 1532 1573 reset: bool, 1533 1574 pw: pstring, 1534 1575 comment: pstring 1535 - ): {} 1576 + ): uint64 1536 1577 var hash: uint8[lib.crypt.algsz.sha256] 1537 1578 if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(lib.crypt.alg.sha256.id), 1538 1579 [&uint8](pw.ptr), pw.ct, &hash[0]) ~= 0 then 1539 1580 lib.bail('cannot hash password') 1540 1581 end 1541 1582 if reset then queries.auth_purge_type.exec(src, nil, uid, 'pw-%') end 1542 - queries.auth_create_pw.exec(src, uid, binblob {ptr = &hash[0], ct = [hash.type.N]}, lib.osclock.time(nil), comment) 1583 + var r = queries.auth_create_pw.exec(src, uid, binblob {ptr = &hash[0], ct = [hash.type.N]}, lib.osclock.time(nil), comment) 1584 + if r.sz == 0 then return 0 end 1585 + var aid = r:int(uint64,0,0) 1586 + r:free() 1587 + return aid 1588 + end]; 1589 + 1590 + auth_privs_set = [terra( 1591 + src: &lib.store.source, 1592 + aid: uint64, 1593 + set: lib.store.privset 1594 + ): {} 1595 + var map = array([lib.store.privmap]) 1596 + queries.auth_privs_clear.exec(src,aid) 1597 + if set:sz() == 0 then return end 1598 + for i=0, [map.type.N] do 1599 + if (set and map[i].val):sz() > 0 then 1600 + queries.auth_priv_install.exec(src,aid,map[i].name) 1601 + end 1602 + end 1543 1603 end]; 1544 1604 1545 1605 auth_purge_pw = [terra(src: &lib.store.source, uid: uint64, handle: rawstring): {} 1546 1606 queries.auth_purge_type.exec(src, handle, uid, 'pw-%') 1547 1607 end]; 1548 1608 1549 1609 auth_purge_otp = [terra(src: &lib.store.source, uid: uint64, handle: rawstring): {} ................................................................................ 1699 1759 post.id, post.chgcount, post.edited, 1700 1760 post.subject, post.acl, post.body) 1701 1761 end]; 1702 1762 1703 1763 post_enum_parent = [terra( 1704 1764 src: &lib.store.source, 1705 1765 post: uint64 1706 - ): lib.mem.ptr(lib.mem.ptr(lib.store.post)) 1766 + ): lib.mem.lstptr(lib.store.post) 1707 1767 var r = queries.post_enum_parent.exec(src,post) 1708 1768 if r.sz == 0 then 1709 1769 return [lib.mem.ptr(lib.mem.ptr(lib.store.post))].null() 1710 1770 end 1711 1771 defer r:free() 1712 1772 var lst = lib.mem.heapa([lib.mem.ptr(lib.store.post)], r.sz) 1713 1773 1714 1774 for i=0, r.sz do lst.ptr[i] = row_to_post(&r, i) end 1715 1775 1716 1776 return lst 1717 1777 end]; 1778 + 1779 + post_act_fetch_notice = [terra( 1780 + src: &lib.store.source, 1781 + act: uint64 1782 + ): lib.store.notice 1783 + var r = queries.post_act_fetch_notice.exec(src,act) 1784 + if r.sz == 0 then return lib.store.notice { kind = lib.store.noticetype.none } end 1785 + defer r:free() 1786 + 1787 + var n: lib.store.notice 1788 + n.kind = r:int(uint16,0,0) 1789 + n.when = r:int(int64,0,1) 1790 + n.who = r:int(int64,0,2) 1791 + n.what = r:int(uint64,0,3) 1792 + if n.kind == lib.store.noticetype.react then 1793 + var react = r:_string(0,5) 1794 + lib.str.ncpy(n.reaction, react.ptr, lib.math.smallest(react.ct,[(`n.reaction).tree.type.N])) 1795 + end 1796 + 1797 + return n 1798 + end]; 1718 1799 1719 1800 thread_latest_arrival_calc = [terra( 1720 1801 src: &lib.store.source, 1721 1802 post: uint64 1722 1803 ): lib.store.timepoint 1723 1804 var r = queries.thread_latest_arrival_calc.exec(src,post) 1724 1805 if r.sz == 0 or r:null(0,0) then return 0 end
Modified backend/schema/pgsql-views.sql from [25f9d405bc] to [c997d2f5ad].
40 40 kind smallint, 41 41 "when" bigint, 42 42 who bigint, 43 43 what bigint, 44 44 reply bigint, 45 45 reaction text 46 46 ); 47 + 48 +create or replace function 49 +pg_temp.parsavpg_translate_act(parsav_acts) 50 +returns pg_temp.parsavpg_intern_notice as $$ 51 + select row( 52 + kmap.kind::smallint, 53 + ($1).time, 54 + ($1).actor, 55 + ($1).subject, 56 + null::bigint, 57 + ($1).body 58 + )::pg_temp.parsavpg_intern_notice as notice 59 + from (values 60 + ('rt', <notice:rt> ), 61 + ('like', <notice:like> ), 62 + ('react', <notice:react>) 63 + ) as kmap(kstr,kind) where kmap.kstr = ($1).kind 64 +$$ language sql; 47 65 48 66 create type pg_temp.parsavpg_intern_actor as ( 49 67 id bigint, 50 68 nym text, 51 69 handle text, 52 70 origin bigint, 53 71 bio text, ................................................................................ 156 174 --); 157 175 158 176 create temp view parsavpg_notices as ( 159 177 -- TODO add mentions 160 178 with ntimes as ( 161 179 select uid, value as when from parsav_actor_conf_ints where key = 'notice-clear-time' 162 180 ), acts as ( 163 - select row( 164 - kmap.kind::smallint, 165 - a.time, 166 - a.actor, 167 - a.subject, 168 - null::bigint, 169 - null::text 170 - )::pg_temp.parsavpg_intern_notice as notice, 181 + select 182 + pg_temp.parsavpg_translate_act(a) as notice, 183 + -- row( 184 + -- kmap.kind::smallint, 185 + -- a.time, 186 + -- a.actor, 187 + -- a.subject, 188 + -- null::bigint, 189 + -- null::text 190 + -- )::pg_temp.parsavpg_intern_notice as notice, 171 191 p.author as rcpt 172 192 from parsav_acts as a 173 193 inner join parsav_posts as p on a.subject = p.id 174 - inner join (values 175 - ('rt', <notice:rt> ), 176 - ('like', <notice:like> ), 177 - ('react', <notice:react>) 178 - ) as kmap(kstr,kind) on kmap.kstr = a.kind 194 + -- inner join (values 195 + -- ('rt', <notice:rt> ), 196 + -- ('like', <notice:like> ), 197 + -- ('react', <notice:react>) 198 + -- ) as kmap(kstr,kind) on kmap.kstr = a.kind 179 199 left join ntimes as nt on nt.uid = p.author 180 200 where a.time >= coalesce(nt.when,0) 181 201 ), replies as ( 182 202 select row( 183 203 <notice:reply>::smallint, 184 204 coalesce(p.posted,p.discovered), 185 205 p.author,
Modified mgtool.t from [4f69a41277] to [b1a646e421].
487 487 dlg:actor_create(&na) 488 488 lib.report('created new user @',na.handle,'; assign credentials to enable login') 489 489 elseif umode.arglist.ct >= 3 then 490 490 var grant = lib.str.cmp(umode.arglist(1),'grant') == 0 491 491 if not usr then lib.bail('no such user') end 492 492 if grant or lib.str.cmp(umode.arglist(1),'revoke') == 0 then 493 493 var newprivs = usr.ptr.rights.powers 494 - var map = array([lib.store.privmap]) 494 + var map = array([lib.store.powmap]) 495 495 if umode.arglist.ct == 3 and lib.str.cmp(umode.arglist(2),'all') == 0 then 496 496 if grant 497 497 then newprivs:fill() 498 498 else newprivs:clear() 499 499 end 500 500 else 501 501 for i=2,umode.arglist.ct do 502 502 var priv = umode.arglist(i) 503 503 for j=0,[map.type.N] do 504 504 var p = map[j] 505 505 if p.name:cmp_raw(priv) then 506 506 if grant then 507 507 lib.dbg('enabling power ', {p.name.ptr,p.name.ct}) 508 - newprivs = newprivs + p.priv 508 + newprivs = newprivs + p.val 509 509 else 510 510 lib.dbg('disabling power ', {p.name.ptr,p.name.ct}) 511 - newprivs = newprivs - p.priv 511 + newprivs = newprivs - p.val 512 512 end 513 513 break 514 514 end 515 515 end 516 516 end 517 517 end 518 518
Modified render/conf/sec.t from [7f83a40056] to [f6e2d18341].
9 9 lib.osclock.ctime_r(&time, &tstr[0]) 10 10 var body = data.view.conf_sec { 11 11 lastreset = pstr { 12 12 ptr = &tstr[0], ct = lib.str.sz(&tstr[0]) 13 13 } 14 14 } 15 15 16 + var a: lib.str.acc a:init(768) defer a:free() 17 + 16 18 if co.srv.cfg.credmgd then 17 19 var new = co:pgetv('new') 18 - var a: lib.str.acc a:init(768) 19 20 if not new then 20 21 body:append(&a) 21 22 var credmgr = data.view.conf_sec_credmg { 22 23 credlist = pstr{'',0}; 23 24 } 24 25 var creds = co.srv:auth_enum_uid(uid) 25 26 if creds.ct > 0 then defer creds:free() ................................................................................ 33 34 cl:lpush('</option>') 34 35 end 35 36 end 36 37 credmgr.credlist = cl:finalize() 37 38 end 38 39 credmgr:append(&a) 39 40 if credmgr.credlist.ct > 0 then credmgr.credlist:free() end 40 - elseif new:cmp(lib.str.plit'pw') then 41 - var d: data.view.conf_sec_pwnew 42 - var time = lib.osclock.time(nil) 43 - var timestr: int8[26] lib.osclock.ctime_r(&time, ×tr[0]) 44 - var cmt: lib.str.acc 45 - cmt:init(48):lpush('enrolled over http on '):push(×tr[0],0) 46 - d.comment = cmt:finalize() 41 + else 42 + if new:cmp(lib.str.plit'pw') then 43 + var d: data.view.conf_sec_pwnew 44 + var time = lib.osclock.time(nil) 45 + var timestr: int8[26] lib.osclock.ctime_r(&time, ×tr[0]) 46 + var cmt: lib.str.acc 47 + cmt:init(48):lpush('enrolled over http on '):push(×tr[0],0) 48 + d.comment = cmt:finalize() 49 + 50 + var st = d:tostr() 51 + d.comment:free() 52 + return st 53 + elseif new:cmp(lib.str.plit'challenge') then 54 + -- we're going to break the rules a bit and do database munging from 55 + -- the rendering code, because doing otherwise in this case would be 56 + -- genuinely nightmarish 57 + elseif new:cmp(lib.str.plit'otp') then 58 + elseif new:cmp(lib.str.plit'api') then 59 + else return pstr.null() end 60 + end 61 + else body:append(&a) end 47 62 48 - var st = d:tostr() 49 - d.comment:free() 50 - return st 51 - elseif new:cmp(lib.str.plit'challenge') then 52 - -- we're going to break the rules a bit and do database munging from 53 - -- the rendering code, because doing otherwise in this case would be 54 - -- genuinely nightmarish 55 - elseif new:cmp(lib.str.plit'otp') then 56 - elseif new:cmp(lib.str.plit'api') then 57 - else return pstr.null() end 58 - return a:finalize() 59 - else return body:tostr() end 63 + return a:finalize() 60 64 end 61 65 62 66 terra lib.render.conf.sec_overlay 63 67 (co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr 64 68 -- render the credential panel for the current user, allowing 65 69 -- it to be reused in the administration UI 66 70 return render_conf_sec(co,co.who.id) 67 71 end 68 72 69 73 return render_conf_sec
Modified render/conf/users.t from [41f55e0682] to [59cb9f4fac].
227 227 push_checkbox(&cinp, 'staff', pstr.null(), 'site staff member', user.ptr.rights.rank > 0, true, pstr.null()) 228 228 end 229 229 230 230 cinp:lpush('</div></div>') 231 231 232 232 if (co.who.rights.powers.elevate() or 233 233 co.who.rights.powers.demote()) and user.ptr.id ~= co.who.id then 234 - var map = array([lib.store.privmap]) 234 + var map = array([lib.store.powmap]) 235 235 cinp:lpush('<details><summary>powers</summary><div class="pick-list">') 236 236 for i=0, [map.type.N] do 237 - if (co.who.rights.powers and map[i].priv):sz() > 0 then 238 - var on = (user.ptr.rights.powers and map[i].priv):sz() > 0 237 + if (co.who.rights.powers and map[i].val):sz() > 0 then 238 + var on = (user.ptr.rights.powers and map[i].val):sz() > 0 239 239 var enabled = ( on and co.who.rights.powers.demote() ) or 240 240 ((not on) and co.who.rights.powers.elevate()) 241 241 var namea: lib.str.acc namea:compose('power-', map[i].name) 242 242 var name = namea:finalize() 243 243 push_pickbox(&cinp, name, pstr.null(), map[i].name, on, enabled, pstr.null()) 244 244 name:free() 245 245 end
Modified render/tweet-page.t from [57c7b287c5] to [c0b864229b].
39 39 pg:lpush('<form class="action-bar" method="post">') 40 40 if not co.srv:post_liked_uid(co.who.id, p.id) 41 41 then pg:lpush('<button class="pos" name="act" accesskey="l" value="like">like</button>') 42 42 else pg:lpush('<button class="neg" name="act" accesskey="l" value="dislike">dislike</button>') 43 43 end 44 44 pg:lpush('<button class="pos" name="act" accesskey="r" value="rt">retweet</button>') 45 45 if p.author == co.who.id then 46 - pg:lpush('<a class="button" accesskey="e" href="/post/'):rpush(path(1)):lpush('/edit">edit</a><a class="neg button" accesskey="d" href="/post/'):rpush(path(1)):lpush('/del">delete</a>') 46 + if co.who.rights.powers.edit() then 47 + pg:lpush('<a class="button" accesskey="e" href="/post/'):rpush(path(1)):lpush('/edit">edit</a>') 48 + end 49 + pg:lpush('<a class="neg button" accesskey="d" href="/post/'):rpush(path(1)):lpush('/del">delete</a>') 50 + elseif co.who.rights.powers.snitch() then 51 + pg:lpush('<a class="neg button" accesskey="s" href="/post/'):rpush(path(1)):lpush('/report">report</a>') 47 52 end 48 53 -- TODO list user's chosen reaction emoji 49 54 pg:lpush('</form>') 50 55 51 56 end 52 57 pg:lpush('<div id="convo" data-live="10">') 53 58 render_tweet_replies(co, &pg, p.id)
Modified route.t from [b4f49ac3f6] to [a8702e1420].
197 197 terra http.tweet_page(co: &lib.srv.convo, path: hpath, meth: method.t) 198 198 var pid, ok = lib.math.shorthand.parse(path(1).ptr, path(1).ct) 199 199 if not ok then 200 200 co:complain(400, 'bad post ID', 'that post ID is not valid') 201 201 return 202 202 end 203 203 var post = co.srv:post_fetch(pid) 204 + var rt: lib.store.notice 204 205 if not post then 205 - co:complain(404, 'post not found', 'no such post is known to this server') 206 - return 206 + rt = co.srv:post_act_fetch_notice(pid) 207 + if rt.kind ~= lib.store.noticetype.rt then 208 + co:complain(404, 'post not found', 'no such post is known to this server') 209 + return 210 + elseif rt.who ~= co.who.id then 211 + co:complain(403, 'forbidden', 'you cannot cancel other people\'s retweets') 212 + return 213 + end 207 214 end 208 - defer post:free() 215 + defer post:free() -- NOP on null 209 216 210 217 if path.ct == 3 then 211 218 var lnk: lib.str.acc lnk:compose('/post/', path(1)) 212 219 var lnkp = lnk:finalize() defer lnkp:free() 213 - if post(0).author ~= co.who.id then 220 + if post:ref() and post(0).author ~= co.who.id then 214 221 co:complain(403, 'forbidden', 'you cannot alter other people\'s posts') 215 222 return 216 - elseif path(2):cmp(lib.str.lit 'edit') then 223 + elseif post:ref() and path(2):cmp(lib.str.lit 'edit') then 224 + if not co:assertpow('edit') then return end 217 225 if meth_get(meth) then 218 226 lib.render.compose(co, post.ptr, nil) 219 227 return 220 228 elseif meth == method.post then 221 229 var newbody = co:postv('post')._0 222 230 var newacl = co:postv('acl')._0 223 231 var newsubj = co:postv('subject')._0 ................................................................................ 226 234 if newsubj ~= nil then post(0).subject = newsubj end 227 235 post(0):save(true) 228 236 co:reroute(lnkp.ptr) 229 237 end 230 238 return 231 239 elseif path(2):cmp(lib.str.lit 'del') then 232 240 if meth_get(meth) then 233 - var conf = data.view.confirm { 234 - title = lib.str.plit 'delete post'; 235 - query = lib.str.plit 'are you sure you want to delete this post?'; 236 - cancel = lnkp 237 - } 241 + var conf: data.view.confirm 242 + if post:ref() then 243 + conf = data.view.confirm { 244 + title = lib.str.plit 'delete post'; 245 + query = lib.str.plit 'are you sure you want to delete this post?'; 246 + cancel = lnkp 247 + } 248 + else 249 + conf = data.view.confirm { 250 + title = lib.str.plit 'cancel retweet'; 251 + query = lib.str.plit 'are you sure you want to undo this retweet?'; 252 + cancel = lib.str.plit'/'; 253 + } 254 + end 238 255 var body = conf:tostr() defer body:free() 239 256 co:stdpage([lib.srv.convo.page] { 240 257 title = lib.str.plit 'post :: delete'; 241 258 class = lib.str.plit 'query'; 242 259 body = body; cache = false; 243 260 }) 244 261 return 245 262 elseif meth == method.post then 246 263 var act = co:ppostv('act') 247 264 if act:cmp(lib.str.plit 'confirm') then 248 - post(0).source:post_destroy(post(0).id) 265 + if post:ref() then 266 + post(0).source:post_destroy(post(0).id) 267 + elseif rt.kind ~= 0 then 268 + co.srv:post_act_cancel(pid) 269 + end 249 270 co:reroute('/') -- TODO maybe return to parent or conversation if possible 250 271 return 251 272 else goto badop end 252 273 end 253 274 else goto badurl end 254 275 end 255 276 256 - if meth == method.post then 277 + if post:ref() and meth == method.post then 257 278 if co.aid == 0 then goto noauth end 258 279 var act = co:ppostv('act') 259 280 if act:cmp(lib.str.plit 'like') and not co.srv:post_liked_uid(co.who.id,pid) then 260 281 co.srv:post_like(co.who.id, pid, false) 261 282 post.ptr.likes = post.ptr.likes + 1 262 283 elseif act:cmp(lib.str.plit 'dislike') and co.srv:post_liked_uid(co.who.id,pid) then 263 284 co.srv:post_like(co.who.id, pid, true) ................................................................................ 276 297 author = co.who.id, parent = pid; 277 298 subject = subj.ptr, acl = acl.ptr, body = replytext.ptr; 278 299 } 279 300 280 301 reply:publish(co.srv) 281 302 else goto badop end 282 303 end 304 + 305 + if not post then goto badurl end 283 306 284 307 lib.render.tweet_page(co, path, post.ptr) 285 308 do return end 286 309 287 310 ::badurl:: do co:complain(404, 'invalid URL', 'this URL does not reference extant content or functionality') return end 288 311 ::badop :: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end 289 312 ::noauth:: do co:complain(401, 'unauthorized', 'you have not supplied the necessary credentials to perform this operation') return end 290 313 end 291 314 292 315 local terra 293 316 credsec_for_uid(co: &lib.srv.convo, uid: uint64) 294 317 var act = co:ppostv('act') 318 + lib.dbg('showing credentials') 295 319 if act:cmp(lib.str.plit 'invalidate') then 296 320 lib.dbg('setting user\'s cookie validation time to now') 297 321 co.who.source:auth_sigtime_user_alter(uid, lib.osclock.time(nil)) 298 322 -- the current session has been invalidated as well, so we need to immediately install a new authentication cookie with the same aid so the user doesn't need to log back in all over again 299 323 co:installkey('/conf/sec',co.aid) 300 324 return 301 325 elseif act:cmp(lib.str.plit 'newcred') then 302 326 var cmt = co:ppostv('comment') 303 327 var pw = co:ppostv('newpw') 328 + var aid: uint64 = 0 304 329 if pw:ref() then 305 330 var cpw = co:ppostv('rptpw') 306 331 if not pw:cmp(cpw) then 307 332 co:complain(400,'enrollment failure','the passwords you supplied do not match') 308 333 return 309 334 end 310 - co.srv:auth_attach_pw(uid, false, pw, cmt) 311 - co:reroute('?') 312 - return 335 + aid = co.srv:auth_attach_pw(uid, false, pw, cmt) 313 336 else 314 337 var key = co:ppostv('newkey') 315 338 if key:ref() then 316 339 317 340 end 318 341 end 342 + if aid ~= 0 then 343 + lib.dbg('setting credential restrictions') 344 + var privs = [(function() 345 + local check = quote end 346 + local me = symbol(lib.store.privset) 347 + for i,v in ipairs(lib.store.privset.members) do 348 + check = quote [check] 349 + var val = co:pgetv(['allow-' .. v]) 350 + if val:ref() and val:cmp(lib.str.plit'on') 351 + then ([me].[v] << true) 352 + else ([me].[v] << false) 353 + end 354 + end 355 + end 356 + return quote 357 + var [me] 358 + [check] 359 + in [me] end 360 + end)()] 361 + privs:dump() 362 + if privs:sz() > 0 then 363 + lib.dbg('installing credential restrictions') 364 + lib.io.fmt('on priv %llu\n',aid) 365 + co.srv:auth_privs_set(aid, privs) 366 + end 367 + end 368 + co:reroute('?') 369 + return 319 370 end 320 371 co:complain(400,'bad request','the operation you have requested is not meaningful in this context') 321 372 end 322 373 323 374 terra http.configure(co: &lib.srv.convo, path: hpath, meth: method.t) 324 375 var msg = pstring.null() 325 376 -- first things first, do priv checks 326 - if path.ct >= 1 then 377 + if path.ct >= 2 then 327 378 if not co.who.rights.powers.config() and ( 328 379 path(1):cmp(lib.str.lit 'srv') or 329 380 path(1):cmp(lib.str.lit 'badge') or 330 381 path(1):cmp(lib.str.lit 'emoji') 331 382 ) then goto nopriv 332 383 333 384 elseif not co.who.rights.powers.rebrand() and ( 334 385 path(1):cmp(lib.str.lit 'brand') 335 386 ) then goto nopriv 336 387 337 388 elseif not co.who.rights.powers.account() and ( 338 389 path(1):cmp(lib.str.lit 'profile') or 339 - path(1):cmp(lib.str.lit 'acct') 390 + path(1):cmp(lib.str.lit 'sec') or 391 + path(1):cmp(lib.str.lit 'avi') or 392 + path(1):cmp(lib.str.lit 'ui') 340 393 ) then goto nopriv 341 394 342 395 elseif not co.who.rights.powers:affect_users() and ( 343 396 path(1):cmp(lib.str.lit 'users') 344 397 ) then goto nopriv end 345 398 end 346 399 ................................................................................ 384 437 elseif path(1):cmp(lib.str.lit 'users') then 385 438 if path.ct >= 3 then 386 439 var userid, ok = lib.math.shorthand.parse(path(2).ptr, path(2).ct) 387 440 if ok then 388 441 var usr = co.srv:actor_fetch_uid(userid) 389 442 if usr:ref() then defer usr:free() 390 443 if not co.who:overpowers(usr.ptr) then goto nopriv end 444 + end 445 + if path.ct == 4 then 446 + if path(3):cmp(lib.str.lit 'cred') then 447 + credsec_for_uid(co, userid) 448 + end 391 449 end 392 450 end 393 451 elseif path.ct == 2 and meth == method.post then 394 452 var act = co:ppostv('act') 395 453 if act:cmp(lib.str.plit'create') then 396 454 var newname = co:ppostv('handle') 397 455 if not newname or not lib.store.actor.handle_validate(newname.ptr) then
Modified srv.t from [56e1fe84a6] to [80adbc5ad3].
516 516 end ::nocookie::; 517 517 end 518 518 519 519 if co.aid ~= 0 then 520 520 var sess, usr = co.srv:actor_session_fetch(co.aid, peer, co.aid_issue) 521 521 if sess.ok == false then co.aid = 0 co.aid_issue = 0 else 522 522 co.who = usr.ptr 523 - co.who.rights.powers = server:actor_powers_fetch(co.who.id) 523 + var pows = server:actor_powers_fetch(co.who.id) 524 + var privs = sess.val.privs 525 + if not privs.post() then (pows.post << false) end 526 + if not privs.edit() then (pows.edit << false) end 527 + if not privs.account() then (pows.account << false) end 528 + if not privs.artifact() then (pows.artifact << false) end 529 + if not privs.invite() then (pows.invite << false) end 530 + if not privs.moderate() then 531 + (pows.censor << false) 532 + (pows.discipline << false) 533 + (pows.vacate << false) 534 + (pows.crier << false) 535 + end 536 + if not privs.admin() then 537 + (pows.cred << false) 538 + (pows.elevate << false) 539 + (pows.demote << false) 540 + (pows.rebrand << false) 541 + (pows.herald << false) 542 + (pows.config << false) 543 + (pows.purge << false) 544 + end 545 + co.who.rights.powers = pows 524 546 var userhue, hueok = server:actor_conf_int_get(co.who.id, 'ui-accent') 525 547 if hueok then co.ui_hue = userhue end 526 548 end 527 549 end 528 550 529 551 var livelast_p = lib.http.findheader(msg, 'X-Live-Last-Arrival') 530 552 if livelast_p ~= nil and livelast_p.ptr ~= nil then
Modified static/live.js from [76c21b64a1] to [c1f304b23e].
59 59 if (event.key == 'f') { // fave 60 60 postReq(root, 'like', post.querySelector('.stats>.like')) 61 61 } else if (event.key == 'r') { // rt 62 62 postReq(root, 'rt', post.querySelector('.stats>.rt')) 63 63 } else if (event.key == 'Enter') { // nav 64 64 window.location = root; 65 65 return; 66 + } else if (event.key == 'u' && root != cururl) { 67 + window.location = cururl; // detweet TODO don't try to delete other's retweets 66 68 } else if (post.attributes.getNamedItem('data-own')) { 67 69 if (event.key == 'd') { window.location = root + '/del'; } 68 70 else if (event.key == 'e') { window.location = root + '/edit'; } 69 - else if (event.key == 'u' && root != cururl) { window.location = cururl; } // detweet 70 71 } 71 72 } 72 73 if (nexturl != null) { 73 74 if (cururl != null) { 74 75 let cur = window._liveTweetMap.map.get(cururl); 75 76 cur.me.classList.remove('live-selected') 76 77 }
Modified store.t from [b8bbc4e0ec] to [0ebd27b207].
20 20 'avoid', -- posts will be kept out of the timeline but will show on users' posts and in conversations 21 21 'exclude', -- own posts will not be visible to this user 22 22 }; 23 23 credset = lib.set { 24 24 'pw', 'otp', 'challenge', 'trust' 25 25 }; 26 26 privset = lib.set { 27 - 'post', 'edit', 'account', 'upload', 'moderate', 'admin', 'invite' 27 + 'post', 'edit', 'account', 'upload', 'artifact', 'moderate', 'admin', 'invite' 28 28 }; 29 29 powerset = lib.set { 30 30 -- user powers -- default on 31 31 'login', -- not locked out 32 32 'visible', -- account & posts can be seen by others 33 33 'post', -- can do poasts 34 34 'shout', -- posts show up on local timeline ................................................................................ 52 52 'invite' -- *unlimited* invites 53 53 }; 54 54 prepmode = lib.enum { 55 55 'full','conf','admin' 56 56 } 57 57 } 58 58 59 -m.privmap = {} 60 -do local struct pt { name:lib.mem.ptr(int8), priv:m.powerset } 61 -for k,v in pairs(m.powerset.members) do 62 - m.privmap[#m.privmap + 1] = quote 63 - var ps: m.powerset ps:clear() 64 - (ps.[v] << true) 65 - in pt {name = lib.str.plit(v), priv = ps} end 66 -end end 59 +local function setmap(set) 60 + local map = {} 61 + local struct pt { name:lib.mem.ptr(int8), val:set } 62 + for k,v in pairs(set.members) do 63 + map[#map + 1] = quote 64 + var ps: set ps:clear() 65 + (ps.[v] << true) 66 + in pt {name = lib.str.plit(v), val = ps} end 67 + end 68 + return map 69 +end 70 +m.powmap = setmap(m.powerset) 71 +m.privmap = setmap(m.privset) 67 72 68 73 terra m.powerset:affect_users() 69 74 return self.purge() or self.discipline() or self.herald() or 70 75 self.elevate() or self.demote() or self.cred() 71 76 end 72 77 73 78 local str = rawstring ................................................................................ 408 413 actor_notice_enum: {&m.source, uint64} -> lib.mem.ptr(m.notice) 409 414 actor_rel_create: {&m.source, uint16, uint64, uint64} -> {} 410 415 actor_rel_destroy: {&m.source, uint16, uint64, uint64} -> {} 411 416 actor_rel_calc: {&m.source, uint64, uint64} -> m.relationship 412 417 413 418 auth_enum_uid: {&m.source, uint64} -> lib.mem.lstptr(m.auth) 414 419 auth_enum_handle: {&m.source, rawstring} -> lib.mem.lstptr(m.auth) 415 - auth_attach_pw: {&m.source, uint64, bool, pstr, pstr} -> {} 420 + auth_attach_pw: {&m.source, uint64, bool, pstr, pstr} -> uint64 416 421 auth_attach_key: {&m.source, uint64, bool, pstr, pstr} -> {} 417 422 -- uid: uint64 418 423 -- reset: bool (delete other passwords?) 419 424 -- pw: pstring 420 425 -- comment: pstring 426 + auth_privs_set: {&m.source, uint64, m.privset} -> {} 421 427 auth_purge_pw: {&m.source, uint64, rawstring} -> {} 422 428 auth_purge_otp: {&m.source, uint64, rawstring} -> {} 423 429 auth_purge_trust: {&m.source, uint64, rawstring} -> {} 424 430 auth_sigtime_user_fetch: {&m.source, uint64} -> m.timepoint 425 431 -- authentication tokens and accounts have a property that controls 426 432 -- whether auth cookies dated to a certain point are valid. cookies 427 433 -- that are generated before the timepoint are considered invalid. ................................................................................ 443 449 -- artifact id: uint64 444 450 -- detach: bool 445 451 post_retweet: {&m.source, uint64, uint64, bool} -> {} 446 452 post_like: {&m.source, uint64, uint64, bool} -> {} 447 453 -- undo: bool 448 454 post_react: {&m.source, uint64, uint64, pstring} -> {} 449 455 -- emoji: pstring (null to delete previous reaction, otherwise adds/changes) 456 + post_act_cancel: {&m.source, uint64} -> {} 450 457 post_liked_uid: {&m.source, uint64, uint64} -> bool 451 458 post_reacted_uid: {&m.source, uint64, uint64} -> bool 459 + post_act_fetch_notice: {&m.source, uint64} -> m.notice 452 460 453 461 thread_latest_arrival_calc: {&m.source, uint64} -> m.timepoint 454 462 455 463 artifact_instantiate: {&m.source, lib.mem.ptr(uint8), lib.mem.ptr(int8)} -> uint64 456 464 -- instantiate an artifact in the database, either installing a new 457 465 -- artifact or returning the id of an existing artifact with the same hash 458 466 -- artifact: bytea
Modified view/conf-sec-credmg.tpl from [6f4f9fa693] to [c30c9309b5].
11 11 </form> 12 12 <hr> 13 13 <form method="get"> 14 14 <p>you can associate extra credentials with this account. you can also limit how much of this account’s authority these credentials can be used to exercise — for instance, it might be useful to create API keys that can read the account timeline, but not post as the account owner or access any of his administrative powers. if you don't select a capability set, the credential will be able to wield the full scope of the associated account‘s powers.</p> 15 15 <div class="check-panel"> 16 16 <label><input type="checkbox" name="allow-post"> post</label> 17 17 <label><input type="checkbox" name="allow-edit"> edit</label> 18 - <label><input type="checkbox" name="allow-acct"> manage account</label> 18 + <label><input type="checkbox" name="allow-account"> manage account settings</label> 19 19 <label><input type="checkbox" name="allow-upload"> upload artifacts</label> 20 - <label><input type="checkbox" name="allow-censor"> moderation</label> 20 + <label><input type="checkbox" name="allow-artifact"> edit and delete artifacts</label> 21 + <label><input type="checkbox" name="allow-moderate"> moderation</label> 21 22 <label><input type="checkbox" name="allow-admin"> other admin powers</label> 22 23 <label><input type="checkbox" name="allow-invite"> invite</label> 23 24 </div> 24 25 <p>you can also specify an IP address range in CIDR format to associate with this credential. if you do so, this credential will only be usable when connecting from an IP address in that range. otherwise, it will be valid when connecting from anywhere on the internet.</p> 25 26 <div class="elem"> 26 27 <label for="netmask">netmask</label> 27 28 <input type="text" name="netmask" id="netmask" placeholder="10.0.0.0/8">