| Comment: | enable passwords |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
d6024624c61e176bcbd760d0797db07a |
| User & Date: | lexi on 2021-01-08 05:58:30 |
| Other Links: | manifest | tags |
|
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 | |
|
2021-01-07
| ||
| 20:39 | media uploads work now, some types can be viewed check-in: 93aea04a05 user: lexi tags: trunk | |
Modified backend/pgsql.t from [cb3e1743a5] to [9c53eed84d].
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 ... 502 503 504 505 506 507 508 509 510 511 512 513 514 515 ... 946 947 948 949 950 951 952 953 954 955 956 957 958 959 .... 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 .... 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 |
actor_enum_local = {
params = {}, sql = [[
select id, nym, handle, origin, bio,
null::text, rank, quota, key, epithet,
knownsince::bigint,
'@' || handle,
invites
from parsav_actors where origin is null
order by nullif(rank,0) nulls last, handle
]];
};
actor_enum = {
params = {}, sql = [[
................................................................................
};
artifact_disclaim = {
params = {uint64, uint64}, cmd = true, sql = [[
delete from parsav_artifact_claims where
uid = $1::bigint and
rid = $2::bigint
]];
};
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
]];
};
................................................................................
}) ]
a.ptr.id = r:int(uint64, row, 0);
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);
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
a.ptr.origin = origin
if avia.buf ~= nil then avia:free() end
................................................................................
var r = queries.auth_enum_uid.exec(src,uid)
if r.sz == 0 then return [lib.mem.ptr(lib.mem.ptr(lib.store.auth))].null() end
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};
}) ]
a.ptr.aid = r:int(uint64, i, 0)
a.ptr.netmask = r:cidr(i, 3)
a.ptr.blacklist = r:bool(i, 4)
ret.ptr[i] = a
end
return ret
end];
auth_attach_pw = [terra(
................................................................................
uid: uint64,
artifact: uint64,
desc: pstring,
folder: pstring
): {}
queries.artifact_expropriate.exec(src,uid,artifact,desc,folder, lib.osclock.time(nil))
end];
artifact_enum_uid = [terra(
src: &lib.store.source,
uid: uint64,
folder: pstring
)
var res = queries.artifact_enum_uid.exec(src,uid,folder)
|
| > > > > > > > > | | > > | > > > > > > > > > > |
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 ... 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 ... 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 .... 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 .... 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 |
actor_enum_local = {
params = {}, sql = [[
select id, nym, handle, origin, bio,
null::text, rank, quota, key, epithet,
knownsince::bigint,
'@' || handle,
invites, avatarid
from parsav_actors where origin is null
order by nullif(rank,0) nulls last, handle
]];
};
actor_enum = {
params = {}, sql = [[
................................................................................
};
artifact_disclaim = {
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
]];
};
................................................................................
}) ]
a.ptr.id = r:int(uint64, row, 0);
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
a.ptr.origin = origin
if avia.buf ~= nil then avia:free() end
................................................................................
var r = queries.auth_enum_uid.exec(src,uid)
if r.sz == 0 then return [lib.mem.ptr(lib.mem.ptr(lib.store.auth))].null() end
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+1};
comment = {`comment.ptr, `comment.ct+1};
}) ]
a.ptr.aid = r:int(uint64, i, 0)
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];
auth_attach_pw = [terra(
................................................................................
uid: uint64,
artifact: uint64,
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
)
var res = queries.artifact_enum_uid.exec(src,uid,folder)
|
Modified backend/schema/pgsql-views.sql from [ca832d14af] to [25f9d405bc].
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
avataruri text, rank smallint, quota integer, key bytea, epithet text, knownsince bigint, xid text, invites integer ); create or replace function pg_temp.parsavpg_translate_actor(parsav_actors) returns pg_temp.parsavpg_intern_actor as $$ select ($1).id, ($1).nym, ($1).handle, ($1).origin, ($1).bio, ($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 $$ 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 -- in row_to_actor. names don't matter localpost bool, |
| > | |
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
avataruri text, rank smallint, quota integer, key bytea, epithet text, knownsince bigint, xid text, invites integer, avatarid bigint ); create or replace function pg_temp.parsavpg_translate_actor(parsav_actors) returns pg_temp.parsavpg_intern_actor as $$ select ($1).id, ($1).nym, ($1).handle, ($1).origin, ($1).bio, ($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).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 -- in row_to_actor. names don't matter localpost bool, |
Modified parsav.md from [7a2c43b008] to [52b33381db].
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
additional preconfigure dependencies are necessary if you are building directly from trunk, rather than from a release tarball that includes certain build artifacts which need to be embedded in the binary: * 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. 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. 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 first, either install any missing dependencies as shared libraries, or build them as static libraries with the command `make dep.$LIBRARY`. as a shortcut, `make dep` will build all dependencies as static libraries. note that if the build system finds a static version of a library in the `lib/` folder, it will use that instead of any system library. note that these commands require GNU make (it may be installed as `gmake` on your system), although this is a fairly soft dependency -- if you really need to build it on BSD make, you can probably translate it with a minute or so of work; you'll just have to do some of the various gmake functions' work manually. this may be worthwhile if you're packaging for a BSD. |
| | |
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
additional preconfigure dependencies are necessary if you are building directly from trunk, rather than from a release tarball that includes certain build artifacts which need to be embedded in the binary: * 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. 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 first, either install any missing dependencies as shared libraries, or build them as static libraries with the command `make dep.$LIBRARY`. as a shortcut, `make dep` will build all dependencies as static libraries. note that if the build system finds a static version of a library in the `lib/` folder, it will use that instead of any system library. note that these commands require GNU make (it may be installed as `gmake` on your system), although this is a fairly soft dependency -- if you really need to build it on BSD make, you can probably translate it with a minute or so of work; you'll just have to do some of the various gmake functions' work manually. this may be worthwhile if you're packaging for a BSD. |
Modified parsav.t from [7b59e1e979] to [2fffe11917].
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
...
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
if v.tree.type == ty then return fn(v,...) end
end
return (tbl[false])(v,...)
end)
end;
emit_unitary = function(nl,fd,...)
local code = {}
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])
else
code[#code+1] = quote var n = v in
lib.io.send(2, n, lib.str.sz(n)) end
end
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
end;
emitv = function(nl,fd,...)
local vec = {}
local defs = {}
for i,v in ipairs{...} do
local str, ct
if type(v) == 'table' and v.tree and not (v.tree:is 'constant') then
................................................................................
end;
osclock = terralib.includec 'time.h';
}
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);
header = function(code,txt,mod)
if mod then
|
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
<
<
<
|
|
<
<
<
<
|
>
>
>
>
|
<
|
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
...
170
171
172
173
174
175
176
177
178
179
180
181
182
183
|
if v.tree.type == ty then return fn(v,...) end
end
return (tbl[false])(v,...)
end)
end;
emit_unitary = function(nl,fd,...)
local code = {}
local defs = {}
for i,v in ipairs{...} do
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
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 quote [defs] in [code] end
end;
emitv = function(nl,fd,...)
local vec = {}
local defs = {}
for i,v in ipairs{...} do
local str, ct
if type(v) == 'table' and v.tree and not (v.tree:is 'constant') then
................................................................................
end;
osclock = terralib.includec 'time.h';
}
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);
header = function(code,txt,mod)
if mod then
|
Modified render/conf.t from [60d6b764a8] to [241a1c4277].
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
..
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
local pstr = lib.mem.ptr(int8)
local pref = lib.mem.ref(int8)
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 = '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'};
{url = 'srv', title = 'server settings', render = 'srv'};
................................................................................
for i, m in ipairs(mappings) do
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(['<h1>' .. m.title .. '</h1>']):ppush(body)
panel = a:finalize()
body:free()
else [invoker] end
end
end
end
local terra
render_conf([co], [path], notify: pstr)
|
|
>
>
>
>
>
|
|
|
>
|
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
..
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
local pstr = lib.mem.ptr(int8)
local pref = lib.mem.ref(int8)
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_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'};
{url = 'srv', title = 'server settings', render = 'srv'};
................................................................................
for i, m in ipairs(mappings) do
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)
if not body then
a:lpush(['<h1>' .. m.title .. ' :: error</h1>' ..
'<p>the requested resource is not available.</p>'])
panel = a:finalize()
else
a:lpush(['<h1>' .. m.title .. '</h1>']):ppush(body)
panel = a:finalize()
body:free()
end
else [invoker] end
end
end
end
local terra
render_conf([co], [path], notify: pstr)
|
Modified render/conf/sec.t from [2ed8642241] to [7f83a40056].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
-- 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) 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 a: lib.str.acc a:init(768) body:append(&a) var credmgr = data.view.conf_sec_credmg { credlist = '<option>your password</option>' } credmgr:append(&a) return a:finalize() else return body:tostr() end end return render_conf_sec |
> | | > > | | < > | > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 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, 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) 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('<option value="'):shpush(c.aid):lpush('"> ['):push(c.kind,0):lpush('] '):push(c.comment,0) if c.netmask.pv ~= 0 then -- push string rep end cl:lpush('</option>') 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 |
Modified render/conf/users.t from [0f343f8c98] to [41f55e0682].
103
104
105
106
107
108
109
110
111
112
113
114
115
116
...
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
|
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 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)
................................................................................
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
var cinp: lib.str.acc cinp:init(256)
var clnk: lib.str.acc clnk:init(512)
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
-- 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 clnkp: pstr
if clnk.sz > 0 then clnkp = clnk:finalize() else
clnk:free()
clnkp = pstr { ptr='', ct=0 }
end
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 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
var modestr = co:pgetv('show')
ulst:lpush('<div style="text-align: right"><em>showing ')
|
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
|
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
<
<
<
<
<
|
|
|
|
|
|
|
|
|
|
|
|
<
>
|
<
>
>
>
>
>
|
<
>
>
|
>
|
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
...
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
|
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)
................................................................................
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 ')
|
Modified render/nav.t from [5194b2263f] to [50b4e7c2b2].
3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
render_nav(co: &lib.srv.convo)
var t: lib.str.acc t:init(64)
if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then
t:lpush(' <a accesskey="t" href="/">timeline</a>')
end
if co.who ~= nil then
t:lpush(' <a accesskey="c" href="/compose">compose</a> <a accesskey="p" href="/'):push(co.who.xid,0)
t:lpush('">profile</a> <a accesskey="m" href="/media">media</a> <a accesskey="o" href="/conf">configure</a> <a accesskey="d" href="/doc">docs</a> <a accesskey="g" href="/logout">log out</a> <a class="bell" href="/notices">notices</a>')
else
t:lpush(' <a accesskey="d" href="/doc">docs</a> <a accesskey="g" href="/login">log in</a>')
end
return t:finalize()
end
return render_nav
|
| |
3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
render_nav(co: &lib.srv.convo)
var t: lib.str.acc t:init(64)
if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then
t:lpush(' <a accesskey="t" href="/">timeline</a>')
end
if co.who ~= nil then
t:lpush(' <a accesskey="c" href="/compose">compose</a> <a accesskey="p" href="/'):push(co.who.xid,0)
t:lpush('">profile</a> <a accesskey="m" href="/media">media</a> <a accesskey="o" href="/conf">configure</a> <a accesskey="d" href="/doc">docs</a> <a accesskey="g" href="/logout">log out</a> <a class="bell" accesskey="x" href="/notices">notices</a>')
else
t:lpush(' <a accesskey="d" href="/doc">docs</a> <a accesskey="g" href="/login">log in</a>')
end
return t:finalize()
end
return render_nav
|
Modified render/tweet.t from [18c58bf27c] to [83917dbbe9].
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
author = co.actorcache:insert(co.srv:actor_fetch_uid(p.author)).ptr end 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() var idbuf: int8[lib.math.shorthand.maxlen] |
< < < |
31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
author = co.actorcache:insert(co.srv:actor_fetch_uid(p.author)).ptr
end
if p.rtdby ~= 0 and retweeter == nil then
retweeter = co.actorcache:insert(co.srv:actor_fetch_uid(p.rtdby)).ptr
end
::foundauth::
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()
var idbuf: int8[lib.math.shorthand.maxlen]
|
Modified route.t from [881176d2e1] to [b4f49ac3f6].
284 285 286 287 288 289 290 291 292 293 294 295 296 297 ... 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 ... 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 ... 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 ... 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 ... 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 ... 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 |
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 ::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 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 not co.who.rights.powers.config() and ( path(1):cmp(lib.str.lit 'srv') or ................................................................................ co.ui_hue = co.srv.cfg.ui_hue end 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 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 end end if user_refresh then -- refresh the user info for the renderer var usr = co.srv:actor_fetch_uid(co.who.id) lib.mem.heapf(co.who) co.who = usr.ptr ................................................................................ co:reroute(go) return 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') end terra http.user_notices(co: &lib.srv.convo, meth: method.t) if meth == method.post then var act = co:ppostv('act') if act:cmp(lib.str.plit'clear') then co.srv:actor_conf_int_set(co.who.id, 'notice-clear-time', lib.osclock.time(nil)) ................................................................................ lib.render.notices(co) 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 if meth == method.get then var view = data.view.media_upload { folders = '' } var pg = view:tostr() defer pg:free() co:stdpage([lib.srv.convo.page] { title = lib.str.plit'media :: upload'; ................................................................................ var idbuf: int8[lib.math.shorthand.maxlen] var idlen = lib.math.shorthand.gen(id,&idbuf[0]) var url = lib.str.acc{}:compose('/media/a/',pstring{&idbuf[0],idlen}):finalize() co:reroute(url.ptr) url:free() else goto badop end else if meth == method.post then goto badop end lib.render.media_gallery(co,path,co.who.id,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 end ................................................................................ ::[send]:: page:send(co.con) return true end end terra http.local_avatar(co: &lib.srv.convo, handle: lib.mem.ptr(int8)) -- TODO retrieve user avatars 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) return end ::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded to this instance') return end end -- entry points terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t) ................................................................................ http.actor_profile_uid(co, path, meth) elseif path.ct > 1 and path(0):cmp(lib.str.lit('post')) then 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) 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 http.configure(co,path,meth) else goto notfound end |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < < < < < < < | > > > > > > > > > > > > > > > > > > > > > > | > | < < < < | > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > | | < < < < < < < < < < < < < < < < | |
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 ... 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 ... 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 ... 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 ... 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 ... 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 ... 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 |
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 ::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 if not co.who.rights.powers.config() and ( path(1):cmp(lib.str.lit 'srv') or ................................................................................ co.ui_hue = co.srv.cfg.ui_hue end msg = lib.str.plit 'profile changes saved' --user_refresh = true -- not really necessary here, actually elseif path(1):cmp(lib.str.lit 'sec') then 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 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) lib.mem.heapf(co.who) co.who = usr.ptr ................................................................................ co:reroute(go) return end end lib.render.conf(co,path,msg) do return end ::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') if act:cmp(lib.str.plit'clear') then co.srv:actor_conf_int_set(co.who.id, 'notice-clear-time', lib.osclock.time(nil)) ................................................................................ lib.render.notices(co) 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, 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() co:stdpage([lib.srv.convo.page] { title = lib.str.plit'media :: upload'; ................................................................................ var idbuf: int8[lib.math.shorthand.maxlen] var idlen = lib.math.shorthand.gen(id,&idbuf[0]) 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,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 end ................................................................................ ::[send]:: page:send(co.con) return true end end terra http.local_avatar(co: &lib.srv.convo, handle: lib.mem.ptr(int8)) -- TODO retrieve user avatars 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() 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 -- entry points terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t) ................................................................................ http.actor_profile_uid(co, path, meth) elseif path.ct > 1 and path(0):cmp(lib.str.lit('post')) then 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, 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 http.configure(co,path,meth) else goto notfound end |
Modified srv.t from [ca6d27c8d7] to [56e1fe84a6].
228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
self:rawpage(200, pg, [lib.mem.ptr(lib.http.header)] {
ptr = &hdrs[0], ct = 3
})
end
end
terra convo:stdpage(pg: convo.page) self:statpage(200, pg) 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 },
lib.http.header { key = 'Set-Cookie', value = cookie }
)
|
> > > > > > > > > > > > > > > > > > > |
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
self:rawpage(200, pg, [lib.mem.ptr(lib.http.header)] {
ptr = &hdrs[0], ct = 3
})
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 },
lib.http.header { key = 'Set-Cookie', value = cookie }
)
|
Modified static/style.scss from [fb57063208] to [f9852f8724].
631 632 633 634 635 636 637 638 639 640 641 642 643 644 |
}
menu { all: unset; display: block; }
body.conf main {
display: grid;
grid-template-columns: 2in 1fr;
grid-template-rows: max-content 1fr;
> menu { @extend %navmenu; }
> .panel {
grid-column: 2/3; grid-row: 1/3;
padding-left: 0.15in;
> h1 {
padding-bottom: 0.1in;
margin-bottom: 0.1in;
|
> > > > > > > > > |
631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 |
}
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 {
padding-bottom: 0.1in;
margin-bottom: 0.1in;
|
Modified store.t from [eca94a58d5] to [b8bbc4e0ec].
163
164
165
166
167
168
169
170
171
172
173
174
175
176
...
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
|
var newkp = lib.crypt.genkp()
var privsz = lib.crypt.der(false,&newkp,kbuf)
return m.actor {
id = 0; nym = nil; handle = nil;
origin = 0; bio = nil; avatar = nil;
knownsince = lib.osclock.time(nil);
rights = m.rights_default();
epithet = nil, key = [lib.mem.ptr(uint8)] {
ptr = &kbuf[0], ct = privsz
};
}
end
struct m.actor_stats {
................................................................................
actor_notice_enum: {&m.source, uint64} -> lib.mem.ptr(m.notice)
actor_rel_create: {&m.source, uint16, uint64, uint64} -> {}
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} -> {}
-- uid: uint64
-- reset: bool (delete other passwords?)
-- pw: pstring
-- comment: pstring
auth_purge_pw: {&m.source, uint64, rawstring} -> {}
auth_purge_otp: {&m.source, uint64, rawstring} -> {}
auth_purge_trust: {&m.source, uint64, rawstring} -> {}
|
>
>
|
|
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
...
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
|
var newkp = lib.crypt.genkp()
var privsz = lib.crypt.der(false,&newkp,kbuf)
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
struct m.actor_stats {
................................................................................
actor_notice_enum: {&m.source, uint64} -> lib.mem.ptr(m.notice)
actor_rel_create: {&m.source, uint16, uint64, uint64} -> {}
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_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} -> {}
auth_purge_otp: {&m.source, uint64, rawstring} -> {}
auth_purge_trust: {&m.source, uint64, rawstring} -> {}
|
Modified view/conf-sec-credmg.tpl from [43efff9618] to [6f4f9fa693].
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
..
23
24
25
26
27
28
29
30
31
32
33
34
35
|
<hr> <form method="post"> <p>your account can currently be accessed with the credentials listed below. if you fear a credential has been compromised, you can revoke or reset it.</p> <select size="6" name="cred"> @credlist </select> <menu class="horizontal choice"> <button name="act" value="reset">reset</button> <button name="act" value="revoke">revoke</button> </menu> </form> <hr> <form method="post"> <p>you can associate extra credentials with your account. you can also limit how much of your authority these credentials can be used to exercise — for instance, it might be useful to create API keys that can read your timeline, but not post as you or access any administrative powers you may have. if you don't select a capability set, the credential will be able to wield the full scope of your powers.</p> <div class="check-panel"> <label><input type="checkbox" name="allow-post"> post</label> <label><input type="checkbox" name="allow-edit"> edit</label> <label><input type="checkbox" name="allow-acct"> manage account</label> <label><input type="checkbox" name="allow-upload"> upload artifacts</label> <label><input type="checkbox" name="allow-censor"> moderation</label> <label><input type="checkbox" name="allow-admin"> other admin powers</label> ................................................................................ </div> <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> <div class="elem"> <label for="netmask">netmask</label> <input type="text" name="netmask" id="netmask" placeholder="10.0.0.0/8"> </div> <menu class="vertical choice"> <button name="kind" value="pw">new password</button> <button name="kind" value="otp">new OTP key</button> <button name="kind" value="api">new API token</button> <button name="kind" value="challenge">new challenge key</button> </div> </form> |
|
|
|
|
|
|
|
|
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
..
23
24
25
26
27
28
29
30
31
32
33
34
35
|
<hr> <form method="post"> <p>this account can currently be accessed with the credentials listed below. if you fear a credential has been compromised, you can revoke or reset it.</p> <select size="6" name="cred"> @credlist </select> <menu class="horizontal choice"> <button name="act" value="reset">reset</button> <button name="act" value="revoke">revoke</button> </menu> </form> <hr> <form method="get"> <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> <div class="check-panel"> <label><input type="checkbox" name="allow-post"> post</label> <label><input type="checkbox" name="allow-edit"> edit</label> <label><input type="checkbox" name="allow-acct"> manage account</label> <label><input type="checkbox" name="allow-upload"> upload artifacts</label> <label><input type="checkbox" name="allow-censor"> moderation</label> <label><input type="checkbox" name="allow-admin"> other admin powers</label> ................................................................................ </div> <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> <div class="elem"> <label for="netmask">netmask</label> <input type="text" name="netmask" id="netmask" placeholder="10.0.0.0/8"> </div> <menu class="vertical choice"> <button name="new" value="pw">new password</button> <button name="new" value="otp">new OTP key</button> <button name="new" value="api">new API token</button> <button name="new" value="challenge">new challenge key</button> </menu> </form> |
Added view/conf-sec-pwnew.tpl version [5878dd4701].
> > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<form method="post"> <div class="elem"> <label for="comment">comment</label> <input type="text" id="comment" name="comment" value="@comment" required> </div> <div class="elem"> <label for="newpw">new password</label> <input type="password" id="newpw" name="newpw" required> </div> <div class="elem"> <label for="rptpw">confirm password</label> <input type="password" id="rptpw" name="rptpw" required> </div> <menu class="choice horizontal"> <button name="act" value="newcred">enroll</button> <a class="button" href="?">cancel</a> </menu> </form> |
Modified view/conf-sec.tpl from [de1cf7e8f0] to [49c75b1abd].
1 2 3 4 5 6 7 8 9 10 |
<form method="post"> <p>if you are concerned that your account may have been compromised, you can terminate all other login sessions by invalidating their session cookies. note that this will not have any effect on API tokens; these must be revoked separately!</p> <div class="elem"> <label> sessions valid from </label> <div class="txtbox">@lastreset</div> </div> <button type="submit" name="act" value="invalidate"> invalidate other sessions </button> </form> |
| |
1 2 3 4 5 6 7 8 9 10 |
<form method="post"> <p>if you are concerned that this account may have been compromised, you can terminate conflicting login sessions by invalidating their session cookies. note that this will not have any effect on API tokens; these must be revoked separately!</p> <div class="elem"> <label> sessions valid from </label> <div class="txtbox">@lastreset</div> </div> <button type="submit" name="act" value="invalidate"> invalidate other sessions </button> </form> |
Modified view/conf-user-ctl.tpl from [7abbc95a91] to [7e2cfb9c6e].
1 2 3 4 5 6 7 8 9 |
<form method="post">
<div class="elem">
<label>user</label>
<div class="txtbox">@name</div>
</div>
@inputcontent
<button>alter</button>
</form>
@linkcontent
|
> | > > < |
1 2 3 4 5 6 7 8 9 10 11 |
<form method="post"> <div class="elem"> <label>user</label> <div class="txtbox">@name</div> </div> @inputcontent <menu class="vertical choice"> <button>alter</button> @btns </menu> </form> |
Modified view/load.lua from [9f4f065de0] to [15344e4760].
19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
'login-username';
'login-challenge';
'conf';
'conf-profile';
'conf-sec';
'conf-sec-credmg';
'conf-user-ctl';
}
local ingest = function(filename)
local hnd = io.open(path..'/'..filename)
local txt = hnd:read('*a')
io.close(hnd)
|
> |
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
'login-username';
'login-challenge';
'conf';
'conf-profile';
'conf-sec';
'conf-sec-credmg';
'conf-sec-pwnew';
'conf-user-ctl';
}
local ingest = function(filename)
local hnd = io.open(path..'/'..filename)
local txt = hnd:read('*a')
io.close(hnd)
|