Index: backend/pgsql.t ================================================================== --- backend/pgsql.t +++ backend/pgsql.t @@ -189,12 +189,14 @@ au.restrict, array['post' ] <@ au.restrict, array['edit' ] <@ au.restrict, array['account' ] <@ au.restrict, array['upload' ] <@ au.restrict, + array['artifact'] <@ au.restrict, array['moderate'] <@ au.restrict, - array['admin' ] <@ au.restrict + array['admin' ] <@ au.restrict, + array['invite' ] <@ au.restrict from parsav_auth au left join parsav_actors a on au.uid = a.id where au.aid = $1::bigint and au.blacklist = false and @@ -273,27 +275,45 @@ where id = $1::bigint ]]; }; auth_create_pw = { - params = {uint64, binblob, int64, pstring}, cmd = true, sql = [[ + params = {uint64, binblob, int64, pstring}, sql = [[ insert into parsav_auth (uid, name, kind, cred, valperiod, comment) values ( $1::bigint, (select handle from parsav_actors where id = $1::bigint), 'pw-sha256', $2::bytea, $3::bigint, $4::text - ) + ) on conflict (name,kind,cred) do update set comment = $4::text returning aid ]] }; + + auth_privs_clear = { + params = {uint64}, cmd = true, sql = [[ + update parsav_auth set restrict = array[]::text[] where aid = $1::bigint + ]]; + }; + + auth_priv_install = { + params = {uint64,pstring}, cmd = true, sql = [[ + update parsav_auth set restrict = restrict || $2::text where aid = $1::bigint + ]]; + }; auth_purge_type = { params = {rawstring, uint64, rawstring}, cmd = true, sql = [[ delete from parsav_auth where ((uid = 0 and name = $1::text) or uid = $2::bigint) and kind like $3::text ]] }; + + auth_purge_aid = { + params = {uint64}, cmd = true, sql = [[ + delete from parsav_auth where aid = $1::bigint + ]] + }; auth_enum_uid = { params = {uint64}, sql = [[ select aid, kind, comment, netmask, blacklist from parsav_auth where uid = $1::bigint ]]; @@ -403,18 +423,32 @@ actor = $1::bigint and subject = $2::bigint and kind = $3::text ]]; }; + + post_react_delete = { + params = {uint64}, cmd = true, sql = [[ + delete from parsav_acts where id = $1::bigint + ]]; + }; post_reacts_fetch_uid = { params = {uint64, uint64, pstring}, sql = [[ select id, actor, subject, kind, body, time from parsav_acts where ($1::bigint = 0 or actor = $1::bigint) and ($2::bigint = 0 or subject = $2::bigint) and ($3::text is null or kind = $3::text ) - ]] + ]]; + }; + + post_act_fetch_notice = { + params = {uint64}, sql = [[ + select (pg_temp.parsavpg_translate_act(a)).* + from parsav_acts as a + where id = $1::bigint + ]]; }; post_enum_author_uid = { params = {uint64,uint64,uint64,uint64, uint64}, sql = [[ select (c.post).* @@ -966,11 +1000,11 @@ a.ptr.origin = origin if avia.buf ~= nil then avia:free() end return a end -local privmap = lib.store.privmap +local privmap = lib.store.powmap local checksha = function(src, hash, origin, username, pw) local validate = function(kind, cred, credlen) return quote var r = queries.actor_auth_pw.exec( @@ -1017,12 +1051,12 @@ ac: &lib.store.actor ): {} var pdef: lib.store.powerset pdef:clear() var map = array([privmap]) for i=0, [map.type.N] do - var d = pdef and map[i].priv - var u = ac.rights.powers and map[i].priv + var d = pdef and map[i].val + var u = ac.rights.powers and map[i].val queries.actor_power_delete.exec(src, ac.id, map[i].name) if d:sz() > 0 and u:sz() == 0 then lib.dbg('blocking power ', {map[i].name.ptr, map[i].name.ct}) queries.actor_power_insert.exec(src, ac.id, map[i].name, 0) elseif d:sz() == 0 and u:sz() > 0 then @@ -1043,12 +1077,12 @@ for i=0, r.sz do for j=0, [map.type.N] do var pn = r:_string(i,0) if map[j].name:cmp(pn) then if r:bool(i,1) - then powers = powers + map[j].priv - else powers = powers - map[j].priv + then powers = powers + map[j].val + else powers = powers - map[j].val end end end end @@ -1270,18 +1304,20 @@ a.ptr.source = src var au = [lib.stat(lib.store.auth)] { ok = true } au.val.aid = aid au.val.uid = a.ptr.id - if not r:null(0,13) then -- restricted? + if not r:null(0,14) then -- restricted? au.val.privs:clear() - (au.val.privs.post << r:bool(0,14)) - (au.val.privs.edit << r:bool(0,15)) - (au.val.privs.account << r:bool(0,16)) - (au.val.privs.upload << r:bool(0,17)) - (au.val.privs.moderate<< r:bool(0,18)) - (au.val.privs.admin << r:bool(0,19)) + (au.val.privs.post << r:bool(0,15)) + (au.val.privs.edit << r:bool(0,16)) + (au.val.privs.account << r:bool(0,17)) + (au.val.privs.upload << r:bool(0,18)) + (au.val.privs.artifact<< r:bool(0,19)) + (au.val.privs.moderate<< r:bool(0,20)) + (au.val.privs.admin << r:bool(0,21)) + (au.val.privs.invite << r:bool(0,22)) else au.val.privs:fill() end return au, a end @@ -1323,18 +1359,23 @@ var p = row_to_post(&r, 0) p.ptr.source = src return p end]; + post_act_cancel = [terra( + src: &lib.store.source, + act: uint64 + ): {} queries.post_react_delete.exec(src, act) end]; + post_retweet = [terra( src: &lib.store.source, uid: uint64, post: uint64, undo: bool ): {} - var time = lib.osclock.time(nil) if not undo then + var time = lib.osclock.time(nil) queries.post_react_simple.exec(src,uid,post,"rt",time) else queries.post_react_cancel.exec(src,uid,post,"rt") end end]; @@ -1530,18 +1571,37 @@ src: &lib.store.source, uid: uint64, reset: bool, pw: pstring, comment: pstring - ): {} + ): uint64 var hash: uint8[lib.crypt.algsz.sha256] if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(lib.crypt.alg.sha256.id), [&uint8](pw.ptr), pw.ct, &hash[0]) ~= 0 then lib.bail('cannot hash password') end if reset then queries.auth_purge_type.exec(src, nil, uid, 'pw-%') end - queries.auth_create_pw.exec(src, uid, binblob {ptr = &hash[0], ct = [hash.type.N]}, lib.osclock.time(nil), comment) + var r = queries.auth_create_pw.exec(src, uid, binblob {ptr = &hash[0], ct = [hash.type.N]}, lib.osclock.time(nil), comment) + if r.sz == 0 then return 0 end + var aid = r:int(uint64,0,0) + r:free() + return aid + end]; + + auth_privs_set = [terra( + src: &lib.store.source, + aid: uint64, + set: lib.store.privset + ): {} + var map = array([lib.store.privmap]) + queries.auth_privs_clear.exec(src,aid) + if set:sz() == 0 then return end + for i=0, [map.type.N] do + if (set and map[i].val):sz() > 0 then + queries.auth_priv_install.exec(src,aid,map[i].name) + end + end end]; auth_purge_pw = [terra(src: &lib.store.source, uid: uint64, handle: rawstring): {} queries.auth_purge_type.exec(src, handle, uid, 'pw-%') end]; @@ -1701,11 +1761,11 @@ end]; post_enum_parent = [terra( src: &lib.store.source, post: uint64 - ): lib.mem.ptr(lib.mem.ptr(lib.store.post)) + ): lib.mem.lstptr(lib.store.post) var r = queries.post_enum_parent.exec(src,post) if r.sz == 0 then return [lib.mem.ptr(lib.mem.ptr(lib.store.post))].null() end defer r:free() @@ -1713,10 +1773,31 @@ for i=0, r.sz do lst.ptr[i] = row_to_post(&r, i) end return lst end]; + + post_act_fetch_notice = [terra( + src: &lib.store.source, + act: uint64 + ): lib.store.notice + var r = queries.post_act_fetch_notice.exec(src,act) + if r.sz == 0 then return lib.store.notice { kind = lib.store.noticetype.none } end + defer r:free() + + var n: lib.store.notice + n.kind = r:int(uint16,0,0) + n.when = r:int(int64,0,1) + n.who = r:int(int64,0,2) + n.what = r:int(uint64,0,3) + if n.kind == lib.store.noticetype.react then + var react = r:_string(0,5) + lib.str.ncpy(n.reaction, react.ptr, lib.math.smallest(react.ct,[(`n.reaction).tree.type.N])) + end + + return n + end]; thread_latest_arrival_calc = [terra( src: &lib.store.source, post: uint64 ): lib.store.timepoint Index: backend/schema/pgsql-views.sql ================================================================== --- backend/schema/pgsql-views.sql +++ backend/schema/pgsql-views.sql @@ -42,10 +42,28 @@ who bigint, what bigint, reply bigint, reaction text ); + +create or replace function +pg_temp.parsavpg_translate_act(parsav_acts) +returns pg_temp.parsavpg_intern_notice as $$ + select row( + kmap.kind::smallint, + ($1).time, + ($1).actor, + ($1).subject, + null::bigint, + ($1).body + )::pg_temp.parsavpg_intern_notice as notice + from (values + ('rt', ), + ('like', ), + ('react', ) + ) as kmap(kstr,kind) where kmap.kstr = ($1).kind +$$ language sql; create type pg_temp.parsavpg_intern_actor as ( id bigint, nym text, handle text, @@ -158,26 +176,28 @@ create temp view parsavpg_notices as ( -- TODO add mentions with ntimes as ( select uid, value as when from parsav_actor_conf_ints where key = 'notice-clear-time' ), acts as ( - select row( - kmap.kind::smallint, - a.time, - a.actor, - a.subject, - null::bigint, - null::text - )::pg_temp.parsavpg_intern_notice as notice, + select + pg_temp.parsavpg_translate_act(a) as notice, + -- row( + -- kmap.kind::smallint, + -- a.time, + -- a.actor, + -- a.subject, + -- null::bigint, + -- null::text + -- )::pg_temp.parsavpg_intern_notice as notice, p.author as rcpt from parsav_acts as a inner join parsav_posts as p on a.subject = p.id - inner join (values - ('rt', ), - ('like', ), - ('react', ) - ) as kmap(kstr,kind) on kmap.kstr = a.kind + -- inner join (values + -- ('rt', ), + -- ('like', ), + -- ('react', ) + -- ) as kmap(kstr,kind) on kmap.kstr = a.kind left join ntimes as nt on nt.uid = p.author where a.time >= coalesce(nt.when,0) ), replies as ( select row( ::smallint, Index: mgtool.t ================================================================== --- mgtool.t +++ mgtool.t @@ -489,11 +489,11 @@ elseif umode.arglist.ct >= 3 then var grant = lib.str.cmp(umode.arglist(1),'grant') == 0 if not usr then lib.bail('no such user') end if grant or lib.str.cmp(umode.arglist(1),'revoke') == 0 then var newprivs = usr.ptr.rights.powers - var map = array([lib.store.privmap]) + var map = array([lib.store.powmap]) if umode.arglist.ct == 3 and lib.str.cmp(umode.arglist(2),'all') == 0 then if grant then newprivs:fill() else newprivs:clear() end @@ -503,14 +503,14 @@ for j=0,[map.type.N] do var p = map[j] if p.name:cmp_raw(priv) then if grant then lib.dbg('enabling power ', {p.name.ptr,p.name.ct}) - newprivs = newprivs + p.priv + newprivs = newprivs + p.val else lib.dbg('disabling power ', {p.name.ptr,p.name.ct}) - newprivs = newprivs - p.priv + newprivs = newprivs - p.val end break end end end Index: render/conf/sec.t ================================================================== --- render/conf/sec.t +++ render/conf/sec.t @@ -11,13 +11,14 @@ lastreset = pstr { ptr = &tstr[0], ct = lib.str.sz(&tstr[0]) } } + var a: lib.str.acc a:init(768) defer a:free() + if co.srv.cfg.credmgd then var new = co:pgetv('new') - var a: lib.str.acc a:init(768) if not new then body:append(&a) var credmgr = data.view.conf_sec_credmg { credlist = pstr{'',0}; } @@ -35,30 +36,33 @@ end credmgr.credlist = cl:finalize() end credmgr:append(&a) if credmgr.credlist.ct > 0 then credmgr.credlist:free() end - elseif new:cmp(lib.str.plit'pw') then - var d: data.view.conf_sec_pwnew - var time = lib.osclock.time(nil) - var timestr: int8[26] lib.osclock.ctime_r(&time, ×tr[0]) - var cmt: lib.str.acc - cmt:init(48):lpush('enrolled over http on '):push(×tr[0],0) - d.comment = cmt:finalize() + else + if new:cmp(lib.str.plit'pw') then + var d: data.view.conf_sec_pwnew + var time = lib.osclock.time(nil) + var timestr: int8[26] lib.osclock.ctime_r(&time, ×tr[0]) + var cmt: lib.str.acc + cmt:init(48):lpush('enrolled over http on '):push(×tr[0],0) + d.comment = cmt:finalize() + + var st = d:tostr() + d.comment:free() + return st + elseif new:cmp(lib.str.plit'challenge') then + -- we're going to break the rules a bit and do database munging from + -- the rendering code, because doing otherwise in this case would be + -- genuinely nightmarish + elseif new:cmp(lib.str.plit'otp') then + elseif new:cmp(lib.str.plit'api') then + else return pstr.null() end + end + else body:append(&a) end - var st = d:tostr() - d.comment:free() - return st - elseif new:cmp(lib.str.plit'challenge') then - -- we're going to break the rules a bit and do database munging from - -- the rendering code, because doing otherwise in this case would be - -- genuinely nightmarish - elseif new:cmp(lib.str.plit'otp') then - elseif new:cmp(lib.str.plit'api') then - else return pstr.null() end - return a:finalize() - else return body:tostr() end + return a:finalize() end terra lib.render.conf.sec_overlay (co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr -- render the credential panel for the current user, allowing Index: render/conf/users.t ================================================================== --- render/conf/users.t +++ render/conf/users.t @@ -229,15 +229,15 @@ cinp:lpush('') if (co.who.rights.powers.elevate() or co.who.rights.powers.demote()) and user.ptr.id ~= co.who.id then - var map = array([lib.store.privmap]) + var map = array([lib.store.powmap]) cinp:lpush('
powers
') for i=0, [map.type.N] do - if (co.who.rights.powers and map[i].priv):sz() > 0 then - var on = (user.ptr.rights.powers and map[i].priv):sz() > 0 + if (co.who.rights.powers and map[i].val):sz() > 0 then + var on = (user.ptr.rights.powers and map[i].val):sz() > 0 var enabled = ( on and co.who.rights.powers.demote() ) or ((not on) and co.who.rights.powers.elevate()) var namea: lib.str.acc namea:compose('power-', map[i].name) var name = namea:finalize() push_pickbox(&cinp, name, pstr.null(), map[i].name, on, enabled, pstr.null()) Index: render/tweet-page.t ================================================================== --- render/tweet-page.t +++ render/tweet-page.t @@ -41,11 +41,16 @@ then pg:lpush('') else pg:lpush('') end pg:lpush('') if p.author == co.who.id then - pg:lpush('editdelete') + if co.who.rights.powers.edit() then + pg:lpush('edit') + end + pg:lpush('delete') + elseif co.who.rights.powers.snitch() then + pg:lpush('report') end -- TODO list user's chosen reaction emoji pg:lpush('') end Index: route.t ================================================================== --- route.t +++ route.t @@ -199,23 +199,31 @@ if not ok then co:complain(400, 'bad post ID', 'that post ID is not valid') return end var post = co.srv:post_fetch(pid) + var rt: lib.store.notice if not post then - co:complain(404, 'post not found', 'no such post is known to this server') - return + rt = co.srv:post_act_fetch_notice(pid) + if rt.kind ~= lib.store.noticetype.rt then + co:complain(404, 'post not found', 'no such post is known to this server') + return + elseif rt.who ~= co.who.id then + co:complain(403, 'forbidden', 'you cannot cancel other people\'s retweets') + return + end end - defer post:free() + defer post:free() -- NOP on null if path.ct == 3 then var lnk: lib.str.acc lnk:compose('/post/', path(1)) var lnkp = lnk:finalize() defer lnkp:free() - if post(0).author ~= co.who.id then + if post:ref() and post(0).author ~= co.who.id then co:complain(403, 'forbidden', 'you cannot alter other people\'s posts') return - elseif path(2):cmp(lib.str.lit 'edit') then + elseif post:ref() and path(2):cmp(lib.str.lit 'edit') then + if not co:assertpow('edit') then return end if meth_get(meth) then lib.render.compose(co, post.ptr, nil) return elseif meth == method.post then var newbody = co:postv('post')._0 @@ -228,15 +236,24 @@ co:reroute(lnkp.ptr) end return elseif path(2):cmp(lib.str.lit 'del') then if meth_get(meth) then - var conf = data.view.confirm { - title = lib.str.plit 'delete post'; - query = lib.str.plit 'are you sure you want to delete this post?'; - cancel = lnkp - } + var conf: data.view.confirm + if post:ref() then + conf = data.view.confirm { + title = lib.str.plit 'delete post'; + query = lib.str.plit 'are you sure you want to delete this post?'; + cancel = lnkp + } + else + conf = data.view.confirm { + title = lib.str.plit 'cancel retweet'; + query = lib.str.plit 'are you sure you want to undo this retweet?'; + cancel = lib.str.plit'/'; + } + end var body = conf:tostr() defer body:free() co:stdpage([lib.srv.convo.page] { title = lib.str.plit 'post :: delete'; class = lib.str.plit 'query'; body = body; cache = false; @@ -243,19 +260,23 @@ }) return elseif meth == method.post then var act = co:ppostv('act') if act:cmp(lib.str.plit 'confirm') then - post(0).source:post_destroy(post(0).id) + if post:ref() then + post(0).source:post_destroy(post(0).id) + elseif rt.kind ~= 0 then + co.srv:post_act_cancel(pid) + end co:reroute('/') -- TODO maybe return to parent or conversation if possible return else goto badop end end else goto badurl end end - if meth == method.post then + if post:ref() and meth == method.post then if co.aid == 0 then goto noauth end var act = co:ppostv('act') if act:cmp(lib.str.plit 'like') and not co.srv:post_liked_uid(co.who.id,pid) then co.srv:post_like(co.who.id, pid, false) post.ptr.likes = post.ptr.likes + 1 @@ -278,10 +299,12 @@ } reply:publish(co.srv) else goto badop end end + + if not post then goto badurl end lib.render.tweet_page(co, path, post.ptr) do return end ::badurl:: do co:complain(404, 'invalid URL', 'this URL does not reference extant content or functionality') return end @@ -290,42 +313,70 @@ end local terra credsec_for_uid(co: &lib.srv.convo, uid: uint64) var act = co:ppostv('act') + lib.dbg('showing credentials') if act:cmp(lib.str.plit 'invalidate') then lib.dbg('setting user\'s cookie validation time to now') co.who.source:auth_sigtime_user_alter(uid, lib.osclock.time(nil)) -- 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 co:installkey('/conf/sec',co.aid) return elseif act:cmp(lib.str.plit 'newcred') then var cmt = co:ppostv('comment') var pw = co:ppostv('newpw') + var aid: uint64 = 0 if pw:ref() then var cpw = co:ppostv('rptpw') if not pw:cmp(cpw) then co:complain(400,'enrollment failure','the passwords you supplied do not match') return end - co.srv:auth_attach_pw(uid, false, pw, cmt) - co:reroute('?') - return + aid = co.srv:auth_attach_pw(uid, false, pw, cmt) else var key = co:ppostv('newkey') if key:ref() then end end + if aid ~= 0 then + lib.dbg('setting credential restrictions') + var privs = [(function() + local check = quote end + local me = symbol(lib.store.privset) + for i,v in ipairs(lib.store.privset.members) do + check = quote [check] + var val = co:pgetv(['allow-' .. v]) + if val:ref() and val:cmp(lib.str.plit'on') + then ([me].[v] << true) + else ([me].[v] << false) + end + end + end + return quote + var [me] + [check] + in [me] end + end)()] + privs:dump() + if privs:sz() > 0 then + lib.dbg('installing credential restrictions') + lib.io.fmt('on priv %llu\n',aid) + co.srv:auth_privs_set(aid, privs) + end + end + co:reroute('?') + return end co:complain(400,'bad request','the operation you have requested is not meaningful in this context') end terra http.configure(co: &lib.srv.convo, path: hpath, meth: method.t) var msg = pstring.null() -- first things first, do priv checks - if path.ct >= 1 then + if path.ct >= 2 then if not co.who.rights.powers.config() and ( path(1):cmp(lib.str.lit 'srv') or path(1):cmp(lib.str.lit 'badge') or path(1):cmp(lib.str.lit 'emoji') ) then goto nopriv @@ -334,11 +385,13 @@ path(1):cmp(lib.str.lit 'brand') ) then goto nopriv elseif not co.who.rights.powers.account() and ( path(1):cmp(lib.str.lit 'profile') or - path(1):cmp(lib.str.lit 'acct') + path(1):cmp(lib.str.lit 'sec') or + path(1):cmp(lib.str.lit 'avi') or + path(1):cmp(lib.str.lit 'ui') ) then goto nopriv elseif not co.who.rights.powers:affect_users() and ( path(1):cmp(lib.str.lit 'users') ) then goto nopriv end @@ -386,10 +439,15 @@ var userid, ok = lib.math.shorthand.parse(path(2).ptr, path(2).ct) if ok then var usr = co.srv:actor_fetch_uid(userid) if usr:ref() then defer usr:free() if not co.who:overpowers(usr.ptr) then goto nopriv end + end + if path.ct == 4 then + if path(3):cmp(lib.str.lit 'cred') then + credsec_for_uid(co, userid) + end end end elseif path.ct == 2 and meth == method.post then var act = co:ppostv('act') if act:cmp(lib.str.plit'create') then Index: srv.t ================================================================== --- srv.t +++ srv.t @@ -518,11 +518,33 @@ if co.aid ~= 0 then var sess, usr = co.srv:actor_session_fetch(co.aid, peer, co.aid_issue) if sess.ok == false then co.aid = 0 co.aid_issue = 0 else co.who = usr.ptr - co.who.rights.powers = server:actor_powers_fetch(co.who.id) + var pows = server:actor_powers_fetch(co.who.id) + var privs = sess.val.privs + if not privs.post() then (pows.post << false) end + if not privs.edit() then (pows.edit << false) end + if not privs.account() then (pows.account << false) end + if not privs.artifact() then (pows.artifact << false) end + if not privs.invite() then (pows.invite << false) end + if not privs.moderate() then + (pows.censor << false) + (pows.discipline << false) + (pows.vacate << false) + (pows.crier << false) + end + if not privs.admin() then + (pows.cred << false) + (pows.elevate << false) + (pows.demote << false) + (pows.rebrand << false) + (pows.herald << false) + (pows.config << false) + (pows.purge << false) + end + co.who.rights.powers = pows var userhue, hueok = server:actor_conf_int_get(co.who.id, 'ui-accent') if hueok then co.ui_hue = userhue end end end Index: static/live.js ================================================================== --- static/live.js +++ static/live.js @@ -61,14 +61,15 @@ } else if (event.key == 'r') { // rt postReq(root, 'rt', post.querySelector('.stats>.rt')) } else if (event.key == 'Enter') { // nav window.location = root; return; + } else if (event.key == 'u' && root != cururl) { + window.location = cururl; // detweet TODO don't try to delete other's retweets } else if (post.attributes.getNamedItem('data-own')) { if (event.key == 'd') { window.location = root + '/del'; } else if (event.key == 'e') { window.location = root + '/edit'; } - else if (event.key == 'u' && root != cururl) { window.location = cururl; } // detweet } } if (nexturl != null) { if (cururl != null) { let cur = window._liveTweetMap.map.get(cururl); Index: store.t ================================================================== --- store.t +++ store.t @@ -22,11 +22,11 @@ }; credset = lib.set { 'pw', 'otp', 'challenge', 'trust' }; privset = lib.set { - 'post', 'edit', 'account', 'upload', 'moderate', 'admin', 'invite' + 'post', 'edit', 'account', 'upload', 'artifact', 'moderate', 'admin', 'invite' }; powerset = lib.set { -- user powers -- default on 'login', -- not locked out 'visible', -- account & posts can be seen by others @@ -54,18 +54,23 @@ prepmode = lib.enum { 'full','conf','admin' } } -m.privmap = {} -do local struct pt { name:lib.mem.ptr(int8), priv:m.powerset } -for k,v in pairs(m.powerset.members) do - m.privmap[#m.privmap + 1] = quote - var ps: m.powerset ps:clear() - (ps.[v] << true) - in pt {name = lib.str.plit(v), priv = ps} end -end end +local function setmap(set) + local map = {} + local struct pt { name:lib.mem.ptr(int8), val:set } + for k,v in pairs(set.members) do + map[#map + 1] = quote + var ps: set ps:clear() + (ps.[v] << true) + in pt {name = lib.str.plit(v), val = ps} end + end + return map +end +m.powmap = setmap(m.powerset) +m.privmap = setmap(m.privset) terra m.powerset:affect_users() return self.purge() or self.discipline() or self.herald() or self.elevate() or self.demote() or self.cred() end @@ -410,16 +415,17 @@ actor_rel_destroy: {&m.source, uint16, uint64, uint64} -> {} actor_rel_calc: {&m.source, uint64, uint64} -> m.relationship auth_enum_uid: {&m.source, uint64} -> lib.mem.lstptr(m.auth) auth_enum_handle: {&m.source, rawstring} -> lib.mem.lstptr(m.auth) - auth_attach_pw: {&m.source, uint64, bool, pstr, pstr} -> {} + auth_attach_pw: {&m.source, uint64, bool, pstr, pstr} -> uint64 auth_attach_key: {&m.source, uint64, bool, pstr, pstr} -> {} -- uid: uint64 -- reset: bool (delete other passwords?) -- pw: pstring -- comment: pstring + auth_privs_set: {&m.source, uint64, m.privset} -> {} auth_purge_pw: {&m.source, uint64, rawstring} -> {} auth_purge_otp: {&m.source, uint64, rawstring} -> {} auth_purge_trust: {&m.source, uint64, rawstring} -> {} auth_sigtime_user_fetch: {&m.source, uint64} -> m.timepoint -- authentication tokens and accounts have a property that controls @@ -445,12 +451,14 @@ post_retweet: {&m.source, uint64, uint64, bool} -> {} post_like: {&m.source, uint64, uint64, bool} -> {} -- undo: bool post_react: {&m.source, uint64, uint64, pstring} -> {} -- emoji: pstring (null to delete previous reaction, otherwise adds/changes) + post_act_cancel: {&m.source, uint64} -> {} post_liked_uid: {&m.source, uint64, uint64} -> bool post_reacted_uid: {&m.source, uint64, uint64} -> bool + post_act_fetch_notice: {&m.source, uint64} -> m.notice thread_latest_arrival_calc: {&m.source, uint64} -> m.timepoint artifact_instantiate: {&m.source, lib.mem.ptr(uint8), lib.mem.ptr(int8)} -> uint64 -- instantiate an artifact in the database, either installing a new Index: view/conf-sec-credmg.tpl ================================================================== --- view/conf-sec-credmg.tpl +++ view/conf-sec-credmg.tpl @@ -13,13 +13,14 @@

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.

- + - + +

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.