| Comment: | big ol iteration |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
5b3a03ad34669be0b5e5ecd853db0a2c |
| User & Date: | lexi on 2020-12-25 03:59:32 |
| Other Links: | manifest | tags |
|
2020-12-25
| ||
| 23:37 | iteration and important api adjustments check-in: f9559a83fc user: lexi tags: trunk | |
| 03:59 | big ol iteration check-in: 5b3a03ad34 user: lexi tags: trunk | |
|
2020-12-22
| ||
| 23:01 | milestone check-in: 419d1a1ebe user: lexi tags: trunk | |
Modified backend/pgsql.t from [17bd63f8c3] to [0ea1a47601].
21 21 key = $1::text 22 22 ]]; 23 23 }; 24 24 25 25 actor_fetch_uid = { 26 26 params = {uint64}, sql = [[ 27 27 select 28 - id, nym, handle, origin, 29 - bio, rank, quota, key 28 + id, nym, handle, origin, bio, 29 + avataruri, rank, quota, key, 30 + extract(epoch from knownsince)::bigint 31 + 30 32 from parsav_actors 31 33 where id = $1::bigint 32 34 ]]; 33 35 }; 34 36 35 37 actor_fetch_xid = { 36 38 params = {lib.mem.ptr(int8)}, sql = [[ 37 - select a.id, a.nym, a.handle, a.origin, 38 - a.bio, a.rank, a.quota, a.key, 39 + select a.id, a.nym, a.handle, a.origin, a.bio, 40 + a.avataruri, a.rank, a.quota, a.key, 41 + extract(epoch from knownsince)::bigint, 39 42 coalesce(a.handle || '@' || s.domain, 40 43 '@' || a.handle) as xid, 41 44 42 45 coalesce(s.domain, 43 46 (select value from parsav_config 44 47 where key='domain' limit 1)) as domain 45 48 ................................................................................ 50 53 where $1::text = (a.handle || '@' || domain) or 51 54 $1::text = ('@' || a.handle || '@' || domain) or 52 55 (a.origin is null and 53 56 $1::text = a.handle or 54 57 $1::text = ('@' || a.handle)) 55 58 ]]; 56 59 }; 60 + 61 + actor_auth_pw = { 62 + params = {lib.mem.ptr(int8),rawstring,lib.mem.ptr(int8),lib.store.inet}, sql = [[ 63 + select a.aid from parsav_auth as a 64 + left join parsav_actors as u on u.id = a.uid 65 + where (a.uid is null or u.handle = $1::text or ( 66 + a.uid = 0 and a.name = $1::text 67 + )) and 68 + (a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and 69 + (a.netmask is null or a.netmask >> $4::inet) 70 + order by blacklist desc limit 1 71 + ]]; 72 + }; 57 73 58 74 actor_enum_local = { 59 75 params = {}, sql = [[ 60 - select id, nym, handle, origin, 61 - bio, rank, quota, key, 76 + select id, nym, handle, origin, bio, 77 + null::text, rank, quota, key, 78 + extract(epoch from knownsince)::bigint, 62 79 handle ||'@'|| 63 80 (select value from parsav_config 64 81 where key='domain' limit 1) as xid 65 82 from parsav_actors where origin is null 66 83 ]]; 67 84 }; 68 85 69 86 actor_enum = { 70 87 params = {}, sql = [[ 71 - select a.id, a.nym, a.handle, a.origin, 72 - a.bio, a.rank, a.quota, a.key, 88 + select a.id, a.nym, a.handle, a.origin, a.bio, 89 + a.avataruri, a.rank, a.quota, a.key, 90 + extract(epoch from knownsince)::bigint, 73 91 coalesce(a.handle || '@' || s.domain, 74 92 '@' || a.handle) as xid 75 93 from parsav_actors a 76 94 left join parsav_servers s on s.id = a.origin 77 95 ]]; 78 96 }; 97 + 98 + actor_stats = { 99 + params = {uint64}, sql = ([[ 100 + with tweets as ( 101 + select from parsav_posts where author = $1::bigint 102 + ), 103 + follows as ( 104 + select relatee as user from parsav_rels 105 + where relator = $1::bigint and kind = <follow> 106 + ), 107 + followers as ( 108 + select relator as user from parsav_rels 109 + where relatee = $1::bigint and kind = <follow> 110 + ), 111 + mutuals as (select * from follows intersect select * from followers) 112 + 113 + select count(tweets.*)::bigint, 114 + count(follows.*)::bigint, 115 + count(followers.*)::bigint, 116 + count(mutuals.*)::bigint 117 + from tweets, follows, followers, mutuals 118 + ]]):gsub('<(%w+)>',function(r) return tostring(lib.store.relation[r]) end) 119 + }; 79 120 80 121 actor_auth_how = { 81 122 params = {rawstring, lib.store.inet}, sql = [[ 82 123 with mts as (select a.kind from parsav_auth as a 83 124 left join parsav_actors as u on u.id = a.uid 84 125 where (a.uid is null or u.handle = $1::text or ( 85 126 a.uid = 0 and a.name = $1::text ................................................................................ 93 134 (select count(*) from mts where kind like 'challenge-%') > 0, 94 135 (select count(*) from mts where kind = 'trust') > 0 95 136 ]]; -- cheat 96 137 }; 97 138 98 139 actor_session_fetch = { 99 140 params = {uint64, lib.store.inet}, sql = [[ 100 - select a.id, a.nym, a.handle, a.origin, 101 - a.bio, a.rank, a.quota, a.key, 141 + select a.id, a.nym, a.handle, a.origin, a.bio, 142 + a.avataruri, a.rank, a.quota, a.key, 143 + extract(epoch from knownsince)::bigint, 102 144 coalesce(a.handle || '@' || s.domain, 103 145 '@' || a.handle) as xid, 104 146 105 147 au.restrict, 106 148 array['post' ] <@ au.restrict as can_post, 107 149 array['edit' ] <@ au.restrict as can_edit, 108 150 array['acct' ] <@ au.restrict as can_acct, ................................................................................ 171 213 for j=0,sz do i.v6[j] = v[4 + j] end -- 😬 172 214 return i 173 215 end 174 216 pqr.methods.int = macro(function(self, ty, row, col) 175 217 return quote 176 218 var i: ty:astype() 177 219 var v = lib.pq.PQgetvalue(self.res, row, col) 220 + --i = @[&uint64](v) 178 221 lib.math.netswap_ip(ty, v, &i) 179 222 in i end 180 223 end) 181 224 182 225 local pqt = { 183 226 [lib.store.inet] = function(cidr) 184 227 local tycode = cidr and 0x01 or 0x00 ................................................................................ 216 259 end 217 260 lib.bail('could not prepare PGSQL statement ',k,': ',lib.pq.PQresultErrorMessage(res)) 218 261 end 219 262 lib.dbg('prepared PGSQL statement ',k) 220 263 end 221 264 222 265 local args, casts, counters, fixers, ft, yield = {}, {}, {}, {}, {}, {} 266 + local dumpers = {} 223 267 for i, ty in ipairs(q.params) do 224 268 args[i] = symbol(ty) 225 269 ft[i] = `1 226 270 if ty == rawstring then 227 271 counters[i] = `lib.trn([args[i]] == nil, 0, lib.str.sz([args[i]])) 228 272 casts[i] = `[&int8]([args[i]]) 273 + dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got rawstr %s\n'], [args[i]]) 229 274 elseif ty == lib.store.inet then -- assume not CIDR 230 275 counters[i] = `lib.trn([args[i]].pv == 4,4,16)+4 231 276 casts[i] = quote 232 277 var ipbuf: int8[20] 233 278 ;[pqt[lib.store.inet](false)]([args[i]], [&uint8](&ipbuf)) 234 279 in &ipbuf[0] end 280 + dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got inet\n']) 235 281 elseif ty.ptr_basetype == int8 or ty.ptr_basetype == uint8 then 236 282 counters[i] = `[args[i]].ct 237 283 casts[i] = `[&int8]([args[i]].ptr) 284 + dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got ptr %llu %.*s\n'], [args[i]].ct, [args[i]].ct, [args[i]].ptr) 238 285 elseif ty:isintegral() then 239 286 counters[i] = ty.bytes 240 287 casts[i] = `[&int8](&[args[i]]) 288 + dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got int %llu\n'], [args[i]]) 241 289 fixers[#fixers + 1] = quote 242 290 --lib.io.fmt('uid=%llu(%llx)\n',[args[i]],[args[i]]) 243 291 [args[i]] = lib.math.netswap(ty, [args[i]]) 244 292 end 245 293 end 246 294 end 247 295 248 296 terra q.exec(src: &lib.store.source, [args]) 249 297 var params = arrayof([&int8], [casts]) 250 298 var params_sz = arrayof(int, [counters]) 251 299 var params_ft = arrayof(int, [ft]) 252 300 [fixers] 301 + --[dumpers] 253 302 var res = lib.pq.PQexecPrepared([&lib.pq.PGconn](src.handle), stmt, 254 303 [#args], params, params_sz, params_ft, 1) 255 304 if res == nil then 256 305 lib.bail(['grievous error occurred executing '..k..' against database']) 257 306 elseif lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then 258 307 lib.bail(['PGSQL database procedure '..k..' failed\n'], 259 308 lib.pq.PQresultErrorMessage(res)) ................................................................................ 268 317 end 269 318 end 270 319 end 271 320 272 321 local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor) 273 322 var a: lib.mem.ptr(lib.store.actor) 274 323 275 - if r:cols() >= 8 then 324 + if r:cols() >= 9 then 276 325 a = [ lib.str.encapsulate(lib.store.actor, { 277 326 nym = {`r:string(row,1), `r:len(row,1)+1}; 278 327 bio = {`r:string(row,4), `r:len(row,4)+1}; 328 + avatar = {`r:string(row,5), `r:len(row,5)+1}; 279 329 handle = {`r:string(row, 2); `r:len(row,2) + 1}; 280 - xid = {`r:string(row, 8); `r:len(row,8) + 1}; 330 + xid = {`r:string(row, 10); `r:len(row,10) + 1}; 281 331 }) ] 282 332 else 283 333 a = [ lib.str.encapsulate(lib.store.actor, { 284 334 nym = {`r:string(row,1), `r:len(row,1)+1}; 285 335 bio = {`r:string(row,4), `r:len(row,4)+1}; 336 + avatar = {`r:string(row,5), `r:len(row,5)+1}; 286 337 handle = {`r:string(row, 2); `r:len(row,2) + 1}; 287 338 }) ] 288 339 a.ptr.xid = nil 289 340 end 290 341 a.ptr.id = r:int(uint64, row, 0); 291 342 a.ptr.rights = lib.store.rights_default(); 292 - a.ptr.rights.rank = r:int(uint16, row, 5); 293 - a.ptr.rights.quota = r:int(uint32, row, 6); 294 - if r:null(row,7) then 343 + a.ptr.rights.rank = r:int(uint16, row, 6); 344 + a.ptr.rights.quota = r:int(uint32, row, 7); 345 + a.ptr.knownsince = r:int(int64,row, 9); 346 + if r:null(row,8) then 295 347 a.ptr.key.ct = 0 a.ptr.key.ptr = nil 296 348 else 297 - a.ptr.key = r:bin(row,7) 349 + a.ptr.key = r:bin(row,8) 298 350 end 299 351 if r:null(row,3) then a.ptr.origin = 0 300 352 else a.ptr.origin = r:int(uint64,row,3) end 301 353 return a 302 354 end 303 355 304 -local checksha = function(hnd, query, hash, origin, username, pw) 305 - local inet_buf = symbol(uint8[4 + 16]) 356 +local checksha = function(src, hash, origin, username, pw) 306 357 local validate = function(kind, cred, credlen) 307 358 return quote 308 - var osz: intptr if origin.pv == 4 then osz = 4 else osz = 16 end 309 - var formats = arrayof([int], 1,1,1,1) 310 - var params = arrayof([&int8], username, kind, 311 - [&int8](&cred), [&int8](&inet_buf)) 312 - var lens = arrayof(int, lib.str.sz(username), [#kind], credlen, osz + 4) 313 - var res = lib.pq.PQexecParams([&lib.pq.PGconn](hnd), query, 4, nil, 314 - params, lens, formats, 1) 315 - if res == nil then 316 - lib.bail('grievous failure checking pwhash') 317 - elseif lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then 318 - lib.warn('pwhash query failed: ', lib.pq.PQresultErrorMessage(res), '\n', query) 319 - else 320 - var r = pqr { 321 - sz = lib.pq.PQntuples(res); 322 - res = res; 323 - } 324 - if r.sz > 0 then -- found a record! stop here 325 - var aid = r:int(uint64, 0,0) 326 - r:free() 327 - return aid 328 - end 359 + var r = queries.actor_auth_pw.exec( 360 + [&lib.store.source](src), 361 + username, 362 + kind, 363 + [lib.mem.ptr(int8)] {ptr=[&int8](cred), ct=credlen}, 364 + origin) 365 + if r.sz > 0 then -- found a record! stop here 366 + var aid = r:int(uint64, 0,0) 367 + r:free() 368 + return aid 329 369 end 330 370 end 331 371 end 332 372 333 373 local out = symbol(uint8[64]) 334 374 local vdrs = {} 335 375 336 376 local alg = lib.md['MBEDTLS_MD_SHA' .. tostring(hash)] 337 377 vdrs[#vdrs+1] = quote 338 378 if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(alg), 339 - [&uint8](pw), lib.str.sz(pw), out) ~= 0 then 379 + [&uint8](pw.ptr), pw.ct, out) ~= 0 then 340 380 lib.bail('hashing failure!') 341 381 end 342 - [ validate(string.format('pw-sha%u', hash), out, hash / 8) ] 382 + [ validate(string.format('pw-sha%u', hash), `&out[0], hash / 8) ] 343 383 end 344 384 345 385 return quote 346 386 lib.dbg(['searching for hashed password credentials in format SHA' .. tostring(hash)]) 347 - var [inet_buf] 348 - [pqt[lib.store.inet](false)](origin, inet_buf) 349 387 var [out] 350 388 [vdrs] 351 389 lib.dbg(['could not find password hash']) 352 390 end 353 391 end 354 392 355 393 local b = `lib.store.backend { ................................................................................ 439 477 end 440 478 end]; 441 479 442 480 actor_auth_how = [terra( 443 481 src: &lib.store.source, 444 482 ip: lib.store.inet, 445 483 username: rawstring 446 - ) 484 + ): {lib.store.credset, bool} 447 485 var cs: lib.store.credset cs:clear(); 448 486 var r = queries.actor_auth_how.exec(src, username, ip) 449 - if r.sz == 0 then return cs end -- just in case 487 + if r.sz == 0 then return cs, false end -- just in case 450 488 defer r:free() 451 489 (cs.pw << r:bool(0,0)) 452 490 (cs.otp << r:bool(0,1)) 453 491 (cs.challenge << r:bool(0,2)) 454 492 (cs.trust << r:bool(0,3)) 455 - return cs 493 + return cs, true 456 494 end]; 457 495 458 496 actor_auth_pw = [terra( 459 497 src: &lib.store.source, 460 498 ip: lib.store.inet, 461 - username: rawstring, 462 - cred: rawstring 463 - ) 464 - var q = [[select a.aid from parsav_auth as a 465 - left join parsav_actors as u on u.id = a.uid 466 - where (a.uid is null or u.handle = $1::text or ( 467 - a.uid = 0 and a.name = $1::text 468 - )) and 469 - (a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and 470 - (a.netmask is null or a.netmask >> $4::inet) 471 - order by blacklist desc limit 1]] 499 + username: lib.mem.ptr(int8), 500 + cred: lib.mem.ptr(int8) 501 + ): uint64 472 502 473 - [ checksha(`src.handle, q, 256, ip, username, cred) ] -- most common 474 - [ checksha(`src.handle, q, 512, ip, username, cred) ] -- most secure 475 - [ checksha(`src.handle, q, 384, ip, username, cred) ] -- weird 476 - [ checksha(`src.handle, q, 224, ip, username, cred) ] -- weirdest 503 + [ checksha(`src, 256, ip, username, cred) ] -- most common 504 + [ checksha(`src, 512, ip, username, cred) ] -- most secure 505 + [ checksha(`src, 384, ip, username, cred) ] -- weird 506 + [ checksha(`src, 224, ip, username, cred) ] -- weirdest 477 507 478 508 -- TODO: check pbkdf2-hmac 479 509 -- TODO: check OTP 480 510 return 0 481 511 end]; 512 + 513 + actor_stats = [terra(src: &lib.store.source, uid: uint64) 514 + var r = queries.actor_stats.exec(src, uid) 515 + if r.sz == 0 then lib.bail('error fetching actor stats!') end 516 + var s: lib.store.actor_stats 517 + s.posts = r:int(uint64, 0, 0) 518 + s.follows = r:int(uint64, 0, 1) 519 + s.followers = r:int(uint64, 0, 2) 520 + s.mutuals = r:int(uint64, 0, 3) 521 + return s 522 + end]; 482 523 483 524 actor_session_fetch = [terra( 484 525 src: &lib.store.source, 485 526 aid: uint64, 486 527 ip : lib.store.inet 487 528 ): { lib.stat(lib.store.auth), lib.mem.ptr(lib.store.actor) } 488 529 var r = queries.actor_session_fetch.exec(src, aid, ip) ................................................................................ 493 534 494 535 var a = row_to_actor(&r, 0) 495 536 a.ptr.source = src 496 537 497 538 var au = [lib.stat(lib.store.auth)] { ok = true } 498 539 au.val.aid = aid 499 540 au.val.uid = a.ptr.id 500 - if not r:null(0,10) then -- restricted? 541 + if not r:null(0,12) then -- restricted? 501 542 au.val.privs:clear() 502 - (au.val.privs.post << r:bool(0,11)) 503 - (au.val.privs.edit << r:bool(0,12)) 504 - (au.val.privs.acct << r:bool(0,13)) 505 - (au.val.privs.upload << r:bool(0,14)) 506 - (au.val.privs.censor << r:bool(0,15)) 507 - (au.val.privs.admin << r:bool(0,16)) 543 + (au.val.privs.post << r:bool(0,13)) 544 + (au.val.privs.edit << r:bool(0,14)) 545 + (au.val.privs.acct << r:bool(0,15)) 546 + (au.val.privs.upload << r:bool(0,16)) 547 + (au.val.privs.censor << r:bool(0,17)) 548 + (au.val.privs.admin << r:bool(0,18)) 508 549 else au.val.privs:fill() end 509 550 510 551 return au, a 511 552 end 512 553 513 554 ::fail:: return [lib.stat (lib.store.auth) ] { ok = false }, 514 555 [lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 } 515 556 end]; 516 557 } 517 558 518 559 return b
Modified config.lua from [7cb566b503] to [dc89401662].
27 27 dist = default('parsav_dist', coalesce( 28 28 os.getenv('NIX_PATH') and 'nixos', 29 29 os.getenv('NIX_STORE') and 'nixos', 30 30 '')); 31 31 tgttrip = default('parsav_arch_triple'); -- target triple, used in xcomp 32 32 tgtcpu = default('parsav_arch_cpu'); -- target cpu, used in xcomp 33 33 tgthf = u.tobool(default('parsav_arch_armhf',true)); 34 + outform = default('parsav_emit_type', 'o'); 34 35 endian = default('parsav_arch_endian', 'little'); 35 36 build = { 36 37 id = u.rndstr(6); 37 38 release = u.ingest('release'); 38 39 when = os.date(); 39 40 }; 40 41 feat = {}; 41 42 backends = defaultlist('parsav_backends', 'pgsql'); 42 43 braingeniousmode = false; 43 44 embeds = { 44 45 {'style.css', 'text/css'}; 46 + {'default-avatar.webp', 'image/webp'}; 47 + {'padlock.webp', 'image/webp'}; 48 + {'warn.webp', 'image/webp'}; 45 49 }; 46 50 } 47 51 if u.ping '.fslckout' or u.ping '_FOSSIL_' then 48 52 if u.ping '_FOSSIL_' then default_os = 'windows' end 49 53 conf.build.branch = u.exec { 'fossil', 'branch', 'current' } 50 54 conf.build.checkout = (u.exec { 'fossil', 'sql', 51 55 [[select value from localdb.vvar where name = 'checkout-hash']]
Modified http.t from [e5e590a634] to [654249752e].
1 1 -- vim: ft=terra 2 2 local m = {} 3 3 local util = dofile('common.lua') 4 4 5 5 m.method = lib.enum { 'get', 'post', 'head', 'options', 'put', 'delete' } 6 +m.mime = lib.enum { 7 + 'html'; -- default 8 + 'json'; 9 + 'mkdown'; 10 + 'text'; 11 + 'ansi'; 12 + 'none'; 13 +} 6 14 7 15 m.findheader = terralib.externfunction('mg_http_get_header', {&lib.net.mg_http_message, rawstring} -> &lib.mem.ref(int8)) -- unfortunately necessary to access this function, as its return type conflicts with a function name 8 16 9 17 struct m.header { 10 18 key: rawstring 11 19 value: rawstring 12 20 }
Modified makefile from [2d2acfe121] to [3210eb684d].
1 1 dl = git 2 2 dbg-flags = $(if $(dbg),-g) 3 3 4 -parsav: parsav.t config.lua pkgdata.lua 4 +images = $(addsuffix .webp, $(basename $(wildcard static/*.svg))) 5 +styles = $(addsuffix .css, $(basename $(wildcard static/*.scss))) 6 + 7 +parsav: parsav.t config.lua pkgdata.lua $(images) $(styles) 5 8 terra $(dbg-flags) $< 6 -parsav.o: parsav.t config.lua pkgdata.lua 9 +parsav.o: parsav.t config.lua pkgdata.lua $(images) $(styles) 7 10 env parsav_link=no terra $(dbg-flags) $< 11 +parsav.ll: parsav.t config.lua pkgdata.lua $(images) $(styles) 12 + env parsav_emit_type=ll parsav_link=no terra $(dbg-flags) $< 13 +parsav.s: parsav.ll 14 + llc --march=$(target) $< 15 + 16 +static/%.webp: static/%.png 17 + cwebp -q 90 $< -o $@ 18 +static/%.png: static/%.svg 19 + inkscape -f $< -C -d 180 -e $@ 20 +static/%.css: static/%.scss 21 + sassc -t compressed $< $@ 8 22 9 23 clean: 10 24 rm parsav parsav.o 11 25 12 26 install: parsav 13 27 mkdir $(prefix)/bin 14 28 cp $< $(prefix)/bin/ ................................................................................ 39 53 cd lib/json-c && cmake . 40 54 lib/json-c/libjson-c.a: lib/json-c/Makefile 41 55 $(MAKE) -C lib/json-c 42 56 lib/mbedtls/library/%.a: lib/mbedtls 43 57 $(MAKE) -C lib/mbedtls/library $*.a 44 58 45 59 ifeq ($(dl), git) 60 +clone = git clone --depth 1 # save time 46 61 lib/mongoose: lib 47 - cd lib && git clone https://github.com/cesanta/mongoose.git 62 + cd lib && $(clone) https://github.com/cesanta/mongoose.git 48 63 lib/mbedtls: lib 49 - cd lib && git clone https://github.com/ARMmbed/mbedtls.git 64 + cd lib && $(clone) https://github.com/ARMmbed/mbedtls.git 50 65 lib/json-c: lib 51 - cd lib && git clone https://github.com/json-c/json-c.git 66 + cd lib && $(clone) https://github.com/json-c/json-c.git 52 67 else 53 68 lib/%: lib/%.tar.gz 54 69 cd lib && tar zxf $*.tar.gz 55 70 mv lib/$$(tar tf $< | head -n1) $@ 56 71 57 72 ifeq ($(dl), wget) 58 73 dlfile = wget "$(1)" -O "$(2)"
Modified math.t from [f661c3d77e] to [573a13128c].
145 145 buf = buf + 1 146 146 end 147 147 end 148 148 149 149 terra m.b32str(a: lib.mem.ptr(uint64)) 150 150 151 151 end 152 + 153 +terra m.decstr(val: intptr, buf: &int8): rawstring 154 +-- works backwards to avoid copies. log10(2^64) ≈ 19.2 and we 155 +-- need a byte for NUL so buf MUST point to THE END OF a buffer 156 +-- at least 21 bytes long 157 + @buf = 0 158 + if val > 0 then while val > 0 do 159 + buf = buf - 1 160 + var dgt = val % 10 161 + val = val / 10 162 + @buf = 0x30 + dgt 163 + end else 164 + buf = buf - 1 165 + @buf = 0x30 166 + end 167 + return buf 168 +end 169 + 170 +terra m.decstr_friendly(val: intptr, buf: &int8): rawstring 171 +-- as above except needs size-28 buffers, on account of all the commas 172 + @buf = 0 173 + var dgtct: uint8 = 0 174 + if val > 0 then while val > 0 do 175 + buf = buf - 1 176 + var dgt = val % 10 177 + val = val / 10 178 + @buf = 0x30 + dgt 179 + if dgtct == 2 and val > 0 then 180 + buf = buf - 1 @buf = @',' 181 + dgtct = 0 182 + else dgtct = dgtct + 1 end 183 + end else 184 + buf = buf - 1 185 + @buf = 0x30 186 + end 187 + return buf 188 +end 152 189 153 190 return m
Modified parsav.md from [93a3706cc3] to [eb5d145ae6].
11 11 12 12 * mongoose 13 13 * json-c 14 14 * mbedtls 15 15 * **postgresql backend:** 16 16 * postgresql-libs 17 17 18 +additional build-time 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: 19 + 20 +* inkscape, for rendering out UI graphics 21 +* cwebp (libwebp package), for transforming inkscape PNGs to webp 22 +* sassc, for compiling the SCSS stylesheet into its final CSS 23 + 24 +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. 25 + 26 +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 (ostensible 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 people will fix this eventually. 27 + 28 +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. 29 + 18 30 ## building 19 31 20 -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 librari in the `lib/` folder, it will use that instead of any system library. 32 +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 33 22 34 postgresql-libs must be installed systemwide, as `parsav` does not currently provide for statically compiling and linking it 23 35 24 36 ## configuring 25 37 26 38 the `parsav` configuration is comprised of two components: the backends list and the config store. the backends list is a simple text file that tells `parsav` which data sources to draw from. the config store is a key-value store which contains the rest of the server's configuration, and is loaded from the backends. the configuration store can be spread across the backends; backends will be checked for configuration keys according to the order in which they are listed. changes to the configuration store affect parsav in real time; you only need to restart the server if you make a change to the backend list. 27 39
Modified parsav.t from [41c6f93980] to [11ad9b3025].
197 197 end) 198 198 lib.enum = function(tbl) 199 199 local ty = uint8 200 200 if #tbl >= 2^32 then ty = uint64 -- hey, can't be too safe 201 201 elseif #tbl >= 2^16 then ty = uint32 202 202 elseif #tbl >= 2^8 then ty = uint16 end 203 203 local o = { t = ty } 204 + local strings = {} 204 205 for i, name in ipairs(tbl) do 205 - o[name] = i 206 + o[name] = i - 1 207 + strings[i] = `[lib.mem.ref(int8)]{ptr=[name], ct=[#name]} 208 + end 209 + o._str = terra(val: ty) 210 + var l = array([strings]) 211 + return l[val] 206 212 end 207 213 return o 208 214 end 209 215 lib.set = function(tbl) 210 216 local bytes = math.ceil(#tbl / 8) 211 217 local o = {} 212 218 for i, name in ipairs(tbl) do o[name] = i end ................................................................................ 214 220 local struct bit { _v: intptr _set: &set} 215 221 terra set:clear() for i=0,bytes do self._store[i] = 0 end end 216 222 terra set:fill() for i=0,bytes do self._store[i] = 0xFF end end 217 223 set.members = tbl 218 224 set.name = string.format('set<%s>', table.concat(tbl, '|')) 219 225 set.metamethods.__entrymissing = macro(function(val, obj) 220 226 if o[val] == nil then error('value ' .. val .. ' not in set') end 221 - return `bit { _v=[o[val] - 1], _set = &obj } 227 + return `bit { _v=[o[val] - 1], _set = &(obj) } 222 228 end) 229 + terra set:sz() 230 + var ct: intptr = 0 231 + for i = 0, [#tbl] do 232 + if (self._store[i/8] and (1 << i % 8)) ~= 0 then ct = ct + 1 end 233 + end 234 + return ct 235 + end 223 236 set.methods.dump = macro(function(self) 224 237 local q = quote lib.io.say('dumping set:\n') end 225 238 for i,v in ipairs(tbl) do 226 239 q = quote 227 240 [q] 228 241 if [bool](self.[v]) 229 242 then lib.io.say([' - ' .. v .. ': true\n']) ................................................................................ 306 319 for k,v in pairs(data.view) do 307 320 local t = lib.tpl.mk { body = v, id = 'view/'..k } 308 321 data.view[k] = t 309 322 end 310 323 311 324 lib.load { 312 325 'srv'; 326 + 'render:nav'; 327 + 'render:login'; 313 328 'render:profile'; 314 329 'render:userpage'; 330 + 'render:compose'; 315 331 'route'; 316 332 } 317 333 318 334 do 319 335 local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when) 320 336 terra version() lib.io.send(1, p, [#p]) end 321 337 end ................................................................................ 427 443 428 444 if bflag('dump-config','C') then 429 445 print(util.dump(config)) 430 446 os.exit(0) 431 447 end 432 448 433 449 local holler = print 434 -local out = config.exe and 'parsav' or 'parsav.o' 435 -local linkargs = {} 450 +local out = config.exe and 'parsav' or ('parsav.' .. config.outform) 451 +local linkargs = {'-O4'} 436 452 437 453 if bflag('quiet','q') then holler = function() end end 438 454 if bflag('asan','s') then linkargs[#linkargs+1] = '-fsanitize=address' end 439 455 if bflag('lsan','S') then linkargs[#linkargs+1] = '-fsanitize=leak' end 440 456 441 457 if config.posix then 442 458 linkargs[#linkargs+1] = '-pthread'
Added render/compose.t version [7a7e8f43ac].
1 +-- vim: ft=terra 2 +local terra 3 +render_compose(co: &lib.srv.convo, edit: &lib.store.post) 4 + var target, tgtlen = co:getv('to') 5 + var form: data.view.compose 6 + if edit == nil then 7 + form = data.view.compose { 8 + content = lib.coalesce(target, ''); 9 + acl = lib.trn(target == nil, 'all', 'mentioned'); -- TODO default acl setting? 10 + handle = co.who.handle; 11 + } 12 + end 13 + var cotxt = form:tostr() defer cotxt:free() 14 + 15 + var doc = data.view.docskel { 16 + instance = co.srv.cfg.instance.ptr; 17 + title = 'compose'; 18 + body = cotxt.ptr; 19 + class = 'compose'; 20 + navlinks = co.navbar.ptr; 21 + } 22 + 23 + var hdrs = array( 24 + lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' } 25 + ) 26 + doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]}) 27 +end 28 + 29 +return render_compose
Added render/login.t version [0d69ec17a3].
1 +-- vim: ft=terra 2 +local terra 3 +login_form(co: &lib.srv.convo, user: &lib.store.actor, creds: &lib.store.credset, msg: &int8) 4 + var doc = data.view.docskel { 5 + instance = co.srv.cfg.instance.ptr; 6 + title = 'instance logon'; 7 + class = 'login'; 8 + navlinks = co.navbar.ptr; 9 + } 10 + 11 + if user == nil then 12 + var form = data.view.login_username { 13 + loginmsg = msg; 14 + } 15 + if form.loginmsg == nil then 16 + form.loginmsg = 'identify yourself for access to this instance.' 17 + end 18 + var formtxt = form:tostr() 19 + doc.body = formtxt.ptr 20 + elseif creds:sz() == 0 then 21 + co:complain(403,'access denied','your host is not eligible to authenticate as this user') 22 + return 23 + elseif creds:sz() == 1 then 24 + if creds.trust() then 25 + -- TODO log in immediately 26 + return 27 + end 28 + 29 + var ch = data.view.login_challenge { 30 + handle = user.handle; 31 + name = lib.coalesce(user.nym, user.handle); 32 + } 33 + if creds.pw() then 34 + ch.challenge = 'enter the password associated with your account' 35 + ch.label = 'password' 36 + ch.method = 'pw' 37 + elseif creds.otp() then 38 + ch.challenge = 'enter a valid one-time password for your account' 39 + ch.label = 'OTP code' 40 + ch.method = 'otp' 41 + elseif creds.challenge() then 42 + ch.challenge = 'sign the challenge token: <code>...</code>' 43 + ch.label = 'digest' 44 + ch.method = 'challenge' 45 + else 46 + co:complain(500,'login failure','unknown login method') 47 + return 48 + end 49 + 50 + doc.body = ch:tostr().ptr 51 + else 52 + -- pick a method 53 + end 54 + 55 + var hdrs = array( 56 + lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' } 57 + ) 58 + doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]}) 59 + lib.mem.heapf(doc.body) 60 +end 61 + 62 +return login_form
Added render/nav.t version [2d4aa38bec].
1 +-- vim: ft=terra 2 +local terra 3 +render_nav(co: &lib.srv.convo) 4 + var t: lib.str.acc t:init(64) 5 + if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then 6 + t:lpush('<a href="/">timeline</a>') 7 + end 8 + if co.who ~= nil then 9 + t:lpush('<a href="/compose">compose</a> <a href="/'):push(co.who.xid,0) 10 + t:lpush('">profile</a> <a href="/conf">configure</a> <a href="/logout">log out</a>') 11 + else 12 + t:lpush('<a href="/login">log in</a>') 13 + end 14 + return t:finalize() 15 +end 16 +return render_nav
Modified render/profile.t from [a405db9158] to [ecfc7ba460].
1 1 -- vim: ft=terra 2 2 local terra 3 -render_profile(actor: &lib.store.actor) 3 +render_profile(co: &lib.srv.convo, actor: &lib.store.actor) 4 + var aux: lib.str.acc 5 + var auxp: rawstring 6 + if co.aid ~= 0 and co.who.id == actor.id then 7 + auxp = '<a href="/conf/profile">alter</a>' 8 + elseif co.aid ~= 0 then 9 + aux:compose('<a href="/', actor.xid, '/follow">follow</a><a href="/', 10 + actor.xid, '/chat">chat</a>') 11 + if co.who.rights.powers:affect_users() then 12 + aux:push('<a href="/',11):push(actor.xid,0):push('/ctl">control</a>',17) 13 + end 14 + auxp = aux.buf 15 + else 16 + aux:compose('<a href="/', actor.xid, '/follow">remote follow</a>') 17 + end 18 + var avistr: lib.str.acc if actor.origin == 0 then 19 + avistr:compose('/avi/',actor.handle) 20 + end 21 + var timestr: int8[26] lib.osclock.ctime_r(&actor.knownsince, ×tr[0]) 22 + 23 + var strfbuf: int8[28*4] 24 + var stats = co.srv:actor_stats(actor.id) 25 + var sn_posts = lib.math.decstr_friendly(stats.posts, &strfbuf[ [strfbuf.type.N - 1] ]) 26 + var sn_follows = lib.math.decstr_friendly(stats.follows, sn_posts - 1) 27 + var sn_followers = lib.math.decstr_friendly(stats.followers, sn_follows - 1) 28 + var sn_mutuals = lib.math.decstr_friendly(stats.mutuals, sn_followers - 1) 29 + 4 30 var profile = data.view.profile { 5 31 nym = lib.coalesce(actor.nym, actor.handle); 6 - bio = lib.coalesce(actor.bio, "tall, dark, and mysterious"); 32 + bio = lib.coalesce(actor.bio, "<em>tall, dark, and mysterious</em>"); 7 33 xid = actor.xid; 8 - avatar = "/no-avatars-yet.png"; 34 + avatar = lib.trn(actor.origin == 0, avistr.buf, 35 + lib.coalesce(actor.avatar, '/s/default-avatar.webp')); 36 + 37 + nposts = sn_posts, nfollows = sn_follows; 38 + nfollowers = sn_followers, nmutuals = sn_mutuals; 39 + tweetday = timestr; 40 + timephrase = lib.trn(actor.origin == 0, 'joined', 'known since'); 9 41 10 - nposts = '0', nfollows = '0'; 11 - nfollowers = '0', nmutuals = '0'; 12 - tweetday = 'novembuary 67th'; 42 + auxbtn = auxp; 13 43 } 14 44 15 - return profile:tostr() 45 + var ret = profile:tostr() 46 + if actor.origin == 0 then avistr:free() end 47 + if not (co.aid ~= 0 and co.who.id == actor.id) then aux:free() end 48 + return ret 16 49 end 17 50 18 51 return render_profile
Modified render/userpage.t from [052285d84c] to [cdf1e65d22].
3 3 render_userpage(co: &lib.srv.convo, actor: &lib.store.actor) 4 4 var ti: lib.str.acc defer ti:free() 5 5 if co.aid ~= 0 and co.who.id == actor.id then 6 6 ti:compose('my profile') 7 7 else 8 8 ti:compose('profile :: ', actor.handle) 9 9 end 10 - var pftxt = lib.render.profile(actor) defer pftxt:free() 10 + var pftxt = lib.render.profile(co,actor) defer pftxt:free() 11 11 12 12 var doc = data.view.docskel { 13 13 instance = co.srv.cfg.instance.ptr; 14 14 title = ti.buf; 15 15 body = pftxt.ptr; 16 16 class = 'profile'; 17 + navlinks = co.navbar.ptr; 17 18 } 18 19 19 20 var hdrs = array( 20 21 lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' } 21 22 ) 22 23 doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]}) 23 24 end 24 25 25 26 return render_userpage
Modified route.t from [70d0da6b8e] to [d7d680b0a3].
53 53 co:complain(404, 'no such user', 'no user by that ID is known to this instance') 54 54 return 55 55 end 56 56 defer actor:free() 57 57 58 58 lib.render.userpage(co, actor.ptr) 59 59 end 60 + 61 +terra http.login_form(co: &lib.srv.convo, meth: method.t) 62 + if meth == method.get then 63 + -- request a username 64 + lib.render.login(co, nil, nil, nil) 65 + elseif meth == method.post then 66 + var usn, usnl = co:postv('user') 67 + lib.dbg('got name ',{usn,usnl}) 68 + lib.io.fmt('name len %llu\n',usnl) 69 + var am, aml = co:postv('authmethod') 70 + var chrs, chrsl = co:postv('response') 71 + var cs, authok = co.srv:actor_auth_how(co.peer, usn) 72 + var act = co.srv:actor_fetch_xid([lib.mem.ptr(int8)] { 73 + ptr = usn, ct = usnl 74 + }) 75 + if authok == false then 76 + lib.render.login(co, nil, nil, 'access denied') 77 + return 78 + end 79 + var fakeact = false 80 + var fakeactor: lib.store.actor 81 + if act.ptr == nil then 82 + -- the user is known to us but has not yet claimed an 83 + -- account on the server. create a template for the 84 + -- account that will be created once they log in 85 + fakeact = true 86 + fakeactor = lib.store.actor { 87 + id = 0, handle = usn, nym = usn; 88 + origin = 0, bio = nil; 89 + key = [lib.mem.ptr(uint8)] {ptr=nil, ct=0} 90 + } 91 + act.ct = 1 92 + act.ptr = &fakeactor 93 + act.ptr.rights = lib.store.rights_default() 94 + end 95 + if am == nil then 96 + -- pick an auth method 97 + lib.render.login(co, act.ptr, &cs, nil) 98 + else var aid: uint64 = 0 99 + lib.dbg('authentication attempt beginning') 100 + -- attempt login with provided method 101 + if lib.str.ncmp('pw', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then 102 + aid = co.srv:actor_auth_pw(co.peer, 103 + [lib.mem.ptr(int8)]{ptr=usn,ct=usnl}, 104 + [lib.mem.ptr(int8)]{ptr=chrs,ct=chrsl}) 105 + elseif lib.str.ncmp('otp', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then 106 + lib.dbg('using otp auth') 107 + -- ··· -- 108 + else 109 + lib.dbg('invalid auth method') 110 + end 111 + 112 + lib.io.fmt('login got aid = %llu\n', aid) 113 + -- error out 114 + if aid == 0 then 115 + lib.render.login(co, nil, nil, 'authentication failure') 116 + else 117 + var sesskey: int8[lib.session.maxlen + #lib.session.cookiename + #"=; Path=/" + 1] 118 + do var p = &sesskey[0] 119 + p = lib.str.ncpy(p, [lib.session.cookiename .. '='], [#lib.session.cookiename + 1]) 120 + p = p + lib.session.cookie_gen(co.srv.cfg.secret, aid, lib.osclock.time(nil), p) 121 + lib.dbg('sending cookie',&sesskey[0]) 122 + p = lib.str.ncpy(p, '; Path=/', 9) 123 + end 124 + co:reroute_cookie('/', &sesskey[0]) 125 + end 126 + end 127 + if act.ptr ~= nil and fakeact == false then act:free() end 128 + else 129 + ::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end 130 + end 131 + return 132 +end 133 + 134 +terra http.post_compose(co: &lib.srv.convo, meth: method.t) 135 + if meth == method.get then 136 + lib.render.compose(co, nil) 137 + elseif meth == method.post then 138 + if co.who.rights.powers.post() == false then 139 + co:complain(401,'insufficient privileges','you lack the <strong>post</strong> power and cannot perform this action') return 140 + end 141 + 142 + end 143 +end 60 144 61 145 do local branches = quote end 62 146 local filename, flen = symbol(&int8), symbol(intptr) 63 147 local page = symbol(lib.http.page) 64 148 local send = label() 65 149 local storage = data.stmap 66 150 for i,e in ipairs(config.embeds) do local id,mime = e[1],e[2] ................................................................................ 86 170 } 87 171 [branches] 88 172 do return false end 89 173 ::[send]:: page:send(co.con) return true 90 174 end 91 175 end 92 176 93 -http.static_content:printpretty() 177 + 178 +terra http.local_avatar(co: &lib.srv.convo, handle: lib.mem.ptr(int8)) 179 + -- TODO retrieve user avatars 180 + co:reroute('/s/default-avatar.webp') 181 +end 94 182 95 183 -- entry points 96 184 terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t) 185 + lib.dbg('handling URI of form ', {uri.ptr,uri.ct}) 186 + co.navbar = lib.render.nav(co) 187 + -- some routes are non-hierarchical, and can be resolved with a simple strcmp 188 + -- we run through those first before giving up and parsing the URI 97 189 if uri.ptr[0] ~= @'/' then 98 190 co:complain(404, 'what the hell', 'how did you do that') 191 + return 192 + elseif uri.ct == 1 then -- root 193 + lib.io.fmt('root directory, aid is %llu\n', co.aid) 194 + if (co.srv.cfg.pol_sec == lib.srv.secmode.private or 195 + co.srv.cfg.pol_sec == lib.srv.secmode.lockdown) and co.aid == 0 then 196 + http.login_form(co, meth) 197 + else 198 + -- FIXME display home screen 199 + goto notfound 200 + end 201 + return 99 202 elseif uri.ptr[1] == @'@' then 100 203 http.actor_profile_xid(co, uri, meth) 204 + return 101 205 elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then 102 206 if meth ~= method.get then goto wrongmeth end 103 207 if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end 104 - else 208 + return 209 + elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then 210 + http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5}) 211 + return 212 + elseif lib.str.ncmp('/compose', uri.ptr, lib.math.biggest(uri.ct,8)) == 0 then 213 + if co.aid == 0 then co:reroute('/login') return end 214 + http.post_compose(co,meth) 215 + return 216 + elseif lib.str.ncmp('/login', uri.ptr, lib.math.biggest(uri.ct,6)) == 0 then 217 + if co.aid == 0 218 + then http.login_form(co, meth) 219 + else co:reroute('/') 220 + end 221 + return 222 + elseif lib.str.ncmp('/logout', uri.ptr, lib.math.biggest(uri.ct,7)) == 0 then 223 + if co.aid == 0 224 + then goto notfound 225 + else co:reroute_cookie('/','auth=; Path=/') 226 + end 227 + return 228 + else -- hierarchical routes 105 229 var path = lib.http.hier(uri) defer path:free() 106 230 if path.ptr[0]:cmp(lib.str.lit('user')) then 107 231 http.actor_profile_uid(co, path, meth) 108 232 else goto notfound end 233 + return 109 234 end 110 235 111 - ::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this path') do return end 236 + ::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end 112 237 ::notfound:: co:complain(404, 'not found', 'no such resource available') do return end 113 238 end
Modified schema.sql from [636689e0dd] to [a3359b8b76].
1 1 \prompt 'domain name: ' domain 2 2 \prompt 'instance name: ' inst 3 3 \prompt 'bind to socket: ' bind 4 -\qecho 'by default, parsav tracks rights on its own. you can override this later by replacing the rights table with a view, but you''ll then need to set appropriate rules on the view to allow administrators to modify rights from the web UI, or set the rights-readonly flag in the config table to true. for now, enter the name of an actor who will be granted full rights when she logs in.' 5 -\prompt 'admin actor: ' admin 4 +\qecho 'how locked down should this server be? public = anyone can see public timeline and tweets, private = anyone can see tweets with a link but login required for everything else, lockdown = login required for all activities, isolate = like lockdown but with federation protocols completely disabled' 5 +\prompt 'security mode: ' secmode 6 +\qecho 'should user self-registration be allowed? yes or no' 7 +\prompt 'registration: ' regpol 8 +\qecho 'by default, parsav tracks rights on its own. you can override this later by replacing the rights table with a view, but you''ll then need to set appropriate rules on the view to allow administrators to modify rights from the web UI, or set the rights-readonly flag in the config table to true. for now, enter the name of an actor who will be granted full rights when she logs in and identified as the server owner.' 9 +\prompt 'master actor: ' admin 6 10 \qecho 'you will need to create an authentication view named parsav_auth mapping your user database to something parsav can understand; see auth.sql for an example.' 7 11 8 12 begin; 9 13 10 14 drop table if exists parsav_config; 11 15 create table if not exists parsav_config ( 12 - key text primary key, 16 + key text primary key, 13 17 value text 14 18 ); 15 19 16 20 insert into parsav_config (key,value) values 17 21 ('bind',:'bind'), 18 22 ('domain',:'domain'), 19 23 ('instance-name',:'inst'), 20 - ('administrator',:'admin'), 24 + ('policy-security',:'secmode'), 25 + ('policy-self-register',:'regpol'), 26 + ('master',:'admin'), 21 27 ('server-secret', encode( 22 28 digest(int8send((2^63 * (random()*2 - 1))::bigint), 23 29 'sha512'), 'base64')); 24 30 25 31 -- note that valid ids should always > 0, as 0 is reserved for null 26 32 -- on the client side, vastly simplifying code 27 33 drop table if exists parsav_servers cascade; 28 34 create table parsav_servers ( 29 - id bigint primary key default (1+random()*(2^63-1))::bigint, 35 + id bigint primary key default (1+random()*(2^63-1))::bigint, 30 36 domain text not null, 31 - key bytea 37 + key bytea, 38 + parsav boolean -- whether to use parsav protocol extensions 32 39 ); 40 + 33 41 drop table if exists parsav_actors cascade; 34 42 create table parsav_actors ( 35 - id bigint primary key default (1+random()*(2^63-1))::bigint, 36 - nym text, 37 - handle text not null, -- nym [@handle@origin] 38 - origin bigint references parsav_servers(id) 43 + id bigint primary key default (1+random()*(2^63-1))::bigint, 44 + nym text, 45 + handle text not null, -- nym [@handle@origin] 46 + origin bigint references parsav_servers(id) 39 47 on delete cascade, -- null origin = local actor 40 - bio text, 41 - rank smallint not null default 0, 42 - quota integer not null default 1000, 43 - key bytea, -- private if localactor; public if remote 48 + bio text, 49 + avataruri text, -- null if local 50 + rank smallint not null default 0, 51 + quota integer not null default 1000, 52 + key bytea, -- private if localactor; public if remote 53 + title text 44 54 45 55 unique (handle,origin) 46 56 ); 47 57 48 58 drop table if exists parsav_rights cascade; 49 59 create table parsav_rights ( 50 60 key text, ................................................................................ 63 73 ('censor',true), 64 74 ('suspend',true), 65 75 ('rebrand',true) 66 76 ) as a; 67 77 68 78 drop table if exists parsav_posts cascade; 69 79 create table parsav_posts ( 70 - id bigint primary key default (1+random()*(2^63-1))::bigint, 71 - author bigint references parsav_actors(id) 80 + id bigint primary key default (1+random()*(2^63-1))::bigint, 81 + author bigint references parsav_actors(id) 72 82 on delete cascade, 73 - subject text, 74 - body text, 75 - posted timestamp not null, 83 + subject text, 84 + acl text not null default 'all', -- just store the script raw 🤷 85 + body text, 86 + posted timestamp not null, 76 87 discovered timestamp not null, 77 - scope smallint not null, 78 - convo bigint, parent bigint, 79 - circles bigint[], mentions bigint[] 88 + scope smallint not null, 89 + convo bigint, 90 + parent bigint, 91 + circles bigint[], 92 + mentions bigint[] 80 93 ); 81 94 82 95 drop table if exists parsav_conversations cascade; 83 96 create table parsav_conversations ( 84 - id bigint primary key default (1+random()*(2^63-1))::bigint, 85 - uri text not null, 97 + id bigint primary key default (1+random()*(2^63-1))::bigint, 98 + uri text not null, 86 99 discovered timestamp not null, 87 - head bigint references parsav_posts(id) 100 + head bigint references parsav_posts(id) 88 101 ); 89 102 90 103 drop table if exists parsav_rels cascade; 91 104 create table parsav_rels ( 92 105 relator bigint references parsav_actors(id) 93 106 on delete cascade, -- e.g. follower 94 107 relatee bigint references parsav_actors(id) 95 - on delete cascade, -- e.g. follower 96 - kind smallint, -- e.g. follow, block, mute 108 + on delete cascade, -- e.g. followed 109 + kind smallint, -- e.g. follow, block, mute 97 110 98 111 primary key (relator, relatee, kind) 99 112 ); 100 113 101 114 drop table if exists parsav_acts cascade; 102 115 create table parsav_acts ( 103 - id bigint primary key default (1+random()*(2^63-1))::bigint, 104 - kind text not null, -- like, react, so on 105 - time timestamp not null, 106 - actor bigint references parsav_actors(id) 116 + id bigint primary key default (1+random()*(2^63-1))::bigint, 117 + kind text not null, -- like, react, so on 118 + time timestamp not null default now(), 119 + actor bigint references parsav_actors(id) 107 120 on delete cascade, 108 121 subject bigint -- may be post or act, depending on kind 109 122 ); 110 123 111 124 drop table if exists parsav_log cascade; 112 125 create table parsav_log ( 113 126 -- accesses are tracked for security & sending delete acts 114 - id bigint primary key default (1+random()*(2^63-1))::bigint, 115 - time timestamp not null, 127 + id bigint primary key default (1+random()*(2^63-1))::bigint, 128 + time timestamp not null default now(), 116 129 actor bigint references parsav_actors(id) 117 130 on delete cascade, 118 - post bigint not null 131 + post bigint not null 132 +); 133 + 134 +drop table if exists parsav_attach cascade; 135 +create table parsav_attach ( 136 + id bigint primary key default (1+random()*(2^63-1))::bigint, 137 + birth timestamp not null default now(), 138 + content bytea not null, 139 + mime text, -- null if unknown, will be reported as x-octet-stream 140 + description text, 141 + parent bigint -- post id, or userid for avatars 142 +); 143 + 144 +drop table if exists parsav_circles cascade; 145 +create table parsav_circles ( 146 + id bigint primary key default (1+random()*(2^63-1))::bigint, 147 + owner bigint not null references parsav_actors(id), 148 + name text not null, 149 + members bigint[] not null default array[], 150 + 151 + unique (owner,name) 152 +); 153 + 154 +drop table if exists parsav_rooms cascade; 155 +create table parsav_rooms ( 156 + id bigint primary key default (1+random()*(2^63-1))::bigint, 157 + origin bigint references parsav_servers(id), 158 + name text not null, 159 + description text not null, 160 + policy smallint not null 161 +); 162 + 163 +drop table if exists parsav_room_members cascade; 164 +create table parsav_room_members ( 165 + room bigint references parsav_rooms(id), 166 + member bigint references parsav_actors(id), 167 + rank smallint not null default 0, 168 + admin boolean not null default false, -- non-admins with rank can only moderate + invite 169 + title text -- admin-granted title like reddit flair 170 +); 171 + 172 +drop table if exists parsav_invites cascade; 173 +create table parsav_invites ( 174 + id bigint primary key default (1+random()*(2^63-1))::bigint, 175 + -- when a user is created from an invite, the invite is deleted and the invite 176 + -- ID becomes the user ID. privileges granted on the invite ID during the invite 177 + -- process are thus inherited by the user 178 + handle text, -- admin can lock invite to specific handle 179 + rank smallint not null default 0, 180 + quota integer not null default 1000 181 +}; 182 + 183 +drop table if exists parsav_interventions cascade; 184 +create table parsav_interventions ( 185 + id bigint primary key default (1+random()*(2^63-1))::bigint, 186 + issuer bigint references parsav_actors(id) not null, 187 + scope bigint, -- can be null or room for local actions 188 + nature smallint not null, -- silence, suspend, disemvowel, etc 189 + victim bigint not null, -- could potentially target group as well 190 + expire timestamp -- auto-expires if set 119 191 ); 192 + 120 193 end;
Modified session.t from [58f0eab21d] to [e8a79576f0].
3 3 -- are tracked by storing an encrypted cookie which contains an authid, 4 4 -- a login epoch time, and a truncated hmac code authenticating both, all 5 5 -- encoded using Shorthand. we need functions to generate and parse these 6 6 7 7 local m = { 8 8 maxlen = lib.math.shorthand.maxlen*3 + 2; 9 9 maxage = 2 * 60 * 60; -- 2 hours 10 + cookiename = 'auth'; 10 11 } 11 12 12 13 terra m.cookie_gen(secret: lib.mem.ptr(int8), authid: uint64, time: uint64, out: &int8): intptr 13 14 var ptr = out 14 15 ptr = ptr + lib.math.shorthand.gen(authid, ptr) 15 16 @ptr = @'.' ptr = ptr + 1 16 17 ptr = ptr + lib.math.shorthand.gen(time, ptr)
Modified srv.t from [ed3d5ec62e] to [afa0417e30].
1 1 -- vim: ft=terra 2 2 local util = dofile 'common.lua' 3 - 3 +local secmode = lib.enum { 'public', 'private', 'lockdown', 'isolate' } 4 4 local struct srv 5 5 local struct cfgcache { 6 6 secret: lib.mem.ptr(int8) 7 7 instance: lib.mem.ptr(int8) 8 8 overlord: &srv 9 + pol_sec: secmode.t 10 + pol_reg: bool 9 11 } 10 12 local struct srv { 11 13 sources: lib.mem.ptr(lib.store.source) 12 14 webmgr: lib.net.mg_mgr 13 15 webcon: &lib.net.mg_connection 14 16 cfg: cfgcache 15 17 } ................................................................................ 68 70 69 71 local struct convo { 70 72 srv: &srv 71 73 con: &lib.net.mg_connection 72 74 msg: &lib.net.mg_http_message 73 75 aid: uint64 -- 0 if logged out 74 76 who: &lib.store.actor -- who we're logged in as, if aid ~= 0 77 + peer: lib.store.inet 78 + reqtype: lib.http.mime.t -- negotiated content type 79 +-- cache 80 + navbar: lib.mem.ptr(int8) 81 +-- private 82 + varbuf: lib.mem.ptr(int8) 83 + vbofs: &int8 75 84 } 76 85 77 86 -- this is unfortunately necessary to work around a terra bug 78 87 -- it can't seem to handle forward-declarations of structs in C 79 88 80 89 local getpeer 81 90 do local struct strucheader { ................................................................................ 84 93 peer: lib.net.mg_addr 85 94 } 86 95 terra getpeer(con: &lib.net.mg_connection) 87 96 return [&strucheader](con).peer 88 97 end 89 98 end 90 99 100 +terra convo:reroute_cookie(dest: rawstring, cookie: rawstring) 101 + var hdrs = array( 102 + lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }, 103 + lib.http.header { key = 'Location', value = dest }, 104 + lib.http.header { key = 'Set-Cookie', value = cookie } 105 + ) 106 + 107 + var body = data.view.docskel { 108 + instance = self.srv.cfg.instance.ptr; 109 + title = 'rerouting'; 110 + body = 'you are being redirected'; 111 + class = 'error'; 112 + navlinks = ''; 113 + } 114 + 115 + body:send(self.con, 303, [lib.mem.ptr(lib.http.header)] { 116 + ptr = &hdrs[0], ct = [hdrs.type.N] - lib.trn(cookie == nil,1,0) 117 + }) 118 +end 119 + 120 +terra convo:reroute(dest: rawstring) self:reroute_cookie(dest,nil) end 121 + 91 122 terra convo:complain(code: uint16, title: rawstring, msg: rawstring) 92 123 var hdrs = array(lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }) 93 124 94 125 var ti: lib.str.acc ti:compose('error :: ', title) defer ti:free() 126 + var bo: lib.str.acc bo:compose('<div class="message"><img class="icon" src="/s/warn.webp"><h1>error</h1><p>',msg,'</p></div>') defer bo:free() 95 127 var body = data.view.docskel { 96 128 instance = self.srv.cfg.instance.ptr; 97 129 title = ti.buf; 98 - body = msg; 130 + body = bo.buf; 99 131 class = 'error'; 132 + navlinks = lib.coalesce(self.navbar.ptr, ''); 100 133 } 101 134 102 135 if body.body == nil then 103 136 body.body = "i'm sorry, dave. i can't let you do that" 104 137 end 105 138 106 139 body:send(self.con, code, [lib.mem.ptr(lib.http.header)] { 107 140 ptr = &hdrs[0], ct = [hdrs.type.N] 108 141 }) 109 142 end 143 + 144 +-- CALL ONLY ONCE PER VAR 145 +terra convo:postv(name: rawstring) 146 + if self.varbuf.ptr == nil then 147 + self.varbuf = lib.mem.heapa(int8, self.msg.body.len + self.msg.query.len) 148 + self.vbofs = self.varbuf.ptr 149 + end 150 + var o = lib.net.mg_http_get_var(&self.msg.body, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr)) 151 + if o > 0 then 152 + var r = self.vbofs 153 + self.vbofs = self.vbofs + o 154 + return r, o 155 + else return nil, 0 end 156 +end 157 + 158 +terra convo:getv(name: rawstring) 159 + if self.varbuf.ptr == nil then 160 + self.varbuf = lib.mem.heapa(int8, self.msg.query.len + self.msg.body.len) 161 + self.vbofs = self.varbuf.ptr 162 + end 163 + var o = lib.net.mg_http_get_var(&self.msg.query, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr)) 164 + if o > 0 then 165 + var r = self.vbofs 166 + self.vbofs = self.vbofs + o 167 + return r, o 168 + else return nil, 0 end 169 +end 110 170 111 171 local urimatch = macro(function(uri, ptn) 112 172 return `lib.net.mg_globmatch(ptn, [#ptn], uri.ptr, uri.ct+1) 113 173 end) 114 174 115 175 local route = {} -- these are defined in route.t, as they need access to renderers 116 176 terra route.dispatch_http :: {&convo, lib.mem.ptr(int8), lib.http.method.t} -> {} 177 + 178 +local mimetypes = { 179 + {'html', 'text/html'}; 180 + {'json', 'application/json'}; 181 + {'mkdown', 'text/markdown'}; 182 + {'text', 'text/plain'}; 183 + {'ansi', 'text/x-ansi'}; 184 +} 185 + 186 +local mimevar = symbol(lib.mem.ref(int8)) 187 +local mimeneg = `lib.http.mime.none 188 + 189 +for i, t in ipairs(mimetypes) do 190 + local name, mime = t[1], t[2] 191 + mimeneg = quote 192 + var ret: lib.http.mime.t 193 + if lib.str.ncmp(mimevar.ptr, mime, lib.math.biggest(mimevar.ct, [#mime])) == 0 then 194 + ret = [lib.http.mime[name]] 195 + else ret = [mimeneg] end 196 + in ret end 197 +end 117 198 118 199 local handle = { 119 200 http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque) 120 201 var server = [&srv](ext) 121 202 var mgpeer = getpeer(con) 122 203 var peer = lib.store.inet { port = mgpeer.port; } 123 204 if mgpeer.is_ip6 then peer.pv = 6 else peer.pv = 4 end ................................................................................ 128 209 end 129 210 -- the peer property is currently broken and there is precious 130 211 -- little i can do about this -- it always reports a peer v4 IP 131 212 -- of 0.0.0.0, altho the port seems to come through correctly. 132 213 -- for now i'm leaving it as is, but note that netmask restrictions 133 214 -- WILL NOT WORK until upstream gets its shit together. FIXME 134 215 216 + -- needs to check for an X-Forwarded-For header from nginx and 217 + -- use that instead of the peer iff peer is ::1/127.1 FIXME 218 + -- maybe also haproxy support? 219 + 135 220 switch event do 136 221 case lib.net.MG_EV_HTTP_MSG then 137 222 lib.dbg('routing HTTP request') 138 223 var msg = [&lib.net.mg_http_message](p) 139 224 var co = convo { 140 225 con = con, srv = server, msg = msg; 141 - aid = 0, who = nil; 142 - } 226 + aid = 0, who = nil, peer = peer; 227 + reqtype = lib.http.mime.none; 228 + } co.varbuf.ptr = nil 229 + co.navbar.ptr = nil 230 + 231 + -- first, check for an accept header. if it's there, we need to 232 + -- iterate over the values and pick the highest-priority one 233 + do var acc = lib.http.findheader(msg, 'Accept') 234 + -- TODO handle q-value 235 + if acc.ptr ~= nil then 236 + var [mimevar] = [lib.mem.ref(int8)] { ptr = acc.ptr } 237 + var i = 0 while i < acc.ct do 238 + if acc.ptr[i] == @',' or acc.ptr[i] == @';' then 239 + mimevar.ct = (acc.ptr+i) - mimevar.ptr 240 + var t = [mimeneg] 241 + if t ~= lib.http.mime.none then 242 + co.reqtype = t 243 + goto foundtype 244 + end 245 + 246 + if acc.ptr[i] == @';' then -- fast-forward over q 247 + for j=i+1,acc.ct do i=j 248 + if acc.ptr[j] == @',' then break end 249 + end 250 + end 251 + 252 + while i < acc.ct and -- fast-forward over ws 253 + acc.ptr[i+1] == @' ' or 254 + acc.ptr[i+1] == @'\t' 255 + do i=i+1 end 256 + 257 + mimevar.ptr = acc.ptr + i + 1 258 + end 259 + i=i+1 260 + end 261 + if co.reqtype == lib.http.mime.none then 262 + mimevar.ct = acc.ct - (mimevar.ptr - acc.ptr) 263 + co.reqtype = [mimeneg] 264 + if co.reqtype == lib.http.mime.none then 265 + co.reqtype = lib.http.mime.html 266 + end 267 + end 268 + else co.reqtype = lib.http.mime.html end 269 + ::foundtype::end 143 270 144 271 -- we need to check if there's any cookies sent with the request, 145 272 -- and if so, whether they contain any credentials. this will be 146 273 -- used to set the auth parameters in the http conversation 147 274 var cookies_p = lib.http.findheader(msg, 'Cookie') 148 275 if cookies_p ~= nil then 149 276 var cookies = cookies_p.ptr ................................................................................ 158 285 key.ct = (cookies + i) - key.ptr 159 286 val.ptr = cookies + i + 1 160 287 end 161 288 i = i + 1 162 289 else 163 290 if cookies[i] == @';' then 164 291 val.ct = (cookies + i) - val.ptr 165 - if lib.str.ncmp(key.ptr, 'auth', key.ct) == 0 then 292 + if lib.str.ncmp(key.ptr, lib.session.cookiename, lib.math.biggest([#lib.session.cookiename],key.ct)) == 0 then 166 293 goto foundcookie 167 294 end 168 295 169 296 i = i + 1 170 297 i = lib.str.ffw(cookies + i, cookies_p.ct - i) - cookies 171 298 key.ptr = cookies + i 172 299 val.ptr = nil 173 300 else i = i + 1 end 174 301 end 175 302 end 176 303 if val.ptr == nil then goto nocookie end 177 304 val.ct = (cookies + i) - val.ptr 178 - if lib.str.ncmp(key.ptr, 'auth', key.ct) ~= 0 then 305 + if lib.str.ncmp(key.ptr, lib.session.cookiename, lib.math.biggest([#lib.session.cookiename], key.ct)) ~= 0 then 179 306 goto nocookie 180 307 end 181 308 ::foundcookie:: do 182 309 var aid = lib.session.cookie_interpret(server.cfg.secret, 183 310 [lib.mem.ptr(int8)]{ptr=val.ptr,ct=val.ct}, 184 311 lib.osclock.time(nil)) 185 312 if aid ~= 0 then co.aid = aid end ................................................................................ 204 331 end 205 332 uri.ct = msg.uri.len 206 333 else uri.ct = urideclen end 207 334 lib.dbg('routing URI ', {uri.ptr, uri.ct}) 208 335 209 336 if lib.str.ncmp('GET', msg.method.ptr, msg.method.len) == 0 then 210 337 route.dispatch_http(&co, uri, [lib.http.method.get]) 338 + elseif lib.str.ncmp('POST', msg.method.ptr, msg.method.len) == 0 then 339 + route.dispatch_http(&co, uri, [lib.http.method.post]) 340 + elseif lib.str.ncmp('HEAD', msg.method.ptr, msg.method.len) == 0 then 341 + route.dispatch_http(&co, uri, [lib.http.method.head]) 342 + elseif lib.str.ncmp('OPTIONS', msg.method.ptr, msg.method.len) == 0 then 343 + route.dispatch_http(&co, uri, [lib.http.method.options]) 211 344 else 212 345 co:complain(400,'unknown method','you have submitted an invalid http request') 213 346 end 214 347 215 348 if co.aid ~= 0 then lib.mem.heapf(co.who) end 349 + if co.varbuf.ptr ~= nil then co.varbuf:free() end 350 + if co.navbar.ptr ~= nil then co.navbar:free() end 216 351 end 217 352 end 218 353 end; 219 354 } 220 355 221 356 local terra cfg(s: &srv, befile: rawstring) 222 357 lib.report('configuring backends from ', befile) ................................................................................ 292 427 if c.sz > 0 then 293 428 s.sources = c:crush() 294 429 else 295 430 s.sources.ptr = nil 296 431 s.sources.ct = 0 297 432 end 298 433 end 434 + 435 +terra srv:actor_stats(uid: uint64) 436 + var stats = lib.store.actor_stats { 437 + posts = 0, mutuals = 0; 438 + follows = 0, followers = 0; 439 + } 440 + for i=0,self.sources.ct do 441 + var s = self.sources.ptr[i]:actor_stats(uid) 442 + stats.posts = stats.posts + s.posts 443 + stats.mutuals = stats.mutuals + s.mutuals 444 + stats.followers = stats.followers + s.followers 445 + stats.follows = stats.follows + s.follows 446 + end 447 + return stats 448 +end 299 449 300 450 terra srv:actor_auth_how(ip: lib.store.inet, usn: rawstring) 301 451 var cs: lib.store.credset cs:clear() 452 + var ok = false 302 453 for i=0,self.sources.ct do 303 - var set: lib.store.credset = self.sources.ptr[i]:actor_auth_how(ip, usn) 304 - cs = cs + set 454 + var set, iok = self.sources.ptr[i]:actor_auth_how(ip, usn) 455 + if iok then 456 + cs = cs + set 457 + ok = iok 458 + end 305 459 end 306 - return cs 460 + return cs, ok 307 461 end 308 462 309 463 terra cfgcache.methods.load :: {&cfgcache} -> {} 310 464 terra cfgcache:init(o: &srv) 311 465 self.overlord = o 312 466 self:load() 313 467 end ................................................................................ 336 490 bind = dbbind.ptr 337 491 else bind = '[::]:10917' end 338 492 339 493 lib.report('binding to ', bind) 340 494 lib.net.mg_mgr_init(&self.webmgr) 341 495 self.webcon = lib.net.mg_http_listen(&self.webmgr, bind, handle.http, self) 342 496 343 - var buf: int8[lib.session.maxlen] 344 - var len = lib.session.cookie_gen(self.cfg.secret, 9139084444658983115ULL, lib.osclock.time(nil), &buf[0]) 345 - buf[len] = 0 346 - 347 - var authid = lib.session.cookie_interpret(self.cfg.secret, [lib.mem.ptr(int8)] {ptr=buf, ct=len}, lib.osclock.time(nil)) 348 - lib.io.fmt('generated cookie %s -- got authid %llu\n', buf, authid) 349 - 350 497 if dbbind.ptr ~= nil then dbbind:free() end 351 498 end 352 499 353 500 srv.methods.poll = terra(self: &srv) 354 501 lib.net.mg_mgr_poll(&self.webmgr,1000) 355 502 end 356 503 ................................................................................ 362 509 end 363 510 self.sources:free() 364 511 end 365 512 366 513 terra cfgcache:load() 367 514 self.instance = self.overlord:conf_get('instance-name') 368 515 self.secret = self.overlord:conf_get('server-secret') 516 + 517 + self.pol_reg = false 518 + var sreg = self.overlord:conf_get('policy-self-register') 519 + if sreg.ptr ~= nil then 520 + if lib.str.cmp(sreg.ptr, 'on') == 0 521 + then self.pol_reg = true 522 + else self.pol_reg = false 523 + end 524 + end 525 + sreg:free() 526 + 527 + self.pol_sec = secmode.lockdown 528 + var smode = self.overlord:conf_get('policy-security') 529 + if smode.ptr ~= nil then 530 + if lib.str.cmp(smode.ptr, 'public') == 0 then 531 + self.pol_sec = secmode.public 532 + elseif lib.str.cmp(smode.ptr, 'private') == 0 then 533 + self.pol_sec = secmode.private 534 + elseif lib.str.cmp(smode.ptr, 'lockdown') == 0 then 535 + self.pol_sec = secmode.lockdown 536 + elseif lib.str.cmp(smode.ptr, 'isolate') == 0 then 537 + self.pol_sec = secmode.isolate 538 + end 539 + end 540 + smode:free() 369 541 end 370 542 371 543 return { 372 544 overlord = srv; 373 545 convo = convo; 374 546 route = route; 547 + secmode = secmode; 375 548 }
Added static/default-avatar.svg version [2764158102].
1 +<?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 +<!-- Created with Inkscape (http://www.inkscape.org/) --> 3 + 4 +<svg 5 + xmlns:dc="http://purl.org/dc/elements/1.1/" 6 + xmlns:cc="http://creativecommons.org/ns#" 7 + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 + xmlns:svg="http://www.w3.org/2000/svg" 9 + xmlns="http://www.w3.org/2000/svg" 10 + xmlns:xlink="http://www.w3.org/1999/xlink" 11 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 12 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 13 + width="25.4mm" 14 + height="25.4mm" 15 + viewBox="0 0 25.4 25.400001" 16 + version="1.1" 17 + id="svg8" 18 + sodipodi:docname="default-avatar.svg" 19 + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" 20 + inkscape:export-filename="/home/lexi/dev/parsav/static/default-avatar.png" 21 + inkscape:export-xdpi="128" 22 + inkscape:export-ydpi="128"> 23 + <defs 24 + id="defs2"> 25 + <linearGradient 26 + id="linearGradient5138" 27 + inkscape:collect="always"> 28 + <stop 29 + id="stop5134" 30 + offset="0" 31 + style="stop-color:#ffffff;stop-opacity:1" /> 32 + <stop 33 + id="stop5136" 34 + offset="1" 35 + style="stop-color:#000000;stop-opacity:0.98260868" /> 36 + </linearGradient> 37 + <linearGradient 38 + inkscape:collect="always" 39 + id="linearGradient5126"> 40 + <stop 41 + style="stop-color:#ff648d;stop-opacity:0.68235296" 42 + offset="0" 43 + id="stop5122" /> 44 + <stop 45 + style="stop-color:#ff628a;stop-opacity:0.52549022" 46 + offset="1" 47 + id="stop5124" /> 48 + </linearGradient> 49 + <linearGradient 50 + inkscape:collect="always" 51 + id="linearGradient5075"> 52 + <stop 53 + style="stop-color:#100004;stop-opacity:0" 54 + offset="0" 55 + id="stop5071" /> 56 + <stop 57 + style="stop-color:#100004;stop-opacity:0.59130436" 58 + offset="1" 59 + id="stop5073" /> 60 + </linearGradient> 61 + <radialGradient 62 + inkscape:collect="always" 63 + xlink:href="#linearGradient5075" 64 + id="radialGradient5077" 65 + cx="12.7" 66 + cy="284.29998" 67 + fx="12.7" 68 + fy="284.29998" 69 + r="12.7" 70 + gradientUnits="userSpaceOnUse" /> 71 + <radialGradient 72 + inkscape:collect="always" 73 + xlink:href="#linearGradient5126" 74 + id="radialGradient5128" 75 + cx="47.583008" 76 + cy="72.722656" 77 + fx="47.583008" 78 + fy="72.722656" 79 + r="29.078285" 80 + gradientTransform="matrix(1,0,0,0.74354777,0,18.649887)" 81 + gradientUnits="userSpaceOnUse" /> 82 + <linearGradient 83 + inkscape:collect="always" 84 + xlink:href="#linearGradient5138" 85 + id="linearGradient5132" 86 + x1="47.611866" 87 + y1="62.544083" 88 + x2="47.611866" 89 + y2="83.615517" 90 + gradientUnits="userSpaceOnUse" 91 + gradientTransform="matrix(0.26458333,0,0,0.26458333,0,271.59998)" /> 92 + <linearGradient 93 + inkscape:collect="always" 94 + xlink:href="#linearGradient5138" 95 + id="linearGradient5146" 96 + gradientUnits="userSpaceOnUse" 97 + x1="47.611866" 98 + y1="62.544083" 99 + x2="47.611866" 100 + y2="83.615517" /> 101 + <linearGradient 102 + inkscape:collect="always" 103 + xlink:href="#linearGradient5138" 104 + id="linearGradient5152" 105 + gradientUnits="userSpaceOnUse" 106 + x1="47.611866" 107 + y1="62.544083" 108 + x2="47.611866" 109 + y2="83.615517" /> 110 + <mask 111 + maskUnits="userSpaceOnUse" 112 + id="mask5148"> 113 + <path 114 + id="path5150" 115 + d="m 39.580078,61.101544 c -4.433597,0.63549 -9.840689,2.053607 -13.849609,5.18555 -8.081221,6.313436 -7.197266,18.056655 -7.197266,18.056655 h 58.099611 c 0,0 0.883952,-11.743219 -7.197268,-18.056655 -3.931143,-3.071206 -9.20211,-4.489247 -13.585935,-5.142576 -2.90603,2.777877 -5.844385,4.437505 -8.111331,4.437505 -2.278666,0 -5.237507,-1.676296 -8.158202,-4.480479 z" 116 + style="fill:url(#linearGradient5152);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 117 + inkscape:connector-curvature="0" /> 118 + </mask> 119 + <radialGradient 120 + inkscape:collect="always" 121 + xlink:href="#linearGradient5126" 122 + id="radialGradient843" 123 + gradientUnits="userSpaceOnUse" 124 + gradientTransform="matrix(1,0,0,0.74354777,0,18.649887)" 125 + cx="47.583008" 126 + cy="72.722656" 127 + fx="47.583008" 128 + fy="72.722656" 129 + r="29.078285" /> 130 + </defs> 131 + <sodipodi:namedview 132 + id="base" 133 + pagecolor="#1a1a1a" 134 + bordercolor="#666666" 135 + borderopacity="1.0" 136 + inkscape:pageopacity="0" 137 + inkscape:pageshadow="2" 138 + inkscape:zoom="5.6" 139 + inkscape:cx="41.777193" 140 + inkscape:cy="59.103277" 141 + inkscape:document-units="mm" 142 + inkscape:current-layer="layer1" 143 + showgrid="false" 144 + units="mm" 145 + inkscape:window-width="1920" 146 + inkscape:window-height="1042" 147 + inkscape:window-x="0" 148 + inkscape:window-y="38" 149 + inkscape:window-maximized="0" /> 150 + <metadata 151 + id="metadata5"> 152 + <rdf:RDF> 153 + <cc:Work 154 + rdf:about=""> 155 + <dc:format>image/svg+xml</dc:format> 156 + <dc:type 157 + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> 158 + <dc:title /> 159 + </cc:Work> 160 + </rdf:RDF> 161 + </metadata> 162 + <g 163 + inkscape:label="Layer 1" 164 + inkscape:groupmode="layer" 165 + id="layer1" 166 + transform="translate(0,-271.59998)"> 167 + <rect 168 + style="fill:url(#radialGradient5077);fill-opacity:1;stroke:none;stroke-width:1.05821478;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" 169 + id="rect4518" 170 + width="25.4" 171 + height="25.4" 172 + x="-2.220446e-16" 173 + y="271.59998" /> 174 + <g 175 + id="g841" 176 + transform="translate(0.11032924)"> 177 + <path 178 + mask="url(#mask5148)" 179 + transform="matrix(0.26458333,0,0,0.26458333,0,271.59998)" 180 + id="path5086" 181 + d="m 39.580078,61.101562 c -4.433597,0.635491 -9.840689,2.053587 -13.849609,5.185547 -8.081221,6.313437 -7.197266,18.056641 -7.197266,18.056641 h 58.099609 c 0,0 0.883954,-11.743204 -7.197265,-18.056641 -3.931145,-3.071199 -9.202111,-4.489236 -13.585938,-5.142578 -2.906029,2.777894 -5.844384,4.4375 -8.111328,4.4375 -2.278667,0 -5.237509,-1.67631 -8.158203,-4.480469 z" 182 + style="fill:url(#radialGradient843);fill-opacity:1;stroke:none;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 183 + inkscape:connector-curvature="0" /> 184 + <path 185 + sodipodi:nodetypes="saszs" 186 + inkscape:connector-curvature="0" 187 + d="m 17.503541,281.31806 c 0.203041,-3.87199 -2.372092,-5.70473 -4.872977,-5.70473 -2.500884,0 -5.0760174,1.83274 -4.8729766,5.70473 0.2030395,3.87195 3.2371566,7.26639 4.8729766,7.26639 1.63582,0 4.669938,-3.39444 4.872977,-7.26639 z" 188 + style="fill:#ffb6c8;fill-opacity:0.86086958;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 189 + id="path5081" /> 190 + </g> 191 + </g> 192 +</svg>
Added static/padlock.svg version [a621b7bd06].
1 +<?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 +<!-- Created with Inkscape (http://www.inkscape.org/) --> 3 + 4 +<svg 5 + xmlns:dc="http://purl.org/dc/elements/1.1/" 6 + xmlns:cc="http://creativecommons.org/ns#" 7 + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 + xmlns:svg="http://www.w3.org/2000/svg" 9 + xmlns="http://www.w3.org/2000/svg" 10 + xmlns:xlink="http://www.w3.org/1999/xlink" 11 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 12 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 13 + width="0.2in" 14 + height="0.2in" 15 + viewBox="0 0 5.0800002 5.0800002" 16 + version="1.1" 17 + id="svg8" 18 + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" 19 + sodipodi:docname="padlock.svg" 20 + inkscape:export-filename="/home/lexi/dev/parsav/static/padlock.png" 21 + inkscape:export-xdpi="240" 22 + inkscape:export-ydpi="240"> 23 + <defs 24 + id="defs2"> 25 + <linearGradient 26 + inkscape:collect="always" 27 + id="linearGradient884"> 28 + <stop 29 + style="stop-color:#d2a7b2;stop-opacity:1" 30 + offset="0" 31 + id="stop880" /> 32 + <stop 33 + style="stop-color:#bd7c8d;stop-opacity:1" 34 + offset="1" 35 + id="stop882" /> 36 + </linearGradient> 37 + <linearGradient 38 + inkscape:collect="always" 39 + id="linearGradient863"> 40 + <stop 41 + style="stop-color:#894657;stop-opacity:1" 42 + offset="0" 43 + id="stop859" /> 44 + <stop 45 + id="stop867" 46 + offset="0.35023043" 47 + style="stop-color:#733a49;stop-opacity:1" /> 48 + <stop 49 + style="stop-color:#884556;stop-opacity:1" 50 + offset="1" 51 + id="stop861" /> 52 + </linearGradient> 53 + <linearGradient 54 + inkscape:collect="always" 55 + xlink:href="#linearGradient863" 56 + id="linearGradient865" 57 + x1="4.1353216" 58 + y1="294.38614" 59 + x2="4.1353216" 60 + y2="296.71402" 61 + gradientUnits="userSpaceOnUse" 62 + gradientTransform="translate(1.0345579e-4,-0.15310986)" /> 63 + <radialGradient 64 + inkscape:collect="always" 65 + xlink:href="#linearGradient884" 66 + id="radialGradient886" 67 + cx="2.0557182" 68 + cy="292.57834" 69 + fx="2.0557182" 70 + fy="292.57834" 71 + r="1.4498034" 72 + gradientTransform="matrix(1,0,0,0.99890953,0,0.47312247)" 73 + gradientUnits="userSpaceOnUse" /> 74 + <radialGradient 75 + inkscape:collect="always" 76 + xlink:href="#linearGradient884" 77 + id="radialGradient921" 78 + gradientUnits="userSpaceOnUse" 79 + gradientTransform="matrix(1,0,0,0.99890953,5.2916667,0.47312243)" 80 + cx="2.0557182" 81 + cy="292.57834" 82 + fx="2.0557182" 83 + fy="292.57834" 84 + r="1.4498034" /> 85 + <radialGradient 86 + inkscape:collect="always" 87 + xlink:href="#linearGradient884" 88 + id="radialGradient925" 89 + gradientUnits="userSpaceOnUse" 90 + gradientTransform="matrix(1,0,0,0.99890953,4.6302084,-0.40803457)" 91 + cx="2.0557182" 92 + cy="292.57834" 93 + fx="2.0557182" 94 + fy="292.57834" 95 + r="1.4498034" /> 96 + <radialGradient 97 + inkscape:collect="always" 98 + xlink:href="#linearGradient884" 99 + id="radialGradient938" 100 + gradientUnits="userSpaceOnUse" 101 + gradientTransform="matrix(0.92374945,0,0,0.92274213,0.19377986,22.690922)" 102 + cx="2.0557182" 103 + cy="292.57834" 104 + fx="2.0557182" 105 + fy="292.57834" 106 + r="1.4498034" /> 107 + <radialGradient 108 + inkscape:collect="always" 109 + xlink:href="#linearGradient884" 110 + id="radialGradient943" 111 + gradientUnits="userSpaceOnUse" 112 + gradientTransform="matrix(1,0,0,0.99890953,-5.2916667,0.47312247)" 113 + cx="2.0557182" 114 + cy="292.57834" 115 + fx="2.0557182" 116 + fy="292.57834" 117 + r="1.4498034" /> 118 + </defs> 119 + <sodipodi:namedview 120 + id="base" 121 + pagecolor="#4b002d" 122 + bordercolor="#666666" 123 + borderopacity="1.0" 124 + inkscape:pageopacity="0" 125 + inkscape:pageshadow="2" 126 + inkscape:zoom="11.2" 127 + inkscape:cx="25.660286" 128 + inkscape:cy="1.1491243" 129 + inkscape:document-units="mm" 130 + inkscape:current-layer="layer1" 131 + showgrid="false" 132 + inkscape:window-width="1920" 133 + inkscape:window-height="1042" 134 + inkscape:window-x="0" 135 + inkscape:window-y="38" 136 + inkscape:window-maximized="0" 137 + units="in" /> 138 + <metadata 139 + id="metadata5"> 140 + <rdf:RDF> 141 + <cc:Work 142 + rdf:about=""> 143 + <dc:format>image/svg+xml</dc:format> 144 + <dc:type 145 + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> 146 + <dc:title></dc:title> 147 + </cc:Work> 148 + </rdf:RDF> 149 + </metadata> 150 + <g 151 + inkscape:label="Layer 1" 152 + inkscape:groupmode="layer" 153 + id="layer1" 154 + transform="translate(0,-291.91998)"> 155 + <path 156 + style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 157 + d="m 2.5400001,292.32047 c -1.3794528,0 -1.3389364,1.46451 -1.3389364,1.46451 v 0.44804 H 0.94474863 v 2.32802 H 4.1352514 v -2.32802 H 3.8794531 v -0.44804 c 0,0 0.04,-1.46451 -1.339453,-1.46451 z m 0,0.5209 c 0.8656493,0 0.8402588,0.9493 0.8402588,0.9493 v 0.44235 H 1.6997413 v -0.44235 c 0,0 -0.02539,-0.9493 0.8402588,-0.9493 z" 158 + id="path945" 159 + inkscape:connector-curvature="0" /> 160 + <path 161 + id="path936" 162 + style="fill:url(#radialGradient938);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 163 + d="m 3.8792355,293.78476 c 0,0 0.040321,-1.46443 -1.3391321,-1.46443 -1.3794528,0 -1.339132,1.46443 -1.339132,1.46443 v 0.75607 l 0.4987852,0.006 v -0.75607 c 0,0 -0.025302,-0.94927 0.8403468,-0.94927 0.8656493,0 0.8403469,0.94927 0.8403469,0.94927 v 0.75607 l 0.4987852,-0.006 z" 164 + inkscape:connector-curvature="0" 165 + sodipodi:nodetypes="ccccccccccc" /> 166 + <rect 167 + style="fill:url(#linearGradient865);fill-opacity:1;stroke:none;stroke-width:0.69999999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" 168 + id="rect857" 169 + width="3.1906431" 170 + height="2.3278909" 171 + x="0.94478196" 172 + y="294.23303" /> 173 + <path 174 + style="fill:#281419;fill-opacity:1;stroke:#000000;stroke-width:0.09348611;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" 175 + d="m 2.5400001,294.87847 c -0.1706687,1e-5 -0.3090221,0.13836 -0.3090249,0.30903 1.516e-4,0.1325 0.084759,0.25016 0.2103229,0.29248 v 0 l -0.00938,0.74317 h 0.108082 0.1085944 l -0.010409,-0.74317 v 0 c 0.1257675,-0.0422 0.2106132,-0.15984 0.2108396,-0.29248 -2.8e-6,-0.17067 -0.1383562,-0.30902 -0.309025,-0.30903 z" 176 + id="path910" 177 + inkscape:connector-curvature="0" 178 + sodipodi:nodetypes="ccccccccccc" /> 179 + </g> 180 +</svg>
Modified static/style.scss from [a7ffc6f8bf] to [7905a3444d].
1 +$color: hsl(323,100%,65%); 2 +%sans { font-family: "Alegreya Sans", "Open Sans", sans-serif; } 3 +%serif { font-family: Alegreya, GaramondNo8, "Garamond Premier Pro", "Adobe Garamond", Garamond, Junicode, serif; } 4 +%teletype { font-family: "Inconsolata LGC", Inconsolata, monospace; font-size: 12pt !important; } 5 + 6 +@function tone($pct) { @return adjust-color($color, $lightness: $pct) } 7 + 8 +body { 9 + @extend %sans; 10 + background-color: adjust-color($color, $lightness: -55%); 11 + color: adjust-color($color, $lightness: 25%); 12 + font-size: 14pt; 13 + margin: 0; 14 + padding: 0; 15 +} 16 +a[href] { 17 + color: adjust-color($color, $lightness: 10%); 18 + &:hover { 19 + color: white; 20 + text-shadow: 0 0 15px adjust-color($color, $lightness: 20%); 21 + } 22 +} 23 +a[href^="//"], 24 +a[href^="http://"], 25 +a[href^="https://"] { // external link 26 + &:hover::after { 27 + color: black; 28 + background-color: white; 29 + } 30 + &::after { 31 + content: "↗"; 32 + display: inline-block; 33 + color: black; 34 + margin-left: 4pt; 35 + background-color: adjust-color($color, $lightness: 10%); 36 + padding: 0 4px; 37 + text-shadow: none; 38 + padding-right: 5px; 39 + vertical-align: baseline; 40 + font-size: 80%; 41 + } 42 +} 43 + 44 +%content { 45 + width: 8in; 46 + margin: auto; 47 +} 48 + 49 +%glow { 50 + box-shadow: 0 0 20px adjust-color($color, $alpha: -0.8); 51 +} 52 + 53 +%button { 54 + @extend %sans; 55 + font-size: 14pt; 56 + padding: 0.1in 0.2in; 57 + border: 1px solid black; 58 + color: adjust-color($color, $lightness: 25%); 59 + text-shadow: 1px 1px black; 60 + text-decoration: none; 61 + text-align: center; 62 + background: linear-gradient(to bottom, 63 + adjust-color($color, $lightness: -45%), 64 + adjust-color($color, $lightness: -50%) 15%, 65 + adjust-color($color, $lightness: -50%) 75%, 66 + adjust-color($color, $lightness: -55%) 67 + ); 68 + &:hover, &:focus { 69 + @extend %glow; 70 + outline: none; 71 + color: adjust-color($color, $lightness: -55%); 72 + text-shadow: none; 73 + background: linear-gradient(to bottom, 74 + adjust-color($color, $lightness: -25%), 75 + adjust-color($color, $lightness: -30%) 15%, 76 + adjust-color($color, $lightness: -30%) 75%, 77 + adjust-color($color, $lightness: -35%) 78 + ); 79 + } 80 + &:active { 81 + color: black; 82 + padding-bottom: calc(0.1in - 2px); 83 + padding-top: calc(0.1in + 2px); 84 + background: linear-gradient(to top, 85 + adjust-color($color, $lightness: -25%), 86 + adjust-color($color, $lightness: -30%) 15%, 87 + adjust-color($color, $lightness: -30%) 75%, 88 + adjust-color($color, $lightness: -35%) 89 + ); 90 + } 91 +} 92 + 93 +button { @extend %button; 94 + &:first-of-type { 95 + @extend %button; 96 + color: white; 97 + box-shadow: inset 0 1px adjust-color($color, $lightness: -25%), 98 + inset 0 -1px adjust-color($color, $lightness: -50%); 99 + background: linear-gradient(to bottom, 100 + adjust-color($color, $lightness: -35%), 101 + adjust-color($color, $lightness: -40%) 15%, 102 + adjust-color($color, $lightness: -40%) 75%, 103 + adjust-color($color, $lightness: -45%) 104 + ); 105 + &:hover, &:focus { 106 + box-shadow: inset 0 1px adjust-color($color, $lightness: -15%), 107 + inset 0 -1px adjust-color($color, $lightness: -40%); 108 + } 109 + &:active { 110 + box-shadow: inset 0 1px adjust-color($color, $lightness: -50%), 111 + inset 0 -1px adjust-color($color, $lightness: -25%); 112 + background: linear-gradient(to top, 113 + adjust-color($color, $lightness: -30%), 114 + adjust-color($color, $lightness: -35%) 15%, 115 + adjust-color($color, $lightness: -35%) 75%, 116 + adjust-color($color, $lightness: -40%) 117 + ); 118 + } 119 + } 120 + &:hover { font-weight: bold; } 121 +} 122 + 123 +$grad-ui-focus: linear-gradient(to bottom, 124 + adjust-color($color, $lightness: -50%), 125 + adjust-color($color, $lightness: -35%) 126 +); 127 + 128 +input[type='text'], input[type='password'], textarea { 129 + @extend %serif; 130 + padding: 0.08in 0.1in; 131 + border: 1px solid black; 132 + background: linear-gradient(to bottom, 133 + adjust-color($color, $lightness: -55%), 134 + adjust-color($color, $lightness: -40%) 135 + ); 136 + font-size: 16pt; 137 + color: adjust-color($color, $lightness: 25%); 138 + box-shadow: inset 0 0 20px -3px adjust-color($color, $lightness: -55%); 139 + &:focus { 140 + color: white; 141 + border-image: linear-gradient(to bottom, 142 + adjust-color($color, $lightness: -10%), 143 + adjust-color($color, $lightness: -30%) 144 + ) 1 / 1px; 145 + background: $grad-ui-focus; 146 + outline: none; 147 + @extend %glow; 148 + } 149 +} 150 + 151 +@mixin glass { 152 + @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) { 153 + backdrop-filter: blur(40px); 154 + -webkit-backdrop-filter: blur(40px); 155 + background-color: adjust-color($color, $lightness: -53%, $alpha: -0.7); 156 + } 157 + @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { 158 + background-color: adjust-color($color, $lightness: -53%, $alpha: -0.1); 159 + } 160 +} 161 + 162 +header { 163 + position: fixed; 164 + height: min-content; 165 + width: 100vw; 166 + margin: 0; 167 + padding: 0; 168 + border-bottom: 1px solid black; 169 + z-index: 1; 170 + @include glass; 171 + background: linear-gradient(to bottom, transparent, rgba(0,0,0,0.3)); 172 + > div { 173 + position: relative; 174 + max-width: 10in; 175 + margin: auto; 176 + 177 + display: grid; 178 + grid-template-columns: 1fr max-content; 179 + grid-template-rows: 1fr; 180 + h1 { 181 + all: unset; 182 + display: block; 183 + font-size: 1.4em; 184 + padding: 0.25in 0; 185 + text-shadow: 2px 2px 1px black; 186 + grid-column: 1/2; grid-row: 1/2; 187 + } 188 + nav { 189 + all: unset; 190 + display: flex; 191 + justify-content: flex-end; 192 + align-items: center; 193 + grid-column: 2/3; grid-row: 1/2; 194 + > a[href] { 195 + display: block; 196 + padding: 0.25in 0.15in; 197 + //padding: calc((25% - 1em)/2) 0.15in; 198 + &, &::after { transition: 0.3s; } 199 + text-shadow: 1px 1px 1px black; 200 + &:hover{ 201 + transform: scale(120%); 202 + } 203 + } 204 + } 205 + } 206 +} 207 + 208 +main { 209 + @extend %content; 210 + display: block; 211 + position: relative; 212 + min-height: calc(100vh - 1.1in); 213 + margin-top: 0; 214 + margin-bottom: 0; 215 + padding: 0 0.4in; 216 + padding-top: 1.1in; 217 + background-color: adjust-color($color, $lightness: -45%, $alpha: 0.4); 218 + border: { 219 + left: 1px solid black; 220 + right: 1px solid black; 221 + } 222 +} 223 + 224 +div.profile { 225 + @extend %box; 226 + padding: 0.1in; 227 + position: relative; 228 + display: grid; 229 + grid-template-columns: 2fr 1fr; 230 + grid-template-rows: 1fr 1fr; 231 + width: 100%; 232 + > .banner { 233 + grid-column: 1 / 3; 234 + grid-row: 1 / 2; 235 + display: grid; 236 + grid-template-columns: 1.1in 1fr; 237 + grid-template-rows: 0.3in 1fr; 238 + > .avatar { 239 + display: block; 240 + width: 1in; height: 1in; 241 + grid-column: 1 / 2; 242 + grid-row: 1 / 3; 243 + border: 1px solid black; 244 + } 245 + > .id { 246 + grid-column: 2 / 3; 247 + grid-row: 1 / 2; 248 + color: adjust-color($color, $lightness: 25%, $alpha: -0.4); 249 + > .nym { 250 + font-weight: bold; 251 + color: adjust-color($color, $lightness: 25%); 252 + } 253 + > .xid { 254 + color: adjust-color($color, $lightness: 20%, $alpha: -0.1); 255 + font-size: 80%; 256 + vertical-align: text-top; 257 + } 258 + } 259 + > .bio { 260 + grid-column: 2 / 3; 261 + grid-row: 2 / 3; 262 + } 263 + } 264 + > .stats { 265 + grid-column: 3 / 4; 266 + grid-row: 1 / 3; 267 + } 268 + > .menu { 269 + grid-column: 1 / 3; 270 + grid-row: 2 / 3; 271 + display: flex; 272 + justify-content: center; 273 + align-items: center; 274 + > a[href] { 275 + @extend %button; 276 + display: block; 277 + margin: 0 0.05in; 278 + } 279 + > hr { 280 + all: unset; 281 + display: block; 282 + height: 0.3in; 283 + width: 1px; 284 + border-left: 1px solid rgba(0,0,0,0.6); 285 + } 286 + } 287 +} 288 + 289 +%box { 290 + margin: auto; 291 + border: 1px solid adjust-color($color, $lightness: -55%); 292 + border-bottom: 3px solid black; 293 + box-shadow: 0 0 1px black; 294 + border-image: linear-gradient(to bottom, 295 + adjust-color($color, $lightness: -40%), 296 + adjust-color($color, $lightness: -52%) 10%, 297 + adjust-color($color, $lightness: -55%) 90%, 298 + adjust-color($color, $lightness: -60%) 299 + ) 1 / 1px; 300 + background: linear-gradient(to bottom, 301 + adjust-color($color, $lightness: -58%), 302 + adjust-color($color, $lightness: -55%) 10%, 303 + adjust-color($color, $lightness: -50%) 80%, 304 + adjust-color($color, $lightness: -45%) 305 + ); 306 + // outline: 1px solid black; 307 +} 308 + 309 +body.error .message { 310 + @extend %box; 311 + width: 4in; 312 + margin:auto; 313 + padding: 0.5in; 314 + text-align: center; 315 +} 316 + 317 +div.login { 318 + @extend %box; 319 + width: 4in; 320 + padding: 0.4in; 321 + > .msg { 322 + text-align: center; 323 + padding: 0.3in; 324 + } 325 + > .msg:first-child { padding-top: 0; } 326 + > .user { 327 + width: min-content; margin: auto; 328 + background: adjust-color($color, $lightness: -20%, $alpha: -0.3); 329 + border: 1px solid black; 330 + color: adjust-color($color, $lightness: -50%); 331 + padding: 0.1in; 332 + > img { width: 1in; height: 1in; border: 1px solid black; } 333 + > .name { @extend %serif; text-align: center; font-size: 130%; font-weight: bold; margin-top: 0.08in; } 334 + } 335 + >form { 336 + display: grid; 337 + grid-template-columns: 1fr 1fr; 338 + grid-template-rows: 1.2em 1fr 1fr; 339 + grid-gap: 5px; 340 + > label, input, button { display: block; } 341 + > label { grid-column: 1 / 3; grid-row: 1/2; font-weight: bold } 342 + > input { grid-column: 1 / 3; grid-row: 2/3; } 343 + > button { grid-column: 2 / 3; grid-row: 3/4; } 344 + > a { @extend %button; grid-column: 1 / 2; grid-row: 3/4; } 345 + } 346 +} 347 + 348 +form.compose { 349 + @extend %box; 350 + display: grid; 351 + grid-template-columns: 1.1in 2fr min-content 1fr; 352 + grid-template-rows: 1fr min-content; 353 + grid-gap: 2px; 354 + padding: 0.1in; 355 + > img { grid-column: 1/2; grid-row: 1/3; width: 1in; height: 1in;} 356 + > textarea { grid-column: 2/5; grid-row: 1/2; height: 3in;} 357 + > input[name="acl"] { grid-column: 2/3; grid-row: 2/3; } 358 + > button { grid-column: 4/5; grid-row: 2/3; } 359 + a.help[href] { margin-right: 0.05in } 360 +} 361 + 362 +a.help[href] { 363 + display: block; 364 + text-align: center; 365 + padding: 0.09in 0.2in; 366 + background: tone(-40%); 367 + border: 1px solid black; 368 + font-weight: bold; 369 + text-decoration: none; 370 + cursor: help; 371 +} 372 + 373 +input.acl { 374 + @extend %teletype; 375 + background: url(/s/padlock.webp) no-repeat; 376 + background-size: 20pt; 377 + background-position: 0.05in 50%; 378 + &:focus { 379 + background: url(/s/padlock.webp) no-repeat, $grad-ui-focus; 380 + background-size: 20pt; 381 + background-position: 0.05in 50%; 382 + }; 383 + padding-left: 0.40in; 384 +} 385 + 386 +div.modal { 387 + @extend %box; 388 + position: fixed; 389 + display: none; 390 + left: 0; right: 0; bottom: 0; top: 0; 391 + max-width: 7in; 392 + margin: 1in auto; 393 + padding: 0.2in 0.3in; 394 + &:target { display: block; } 395 + box-shadow: 0 0 4in 5in rgba(0,0,0,0.5); 396 + z-index: 2; 397 + > div { 398 + height: 100%; 399 + overflow-y: scroll; 400 + >p:first-of-type { margin-top: 0; } 401 + } 402 + >a[href="#0"] { // close link 403 + @extend %button; 404 + cursor: default; 405 + display: block; 406 + position: absolute; 407 + top: -0.3in; 408 + right: 0.1in; 409 + margin: 0.1in; 410 + padding: 0.1in; 411 + &:hover { font-weight: bold; } 412 + } 413 +} 414 + 415 +code { 416 + @extend %teletype; 417 + background: adjust-color($color, $lightness: -50%); 418 + border: 1px solid adjust-color($color, $lightness: -20%); 419 + padding: 2px 6px; 420 + text-shadow: 2px 2px black; 421 +}
Added static/warn.svg version [a819eb65cc].
1 +<?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 +<!-- Created with Inkscape (http://www.inkscape.org/) --> 3 + 4 +<svg 5 + xmlns:dc="http://purl.org/dc/elements/1.1/" 6 + xmlns:cc="http://creativecommons.org/ns#" 7 + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 + xmlns:svg="http://www.w3.org/2000/svg" 9 + xmlns="http://www.w3.org/2000/svg" 10 + xmlns:xlink="http://www.w3.org/1999/xlink" 11 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 12 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 13 + width="1in" 14 + height="1in" 15 + viewBox="0 0 25.4 25.400001" 16 + version="1.1" 17 + id="svg8" 18 + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" 19 + sodipodi:docname="warn.svg" 20 + inkscape:export-filename="/home/lexi/dev/parsav/static/warn.png" 21 + inkscape:export-xdpi="200" 22 + inkscape:export-ydpi="200"> 23 + <defs 24 + id="defs2"> 25 + <linearGradient 26 + inkscape:collect="always" 27 + id="linearGradient2322"> 28 + <stop 29 + style="stop-color:#ca0050;stop-opacity:1;" 30 + offset="0" 31 + id="stop2318" /> 32 + <stop 33 + style="stop-color:#7b0031;stop-opacity:1" 34 + offset="1" 35 + id="stop2320" /> 36 + </linearGradient> 37 + <linearGradient 38 + inkscape:collect="always" 39 + id="linearGradient2302"> 40 + <stop 41 + style="stop-color:#ffffff;stop-opacity:1;" 42 + offset="0" 43 + id="stop2298" /> 44 + <stop 45 + style="stop-color:#ffffff;stop-opacity:0;" 46 + offset="1" 47 + id="stop2300" /> 48 + </linearGradient> 49 + <linearGradient 50 + inkscape:collect="always" 51 + id="linearGradient1228"> 52 + <stop 53 + style="stop-color:#8c0037;stop-opacity:1" 54 + offset="0" 55 + id="stop1224" /> 56 + <stop 57 + style="stop-color:#ff1a75;stop-opacity:1" 58 + offset="1" 59 + id="stop1226" /> 60 + </linearGradient> 61 + <linearGradient 62 + inkscape:collect="always" 63 + id="linearGradient851"> 64 + <stop 65 + style="stop-color:#ffffff;stop-opacity:0" 66 + offset="0" 67 + id="stop847" /> 68 + <stop 69 + style="stop-color:#ffa9c6;stop-opacity:1" 70 + offset="1" 71 + id="stop849" /> 72 + </linearGradient> 73 + <radialGradient 74 + inkscape:collect="always" 75 + xlink:href="#linearGradient851" 76 + id="radialGradient853" 77 + cx="12.699999" 78 + cy="285.82184" 79 + fx="12.699999" 80 + fy="285.82184" 81 + r="1.7905753" 82 + gradientTransform="matrix(2.2137788,-2.5697531e-5,6.7771541e-5,5.8383507,-15.43436,-1382.906)" 83 + gradientUnits="userSpaceOnUse" /> 84 + <linearGradient 85 + inkscape:collect="always" 86 + xlink:href="#linearGradient1228" 87 + id="linearGradient1230" 88 + x1="87.388672" 89 + y1="90.857147" 90 + x2="67.185623" 91 + y2="-29.734592" 92 + gradientUnits="userSpaceOnUse" /> 93 + <filter 94 + inkscape:collect="always" 95 + style="color-interpolation-filters:sRGB" 96 + id="filter2294" 97 + x="-0.38068429" 98 + width="1.7613686" 99 + y="-0.089708175" 100 + height="1.1794164"> 101 + <feGaussianBlur 102 + inkscape:collect="always" 103 + stdDeviation="0.5243555" 104 + id="feGaussianBlur2296" /> 105 + </filter> 106 + <radialGradient 107 + inkscape:collect="always" 108 + xlink:href="#linearGradient2302" 109 + id="radialGradient2304" 110 + cx="12.58085" 111 + cy="285.25314" 112 + fx="12.58085" 113 + fy="285.25314" 114 + r="10.573204" 115 + gradientTransform="matrix(1.1874875,-1.9679213e-8,1.9699479e-8,1.1887104,-2.3811363,-53.650199)" 116 + gradientUnits="userSpaceOnUse" /> 117 + <linearGradient 118 + inkscape:collect="always" 119 + xlink:href="#linearGradient2322" 120 + id="linearGradient2324" 121 + x1="20.298828" 122 + y1="97.196846" 123 + x2="20.298828" 124 + y2="12.911131" 125 + gradientUnits="userSpaceOnUse" 126 + gradientTransform="translate(1.3858268e-6)" /> 127 + </defs> 128 + <sodipodi:namedview 129 + id="base" 130 + pagecolor="#313131" 131 + bordercolor="#666666" 132 + borderopacity="1.0" 133 + inkscape:pageopacity="0" 134 + inkscape:pageshadow="2" 135 + inkscape:zoom="1.4" 136 + inkscape:cx="-71.210763" 137 + inkscape:cy="60.089308" 138 + inkscape:document-units="mm" 139 + inkscape:current-layer="layer1" 140 + showgrid="false" 141 + units="in" 142 + inkscape:window-width="1920" 143 + inkscape:window-height="1042" 144 + inkscape:window-x="0" 145 + inkscape:window-y="38" 146 + inkscape:window-maximized="0" /> 147 + <metadata 148 + id="metadata5"> 149 + <rdf:RDF> 150 + <cc:Work 151 + rdf:about=""> 152 + <dc:format>image/svg+xml</dc:format> 153 + <dc:type 154 + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> 155 + <dc:title></dc:title> 156 + </cc:Work> 157 + </rdf:RDF> 158 + </metadata> 159 + <g 160 + inkscape:label="Layer 1" 161 + inkscape:groupmode="layer" 162 + id="layer1" 163 + transform="translate(0,-271.59998)"> 164 + <path 165 + id="rect829" 166 + style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" 167 + d="m 11.543949,291.41721 -7e-6,0.52521 0.893337,0.89333 0.525444,2.4e-4 0.893329,-0.89333 -2.34e-4,-0.52545 -0.893338,-0.89333 h -0.525203 z m 1.995206,-2.42709 0.813727,-9.44222 -0.826441,-0.74021 h -1.652884 l -0.826441,0.74021 0.813726,9.44222 0.419578,0.5079 h 0.839157 z" 168 + inkscape:connector-curvature="0" /> 169 + <path 170 + style="fill:url(#linearGradient1230);fill-opacity:1;stroke:none;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 171 + d="M 48.013672 8.5 A 46.78571 46.78571 0 0 0 44.353516 8.6621094 L 8.6132812 80.511719 A 46.78571 46.78571 0 0 0 14.115234 87.5 L 81.867188 87.5 A 46.78571 46.78571 0 0 0 87.388672 80.460938 L 51.685547 8.6855469 A 46.78571 46.78571 0 0 0 48.013672 8.5 z M 44.876953 27.242188 L 51.123047 27.242188 L 54.248047 30.039062 L 51.171875 65.726562 L 49.585938 67.646484 L 46.414062 67.646484 L 44.828125 65.726562 L 41.751953 30.039062 L 44.876953 27.242188 z M 47.007812 71.523438 L 48.992188 71.523438 L 52.369141 74.900391 L 52.369141 76.884766 L 48.992188 80.261719 L 47.007812 80.261719 L 43.630859 76.884766 L 43.630859 74.900391 L 47.007812 71.523438 z " 172 + id="path817" 173 + transform="matrix(0.26458333,0,0,0.26458333,0,271.59998)" /> 174 + <path 175 + inkscape:connector-curvature="0" 176 + id="path838" 177 + d="m -29.39374,273.80193 a 12.378719,12.378719 0 0 0 -0.968416,0.0424 l -9.456272,19.01021 a 12.378719,12.378719 0 0 0 1.455725,1.84899 h 17.926038 a 12.378719,12.378719 0 0 0 1.460894,-1.86242 l -9.446452,-18.99057 a 12.378719,12.378719 0 0 0 -0.971517,-0.0486 z" 178 + style="fill:#ca0050;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> 179 + <path 180 + id="path1146" 181 + d="m 12.703617,273.84894 c -0.323241,0.002 -0.646295,0.016 -0.968416,0.0429 l -9.4562703,19.01021 c 0.425424,0.66116 0.9128677,1.28028 1.4557249,1.84898 H 21.660694 c 0.545337,-0.57272 1.034537,-1.19637 1.460892,-1.86242 l -9.446452,-18.99058 c -0.32307,-0.0291 -0.64716,-0.0455 -0.971517,-0.0491 z" 182 + style="fill:none;fill-opacity:1;stroke:url(#radialGradient2304);stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 183 + inkscape:connector-curvature="0" 184 + sodipodi:nodetypes="ccccccccc" /> 185 + <path 186 + id="path1232" 187 + style="opacity:1;fill:#ffe7f0;fill-opacity:1;stroke:none;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" 188 + d="m 12.134766,279.49414 -0.375,0.33594 0.647507,8.88867 0.07813,0.12618 h 0.429595 l 0.07813,-0.12618 0.647507,-8.88867 -0.375,-0.33594 z m 0.06945,12.07312 -3e-6,0.22524 0.383115,0.38311 0.225341,1.1e-4 0.383112,-0.38311 -1.01e-4,-0.22535 -0.383115,-0.38311 h -0.225238 z" 189 + inkscape:connector-curvature="0" 190 + sodipodi:nodetypes="cccccccccccccccccc" /> 191 + <path 192 + inkscape:connector-curvature="0" 193 + d="m 11.543949,291.41721 -7e-6,0.52521 0.893337,0.89333 0.525444,2.4e-4 0.893329,-0.89333 -2.34e-4,-0.52545 -0.893338,-0.89333 h -0.525203 z m 1.995206,-2.42709 0.813727,-9.44222 -0.826441,-0.74021 h -1.652884 l -0.826441,0.74021 0.813726,9.44222 0.419578,0.5079 h 0.839157 z" 194 + style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter2294)" 195 + id="path1856" /> 196 + </g> 197 +</svg>
Modified store.t from [213b3d2729] to [5ad659a834].
12 12 'follow', 'mute', 'block' 13 13 }; 14 14 credset = lib.set { 15 15 'pw', 'otp', 'challenge', 'trust' 16 16 }; 17 17 privset = lib.set { 18 18 'post', 'edit', 'acct', 'upload', 'censor', 'admin' 19 + }; 20 + powerset = lib.set { 21 + -- user powers -- default on 22 + 'login', 'visible', 'post', 'shout', 23 + 'propagate', 'upload', 'acct', 'edit'; 24 + 25 + -- admin powers -- default off 26 + 'purge', 'config', 'censor', 'suspend', 27 + 'cred', 'elevate', 'demote', 'rebrand' -- modify site's brand identity 19 28 } 20 29 } 30 + 31 +terra m.powerset:affect_users() 32 + return self.purge() or self.censor() or self.suspend() or 33 + self.elevate() or self.demote() or self.rebrand() or 34 + self.cred() 35 +end 21 36 22 37 local str = rawstring --lib.mem.ptr(int8) 23 38 24 39 struct m.source 25 40 26 41 struct m.rights { 27 42 rank: uint16 -- lower = more powerful except 0 = regular user 28 43 -- creating staff automatically assigns rank immediately below you 29 44 quota: uint32 -- # of allowed tweets per day; 0 = no limit 30 45 31 - -- user powers -- default on 32 - login: bool 33 - visible: bool 34 - post: bool 35 - shout: bool 36 - propagate: bool 37 - upload: bool 38 - acct: bool 39 - edit: bool 40 - 41 - -- admin powers -- default off 42 - ban: bool 43 - config: bool 44 - censor: bool 45 - suspend: bool 46 - rebrand: bool -- modify site's brand identity 46 + powers: m.powerset 47 47 } 48 48 49 49 terra m.rights_default() 50 - return m.rights { 51 - rank = 0, quota = 1000; 52 - 53 - login = true, visible = true, post = true; 54 - shout = true, propagate = true, upload = true; 55 - 56 - ban = false, config = false, censor = false; 57 - suspend = false, rebrand = false; 58 - } 50 + var pow: m.powerset pow:fill() 51 + (pow.purge << false) 52 + (pow.config << false) 53 + (pow.censor << false) 54 + (pow.suspend << false) 55 + (pow.elevate << false) 56 + (pow.demote << false) 57 + (pow.cred << false) 58 + (pow.rebrand << false) 59 + return m.rights { rank = 0, quota = 1000, powers = pow; } 59 60 end 60 61 61 62 struct m.actor { 62 63 id: uint64 63 64 nym: str 64 65 handle: str 65 66 origin: uint64 66 67 bio: str 68 + avatar: str 69 + knownsince: int64 67 70 rights: m.rights 68 71 key: lib.mem.ptr(uint8) 69 72 73 +-- ephemera 70 74 xid: str 71 - 72 75 source: &m.source 73 76 } 77 + 78 +struct m.actor_stats { 79 + posts: intptr 80 + follows: intptr 81 + followers: intptr 82 + mutuals: intptr 83 +} 74 84 75 85 struct m.range { 76 86 time: bool 77 87 union { 78 88 from_time: m.timepoint 79 89 from_idx: uint64 80 90 } ................................................................................ 165 175 actor_save: {&m.source, m.actor} -> bool 166 176 actor_create: {&m.source, m.actor} -> bool 167 177 actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor) 168 178 actor_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.actor) 169 179 actor_notif_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.notif) 170 180 actor_enum: {&m.source} -> lib.mem.ptr(&m.actor) 171 181 actor_enum_local: {&m.source} -> lib.mem.ptr(&m.actor) 182 + actor_stats: {&m.source, uint64} -> m.actor_stats 172 183 173 - actor_auth_how: {&m.source, m.inet, rawstring} -> m.credset 184 + actor_auth_how: {&m.source, m.inet, rawstring} -> {m.credset, bool} 174 185 -- returns a set of auth method categories that are available for a 175 186 -- given user from a certain origin 176 187 -- origin: inet 177 - -- handle: rawstring 188 + -- username: rawstring 178 189 actor_auth_otp: {&m.source, m.inet, rawstring, rawstring} -> uint64 179 - actor_auth_pw: {&m.source, m.inet, rawstring, rawstring} -> uint64 190 + actor_auth_pw: {&m.source, m.inet, lib.mem.ptr(int8), lib.mem.ptr(int8) } -> uint64 180 191 -- handles password-based logins against hashed passwords 181 192 -- origin: inet 182 193 -- handle: rawstring 183 194 -- token: rawstring 184 195 actor_auth_tls: {&m.source, m.inet, rawstring} -> uint64 185 196 -- handles implicit authentication performed as part of an TLS connection 186 197 -- origin: inet ................................................................................ 219 230 terra m.source:free() 220 231 self.id:free() 221 232 self.string:free() 222 233 end 223 234 m.source.metamethods.__methodmissing = macro(function(meth, obj, ...) 224 235 local q = {...} 225 236 -- syntax sugar to forward unrecognized calls onto the backend 226 - return `obj.backend.[meth](&obj, [q]) 237 + return quote var r = obj.backend.[meth](&obj, [q]) in r end 227 238 end) 228 239 229 240 return m
Modified str.t from [c91733fef5] to [c8d105a016].
13 13 fmt = terralib.externfunction('asprintf', 14 14 terralib.types.funcpointer({&rawstring,rawstring},{int},true)); 15 15 bfmt = terralib.externfunction('sprintf', 16 16 terralib.types.funcpointer({rawstring,rawstring},{int},true)); 17 17 span = terralib.externfunction('strspn',{rawstring, rawstring} -> rawstring); 18 18 } 19 19 20 -(lib.mem.ptr(int8)).metamethods.__cast = function(from,to,e) 21 - if from == &int8 then 22 - return `[lib.mem.ptr(int8)]{ptr = e, ct = m.sz(e)} 23 - elseif to == &int8 then 24 - return e.ptr 20 +do local strptr = (lib.mem.ptr(int8)) 21 + local byteptr = (lib.mem.ptr(uint8)) 22 + strptr.metamethods.__cast = function(from,to,e) 23 + if from == &int8 then 24 + return `strptr {ptr = e, ct = m.sz(e)} 25 + elseif to == &int8 then 26 + return e.ptr 27 + end 28 + end 29 + 30 + terra strptr:cmp(other: strptr) 31 + var sz = lib.math.biggest(self.ct, other.ct) 32 + for i = 0, sz do 33 + if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end 34 + if self.ptr[i] ~= other.ptr[i] then return false end 35 + end 36 + return true 37 + end 38 + 39 + terra byteptr:cmp(other: byteptr) 40 + var sz = lib.math.biggest(self.ct, other.ct) 41 + for i = 0, sz do 42 + if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end 43 + if self.ptr[i] ~= other.ptr[i] then return false end 44 + end 45 + return true 25 46 end 26 47 end 27 48 28 49 struct m.acc { 29 50 buf: rawstring 30 51 sz: intptr 31 52 run: intptr ................................................................................ 65 86 var pt: lib.mem.ptr(int8) 66 87 pt.ptr = self.buf 67 88 pt.ct = self.sz 68 89 self.buf = nil 69 90 self.sz = 0 70 91 return pt 71 92 end; 93 + 94 +terra m.acc:cue(sz: intptr) 95 + if sz <= self.run then return end 96 + self.run = sz 97 + if self.space - self.sz < self.run then 98 + self.space = self.sz + self.run 99 + self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space)) 100 + end 101 +end 72 102 73 103 terra m.acc:push(str: rawstring, len: intptr) 74 104 --var llen = len 75 105 if str == nil then return self end 76 106 --if str[len - 1] == 0xA then llen = llen - 1 end -- don't display newlines in debug output 77 107 -- lib.dbg('pushing "',{str,llen},'" onto accumulator') 78 108 if self.buf == nil then self:init(self.run) end ................................................................................ 87 117 return self 88 118 end; 89 119 90 120 m.lit = macro(function(str) 91 121 return `[lib.mem.ref(int8)] {ptr = [str:asvalue()], ct = [#(str:asvalue())]} 92 122 end) 93 123 124 +m.acc.methods.lpush = macro(function(self,str) 125 + return `self:push([str:asvalue()], [#(str:asvalue())]) end) 94 126 m.acc.methods.ppush = terra(self: &m.acc, str: lib.mem.ptr(int8)) 95 127 self:push(str.ptr, str.ct) return self end; 96 128 m.acc.methods.merge = terra(self: &m.acc, str: lib.mem.ptr(int8)) 97 129 self:push(str.ptr, str.ct) str:free() return self end; 98 130 m.acc.methods.compose = macro(function(self, ...) 99 131 local minlen = 0 100 132 local pstrs = {}
Modified tpl.t from [ad44dd6129] to [3cd51c8b03].
27 27 local segs = {} 28 28 local constlen = 0 29 29 -- strip out all irrelevant whitespace to tidy things up 30 30 -- TODO: find way to exclude <pre> tags? 31 31 str = str:gsub('[\n^]%s+','') 32 32 str = str:gsub('%s+[\n$]','') 33 33 str = str:gsub('\n','') 34 + str = str:gsub('</a><a ','</a> <a ') -- keep nav links from getting smooshed 34 35 for start, key, stop in string.gmatch(str,'()'..tplchar..'(%w+)()') do 35 36 if string.sub(str,start-1,start-1) ~= '\\' then 36 37 segs[#segs+1] = string.sub(str,last,start-1) 37 38 fields[#segs] = key 38 39 last = stop 39 40 end 40 41 end ................................................................................ 65 66 [runningtally] = [runningtally] + lib.str.sz([symself].[key])*fac 66 67 end 67 68 end 68 69 end 69 70 70 71 local copiers = {} 71 72 local senders = {} 73 + local appenders = {} 72 74 local symtxt = symbol(lib.mem.ptr(int8)) 73 75 local cpypos = symbol(&opaque) 76 + local accumulator = symbol(&lib.str.acc) 74 77 local destcon = symbol(&lib.net.mg_connection) 75 78 for idx, seg in ipairs(segs) do 76 79 copiers[#copiers+1] = quote [cpypos] = lib.mem.cpy([cpypos], [&opaque]([seg]), [#seg]) end 77 80 senders[#senders+1] = quote lib.net.mg_send([destcon], [seg], [#seg]) end 81 + appenders[#appenders+1] = quote [accumulator]:push([seg], [#seg]) end 78 82 if fields[idx] then 79 83 copiers[#copiers+1] = quote 80 84 [cpypos] = lib.mem.cpy([cpypos], 81 85 [&opaque](symself.[fields[idx]]), 82 86 lib.str.sz(symself.[fields[idx]])) 83 87 end 84 88 senders[#senders+1] = quote ................................................................................ 94 98 lib.dbg(['compiling template ' .. tid]) 95 99 [tallyup] 96 100 var [symtxt] = lib.mem.heapa(int8, [runningtally]) 97 101 var [cpypos] = [&opaque](symtxt.ptr) 98 102 [copiers] 99 103 @[&int8](cpypos) = 0 100 104 return symtxt 105 + end 106 + rec.methods.append = terra([symself], [accumulator]) 107 + lib.dbg(['appending template ' .. tid]) 108 + [tallyup] 109 + accumulator:cue([runningtally]) 110 + [appenders] 111 + return accumulator 101 112 end 102 113 rec.methods.send = terra([symself], [destcon], code: uint16, hd: lib.mem.ptr(lib.http.header)) 103 114 lib.dbg(['transmitting template ' .. tid]) 104 115 [tallyup] 105 116 lib.net.mg_printf([destcon], 'HTTP/1.1 %s', lib.http.codestr(code)) 106 117 for i = 0, hd.ct do 107 118 lib.net.mg_printf([destcon], '%s: %s\r\n', hd.ptr[i].key, hd.ptr[i].value)
Added view/compose.tpl version [09c6180294].
1 +<form class="compose" method="post"> 2 + <img src="/avi/@handle"> 3 + <textarea autofocus name="post" placeholder="it was a dark and stormy night…">@content</textarea> 4 + <input required type="text" name="acl" class="acl" value="@acl"> 5 + <a href="#aclhelp" class="help">?</a> 6 + <button type="submit">commit</button> 7 +</form> 8 + 9 +<div id="aclhelp" class="modal"> <a href="#0">close</a> <div> 10 + <p>to control who can see your post (and how far it is propagated), <code>parsav</code> uses <strong>ACL expressions</strong>. this is roughly equivalent to scoping in pleroma or GNU social terms. an ACL expression consists of one or more space-separated terms, each of which match a certain set of users. a term can be negated by prefixing it with <code>~</code>, a tilde character, so <code>~all</code> matches nobody, and <code>~followed</code> matches users you do not follow.</p> 11 + <ul> 12 + <li><strong>all</strong>: matches any and all users</li> 13 + <li><strong>local</strong>: matches users who belong to this instance</li> 14 + <li><strong>mutuals</strong>: matches users you follow who also follow you</li> 15 + <li><strong>followed</strong>: matches users you follow</li> 16 + <li><strong>followers</strong>: matches users who follow you</li> 17 + <li><strong>groupies</strong>: matches users who follow you, but whom you do not follow</li> 18 + <li><strong>mentioned</strong>: matches users who are mentioned in the post</li> 19 + <li><strong>staff</strong>: matches instance staff (equivalent to <code>~%0</code>)</li> 20 + <li><strong>admin</strong>: matches the individual named as the instance administrator, if any</li> 21 + <li><strong>\@</strong><em>handle</em>: matches the user <em>handle</em></li> 22 + <li><strong>+</strong><em>circle</em>: matches users you have categorized under <em>circle</em></li> 23 + <li><strong>#</strong><em>room</em>: matches users who are members of <em>room</em></li> 24 + <li><strong>%</strong><em>rank</em>: matches users of <em>rank</em> or higher (e.g. <code>%3</code> matches users of rank 3, 2, and 1). as a special case, <code>%0</code> matches ordinary users</li> 25 + <li><strong>#</strong><em>room</em><strong>%</strong><em>rank</em>: matches users who hold <em>rank</em> in <em>room</em></li> 26 + <li><strong><</strong><em>title</em><strong>></strong>: matches peers of the net who have been created <em>title</em> by the sovereign</li> 27 + <li><strong>#</strong><em>room</em><strong><</strong><em>title</em><strong>></strong>: matches peers of the chat who have been created <em>title</em> by <em>room</em> staff</li> 28 + </ul> 29 + <p>to evaluate an ACL expression, <code>parsav</code> reads each term from start to finish. for each term, it considers whether it describes the user who is attempting to access the content. if the term matches, its policy is applied and the expression completes. if the term doesn't match, the server proceeds on to the next term and the process repeats until it finds a matching term or runs out of terms, applying the fallback policy.</p> 30 + <p><strong>policy</strong> is whether a term grants or denies access. the default term policy is <strong>allow</strong>, but you can control the policy with the keywords <code>allow</code> and <code>deny</code>. if a term finishes evaluating without any match being found, a fallback policy is applied; this fallback is the opposite of whatever the current policy is. this sounds confusing but makes ACL expressions much more intuitive; <code>allow \@bob</code> and <code>deny trent</code> do exactly what you'd expect &em; the former allows bob and only bob in; the latter denies access only to trent, but grants access to the rest of the world.</p> 31 + <p>expressions must contain at least one term to be valid. if they consist only of policy keywords, they will be rejected.</p> 32 + <p>in effect, this all means that an ACL expression can be treated as a simple list of who is allowed to view your post. for instance, an expression of <code>local</code> means only local users can view it. however, much more complex expressions are possible.</p> 33 + <ul> 34 + <li><code>deny groupies allow +illuminati</code>: permits access to the illuminati, but excluding those members who are groupies</li> 35 + <li><code>+illuminati deny groupies</code>: allows access to everyone but groupies (unless they're in the illuminati)</li> 36 + <li><code>\@eve \@alice\@nowhere.tld deny \@bob \@trent\@witches.live</code>: grants access to eve and alice, but locks out bob and trent</li> 37 + <li><code><grand duke> #4th-intl<comrade></code>: restricts the post to the eyes of the Fourth International's secret cabal of anointed comrades and the grand dukes of the Empire</li> 38 + <li><code>deny ~%3</code>: blocks a post from being seen by anyone with a staff rank level below 3</li> 39 + </ul> 40 + <p><strong>limitations:</strong> to inhibit potential denial-of-service attacks, ACL expressions can be a maximum of 128 characters, can contain at most 16 words, and cannot trigger queries against other servers. all information needed to evaluate an ACL expression must be known locally. this is particularly relevant with respect to rooms.</p> 41 +</div></div>
Modified view/docskel.tpl from [004398018e] to [cb4a31dcc6].
1 1 <!doctype html> 2 2 <html> 3 3 <head> 4 4 <title>@instance :: @title</title> 5 5 <link rel="stylesheet" href="/s/style.css"> 6 6 </head> 7 7 <body class="@class"> 8 - <h1>@title</h1> 9 - @body 8 + <header><div> 9 + <h1>@title</h1> 10 + <nav> 11 + <a href="/instance">instance</a> 12 + @navlinks 13 + </nav> 14 + </div></header> 15 + <main> 16 + @body 17 + </main> 10 18 </body> 11 19 </html>
Modified view/load.lua from [53cfafaa7c] to [1503c625ad].
3 3 -- copies them into a data structure we can then 4 4 -- create templates from when we return to terra 5 5 local path = ... 6 6 local sources = { 7 7 'docskel'; 8 8 'tweet'; 9 9 'profile'; 10 + 'compose'; 11 + 'login-username'; 12 + 'login-challenge'; 10 13 } 11 14 12 15 local ingest = function(filename) 13 16 local hnd = io.open(path..'/'..filename) 14 17 local txt = hnd:read('*a') 15 18 io.close(hnd) 16 19 txt = txt:gsub('([^\\])!%b[]', '%1') ................................................................................ 18 21 txt = txt:gsub('\\(!%b[])', '%1') 19 22 txt = txt:gsub('\\(!!)', '%1') 20 23 return txt 21 24 end 22 25 23 26 24 27 local views = {} 25 -for _,n in pairs(sources) do views[n] = ingest(n .. '.tpl') end 28 +for _,n in pairs(sources) do views[n:gsub('-','_')] = ingest(n .. '.tpl') end 26 29 return views
Added view/login-challenge.tpl version [c8511de2b7].
1 +<div class="login"> 2 + <div class="user"> 3 + <img src="/avi/@handle"> 4 + <div class="name">@name</div> 5 + </div> 6 + <div class="msg">@challenge</div> 7 + <form action="/login" method="post"> 8 + <label for="response">@label</label> 9 + <input type="hidden" name="user" value="@handle"> 10 + <input type="password" name="response" id="response" autofocus required> 11 + <button type="submit" name="authmethod" value="@method">authenticate</button> 12 + <a href="/login">cancel</a> 13 + </form> 14 +</div>
Added view/login-username.tpl version [4dc628d5ef].
1 +<div class="login"> 2 + <div class="msg">@loginmsg</div> 3 + <form action="/login" method="post"> 4 + <label for="user">local handle</label> 5 + <input type="text" name="user" id="user" autofocus required> 6 + <button type="submit">log on</button> 7 + </form> 8 +</div>
Modified view/profile.tpl from [abc7153f4d] to [6c61b2a3c1].
1 1 <div class="profile"> 2 2 <div class="banner"> 3 3 <img class="avatar" src="@avatar"> 4 - <div class="id">@nym [@xid]</div> 4 + <div class="id"><span class="nym">@nym</span> [<span class="xid">@xid</span>]</div> 5 5 <div class="bio"> 6 6 @bio 7 7 </div> 8 8 </div> 9 9 <table class="stats"> 10 10 <tr><th>posts</th> <td>@nposts</td></tr> 11 11 <tr><th>following</th> <td>@nfollows</td></tr> 12 12 <tr><th>followers</th> <td>@nfollowers</td></tr> 13 13 <tr><th>mutuals</th> <td>@nmutuals</td></tr> 14 - <tr><th>account created</th> <td>@tweetday</td></tr> 14 + <tr><th>@timephrase</th> <td>@tweetday</td></tr> 15 15 </table> 16 16 <div class="menu"> 17 - <a href="/\@@xid">posts</a> 18 - <a href="/\@@xid/media">media</a> 19 - <a href="/\@@xid/follows">follows</a> 20 - <a href="/\@@xid/chat">chat</a> 17 + <a href="/@xid">posts</a> 18 + <a href="/@xid/media">media</a> 19 + <a href="/@xid/social">associates</a> 20 + <hr> 21 + @auxbtn 21 22 </div> 22 23 </div>