parsav  Diff

Differences From Artifact [17bd63f8c3]:

To Artifact [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