Index: backend/pgsql.t
==================================================================
--- backend/pgsql.t
+++ backend/pgsql.t
@@ -121,11 +121,11 @@
params = {}, sql = [[
select id, nym, handle, origin, bio,
null::text, rank, quota, key, epithet,
knownsince::bigint,
'@' || handle,
- invites
+ invites, avatarid
from parsav_actors where origin is null
order by nullif(rank,0) nulls last, handle
]];
};
@@ -504,10 +504,17 @@
params = {uint64, uint64}, cmd = true, sql = [[
delete from parsav_artifact_claims where
uid = $1::bigint and
rid = $2::bigint
]];
+ };
+ artifact_collect_garbage = {
+ params = {}, cmd = true, sql = [[
+ delete from parsav_artifacts where
+ id not in (select rid from parsav_artifact_claims) and
+ content is not null -- avoid stepping on toes of ban mech
+ ]];
};
artifact_excise_forget = {
-- delete the blasted thing and pretend it never existed
params = {uint64}, cmd=true, sql = [[
delete from parsav_artifacts where id = $1::bigint
@@ -948,10 +955,11 @@
a.ptr.rights = lib.store.rights_default();
a.ptr.rights.rank = r:int(uint16, row, 6);
a.ptr.rights.quota = r:int(uint32, row, 7);
a.ptr.rights.invites = r:int(uint32, row, 12);
a.ptr.knownsince = r:int(int64,row, 10);
+ a.ptr.avatarid = r:int(uint64,row, 13);
if r:null(row,8) then
a.ptr.key.ct = 0 a.ptr.key.ptr = nil
else
a.ptr.key = r:bin(row,8)
end
@@ -1502,15 +1510,18 @@
var ret = lib.mem.heapa([lib.mem.ptr(lib.store.auth)], r.sz)
for i=0, r.sz do
var kind = r:_string(i, 1)
var comment = r:_string(i, 2)
var a = [ lib.str.encapsulate(lib.store.auth, {
- kind = {`kind.ptr, `kind.ct};
- comment = {`comment.ptr, `comment.ct};
+ kind = {`kind.ptr, `kind.ct+1};
+ comment = {`comment.ptr, `comment.ct+1};
}) ]
a.ptr.aid = r:int(uint64, i, 0)
- a.ptr.netmask = r:cidr(i, 3)
+ if r:null(i,3)
+ then a.ptr.netmask.pv = 0
+ else a.ptr.netmask = r:cidr(i, 3)
+ end
a.ptr.blacklist = r:bool(i, 4)
ret.ptr[i] = a
end
return ret
end];
@@ -1597,10 +1608,19 @@
desc: pstring,
folder: pstring
): {}
queries.artifact_expropriate.exec(src,uid,artifact,desc,folder, lib.osclock.time(nil))
end];
+
+ artifact_disclaim = [terra(
+ src: &lib.store.source,
+ uid: uint64,
+ artifact: uint64
+ )
+ queries.artifact_disclaim.exec(src,uid,artifact)
+ queries.artifact_collect_garbage.exec(src) -- TODO add a config option to change GC strategies, instead of just always running a cycle after an artifact is disclaimed, which is not very efficient
+ end];
artifact_enum_uid = [terra(
src: &lib.store.source,
uid: uint64,
folder: pstring
Index: backend/schema/pgsql-views.sql
==================================================================
--- backend/schema/pgsql-views.sql
+++ backend/schema/pgsql-views.sql
@@ -56,11 +56,12 @@
quota integer,
key bytea,
epithet text,
knownsince bigint,
xid text,
- invites integer
+ invites integer,
+ avatarid bigint
);
create or replace function
pg_temp.parsavpg_translate_actor(parsav_actors)
returns pg_temp.parsavpg_intern_actor as $$
@@ -69,11 +70,11 @@
($1).avataruri, ($1).rank, ($1).quota, ($1).key, ($1).epithet,
($1).knownsince::bigint,
coalesce(($1).handle || '@' ||
(select domain from parsav_servers as s where s.id = ($1).origin),
'@' || ($1).handle) as xid,
- ($1).invites
+ ($1).invites, ($1).avatarid
$$ language sql;
--drop type if exists pg_temp.parsavpg_intern_post;
create type pg_temp.parsavpg_intern_post as (
-- order is crucially important, and must match the order used
Index: parsav.md
==================================================================
--- parsav.md
+++ parsav.md
@@ -23,13 +23,13 @@
* inkscape, for rendering out some of the UI graphics that can't be represented with standard svg
* cwebp (libwebp package), for transforming inkscape PNGs to webp
* sassc, for compiling the SCSS stylesheet into its final CSS
-all builds require terra, which, unfortunately, requires installing an older version of llvm, v9 at the latest (which i develop parsav under). with any luck, your distro will be clever enough to package terra and its dependencies properly (it's trivial on nix, tho you'll need to tweak the terra expression to select a more recent llvm package); Arch Linux is one of those distros which is not so clever, and whose (AUR) terra package is totally broken. due to these unfortunate circumstances, terra is distributed not just in source form, but also in the the form of LLVM IR. distributions will also be made in the form of tarballed object code and assembly listings for various common platforms, currently including x86-32/64, arm7hf, aarch64, riscv, mips32/64, and ppc64/64le.
+all builds require terra, which, unfortunately, requires installing an older version of llvm, v9 at the latest (which i develop parsav under). with any luck, your distro will be clever enough to package terra and its dependencies properly (it's trivial on nix, tho you'll need to tweak the terra expression to select a more recent llvm package); Arch Linux is one of those distros which is not so clever, and whose (AUR) terra package is totally broken. due to these unfortunate circumstances, terra is distributed not just in source form, but also in the the form of LLVM IR.
-i've noticed that terra (at least with llvm9) seems to get a bit cantankerous and trigger llvm to fail with bizarre errors when you try to cross-compile parsav from x86-64 to any other platform, even x86-32. i don't know if this problem exists on other architectures or in what form, but as a workaround, the current cross-compile process consists of generating LLVM IR (ostensibly for x86-64, though this is in reality an architecture-independent language), and then compiling that down to an object file with llc. this is an enormous hassle; hopefully the terra (or llvm?) people will fix this eventually.
+i've noticed that terra (at least with llvm9) seems to get a bit cantankerous and trigger llvm to fail with bizarre errors when you try to cross-compile parsav from x86-64 to any other platform, even x86-32. i don't know if this problem exists on other architectures or in what form. as a workaround, i've tried generating LLVM IR (ostensibly for x86-64, though this is in reality an architecture-independent language), and then compiling that down to an object file with llc. it doesn't work. the generated binaries seem to run but they crash with bizarre errors and are impossible to debug, as llc refuses to include debug symbols. for these reasons, parsav will (almost certainly) not run on any architecture besides x86-64, at least until terra and/or llvm are fixed.
also note that, while parsav has a flag to build with ASAN, ASAN has proven unusable for most purposes as it routinely reports false positive buffer-heap-overflows. if you figure out how to defuckulate this, i will be overjoyed.
## building
Index: parsav.t
==================================================================
--- parsav.t
+++ parsav.t
@@ -41,27 +41,37 @@
return (tbl[false])(v,...)
end)
end;
emit_unitary = function(nl,fd,...)
local code = {}
+ local defs = {}
for i,v in ipairs{...} do
- if type(v) == 'string' or type(v) == 'number' then
- local str = tostring(v)
- code[#code+1] = `lib.io.send(2, str, [#str])
- elseif type(v) == 'table' and #v == 2 then
- code[#code+1] = `lib.io.send(2, [v[1]], [v[2]])
- elseif v.tree:is 'constant' then
- local str = tostring(v:asvalue())
- code[#code+1] = `lib.io.send(2, str, [#str])
+ local str, ct
+ if type(v) == 'table' and v.tree and not (v.tree:is 'constant') then
+ if v.tree.type.convertible == 'tuple' then
+ str = `v._0
+ ct = `v._1
+ else
+ local n = symbol(v.tree.type)
+ defs[#defs + 1] = quote var [n] = v end
+ str = n
+ ct = `lib.str.sz(n)
+ end
else
- code[#code+1] = quote var n = v in
- lib.io.send(2, n, lib.str.sz(n)) end
+ if type(v) == 'string' or type(v) == 'number' then
+ str = tostring(v)
+ else--if v.tree:is 'constant' then
+ str = tostring(v:asvalue())
+ end
+ ct = ct or #str
end
+
+ code[#code+1] = `lib.io.send(fd, str, ct)
end
if nl == true then code[#code+1] = `lib.io.send(fd, '\n', 1)
elseif nl then code[#code+1] = `lib.io.send(fd, nl, [#nl]) end
- return code
+ return quote [defs] in [code] end
end;
emitv = function(nl,fd,...)
local vec = {}
local defs = {}
for i,v in ipairs{...} do
@@ -162,11 +172,10 @@
}
if config.posix then
lib.uio = terralib.includec 'sys/uio.h';
lib.emit = lib.emitv -- use more efficient call where available
else lib.emit = lib.emit_unitary end
-
lib.noise = {
level = global(uint8,1);
starttime = global(lib.osclock.time_t);
lasttime = global(lib.osclock.time_t);
Index: render/conf.t
==================================================================
--- render/conf.t
+++ render/conf.t
@@ -4,11 +4,11 @@
local mappings = {
{url = 'profile', title = 'account profile', render = 'profile'};
{url = 'avi', title = 'avatar', render = 'avatar'};
{url = 'ui', title = 'user interface', render = 'ui'};
- {url = 'sec', title = 'security', render = 'sec'};
+ {url = 'sec', title = 'security', render = 'sec_overlay'};
{url = 'rel', title = 'relationships', render = 'rel'};
{url = 'qnt', title = 'quarantine', render = 'quarantine'};
{url = 'acl', title = 'access control shortcuts', render = 'acl'};
{url = 'rooms', title = 'chatrooms', render = 'rooms'};
{url = 'circles', title = 'circles', render = 'circles'};
@@ -31,13 +31,19 @@
if lib.render.conf[m.render] then
invoker = quote
if path(1):cmp(lib.str.lit([m.url])) then
var body = [lib.render.conf[m.render]] (co, path)
var a: lib.str.acc a:init(body.ct+48)
- a:lpush(['
' .. m.title .. '
']):ppush(body)
- panel = a:finalize()
- body:free()
+ if not body then
+ a:lpush(['
' .. m.title .. ' :: error
' ..
+ '
the requested resource is not available.
'])
+ panel = a:finalize()
+ else
+ a:lpush(['
' .. m.title .. '
']):ppush(body)
+ panel = a:finalize()
+ body:free()
+ end
else [invoker] end
end
end
end
Index: render/conf/sec.t
==================================================================
--- render/conf/sec.t
+++ render/conf/sec.t
@@ -1,25 +1,69 @@
-- vim: ft=terra
local pstr = lib.mem.ptr(int8)
local pref = lib.mem.ref(int8)
+
local terra
-render_conf_sec(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr
- var time: lib.store.timepoint = co.who.source:auth_sigtime_user_fetch(co.who.id)
+render_conf_sec(co: &lib.srv.convo, uid: uint64): pstr
+ var time: lib.store.timepoint = co.who.source:auth_sigtime_user_fetch(uid)
var tstr: int8[26]
lib.osclock.ctime_r(&time, &tstr[0])
var body = data.view.conf_sec {
lastreset = pstr {
ptr = &tstr[0], ct = lib.str.sz(&tstr[0])
}
}
if co.srv.cfg.credmgd then
+ var new = co:pgetv('new')
var a: lib.str.acc a:init(768)
- body:append(&a)
- var credmgr = data.view.conf_sec_credmg {
- credlist = ''
- }
- credmgr:append(&a)
+ if not new then
+ body:append(&a)
+ var credmgr = data.view.conf_sec_credmg {
+ credlist = pstr{'',0};
+ }
+ var creds = co.srv:auth_enum_uid(uid)
+ if creds.ct > 0 then defer creds:free()
+ var cl: lib.str.acc cl:init(256)
+ for i=0, creds.ct do var c = creds(i).ptr
+ if not c.blacklist then
+ cl:lpush('')
+ end
+ 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()
+
+ 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
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
+ -- it to be reused in the administration UI
+ return render_conf_sec(co,co.who.id)
+end
+
return render_conf_sec
Index: render/conf/users.t
==================================================================
--- render/conf/users.t
+++ render/conf/users.t
@@ -105,10 +105,15 @@
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 = '
'
@@ -156,114 +161,139 @@
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
+ 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
- var cinp: lib.str.acc cinp:init(256)
- var clnk: lib.str.acc clnk:init(512)
- cinp:lpush('
')
- 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)
+ if path.ct == 4 then
+ if path(3):cmp(lib.str.lit'cred') then
+ var pg: lib.str.acc pg:init(1024)
+ pg:lpush('
')
+ 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('
')
+ 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('')
+ 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('')
- 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
+ cinp:lpush('
')
+
+ 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
- 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
+ cinp:lpush('
')
- 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('
')
-
- 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('
')
-
- 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('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
- 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()
+ 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('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
+ 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('
')
+ 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('
')
- end
+ cinp:lpush('purge account
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 '):push(purgeconf.buf,purgeconf.sz):lpush(' below and press the “alter” button to begin the process.
')
+ 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
- -- 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('')
+ lib.render.nym(user.ptr,0,&unym,false)
+ unym:lpush('')
+ 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('security & credentials'):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
- var cinpp = cinp:finalize() defer cinpp:free()
- var clnkp: pstr
- if clnk.sz > 0 then clnkp = clnk:finalize() else
- clnk:free()
- clnkp = pstr { ptr='', ct=0 }
+ return pg:finalize()
end
- var unym: lib.str.acc unym:init(64)
- unym:lpush('')
- lib.render.nym(user.ptr,0,&unym,false)
- unym:lpush('')
- var pg = data.view.conf_user_ctl {
- name = unym:finalize();
- inputcontent = cinpp;
- linkcontent = clnkp;
- }
- var ret = pg:tostr()
- pg.name:free()
- if clnkp.ct > 0 then clnkp:free() end
- return ret
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
Index: render/nav.t
==================================================================
--- render/nav.t
+++ render/nav.t
@@ -5,12 +5,12 @@
if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then
t:lpush(' timeline')
end
if co.who ~= nil then
t:lpush(' composeprofilemediaconfiguredocslog outnotices')
+ t:lpush('">profile mediaconfiguredocslog outnotices')
else
t:lpush(' docslog in')
end
return t:finalize()
end
return render_nav
Index: render/tweet.t
==================================================================
--- render/tweet.t
+++ render/tweet.t
@@ -33,13 +33,10 @@
if p.rtdby ~= 0 and retweeter == nil then
retweeter = co.actorcache:insert(co.srv:actor_fetch_uid(p.rtdby)).ptr
end
::foundauth::
- var avistr: lib.str.acc if author.origin == 0 then
- avistr:compose('/avi/',author.handle)
- end
var timestr: int8[26] lib.osclock.ctime_r(&p.posted, ×tr[0])
for i=0,26 do if timestr[i] == @'\n' then timestr[i] = 0 break end end -- 🙄
var bhtml = lib.smackdown.html([lib.mem.ptr(int8)] {ptr=p.body,ct=0},false)
defer bhtml:free()
Index: route.t
==================================================================
--- route.t
+++ route.t
@@ -286,10 +286,41 @@
::badurl:: do co:complain(404, 'invalid URL', 'this URL does not reference extant content or functionality') return end
::badop :: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end
::noauth:: do co:complain(401, 'unauthorized', 'you have not supplied the necessary credentials to perform this operation') return end
end
+
+local terra
+credsec_for_uid(co: &lib.srv.convo, uid: uint64)
+ var act = co:ppostv('act')
+ 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')
+ 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
+ else
+ var key = co:ppostv('newkey')
+ if key:ref() then
+
+ end
+ end
+ 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
@@ -347,28 +378,43 @@
msg = lib.str.plit 'profile changes saved'
--user_refresh = true -- not really necessary here, actually
elseif path(1):cmp(lib.str.lit 'sec') then
- var act = co:ppostv('act')
- 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(co.who.id, 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
- end
+ credsec_for_uid(co, co.who.id)
elseif path(1):cmp(lib.str.lit 'users') then
if path.ct >= 3 then
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
end
- elseif path.ct == 2 then
+ elseif path.ct == 2 and meth == method.post then
+ var act = co:ppostv('act')
+ if act:cmp(lib.str.plit'create') then
+ var newname = co:ppostv('handle')
+ if not newname or not lib.store.actor.handle_validate(newname.ptr) then
+ co:complain(400,'invalid handle','the handle you have requested is not valid')
+ end
+ var tu = co.srv:actor_fetch_xid(newname)
+ if tu:ref() then tu:free()
+ co:complain(409,'handle clash','that handle conflicts with one that already exists')
+ return
+ end
+ var kbuf: uint8[lib.crypt.const.maxdersz]
+ var na = lib.store.actor.mk(&kbuf[0])
+ na.handle = newname.ptr
+ var newuid = co.srv:actor_create(&na)
+ var shid: int8[lib.math.shorthand.maxlen]
+ var shidlen = lib.math.shorthand.gen(newuid, &shid[0])
+ var url = lib.str.acc{}:compose('/conf/users/',pstring{&shid[0],shidlen}):finalize() defer url:free()
+ co:reroute(url.ptr)
+ return
+ elseif act:cmp(lib.str.plit'inst') then
+ else goto badop end
end
end
if user_refresh then -- refresh the user info for the renderer
var usr = co.srv:actor_fetch_uid(co.who.id)
@@ -382,11 +428,12 @@
end
end
lib.render.conf(co,path,msg)
do return end
- ::nopriv:: co:complain(403,'insufficient privileges','you do not have the necessary powers to perform this action')
+ ::nopriv:: do co:complain(403,'insufficient privileges','you do not have the necessary powers to perform this action') return end
+ ::badop:: do co:complain(400,'bad request','the operation you have requested is not meaningful in this context') return end
end
terra http.user_notices(co: &lib.srv.convo, meth: method.t)
if meth == method.post then
var act = co:ppostv('act')
@@ -401,16 +448,12 @@
do return end
::badop:: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end
end
-terra http.media_manager(co: &lib.srv.convo, path: hpath, meth: method.t)
- if meth == method.post then
- goto badop
- end
-
- if path.ct == 2 and path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then
+terra http.media_manager(co: &lib.srv.convo, path: hpath, meth: method.t, uid: uint64)
+ if co.aid ~= 0 and co.who.id == uid and path.ct == 2 and path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then
if meth == method.get then
var view = data.view.media_upload {
folders = ''
}
var pg = view:tostr() defer pg:free()
@@ -455,13 +498,31 @@
var url = lib.str.acc{}:compose('/media/a/',pstring{&idbuf[0],idlen}):finalize()
co:reroute(url.ptr)
url:free()
else goto badop end
+ elseif co.aid ~= 0 and path.ct == 4 and path(1):cmp(lib.str.lit'a') and meth==method.post then
+ var act = co:ppostv('act')
+ if not act or not act:cmp(lib.str.plit'confirm') then goto badop end
+ var artid, aok = lib.math.shorthand.parse(path(2).ptr,path(2).ct)
+ if not aok then goto e404 end
+ var art = co.srv:artifact_fetch(uid,artid)
+ if not art then goto e404 end
+ defer art:free()
+
+ if path(3):cmp(lib.str.lit'avi') then
+ -- user wants to set avatar
+ co.who.avatarid = artid
+ co.srv:actor_save(co.who)
+ co:reroute('/conf/avi')
+ elseif path(3):cmp(lib.str.lit'del') then
+ co.srv:artifact_disclaim(co.who.id, artid)
+ co:reroute('/media')
+ else goto badop end
else
if meth == method.post then goto badop end
- lib.render.media_gallery(co,path,co.who.id,nil)
+ lib.render.media_gallery(co,path,uid,nil)
end
do return end
::badop:: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end
::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded by this user') return end
@@ -503,36 +564,33 @@
end
terra http.local_avatar(co: &lib.srv.convo, handle: lib.mem.ptr(int8))
-- TODO retrieve user avatars
- co:reroute('/s/default-avatar.webp')
+ var usr = co.srv:actor_fetch_xid(handle)
+ if not usr then
+ goto default end
+ if usr(0).origin == 0 then
+ if usr(0).avatarid == 0 then goto default end
+ var avi, mime = co.srv:artifact_load(usr(0).avatarid)
+ if not avi then goto default end
+ defer avi:free() defer mime:free()
+ co:bytestream(mime,avi)
+ else
+ co:reroute(usr(0).avatar)
+ end
+ do return end
+ ::default:: co:reroute('/s/default-avatar.webp')
end
terra http.file_serve_raw(co: &lib.srv.convo, id: lib.mem.ptr(int8))
var id, idok = lib.math.shorthand.parse(id.ptr, id.ct)
if not idok then goto e404 end
var data, mime = co.srv:artifact_load(id)
if not data then goto e404 end
do defer data:free() defer mime:free()
- var safemime = mime
- -- TODO this is not a satisfactory solution; it's a bandaid on a gaping
- -- chest wound. ultimately we need to compile a whitelist of safe mime
- -- types as part of mimelib, but that is no small task. for now, this
- -- will keep the patient from immediately bleeding out
- if mime:cmp(lib.str.plit'text/html') or
- mime:cmp(lib.str.plit'text/xml') or
- mime:cmp(lib.str.plit'application/xhtml+xml') or
- mime:cmp(lib.str.plit'application/vnd.wap.xhtml+xml')
- then -- danger will robinson
- safemime = lib.str.plit'text/plain'
- elseif mime:cmp(lib.str.plit'application/x-shockwave-flash') then
- safemime = lib.str.plit'application/octet-stream'
- end
- lib.net.mg_printf(co.con, "HTTP/1.1 200 OK\r\nContent-Type: %.*s\r\nContent-Length: %llu\r\nContent-Security-Policy: sandbox; default-src 'none'; form-action 'none'; navigate-to 'none';\r\nX-Content-Options: nosniff\r\n\r\n", safemime.ct, safemime.ptr, data.ct + 2)
- lib.net.mg_send(co.con, data.ptr, data.ct)
- lib.net.mg_send(co.con, '\r\n', 2)
+ co:bytestream(mime,data)
return end
::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded to this instance') return end
end
@@ -582,11 +640,11 @@
http.tweet_page(co, path, meth)
elseif path(0):cmp(lib.str.lit('tl')) then
http.timeline(co, path)
elseif path(0):cmp(lib.str.lit('media')) then
if co.aid == 0 then goto unauth end
- http.media_manager(co, path, meth)
+ http.media_manager(co, path, meth, co.who.id)
elseif path(0):cmp(lib.str.lit('doc')) then
if not meth_get(meth) then goto wrongmeth end
http.documentation(co, path)
elseif path(0):cmp(lib.str.lit('conf')) then
if co.aid == 0 then goto unauth end
Index: srv.t
==================================================================
--- srv.t
+++ srv.t
@@ -230,10 +230,29 @@
})
end
end
terra convo:stdpage(pg: convo.page) self:statpage(200, pg) end
+
+terra convo:bytestream(mime: pstring, data: lib.mem.ptr(uint8))
+ -- TODO this is not a satisfactory solution; it's a bandaid on a gaping
+ -- chest wound. ultimately we need to compile a whitelist of safe mime
+ -- types as part of mimelib, but that is no small task. for now, this
+ -- will keep the patient from immediately bleeding out
+ if mime:cmp(lib.str.plit'text/html') or
+ mime:cmp(lib.str.plit'text/xml') or
+ mime:cmp(lib.str.plit'application/xhtml+xml') or
+ mime:cmp(lib.str.plit'application/vnd.wap.xhtml+xml')
+ then -- danger will robinson
+ mime = lib.str.plit'text/plain'
+ elseif mime:cmp(lib.str.plit'application/x-shockwave-flash') then
+ mime = lib.str.plit'application/octet-stream'
+ end
+ lib.net.mg_printf(self.con, "HTTP/1.1 200 OK\r\nContent-Type: %.*s\r\nContent-Length: %llu\r\nContent-Security-Policy: sandbox; default-src 'none'; form-action 'none'; navigate-to 'none';\r\nX-Content-Options: nosniff\r\n\r\n", mime.ct, mime.ptr, data.ct + 2)
+ lib.net.mg_send(self.con, data.ptr, data.ct)
+ lib.net.mg_send(self.con, '\r\n', 2)
+end
terra convo:reroute_cookie(dest: rawstring, cookie: rawstring)
var hdrs = array(
lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' },
lib.http.header { key = 'Location', value = dest },
Index: static/style.scss
==================================================================
--- static/style.scss
+++ static/style.scss
@@ -633,10 +633,19 @@
menu { all: unset; display: block; }
body.conf main {
display: grid;
grid-template-columns: 2in 1fr;
grid-template-rows: max-content 1fr;
+ div.context {
+ border-radius: 4px;
+ text-align: center;
+ background: tone(-53%);
+ box-shadow: 0 1px 0 1px tone(-55%);
+ border: 1px solid tone(-20%);
+ font-style: italic;
+ padding: 0.1in;
+ }
> menu { @extend %navmenu; }
> .panel {
grid-column: 2/3; grid-row: 1/3;
padding-left: 0.15in;
> h1 {
Index: store.t
==================================================================
--- store.t
+++ store.t
@@ -165,10 +165,11 @@
return m.actor {
id = 0; nym = nil; handle = nil;
origin = 0; bio = nil; avatar = nil;
knownsince = lib.osclock.time(nil);
rights = m.rights_default();
+ avatarid = 0;
epithet = nil, key = [lib.mem.ptr(uint8)] {
ptr = &kbuf[0], ct = privsz
};
}
end
@@ -409,11 +410,12 @@
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} -> {}
+ auth_attach_key: {&m.source, uint64, bool, pstr, pstr} -> {}
-- uid: uint64
-- reset: bool (delete other passwords?)
-- pw: pstring
-- comment: pstring
auth_purge_pw: {&m.source, uint64, rawstring} -> {}
Index: view/conf-sec-credmg.tpl
==================================================================
--- view/conf-sec-credmg.tpl
+++ view/conf-sec-credmg.tpl
@@ -1,19 +1,19 @@
-
ADDED view/conf-sec-pwnew.tpl
Index: view/conf-sec-pwnew.tpl
==================================================================
--- view/conf-sec-pwnew.tpl
+++ view/conf-sec-pwnew.tpl
@@ -0,0 +1,18 @@
+
Index: view/conf-sec.tpl
==================================================================
--- view/conf-sec.tpl
+++ view/conf-sec.tpl
@@ -1,10 +1,10 @@
Index: view/conf-user-ctl.tpl
==================================================================
--- view/conf-user-ctl.tpl
+++ view/conf-user-ctl.tpl
@@ -2,8 +2,10 @@