| 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 119 120 120 actor_enum_local = { 121 121 params = {}, sql = [[ 122 122 select id, nym, handle, origin, bio, 123 123 null::text, rank, quota, key, epithet, 124 124 knownsince::bigint, 125 125 '@' || handle, 126 - invites 126 + invites, avatarid 127 127 from parsav_actors where origin is null 128 128 order by nullif(rank,0) nulls last, handle 129 129 ]]; 130 130 }; 131 131 132 132 actor_enum = { 133 133 params = {}, sql = [[ ................................................................................ 502 502 }; 503 503 artifact_disclaim = { 504 504 params = {uint64, uint64}, cmd = true, sql = [[ 505 505 delete from parsav_artifact_claims where 506 506 uid = $1::bigint and 507 507 rid = $2::bigint 508 508 ]]; 509 + }; 510 + artifact_collect_garbage = { 511 + params = {}, cmd = true, sql = [[ 512 + delete from parsav_artifacts where 513 + id not in (select rid from parsav_artifact_claims) and 514 + content is not null -- avoid stepping on toes of ban mech 515 + ]]; 509 516 }; 510 517 artifact_excise_forget = { 511 518 -- delete the blasted thing and pretend it never existed 512 519 params = {uint64}, cmd=true, sql = [[ 513 520 delete from parsav_artifacts where id = $1::bigint 514 521 ]]; 515 522 }; ................................................................................ 946 953 }) ] 947 954 a.ptr.id = r:int(uint64, row, 0); 948 955 a.ptr.rights = lib.store.rights_default(); 949 956 a.ptr.rights.rank = r:int(uint16, row, 6); 950 957 a.ptr.rights.quota = r:int(uint32, row, 7); 951 958 a.ptr.rights.invites = r:int(uint32, row, 12); 952 959 a.ptr.knownsince = r:int(int64,row, 10); 960 + a.ptr.avatarid = r:int(uint64,row, 13); 953 961 if r:null(row,8) then 954 962 a.ptr.key.ct = 0 a.ptr.key.ptr = nil 955 963 else 956 964 a.ptr.key = r:bin(row,8) 957 965 end 958 966 a.ptr.origin = origin 959 967 if avia.buf ~= nil then avia:free() end ................................................................................ 1500 1508 var r = queries.auth_enum_uid.exec(src,uid) 1501 1509 if r.sz == 0 then return [lib.mem.ptr(lib.mem.ptr(lib.store.auth))].null() end 1502 1510 var ret = lib.mem.heapa([lib.mem.ptr(lib.store.auth)], r.sz) 1503 1511 for i=0, r.sz do 1504 1512 var kind = r:_string(i, 1) 1505 1513 var comment = r:_string(i, 2) 1506 1514 var a = [ lib.str.encapsulate(lib.store.auth, { 1507 - kind = {`kind.ptr, `kind.ct}; 1508 - comment = {`comment.ptr, `comment.ct}; 1515 + kind = {`kind.ptr, `kind.ct+1}; 1516 + comment = {`comment.ptr, `comment.ct+1}; 1509 1517 }) ] 1510 1518 a.ptr.aid = r:int(uint64, i, 0) 1511 - a.ptr.netmask = r:cidr(i, 3) 1519 + if r:null(i,3) 1520 + then a.ptr.netmask.pv = 0 1521 + else a.ptr.netmask = r:cidr(i, 3) 1522 + end 1512 1523 a.ptr.blacklist = r:bool(i, 4) 1513 1524 ret.ptr[i] = a 1514 1525 end 1515 1526 return ret 1516 1527 end]; 1517 1528 1518 1529 auth_attach_pw = [terra( ................................................................................ 1595 1606 uid: uint64, 1596 1607 artifact: uint64, 1597 1608 desc: pstring, 1598 1609 folder: pstring 1599 1610 ): {} 1600 1611 queries.artifact_expropriate.exec(src,uid,artifact,desc,folder, lib.osclock.time(nil)) 1601 1612 end]; 1613 + 1614 + artifact_disclaim = [terra( 1615 + src: &lib.store.source, 1616 + uid: uint64, 1617 + artifact: uint64 1618 + ) 1619 + queries.artifact_disclaim.exec(src,uid,artifact) 1620 + 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 1621 + end]; 1602 1622 1603 1623 artifact_enum_uid = [terra( 1604 1624 src: &lib.store.source, 1605 1625 uid: uint64, 1606 1626 folder: pstring 1607 1627 ) 1608 1628 var res = queries.artifact_enum_uid.exec(src,uid,folder)
Modified backend/schema/pgsql-views.sql from [ca832d14af] to [25f9d405bc].
54 54 avataruri text, 55 55 rank smallint, 56 56 quota integer, 57 57 key bytea, 58 58 epithet text, 59 59 knownsince bigint, 60 60 xid text, 61 - invites integer 61 + invites integer, 62 + avatarid bigint 62 63 ); 63 64 64 65 create or replace function 65 66 pg_temp.parsavpg_translate_actor(parsav_actors) 66 67 returns pg_temp.parsavpg_intern_actor as $$ 67 68 select 68 69 ($1).id, ($1).nym, ($1).handle, ($1).origin, ($1).bio, 69 70 ($1).avataruri, ($1).rank, ($1).quota, ($1).key, ($1).epithet, 70 71 ($1).knownsince::bigint, 71 72 coalesce(($1).handle || '@' || 72 73 (select domain from parsav_servers as s where s.id = ($1).origin), 73 74 '@' || ($1).handle) as xid, 74 - ($1).invites 75 + ($1).invites, ($1).avatarid 75 76 $$ language sql; 76 77 77 78 --drop type if exists pg_temp.parsavpg_intern_post; 78 79 create type pg_temp.parsavpg_intern_post as ( 79 80 -- order is crucially important, and must match the order used 80 81 -- in row_to_actor. names don't matter 81 82 localpost bool,
Modified parsav.md from [7a2c43b008] to [52b33381db].
21 21 22 22 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: 23 23 24 24 * inkscape, for rendering out some of the UI graphics that can't be represented with standard svg 25 25 * cwebp (libwebp package), for transforming inkscape PNGs to webp 26 26 * sassc, for compiling the SCSS stylesheet into its final CSS 27 27 28 -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. 28 +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. 29 29 30 -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. 30 +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. 31 31 32 32 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. 33 33 34 34 ## building 35 35 36 36 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. 37 37
Modified parsav.t from [7b59e1e979] to [2fffe11917].
39 39 if v.tree.type == ty then return fn(v,...) end 40 40 end 41 41 return (tbl[false])(v,...) 42 42 end) 43 43 end; 44 44 emit_unitary = function(nl,fd,...) 45 45 local code = {} 46 + local defs = {} 46 47 for i,v in ipairs{...} do 47 - if type(v) == 'string' or type(v) == 'number' then 48 - local str = tostring(v) 49 - code[#code+1] = `lib.io.send(2, str, [#str]) 50 - elseif type(v) == 'table' and #v == 2 then 51 - code[#code+1] = `lib.io.send(2, [v[1]], [v[2]]) 52 - elseif v.tree:is 'constant' then 53 - local str = tostring(v:asvalue()) 54 - code[#code+1] = `lib.io.send(2, str, [#str]) 48 + local str, ct 49 + if type(v) == 'table' and v.tree and not (v.tree:is 'constant') then 50 + if v.tree.type.convertible == 'tuple' then 51 + str = `v._0 52 + ct = `v._1 53 + else 54 + local n = symbol(v.tree.type) 55 + defs[#defs + 1] = quote var [n] = v end 56 + str = n 57 + ct = `lib.str.sz(n) 58 + end 55 59 else 56 - code[#code+1] = quote var n = v in 57 - lib.io.send(2, n, lib.str.sz(n)) end 60 + if type(v) == 'string' or type(v) == 'number' then 61 + str = tostring(v) 62 + else--if v.tree:is 'constant' then 63 + str = tostring(v:asvalue()) 64 + end 65 + ct = ct or #str 58 66 end 67 + 68 + code[#code+1] = `lib.io.send(fd, str, ct) 59 69 end 60 70 if nl == true then code[#code+1] = `lib.io.send(fd, '\n', 1) 61 71 elseif nl then code[#code+1] = `lib.io.send(fd, nl, [#nl]) end 62 - return code 72 + return quote [defs] in [code] end 63 73 end; 64 74 emitv = function(nl,fd,...) 65 75 local vec = {} 66 76 local defs = {} 67 77 for i,v in ipairs{...} do 68 78 local str, ct 69 79 if type(v) == 'table' and v.tree and not (v.tree:is 'constant') then ................................................................................ 160 170 end; 161 171 osclock = terralib.includec 'time.h'; 162 172 } 163 173 if config.posix then 164 174 lib.uio = terralib.includec 'sys/uio.h'; 165 175 lib.emit = lib.emitv -- use more efficient call where available 166 176 else lib.emit = lib.emit_unitary end 167 - 168 177 169 178 lib.noise = { 170 179 level = global(uint8,1); 171 180 starttime = global(lib.osclock.time_t); 172 181 lasttime = global(lib.osclock.time_t); 173 182 header = function(code,txt,mod) 174 183 if mod then
Modified render/conf.t from [60d6b764a8] to [241a1c4277].
2 2 local pstr = lib.mem.ptr(int8) 3 3 local pref = lib.mem.ref(int8) 4 4 5 5 local mappings = { 6 6 {url = 'profile', title = 'account profile', render = 'profile'}; 7 7 {url = 'avi', title = 'avatar', render = 'avatar'}; 8 8 {url = 'ui', title = 'user interface', render = 'ui'}; 9 - {url = 'sec', title = 'security', render = 'sec'}; 9 + {url = 'sec', title = 'security', render = 'sec_overlay'}; 10 10 {url = 'rel', title = 'relationships', render = 'rel'}; 11 11 {url = 'qnt', title = 'quarantine', render = 'quarantine'}; 12 12 {url = 'acl', title = 'access control shortcuts', render = 'acl'}; 13 13 {url = 'rooms', title = 'chatrooms', render = 'rooms'}; 14 14 {url = 'circles', title = 'circles', render = 'circles'}; 15 15 16 16 {url = 'srv', title = 'server settings', render = 'srv'}; ................................................................................ 29 29 30 30 for i, m in ipairs(mappings) do 31 31 if lib.render.conf[m.render] then 32 32 invoker = quote 33 33 if path(1):cmp(lib.str.lit([m.url])) then 34 34 var body = [lib.render.conf[m.render]] (co, path) 35 35 var a: lib.str.acc a:init(body.ct+48) 36 - a:lpush(['<h1>' .. m.title .. '</h1>']):ppush(body) 37 - panel = a:finalize() 38 - body:free() 36 + if not body then 37 + a:lpush(['<h1>' .. m.title .. ' :: error</h1>' .. 38 + '<p>the requested resource is not available.</p>']) 39 + panel = a:finalize() 40 + else 41 + a:lpush(['<h1>' .. m.title .. '</h1>']):ppush(body) 42 + panel = a:finalize() 43 + body:free() 44 + end 39 45 else [invoker] end 40 46 end 41 47 end 42 48 end 43 49 44 50 local terra 45 51 render_conf([co], [path], notify: pstr)
Modified render/conf/sec.t from [2ed8642241] to [7f83a40056].
1 1 -- vim: ft=terra 2 2 local pstr = lib.mem.ptr(int8) 3 3 local pref = lib.mem.ref(int8) 4 + 4 5 local terra 5 -render_conf_sec(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr 6 - var time: lib.store.timepoint = co.who.source:auth_sigtime_user_fetch(co.who.id) 6 +render_conf_sec(co: &lib.srv.convo, uid: uint64): pstr 7 + var time: lib.store.timepoint = co.who.source:auth_sigtime_user_fetch(uid) 7 8 var tstr: int8[26] 8 9 lib.osclock.ctime_r(&time, &tstr[0]) 9 10 var body = data.view.conf_sec { 10 11 lastreset = pstr { 11 12 ptr = &tstr[0], ct = lib.str.sz(&tstr[0]) 12 13 } 13 14 } 14 15 15 16 if co.srv.cfg.credmgd then 17 + var new = co:pgetv('new') 16 18 var a: lib.str.acc a:init(768) 17 - body:append(&a) 18 - var credmgr = data.view.conf_sec_credmg { 19 - credlist = '<option>your password</option>' 20 - } 21 - credmgr:append(&a) 19 + if not new then 20 + body:append(&a) 21 + var credmgr = data.view.conf_sec_credmg { 22 + credlist = pstr{'',0}; 23 + } 24 + var creds = co.srv:auth_enum_uid(uid) 25 + if creds.ct > 0 then defer creds:free() 26 + var cl: lib.str.acc cl:init(256) 27 + for i=0, creds.ct do var c = creds(i).ptr 28 + if not c.blacklist then 29 + cl:lpush('<option value="'):shpush(c.aid):lpush('"> ['):push(c.kind,0):lpush('] '):push(c.comment,0) 30 + if c.netmask.pv ~= 0 then 31 + -- push string rep 32 + end 33 + cl:lpush('</option>') 34 + end 35 + end 36 + credmgr.credlist = cl:finalize() 37 + end 38 + credmgr:append(&a) 39 + if credmgr.credlist.ct > 0 then credmgr.credlist:free() end 40 + elseif new:cmp(lib.str.plit'pw') then 41 + var d: data.view.conf_sec_pwnew 42 + var time = lib.osclock.time(nil) 43 + var timestr: int8[26] lib.osclock.ctime_r(&time, ×tr[0]) 44 + var cmt: lib.str.acc 45 + cmt:init(48):lpush('enrolled over http on '):push(×tr[0],0) 46 + d.comment = cmt:finalize() 47 + 48 + var st = d:tostr() 49 + d.comment:free() 50 + return st 51 + elseif new:cmp(lib.str.plit'challenge') then 52 + -- we're going to break the rules a bit and do database munging from 53 + -- the rendering code, because doing otherwise in this case would be 54 + -- genuinely nightmarish 55 + elseif new:cmp(lib.str.plit'otp') then 56 + elseif new:cmp(lib.str.plit'api') then 57 + else return pstr.null() end 22 58 return a:finalize() 23 59 else return body:tostr() end 24 60 end 61 + 62 +terra lib.render.conf.sec_overlay 63 +(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr 64 + -- render the credential panel for the current user, allowing 65 + -- it to be reused in the administration UI 66 + return render_conf_sec(co,co.who.id) 67 +end 68 + 25 69 return render_conf_sec
Modified render/conf/users.t from [0f343f8c98] to [41f55e0682].
103 103 if punct ~= nil then a:push(punct, 1) end 104 104 a:ipush(rnd(uint16,0,65535)) 105 105 end 106 106 107 107 if xXx then a:lpush('_xXx') end 108 108 109 109 end 110 + 111 +local terra 112 +suggest_domain(a: &lib.str.acc) 113 + var tlds = array('tld','club','town','space','xxx') 114 +end 110 115 111 116 local push_num_field = macro(function(acc,name,lbl,min,max,value,disable) 112 117 name = name:asvalue() 113 118 lbl = lbl:asvalue() 114 119 local start = '<div class="elem small">' 115 120 local enabled = start .. string.format('<label for="%s">%s</label><input type="number" id="%s" name="%s" min="', name, lbl, name, name) 116 121 local disabled = start .. string.format('<label>%s</label><div class="txtbox">', lbl) ................................................................................ 154 159 local push_checkbox = input_pusher('checkbox',true,false) 155 160 local push_pickbox = input_pusher('checkbox',false,false) 156 161 local push_radio = input_pusher('radio',false,true) 157 162 158 163 local mode_local, mode_remote, mode_staff, mode_peers, mode_peons, mode_all = 0,1,2,3,4,5 159 164 local terra 160 165 render_conf_users(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr 161 - if path.ct == 3 then 166 + if path.ct >= 3 then 162 167 var uid, ok = lib.math.shorthand.parse(path(2).ptr,path(2).ct) 163 168 if not ok then goto e404 end 164 169 var user = co.srv:actor_fetch_uid(uid) 165 170 -- FIXME allow xids as well, for manual queries 166 171 if not user then goto e404 end 167 172 defer user:free() 168 173 if not co.who:overpowers(user.ptr) then goto e403 end 169 174 170 - var cinp: lib.str.acc cinp:init(256) 171 - var clnk: lib.str.acc clnk:init(512) 172 - cinp:lpush('<div class="elem-group">') 173 - if user.ptr.rights.rank > 0 and (co.who.rights.powers.elevate() or co.who.rights.powers.demote()) then 174 - var max = co.who.rights.rank 175 - if not co.who.rights.powers.elevate() then max = user.ptr.rights.rank end 176 - var min = co.srv.cfg.nranks 177 - if not co.who.rights.powers.demote() then min = user.ptr.rights.rank end 178 - 179 - push_num_field(cinp, 'rank', 'rank', max, min, user.ptr.rights.rank, user.ptr.id == co.who.id) 180 - end 181 - if co.who.rights.powers.herald() then 182 - var sanitized: pstr 183 - if user.ptr.epithet == nil 184 - then sanitized = pstr {ptr='', ct=0} 185 - else sanitized = lib.html.sanitize(cs(user.ptr.epithet),true) 175 + if path.ct == 4 then 176 + if path(3):cmp(lib.str.lit'cred') then 177 + var pg: lib.str.acc pg:init(1024) 178 + 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>') 179 + var credmgr = lib.render.conf.sec(co, uid) 180 + pg:ppush(credmgr) 181 + credmgr:free() 182 + return pg:finalize() 183 + else goto e404 end 184 + elseif path.ct == 3 then 185 + var cinp: lib.str.acc cinp:init(256) 186 + cinp:lpush('<div class="elem-group">') 187 + if user.ptr.rights.rank > 0 and (co.who.rights.powers.elevate() or co.who.rights.powers.demote()) then 188 + var max = co.who.rights.rank 189 + if not co.who.rights.powers.elevate() then max = user.ptr.rights.rank end 190 + var min = co.srv.cfg.nranks 191 + if not co.who.rights.powers.demote() then min = user.ptr.rights.rank end 192 + 193 + push_num_field(cinp, 'rank', 'rank', max, min, user.ptr.rights.rank, user.ptr.id == co.who.id) 194 + end 195 + if co.who.rights.powers.herald() then 196 + var sanitized: pstr 197 + if user.ptr.epithet == nil 198 + then sanitized = pstr {ptr='', ct=0} 199 + else sanitized = lib.html.sanitize(cs(user.ptr.epithet),true) 200 + end 201 + cinp:lpush('<div class="elem"><label for="epithet">epithet</label><input type="text" id="epithet" name="epithet" value="'):ppush(sanitized):lpush('"></div>') 202 + if user.ptr.epithet ~= nil then sanitized:free() end 203 + end 204 + if co.who.rights.powers.invite() or co.who.rights.powers.discipline() then 205 + var min: uint32 = 0 206 + if not (co.who.rights.powers.discipline() or 207 + co.who.rights.powers.demote() and co.who.rights.powers.invite()) 208 + then min = user.ptr.rights.invites end 209 + var max: uint32 = co.srv.cfg.maxinvites 210 + if not co.who.rights.powers.invite() then max = user.ptr.rights.invites end 211 + 212 + push_num_field(cinp, 'invites', 'invites', min, max, user.ptr.rights.invites, false) 213 + end 214 + if co.who.rights.powers.elevate() or co.who.rights.powers.demote() then 215 + var max: uint32 = 5000 216 + if not co.who.rights.powers.elevate() then max = user.ptr.rights.quota end 217 + var min: uint32 = 0 218 + if not co.who.rights.powers.demote() then min = user.ptr.rights.quota end 219 + 220 + push_num_field(cinp, 'quota', 'quota', min, max, user.ptr.rights.quota, user.ptr.id == co.who.id and co.who.rights.rank ~= 1) 186 221 end 187 - cinp:lpush('<div class="elem"><label for="epithet">epithet</label><input type="text" id="epithet" name="epithet" value="'):ppush(sanitized):lpush('"></div>') 188 - if user.ptr.epithet ~= nil then sanitized:free() end 189 - end 190 - if co.who.rights.powers.invite() or co.who.rights.powers.discipline() then 191 - var min: uint32 = 0 192 - if not (co.who.rights.powers.discipline() or 193 - co.who.rights.powers.demote() and co.who.rights.powers.invite()) 194 - then min = user.ptr.rights.invites end 195 - var max: uint32 = co.srv.cfg.maxinvites 196 - if not co.who.rights.powers.invite() then max = user.ptr.rights.invites end 222 + cinp:lpush('</div><div class="elem"><div class="check-panel">') 223 + 224 + if user.ptr.id ~= co.who.id and 225 + ((user.ptr.rights.rank == 0 and co.who.rights.powers.elevate()) or 226 + (user.ptr.rights.rank > 0 and co.who.rights.powers.demote())) then 227 + push_checkbox(&cinp, 'staff', pstr.null(), 'site staff member', user.ptr.rights.rank > 0, true, pstr.null()) 228 + end 197 229 198 - push_num_field(cinp, 'invites', 'invites', min, max, user.ptr.rights.invites, false) 199 - end 200 - if co.who.rights.powers.elevate() or co.who.rights.powers.demote() then 201 - var max: uint32 = 5000 202 - if not co.who.rights.powers.elevate() then max = user.ptr.rights.quota end 203 - var min: uint32 = 0 204 - if not co.who.rights.powers.demote() then min = user.ptr.rights.quota end 230 + cinp:lpush('</div></div>') 205 231 206 - push_num_field(cinp, 'quota', 'quota', min, max, user.ptr.rights.quota, user.ptr.id == co.who.id and co.who.rights.rank ~= 1) 207 - end 208 - cinp:lpush('</div><div class="elem"><div class="check-panel">') 209 - 210 - if user.ptr.id ~= co.who.id and 211 - ((user.ptr.rights.rank == 0 and co.who.rights.powers.elevate()) or 212 - (user.ptr.rights.rank > 0 and co.who.rights.powers.demote())) then 213 - push_checkbox(&cinp, 'staff', pstr.null(), 'site staff member', user.ptr.rights.rank > 0, true, pstr.null()) 214 - end 215 - 216 - cinp:lpush('</div></div>') 217 - 218 - if (co.who.rights.powers.elevate() or 219 - co.who.rights.powers.demote()) and user.ptr.id ~= co.who.id then 220 - var map = array([lib.store.privmap]) 221 - cinp:lpush('<details><summary>powers</summary><div class="pick-list">') 222 - for i=0, [map.type.N] do 223 - if (co.who.rights.powers and map[i].priv):sz() > 0 then 224 - var on = (user.ptr.rights.powers and map[i].priv):sz() > 0 225 - var enabled = ( on and co.who.rights.powers.demote() ) or 226 - ((not on) and co.who.rights.powers.elevate()) 227 - var namea: lib.str.acc namea:compose('power-', map[i].name) 228 - var name = namea:finalize() 229 - push_pickbox(&cinp, name, pstr.null(), map[i].name, on, enabled, pstr.null()) 230 - name:free() 232 + if (co.who.rights.powers.elevate() or 233 + co.who.rights.powers.demote()) and user.ptr.id ~= co.who.id then 234 + var map = array([lib.store.privmap]) 235 + cinp:lpush('<details><summary>powers</summary><div class="pick-list">') 236 + for i=0, [map.type.N] do 237 + if (co.who.rights.powers and map[i].priv):sz() > 0 then 238 + var on = (user.ptr.rights.powers and map[i].priv):sz() > 0 239 + var enabled = ( on and co.who.rights.powers.demote() ) or 240 + ((not on) and co.who.rights.powers.elevate()) 241 + var namea: lib.str.acc namea:compose('power-', map[i].name) 242 + var name = namea:finalize() 243 + push_pickbox(&cinp, name, pstr.null(), map[i].name, on, enabled, pstr.null()) 244 + name:free() 245 + end 231 246 end 247 + cinp:lpush('</div></details>') 248 + end 249 + 250 + if co.who.id ~= uid and co.who.rights.powers.purge() then 251 + var purgeconf: lib.str.acc purgeconf:init(48) 252 + var purgestrs = array( 253 + 'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa', 254 + 'emerald', 'carnelian', 'sapphire', 'ruby', 'amethyst', 'glory', 255 + 'hope', 'grace', 'pearl', 'carnation', 'rose', 'peony', 'poppy' 256 + ) 257 + for i=0,3 do 258 + purgeconf:push(purgestrs[lib.crypt.random(intptr,0,[purgestrs.type.N])],0) 259 + if i ~= 2 then purgeconf:lpush('-') end 232 260 end 233 - cinp:lpush('</div></details>') 234 - end 261 + 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>') 262 + purgeconf:free() 263 + end 264 + 265 + -- TODO black mark system? e.g. resolution option for badthink reports 266 + -- adds a black mark to the offending user; they can be automatically banned 267 + -- or brought up for review after a certain number of offenses; possibly lower 268 + -- set of default privs for marked users 235 269 236 - -- TODO black mark system? e.g. resolution option for badthink reports 237 - -- adds a black mark to the offending user; they can be automatically banned 238 - -- or brought up for review after a certain number of offenses; possibly lower 239 - -- set of default privs for marked users 270 + var cinpp = cinp:finalize() defer cinpp:free() 271 + var unym: lib.str.acc unym:init(64) 272 + unym:lpush('<a href="/') 273 + if user(0).origin ~= 0 then unym:lpush('@') end 274 + do var sanxid = lib.html.sanitize(user(0).xid, true) 275 + unym:ppush(sanxid) 276 + sanxid:free() end 277 + unym:lpush('" class="id">') 278 + lib.render.nym(user.ptr,0,&unym,false) 279 + unym:lpush('</a>') 280 + var ctlbox = data.view.conf_user_ctl { 281 + name = unym:finalize(); 282 + inputcontent = cinpp; 283 + btns = pstr{'',0}; 284 + } 285 + if co.who.id ~= uid and co.who.rights.powers.cred() then 286 + ctlbox.btns = lib.str.acc{}:compose('<a class="button" href="/conf/users/',path(2),'/cred">security & credentials</a>'):finalize() 287 + end 288 + var pg: lib.str.acc pg:init(512) 289 + ctlbox:append(&pg) 290 + ctlbox.name:free() 291 + if ctlbox.btns.ct > 0 then ctlbox.btns:free() end 240 292 241 - var cinpp = cinp:finalize() defer cinpp:free() 242 - var clnkp: pstr 243 - if clnk.sz > 0 then clnkp = clnk:finalize() else 244 - clnk:free() 245 - clnkp = pstr { ptr='', ct=0 } 293 + return pg:finalize() 246 294 end 247 - var unym: lib.str.acc unym:init(64) 248 - unym:lpush('<a href="/') 249 - if user(0).origin ~= 0 then unym:lpush('@') end 250 - do var sanxid = lib.html.sanitize(user(0).xid, true) 251 - unym:ppush(sanxid) 252 - sanxid:free() end 253 - unym:lpush('" class="id">') 254 - lib.render.nym(user.ptr,0,&unym,false) 255 - unym:lpush('</a>') 256 - var pg = data.view.conf_user_ctl { 257 - name = unym:finalize(); 258 - inputcontent = cinpp; 259 - linkcontent = clnkp; 260 - } 261 - var ret = pg:tostr() 262 - pg.name:free() 263 - if clnkp.ct > 0 then clnkp:free() end 264 - return ret 265 295 else 266 296 var modes = array(P'local', P'remote', P'staff', P'titled', P'peons', P'all') 267 297 var idbuf: int8[lib.math.shorthand.maxlen] 268 298 var ulst: lib.str.acc ulst:init(256) 269 299 var mode: uint8 = mode_local 270 300 var modestr = co:pgetv('show') 271 301 ulst:lpush('<div style="text-align: right"><em>showing ')
Modified render/nav.t from [5194b2263f] to [50b4e7c2b2].
3 3 render_nav(co: &lib.srv.convo) 4 4 var t: lib.str.acc t:init(64) 5 5 if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then 6 6 t:lpush(' <a accesskey="t" href="/">timeline</a>') 7 7 end 8 8 if co.who ~= nil then 9 9 t:lpush(' <a accesskey="c" href="/compose">compose</a> <a accesskey="p" href="/'):push(co.who.xid,0) 10 - 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>') 10 + 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>') 11 11 else 12 12 t:lpush(' <a accesskey="d" href="/doc">docs</a> <a accesskey="g" href="/login">log in</a>') 13 13 end 14 14 return t:finalize() 15 15 end 16 16 return render_nav
Modified render/tweet.t from [18c58bf27c] to [83917dbbe9].
31 31 author = co.actorcache:insert(co.srv:actor_fetch_uid(p.author)).ptr 32 32 end 33 33 if p.rtdby ~= 0 and retweeter == nil then 34 34 retweeter = co.actorcache:insert(co.srv:actor_fetch_uid(p.rtdby)).ptr 35 35 end 36 36 37 37 ::foundauth:: 38 - var avistr: lib.str.acc if author.origin == 0 then 39 - avistr:compose('/avi/',author.handle) 40 - end 41 38 var timestr: int8[26] lib.osclock.ctime_r(&p.posted, ×tr[0]) 42 39 for i=0,26 do if timestr[i] == @'\n' then timestr[i] = 0 break end end -- 🙄 43 40 44 41 var bhtml = lib.smackdown.html([lib.mem.ptr(int8)] {ptr=p.body,ct=0},false) 45 42 defer bhtml:free() 46 43 47 44 var idbuf: int8[lib.math.shorthand.maxlen]
Modified route.t from [881176d2e1] to [b4f49ac3f6].
284 284 lib.render.tweet_page(co, path, post.ptr) 285 285 do return end 286 286 287 287 ::badurl:: do co:complain(404, 'invalid URL', 'this URL does not reference extant content or functionality') return end 288 288 ::badop :: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end 289 289 ::noauth:: do co:complain(401, 'unauthorized', 'you have not supplied the necessary credentials to perform this operation') return end 290 290 end 291 + 292 +local terra 293 +credsec_for_uid(co: &lib.srv.convo, uid: uint64) 294 + var act = co:ppostv('act') 295 + if act:cmp(lib.str.plit 'invalidate') then 296 + lib.dbg('setting user\'s cookie validation time to now') 297 + co.who.source:auth_sigtime_user_alter(uid, lib.osclock.time(nil)) 298 + -- the current session has been invalidated as well, so we need to immediately install a new authentication cookie with the same aid so the user doesn't need to log back in all over again 299 + co:installkey('/conf/sec',co.aid) 300 + return 301 + elseif act:cmp(lib.str.plit 'newcred') then 302 + var cmt = co:ppostv('comment') 303 + var pw = co:ppostv('newpw') 304 + if pw:ref() then 305 + var cpw = co:ppostv('rptpw') 306 + if not pw:cmp(cpw) then 307 + co:complain(400,'enrollment failure','the passwords you supplied do not match') 308 + return 309 + end 310 + co.srv:auth_attach_pw(uid, false, pw, cmt) 311 + co:reroute('?') 312 + return 313 + else 314 + var key = co:ppostv('newkey') 315 + if key:ref() then 316 + 317 + end 318 + end 319 + end 320 + co:complain(400,'bad request','the operation you have requested is not meaningful in this context') 321 +end 291 322 292 323 terra http.configure(co: &lib.srv.convo, path: hpath, meth: method.t) 293 324 var msg = pstring.null() 294 325 -- first things first, do priv checks 295 326 if path.ct >= 1 then 296 327 if not co.who.rights.powers.config() and ( 297 328 path(1):cmp(lib.str.lit 'srv') or ................................................................................ 345 376 co.ui_hue = co.srv.cfg.ui_hue 346 377 end 347 378 348 379 msg = lib.str.plit 'profile changes saved' 349 380 --user_refresh = true -- not really necessary here, actually 350 381 351 382 elseif path(1):cmp(lib.str.lit 'sec') then 352 - var act = co:ppostv('act') 353 - if act:cmp(lib.str.plit 'invalidate') then 354 - lib.dbg('setting user\'s cookie validation time to now') 355 - co.who.source:auth_sigtime_user_alter(co.who.id, lib.osclock.time(nil)) 356 - -- 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 357 - co:installkey('/conf/sec',co.aid) 358 - return 359 - end 383 + credsec_for_uid(co, co.who.id) 360 384 elseif path(1):cmp(lib.str.lit 'users') then 361 385 if path.ct >= 3 then 362 386 var userid, ok = lib.math.shorthand.parse(path(2).ptr, path(2).ct) 363 387 if ok then 364 388 var usr = co.srv:actor_fetch_uid(userid) 365 389 if usr:ref() then defer usr:free() 366 390 if not co.who:overpowers(usr.ptr) then goto nopriv end 367 391 end 368 392 end 369 - elseif path.ct == 2 then 393 + elseif path.ct == 2 and meth == method.post then 394 + var act = co:ppostv('act') 395 + if act:cmp(lib.str.plit'create') then 396 + var newname = co:ppostv('handle') 397 + if not newname or not lib.store.actor.handle_validate(newname.ptr) then 398 + co:complain(400,'invalid handle','the handle you have requested is not valid') 399 + end 400 + var tu = co.srv:actor_fetch_xid(newname) 401 + if tu:ref() then tu:free() 402 + co:complain(409,'handle clash','that handle conflicts with one that already exists') 403 + return 404 + end 405 + var kbuf: uint8[lib.crypt.const.maxdersz] 406 + var na = lib.store.actor.mk(&kbuf[0]) 407 + na.handle = newname.ptr 408 + var newuid = co.srv:actor_create(&na) 409 + var shid: int8[lib.math.shorthand.maxlen] 410 + var shidlen = lib.math.shorthand.gen(newuid, &shid[0]) 411 + var url = lib.str.acc{}:compose('/conf/users/',pstring{&shid[0],shidlen}):finalize() defer url:free() 412 + co:reroute(url.ptr) 413 + return 414 + elseif act:cmp(lib.str.plit'inst') then 415 + else goto badop end 370 416 end 371 417 end 372 418 373 419 if user_refresh then -- refresh the user info for the renderer 374 420 var usr = co.srv:actor_fetch_uid(co.who.id) 375 421 lib.mem.heapf(co.who) 376 422 co.who = usr.ptr ................................................................................ 380 426 co:reroute(go) 381 427 return 382 428 end 383 429 end 384 430 lib.render.conf(co,path,msg) 385 431 do return end 386 432 387 - ::nopriv:: co:complain(403,'insufficient privileges','you do not have the necessary powers to perform this action') 433 + ::nopriv:: do co:complain(403,'insufficient privileges','you do not have the necessary powers to perform this action') return end 434 + ::badop:: do co:complain(400,'bad request','the operation you have requested is not meaningful in this context') return end 388 435 end 389 436 390 437 terra http.user_notices(co: &lib.srv.convo, meth: method.t) 391 438 if meth == method.post then 392 439 var act = co:ppostv('act') 393 440 if act:cmp(lib.str.plit'clear') then 394 441 co.srv:actor_conf_int_set(co.who.id, 'notice-clear-time', lib.osclock.time(nil)) ................................................................................ 399 446 400 447 lib.render.notices(co) 401 448 do return end 402 449 403 450 ::badop:: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end 404 451 end 405 452 406 -terra http.media_manager(co: &lib.srv.convo, path: hpath, meth: method.t) 407 - if meth == method.post then 408 - goto badop 409 - end 410 - 411 - if path.ct == 2 and path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then 453 +terra http.media_manager(co: &lib.srv.convo, path: hpath, meth: method.t, uid: uint64) 454 + 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 412 455 if meth == method.get then 413 456 var view = data.view.media_upload { 414 457 folders = '' 415 458 } 416 459 var pg = view:tostr() defer pg:free() 417 460 co:stdpage([lib.srv.convo.page] { 418 461 title = lib.str.plit'media :: upload'; ................................................................................ 453 496 var idbuf: int8[lib.math.shorthand.maxlen] 454 497 var idlen = lib.math.shorthand.gen(id,&idbuf[0]) 455 498 456 499 var url = lib.str.acc{}:compose('/media/a/',pstring{&idbuf[0],idlen}):finalize() 457 500 co:reroute(url.ptr) 458 501 url:free() 459 502 else goto badop end 503 + elseif co.aid ~= 0 and path.ct == 4 and path(1):cmp(lib.str.lit'a') and meth==method.post then 504 + var act = co:ppostv('act') 505 + if not act or not act:cmp(lib.str.plit'confirm') then goto badop end 506 + var artid, aok = lib.math.shorthand.parse(path(2).ptr,path(2).ct) 507 + if not aok then goto e404 end 508 + var art = co.srv:artifact_fetch(uid,artid) 509 + if not art then goto e404 end 510 + defer art:free() 511 + 512 + if path(3):cmp(lib.str.lit'avi') then 513 + -- user wants to set avatar 514 + co.who.avatarid = artid 515 + co.srv:actor_save(co.who) 516 + co:reroute('/conf/avi') 517 + elseif path(3):cmp(lib.str.lit'del') then 518 + co.srv:artifact_disclaim(co.who.id, artid) 519 + co:reroute('/media') 520 + else goto badop end 460 521 else 461 522 if meth == method.post then goto badop end 462 - lib.render.media_gallery(co,path,co.who.id,nil) 523 + lib.render.media_gallery(co,path,uid,nil) 463 524 end 464 525 do return end 465 526 466 527 ::badop:: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end 467 528 ::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded by this user') return end 468 529 end 469 530 ................................................................................ 501 562 ::[send]:: page:send(co.con) return true 502 563 end 503 564 end 504 565 505 566 506 567 terra http.local_avatar(co: &lib.srv.convo, handle: lib.mem.ptr(int8)) 507 568 -- TODO retrieve user avatars 508 - co:reroute('/s/default-avatar.webp') 569 + var usr = co.srv:actor_fetch_xid(handle) 570 + if not usr then 571 + goto default end 572 + if usr(0).origin == 0 then 573 + if usr(0).avatarid == 0 then goto default end 574 + var avi, mime = co.srv:artifact_load(usr(0).avatarid) 575 + if not avi then goto default end 576 + defer avi:free() defer mime:free() 577 + co:bytestream(mime,avi) 578 + else 579 + co:reroute(usr(0).avatar) 580 + end 581 + do return end 582 + ::default:: co:reroute('/s/default-avatar.webp') 509 583 end 510 584 511 585 terra http.file_serve_raw(co: &lib.srv.convo, id: lib.mem.ptr(int8)) 512 586 var id, idok = lib.math.shorthand.parse(id.ptr, id.ct) 513 587 if not idok then goto e404 end 514 588 var data, mime = co.srv:artifact_load(id) 515 589 if not data then goto e404 end 516 590 do defer data:free() defer mime:free() 517 - var safemime = mime 518 - -- TODO this is not a satisfactory solution; it's a bandaid on a gaping 519 - -- chest wound. ultimately we need to compile a whitelist of safe mime 520 - -- types as part of mimelib, but that is no small task. for now, this 521 - -- will keep the patient from immediately bleeding out 522 - if mime:cmp(lib.str.plit'text/html') or 523 - mime:cmp(lib.str.plit'text/xml') or 524 - mime:cmp(lib.str.plit'application/xhtml+xml') or 525 - mime:cmp(lib.str.plit'application/vnd.wap.xhtml+xml') 526 - then -- danger will robinson 527 - safemime = lib.str.plit'text/plain' 528 - elseif mime:cmp(lib.str.plit'application/x-shockwave-flash') then 529 - safemime = lib.str.plit'application/octet-stream' 530 - end 531 - 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) 532 - lib.net.mg_send(co.con, data.ptr, data.ct) 533 - lib.net.mg_send(co.con, '\r\n', 2) 591 + co:bytestream(mime,data) 534 592 return end 535 593 536 594 ::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded to this instance') return end 537 595 end 538 596 539 597 -- entry points 540 598 terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t) ................................................................................ 580 638 http.actor_profile_uid(co, path, meth) 581 639 elseif path.ct > 1 and path(0):cmp(lib.str.lit('post')) then 582 640 http.tweet_page(co, path, meth) 583 641 elseif path(0):cmp(lib.str.lit('tl')) then 584 642 http.timeline(co, path) 585 643 elseif path(0):cmp(lib.str.lit('media')) then 586 644 if co.aid == 0 then goto unauth end 587 - http.media_manager(co, path, meth) 645 + http.media_manager(co, path, meth, co.who.id) 588 646 elseif path(0):cmp(lib.str.lit('doc')) then 589 647 if not meth_get(meth) then goto wrongmeth end 590 648 http.documentation(co, path) 591 649 elseif path(0):cmp(lib.str.lit('conf')) then 592 650 if co.aid == 0 then goto unauth end 593 651 http.configure(co,path,meth) 594 652 else goto notfound end
Modified srv.t from [ca6d27c8d7] to [56e1fe84a6].
228 228 self:rawpage(200, pg, [lib.mem.ptr(lib.http.header)] { 229 229 ptr = &hdrs[0], ct = 3 230 230 }) 231 231 end 232 232 end 233 233 234 234 terra convo:stdpage(pg: convo.page) self:statpage(200, pg) end 235 + 236 +terra convo:bytestream(mime: pstring, data: lib.mem.ptr(uint8)) 237 + -- TODO this is not a satisfactory solution; it's a bandaid on a gaping 238 + -- chest wound. ultimately we need to compile a whitelist of safe mime 239 + -- types as part of mimelib, but that is no small task. for now, this 240 + -- will keep the patient from immediately bleeding out 241 + if mime:cmp(lib.str.plit'text/html') or 242 + mime:cmp(lib.str.plit'text/xml') or 243 + mime:cmp(lib.str.plit'application/xhtml+xml') or 244 + mime:cmp(lib.str.plit'application/vnd.wap.xhtml+xml') 245 + then -- danger will robinson 246 + mime = lib.str.plit'text/plain' 247 + elseif mime:cmp(lib.str.plit'application/x-shockwave-flash') then 248 + mime = lib.str.plit'application/octet-stream' 249 + end 250 + 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) 251 + lib.net.mg_send(self.con, data.ptr, data.ct) 252 + lib.net.mg_send(self.con, '\r\n', 2) 253 +end 235 254 236 255 terra convo:reroute_cookie(dest: rawstring, cookie: rawstring) 237 256 var hdrs = array( 238 257 lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }, 239 258 lib.http.header { key = 'Location', value = dest }, 240 259 lib.http.header { key = 'Set-Cookie', value = cookie } 241 260 )
Modified static/style.scss from [fb57063208] to [f9852f8724].
631 631 } 632 632 633 633 menu { all: unset; display: block; } 634 634 body.conf main { 635 635 display: grid; 636 636 grid-template-columns: 2in 1fr; 637 637 grid-template-rows: max-content 1fr; 638 + div.context { 639 + border-radius: 4px; 640 + text-align: center; 641 + background: tone(-53%); 642 + box-shadow: 0 1px 0 1px tone(-55%); 643 + border: 1px solid tone(-20%); 644 + font-style: italic; 645 + padding: 0.1in; 646 + } 638 647 > menu { @extend %navmenu; } 639 648 > .panel { 640 649 grid-column: 2/3; grid-row: 1/3; 641 650 padding-left: 0.15in; 642 651 > h1 { 643 652 padding-bottom: 0.1in; 644 653 margin-bottom: 0.1in;
Modified store.t from [eca94a58d5] to [b8bbc4e0ec].
163 163 var newkp = lib.crypt.genkp() 164 164 var privsz = lib.crypt.der(false,&newkp,kbuf) 165 165 return m.actor { 166 166 id = 0; nym = nil; handle = nil; 167 167 origin = 0; bio = nil; avatar = nil; 168 168 knownsince = lib.osclock.time(nil); 169 169 rights = m.rights_default(); 170 + avatarid = 0; 170 171 epithet = nil, key = [lib.mem.ptr(uint8)] { 171 172 ptr = &kbuf[0], ct = privsz 172 173 }; 173 174 } 174 175 end 175 176 176 177 struct m.actor_stats { ................................................................................ 407 408 actor_notice_enum: {&m.source, uint64} -> lib.mem.ptr(m.notice) 408 409 actor_rel_create: {&m.source, uint16, uint64, uint64} -> {} 409 410 actor_rel_destroy: {&m.source, uint16, uint64, uint64} -> {} 410 411 actor_rel_calc: {&m.source, uint64, uint64} -> m.relationship 411 412 412 413 auth_enum_uid: {&m.source, uint64} -> lib.mem.lstptr(m.auth) 413 414 auth_enum_handle: {&m.source, rawstring} -> lib.mem.lstptr(m.auth) 414 - auth_attach_pw: {&m.source, uint64, bool, pstr, pstr} -> {} 415 + auth_attach_pw: {&m.source, uint64, bool, pstr, pstr} -> {} 416 + auth_attach_key: {&m.source, uint64, bool, pstr, pstr} -> {} 415 417 -- uid: uint64 416 418 -- reset: bool (delete other passwords?) 417 419 -- pw: pstring 418 420 -- comment: pstring 419 421 auth_purge_pw: {&m.source, uint64, rawstring} -> {} 420 422 auth_purge_otp: {&m.source, uint64, rawstring} -> {} 421 423 auth_purge_trust: {&m.source, uint64, rawstring} -> {}
Modified view/conf-sec-credmg.tpl from [43efff9618] to [6f4f9fa693].
1 1 <hr> 2 2 <form method="post"> 3 - <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> 3 + <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> 4 4 <select size="6" name="cred"> 5 5 @credlist 6 6 </select> 7 7 <menu class="horizontal choice"> 8 8 <button name="act" value="reset">reset</button> 9 9 <button name="act" value="revoke">revoke</button> 10 10 </menu> 11 11 </form> 12 12 <hr> 13 -<form method="post"> 14 - <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> 13 +<form method="get"> 14 + <p>you can associate extra credentials with this account. you can also limit how much of this account’s authority these credentials can be used to exercise — for instance, it might be useful to create API keys that can read the account timeline, but not post as the account owner or access any of his administrative powers. if you don't select a capability set, the credential will be able to wield the full scope of the associated account‘s powers.</p> 15 15 <div class="check-panel"> 16 16 <label><input type="checkbox" name="allow-post"> post</label> 17 17 <label><input type="checkbox" name="allow-edit"> edit</label> 18 18 <label><input type="checkbox" name="allow-acct"> manage account</label> 19 19 <label><input type="checkbox" name="allow-upload"> upload artifacts</label> 20 20 <label><input type="checkbox" name="allow-censor"> moderation</label> 21 21 <label><input type="checkbox" name="allow-admin"> other admin powers</label> ................................................................................ 23 23 </div> 24 24 <p>you can also specify an IP address range in CIDR format to associate with this credential. if you do so, this credential will only be usable when connecting from an IP address in that range. otherwise, it will be valid when connecting from anywhere on the internet.</p> 25 25 <div class="elem"> 26 26 <label for="netmask">netmask</label> 27 27 <input type="text" name="netmask" id="netmask" placeholder="10.0.0.0/8"> 28 28 </div> 29 29 <menu class="vertical choice"> 30 - <button name="kind" value="pw">new password</button> 31 - <button name="kind" value="otp">new OTP key</button> 32 - <button name="kind" value="api">new API token</button> 33 - <button name="kind" value="challenge">new challenge key</button> 34 - </div> 30 + <button name="new" value="pw">new password</button> 31 + <button name="new" value="otp">new OTP key</button> 32 + <button name="new" value="api">new API token</button> 33 + <button name="new" value="challenge">new challenge key</button> 34 + </menu> 35 35 </form>
Added view/conf-sec-pwnew.tpl version [5878dd4701].
1 +<form method="post"> 2 + <div class="elem"> 3 + <label for="comment">comment</label> 4 + <input type="text" id="comment" name="comment" value="@comment" required> 5 + </div> 6 + <div class="elem"> 7 + <label for="newpw">new password</label> 8 + <input type="password" id="newpw" name="newpw" required> 9 + </div> 10 + <div class="elem"> 11 + <label for="rptpw">confirm password</label> 12 + <input type="password" id="rptpw" name="rptpw" required> 13 + </div> 14 + <menu class="choice horizontal"> 15 + <button name="act" value="newcred">enroll</button> 16 + <a class="button" href="?">cancel</a> 17 + </menu> 18 +</form>
Modified view/conf-sec.tpl from [de1cf7e8f0] to [49c75b1abd].
1 1 <form method="post"> 2 - <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> 2 + <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> 3 3 <div class="elem"> 4 4 <label> sessions valid from </label> 5 5 <div class="txtbox">@lastreset</div> 6 6 </div> 7 7 <button type="submit" name="act" value="invalidate"> 8 8 invalidate other sessions 9 9 </button> 10 10 </form>
Modified view/conf-user-ctl.tpl from [7abbc95a91] to [7e2cfb9c6e].
1 1 <form method="post"> 2 2 <div class="elem"> 3 3 <label>user</label> 4 4 <div class="txtbox">@name</div> 5 5 </div> 6 6 @inputcontent 7 - <button>alter</button> 7 + <menu class="vertical choice"> 8 + <button>alter</button> 9 + @btns 10 + </menu> 8 11 </form> 9 -@linkcontent
Modified view/load.lua from [9f4f065de0] to [15344e4760].
19 19 'login-username'; 20 20 'login-challenge'; 21 21 22 22 'conf'; 23 23 'conf-profile'; 24 24 'conf-sec'; 25 25 'conf-sec-credmg'; 26 + 'conf-sec-pwnew'; 26 27 'conf-user-ctl'; 27 28 } 28 29 29 30 local ingest = function(filename) 30 31 local hnd = io.open(path..'/'..filename) 31 32 local txt = hnd:read('*a') 32 33 io.close(hnd)