-- vim: ft=terra
local pstr = lib.mem.ptr(int8)
local pref = lib.mem.ref(int8)
local P = lib.str.plit
local terra cs(s: rawstring)
return pstr { ptr = s, ct = lib.str.sz(s) }
end
local terra
regalia(acc: &lib.str.acc, rank: uint16)
switch rank do -- TODO customizability
case [uint16](1) then acc:lpush('π') end
case [uint16](2) then acc:lpush('π±') end
case [uint16](3) then acc:lpush('βοΈ') end
case [uint16](4) then acc:lpush('π‘') end
case [uint16](5) then acc:lpush('π') end
else acc:lpush('π΄')
end
end
local rnd = lib.crypt.random
local terra
suggest_handle(a: &lib.str.acc)
var start = a.sz
var puncts = array('.','_','-')
var xXx = rnd(uint8, 0, 9) == 0
var leet = rnd(uint8, 0, 8) == 0
var caps = rnd(uint8, 0, 5)
var punct: rawstring = nil
var useadj = rnd(uint8, 0, 4) == 0
if rnd(uint8, 0, 4) == 0 then
punct = puncts[rnd(intptr,0,[puncts.type.N])]
end
var nouns = array(
'thunder','bride','blaze','doom','squad','gun','lord','blaster',
'fuck','hell','hound','piss','shit','killa','terror', 'horror',
'fear', 'slaughter','murder','general','commander', 'commissar',
'terrorist','infinity','slut','cunt','whore','bitch', 'bastard',
'cock','prince','princess','pimp','gay','cop','slayer', 'vampire',
'vampyre','blood','pain','brute','wolf','sword','star','sun','moon',
'killer','murderer','thief','arson','fire','ice','frost','hack',
'hacker','god','master','mistress','slave','rage','freeze','flayer',
'pirate','ninja','shadow','fog','mist','misery','glory','bear',
'king','queen','empress','emperor','majesty','space','martian',
'winter','fall','monk','katana','420','warrior','banana','demon',
'devil','ghost','wraith','cuck','legend','hero','heroine','goblin',
'gremlin','troll','dragon','evil','overlord','radiance'
)
var adjs = array(
'dark','super','supreme','ultra','ultimate','total','infinite',
'omnipotent','crazy','final','deathless','immortal', 'elite',
'leet','1337','bloody','fearless','headless','screaming','insane',
'brutal','legendary','space','frozen','flaming','burning',
'mighty','flayed','hidden','secret','lost','mystery','glorious',
'nude','naked','bare','first','radiant','martian','fallen',
'wandering','dank','demonic','satanic','invisible','based','woke',
'deadly','lethal','heroic','evil','majestic','luminous'
)
if xXx then a:lpush('xXx_') end
if useadj then
var len = rnd(uint8,1,3)
for i = 0, len do
var sz = a.sz
a:push(adjs[rnd(intptr,0,[adjs.type.N])], 0)
if punct ~= nil then a:push(punct, 1) end
if caps == 1 then
a.buf[sz] = lib.str.cupcase(a.buf[sz])
end
end
end
var nounct = rnd(uint8,1,3)
for i = 0, nounct do
var sz = a.sz
a:push(nouns[rnd(intptr,0,[nouns.type.N])], 0)
if punct ~= nil and i+1 ~= nounct then a:push(punct, 1) end
if caps == 1 then
a.buf[sz] = lib.str.cupcase(a.buf[sz])
end
end
if leet or caps == 2 then for i=start, a.sz do
if caps == 2 and rnd(uint8,0,5)==0 then
a.buf[i] = lib.str.cupcase(a.buf[i])
end
if leet then
switch lib.str.cdowncase(a.buf[i]) do
case [uint8]([string.byte('e')]) then a.buf[i] = @'3' end
case [uint8]([string.byte('i')]) then a.buf[i] = @'1' end
case [uint8]([string.byte('l')]) then a.buf[i] = @'1' end
case [uint8]([string.byte('t')]) then a.buf[i] = @'7' end
case [uint8]([string.byte('s')]) then a.buf[i] = @'5' end
case [uint8]([string.byte('o')]) then a.buf[i] = @'0' end
case [uint8]([string.byte('b')]) then a.buf[i] = @'6' end
end
end
end end
if (nounct == 1 and not useadj) or rnd(uint8, 0, 5) == 0 then
if punct ~= nil then a:push(punct, 1) end
a:ipush(rnd(uint16,0,65535))
end
if xXx then a:lpush('_xXx') end
end
local terra
suggest_domain(a: &lib.str.acc)
var tlds = array('tld','club','town','space','xxx')
end
local push_num_field = macro(function(acc,name,lbl,min,max,value,disable)
name = name:asvalue()
lbl = lbl:asvalue()
local start = '<div class="elem small">'
local enabled = start .. string.format('<label for="%s">%s</label><input type="number" id="%s" name="%s" min="', name, lbl, name, name)
local disabled = start .. string.format('<label>%s</label><div class="txtbox">', lbl)
return quote
var decbuf: int8[21]
if disable then
acc:lpush([disabled])
:push(lib.math.decstr(value, &decbuf[20]),0):lpush('</div></div>')
else
acc:lpush([enabled]):push(lib.math.decstr(min, &decbuf[20]),0)
:lpush('" max="'):push(lib.math.decstr(max, &decbuf[20]),0)
:lpush('" value="'):push(lib.math.decstr(value, &decbuf[20]),0):lpush('"></div>')
end
end
end)
local input_pusher = function(kind,wrap,uniq)
local fn = terra(acc: &lib.str.acc, name: pstr, val: pstr, lbl: pstr, on: bool, enabled: bool, class: pstr)
if wrap then acc:lpush('<label>') end
acc:lpush(['<input type="'..kind..'" name="']):ppush(name)
if not wrap then
acc:lpush('" id="'):ppush(name)
if uniq then acc:lpush('-'):ppush(val) end
end
if val:ref() then acc:lpush('" value="'):ppush(val) end
if class:ref() then acc:lpush('" class="'):ppush(class) end
acc:lpush('"')
if on then acc:lpush(' checked') end
if not enabled then acc:lpush(' disabled') end
acc:lpush('>')
if not wrap then acc:lpush('<label for="'):ppush(name)
if uniq then acc:lpush('-'):ppush(val) end
acc:lpush('">')
else acc:lpush(' ') end
acc:ppush(lbl):lpush('</label>')
end
fn.name = string.format('push-input-element<%q>',kind)
return fn
end
local push_checkbox = input_pusher('checkbox',true,false)
local push_pickbox = input_pusher('checkbox',false,false)
local push_radio = input_pusher('radio',false,true)
local mode_local, mode_remote, mode_staff, mode_peers, mode_peons, mode_all = 0,1,2,3,4,5
local terra
render_conf_users(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr
if path.ct >= 3 then
var uid, ok = lib.math.shorthand.parse(path(2).ptr,path(2).ct)
if not ok then goto e404 end
var user = co.srv:actor_fetch_uid(uid)
-- FIXME allow xids as well, for manual queries
if not user then goto e404 end
defer user:free()
if not co.who:overpowers(user.ptr) then goto e403 end
if path.ct == 4 then
if path(3):cmp(lib.str.lit'cred') then
var pg: lib.str.acc pg:init(1024)
pg:lpush('<div class="context">editing credentials for user <a href="/conf/users/'):rpush(path(2)):lpush('">'):push(user(0).xid,0):lpush('</a></div>')
var credmgr = lib.render.conf.sec(co, uid)
pg:ppush(credmgr)
credmgr:free()
return pg:finalize()
else goto e404 end
elseif path.ct == 3 then
var cinp: lib.str.acc cinp:init(256)
cinp:lpush('<div class="elem-group">')
if user.ptr.rights.rank > 0 and (co.who.rights.powers.elevate() or co.who.rights.powers.demote()) then
var max = co.who.rights.rank
if not co.who.rights.powers.elevate() then max = user.ptr.rights.rank end
var min = co.srv.cfg.nranks
if not co.who.rights.powers.demote() then min = user.ptr.rights.rank end
push_num_field(cinp, 'rank', 'rank', max, min, user.ptr.rights.rank, user.ptr.id == co.who.id)
end
if co.who.rights.powers.herald() then
var sanitized: pstr
if user.ptr.epithet == nil
then sanitized = pstr {ptr='', ct=0}
else sanitized = lib.html.sanitize(cs(user.ptr.epithet),true)
end
cinp:lpush('<div class="elem"><label for="epithet">epithet</label><input type="text" id="epithet" name="epithet" value="'):ppush(sanitized):lpush('"></div>')
if user.ptr.epithet ~= nil then sanitized:free() end
end
if co.who.rights.powers.invite() or co.who.rights.powers.discipline() then
var min: uint32 = 0
if not (co.who.rights.powers.discipline() or
co.who.rights.powers.demote() and co.who.rights.powers.invite())
then min = user.ptr.rights.invites end
var max: uint32 = co.srv.cfg.maxinvites
if not co.who.rights.powers.invite() then max = user.ptr.rights.invites end
push_num_field(cinp, 'invites', 'invites', min, max, user.ptr.rights.invites, false)
end
if co.who.rights.powers.elevate() or co.who.rights.powers.demote() then
var max: uint32 = 5000
if not co.who.rights.powers.elevate() then max = user.ptr.rights.quota end
var min: uint32 = 0
if not co.who.rights.powers.demote() then min = user.ptr.rights.quota end
push_num_field(cinp, 'quota', 'quota', min, max, user.ptr.rights.quota, user.ptr.id == co.who.id and co.who.rights.rank ~= 1)
end
cinp:lpush('</div><div class="elem"><div class="check-panel">')
if user.ptr.id ~= co.who.id and
((user.ptr.rights.rank == 0 and co.who.rights.powers.elevate()) or
(user.ptr.rights.rank > 0 and co.who.rights.powers.demote())) then
push_checkbox(&cinp, 'staff', pstr.null(), 'site staff member', user.ptr.rights.rank > 0, true, pstr.null())
end
cinp:lpush('</div></div>')
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])
cinp:lpush('<details><summary>powers</summary><div class="pick-list">')
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
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())
name:free()
end
end
cinp:lpush('</div></details>')
end
if co.who.id ~= uid and co.who.rights.powers.purge() then
var purgeconf: lib.str.acc purgeconf:init(48)
var purgestrs = array(
'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa',
'emerald', 'carnelian', 'sapphire', 'ruby', 'amethyst', 'glory',
'hope', 'grace', 'pearl', 'carnation', 'rose', 'peony', 'poppy'
)
for i=0,3 do
purgeconf:push(purgestrs[lib.crypt.random(intptr,0,[purgestrs.type.N])],0)
if i ~= 2 then purgeconf:lpush('-') end
end
cinp:lpush('<details><summary>purge account</summary><p>you have the authority to destroy this account and all its associated content irreversibly and irretrievably. if you really wish to apply such an extreme sanction, enter the confirmation string <strong style="user-select:none">'):push(purgeconf.buf,purgeconf.sz):lpush('</strong> below and press the βalterβ button to begin the process.</p><div class="elem"><label for="purge">purge confirmation string</label><input type="text" id="purge" name="purgekey"></div><input type="hidden" name="purgestr" value="'):push(purgeconf.buf,purgeconf.sz):lpush('"></details>')
purgeconf:free()
end
-- TODO black mark system? e.g. resolution option for badthink reports
-- adds a black mark to the offending user; they can be automatically banned
-- or brought up for review after a certain number of offenses; possibly lower
-- set of default privs for marked users
var cinpp = cinp:finalize() defer cinpp:free()
var unym: lib.str.acc unym:init(64)
unym:lpush('<a href="/')
if user(0).origin ~= 0 then unym:lpush('@') end
do var sanxid = lib.html.sanitize(user(0).xid, true)
unym:ppush(sanxid)
sanxid:free() end
unym:lpush('" class="id">')
lib.render.nym(user.ptr,0,&unym,false)
unym:lpush('</a>')
var ctlbox = data.view.conf_user_ctl {
name = unym:finalize();
inputcontent = cinpp;
btns = pstr{'',0};
}
if co.who.id ~= uid and co.who.rights.powers.cred() then
ctlbox.btns = lib.str.acc{}:compose('<a class="button" href="/conf/users/',path(2),'/cred">security & credentials</a>'):finalize()
end
var pg: lib.str.acc pg:init(512)
ctlbox:append(&pg)
ctlbox.name:free()
if ctlbox.btns.ct > 0 then ctlbox.btns:free() end
return pg:finalize()
end
else
var modes = array(P'local', P'remote', P'staff', P'titled', P'peons', P'all')
var idbuf: int8[lib.math.shorthand.maxlen]
var ulst: lib.str.acc ulst:init(256)
var mode: uint8 = mode_local
var modestr = co:pgetv('show')
ulst:lpush('<div style="text-align: right"><em>showing ')
for i=0,[modes.type.N] do
if modestr:ref() and modes[i]:cmp(modestr) then mode = i end
end
for i=0,[modes.type.N] do
if i > 0 then ulst:lpush(' Β· ') end
if mode == i then
ulst:lpush('<strong>'):ppush(modes[i]):lpush('</strong>')
else
ulst:lpush('<a href="?show='):ppush(modes[i]):lpush('">')
:ppush(modes[i]):lpush('</a>')
end
end
var users: lib.mem.lstptr(lib.store.actor)
if mode == mode_local then
users = co.srv:actor_enum_local()
else
users = co.srv:actor_enum()
end
ulst:lpush('</em></div>')
ulst:lpush('<ul class="user-list">')
for i=0,users.ct do var usr = users(i).ptr
if mode == mode_staff and usr.rights.rank == 0 then goto skip
elseif mode == mode_peons and usr.rights.rank ~= 0 then goto skip
elseif mode == mode_remote and usr.origin == 0 then goto skip
elseif mode == mode_peers and usr.epithet == nil then goto skip end
var idlen = lib.math.shorthand.gen(usr.id, &idbuf[0])
ulst:lpush('<li>')
if usr.rights.rank ~= 0 then
ulst:lpush('<span class="regalia">')
regalia(&ulst, usr.rights.rank)
ulst:lpush('</span>')
end
if co.who:overpowers(usr) then
ulst:lpush('<a class="id" href="users/'):push(&idbuf[0],idlen):lpush('">')
lib.render.nym(usr, 0, &ulst, false)
ulst:lpush('</a></li>')
else
ulst:lpush('<span class="id">')
lib.render.nym(usr, 0, &ulst, false)
ulst:lpush('</span></li>')
end
::skip::end
ulst:lpush('</ul>')
if co.who.rights.powers.invite() or co.who.rights.invites > 0 then
ulst:lpush('<details><summary>create new user</summary><form method="post"><div class="elem"><label for="handle">handle</label><input type="text" name="handle" id="handle" placeholder="')
suggest_handle(&ulst)
ulst:lpush('"></div><button name="act" value="create">create</button></form></details>')
end
ulst:lpush('<details><summary>instantiate remote actor</summary><form method="post"><div class="elem"><label for="xid">xid</label><input type="text" name="xid" id="xid" placeholder="tweetlord@website.tld"></div><button name="act" value="inst">instantiate</button></form></details>')
return ulst:finalize()
end
do return pstr.null() end
::e404:: co:complain(404, 'not found', 'there is no user or resource by that identifier on this server') goto quit
::e403:: co:complain(403, 'forbidden', 'you do not have sufficient authority to control that resource')
::quit:: return pstr.null()
end
return render_conf_users