add barkshark-lib dep

This commit is contained in:
Izalia Mae 2024-04-18 22:36:06 -04:00
parent 848350684f
commit 026f5bd11d
15 changed files with 74 additions and 86 deletions

View file

@ -1,7 +1,8 @@
import aputils
import click import click
import platform import platform
from aputils import Signer
from blib import HttpDate
from pathlib import Path from pathlib import Path
from . import TRANS from . import TRANS
@ -29,7 +30,7 @@ def cli_setup(ctx: click.Context) -> None:
click.confirm(TRANS.fetch("setup", "prompt-database"), abort = True) click.confirm(TRANS.fetch("setup", "prompt-database"), abort = True)
ctx.obj.config.sqlite_path.unlink() ctx.obj.config.sqlite_path.unlink()
current = aputils.HttpDate.new_utc() current = HttpDate.new_utc()
with ctx.obj.database.session(True) as s: with ctx.obj.database.session(True) as s:
s.create_tables() s.create_tables()
@ -47,10 +48,10 @@ def cli_setup(ctx: click.Context) -> None:
}) })
click.echo(TRANS.fetch("setup", "create-key", username = ctx.obj.config.host)) click.echo(TRANS.fetch("setup", "create-key", username = ctx.obj.config.host))
instance_signer = aputils.Signer.new(f"https://{ctx.obj.config.web_host}/actor") instance_signer = Signer.new(f"https://{ctx.obj.config.web_host}/actor")
click.echo(TRANS.fetch("setup", "create-key", username = "relay")) click.echo(TRANS.fetch("setup", "create-key", username = "relay"))
relay_signer = aputils.Signer.new(f"https://{ctx.obj.config.web_host}/relay") relay_signer = Signer.new(f"https://{ctx.obj.config.web_host}/relay")
s.insert("user", { s.insert("user", {
"id": -99, "id": -99,

View file

@ -1,10 +1,12 @@
import aputils
import sqlite3 import sqlite3
from aputils import MessageDate
from blib import HttpDate, Enum
from .connection import Connection from .connection import Connection
from .schema import SCHEMA from .schema import SCHEMA
sqlite3.register_adapter(aputils.Enum, lambda v: v.value) sqlite3.register_adapter(Enum, lambda v: v.value)
sqlite3.register_adapter(aputils.HttpDate, lambda v: v.timestamp()) sqlite3.register_adapter(HttpDate, lambda v: v.timestamp())
sqlite3.register_adapter(aputils.MessageDate, lambda v: v.timestamp()) sqlite3.register_adapter(MessageDate, lambda v: v.timestamp())

View file

@ -1,5 +1,5 @@
from aputils import JsonBase from blib import JsonBase, convert_to_boolean
from bsql import Row, boolean from bsql import Row
from collections.abc import Callable, Iterator, Sequence from collections.abc import Callable, Iterator, Sequence
from typing import Any, Generic, Self, TypeVar from typing import Any, Generic, Self, TypeVar
@ -9,7 +9,7 @@ from .. import TRANS
T = TypeVar("T") T = TypeVar("T")
CONVERTERS: dict[type, tuple[Callable[[str], Any], Callable[[Any], str]]] = { CONVERTERS: dict[type, tuple[Callable[[str], Any], Callable[[Any], str]]] = {
bool: (boolean, str), bool: (convert_to_boolean, str),
int: (int, str), int: (int, str),
dict: (JsonBase.parse, lambda value: JsonBase(value).to_json()), dict: (JsonBase.parse, lambda value: JsonBase(value).to_json()),
str: (str, str) str: (str, str)

View file

@ -1,11 +1,12 @@
from __future__ import annotations from __future__ import annotations
import aputils
import bsql
import secrets import secrets
from aputils import Message, MessageDate, Nodeinfo, ObjectType, Signer, Webfinger
from argon2 import PasswordHasher from argon2 import PasswordHasher
from basgi import Application as App from basgi import Application as App
from blib import HttpDate
from bsql import Connection as BsqlConnection, Row
from collections.abc import Iterator from collections.abc import Iterator
from typing import TYPE_CHECKING, Any, TypeVar from typing import TYPE_CHECKING, Any, TypeVar
@ -23,7 +24,7 @@ if TYPE_CHECKING:
T = TypeVar("T", bound = ObjectMixin) T = TypeVar("T", bound = ObjectMixin)
class Connection(bsql.Connection): class Connection(BsqlConnection):
@property @property
def app(self) -> Application: def app(self) -> Application:
return App.get("BarksharkSocial") # type: ignore[return-value] return App.get("BarksharkSocial") # type: ignore[return-value]
@ -100,8 +101,6 @@ class Connection(bsql.Connection):
if not domain: if not domain:
domain = self.config.host domain = self.config.host
print(username, domain)
with self.select("user", username = username, domain = domain) as cur: with self.select("user", username = username, domain = domain) as cur:
return User.from_possible_row(cur.one()) return User.from_possible_row(cur.one())
@ -156,7 +155,7 @@ class Connection(bsql.Connection):
description: str | None = None, description: str | None = None,
short_description: str | None = None) -> Instance: short_description: str | None = None) -> Instance:
date = aputils.HttpDate.new_utc() date = HttpDate.new_utc()
data = Instance.new( data = Instance.new(
domain = domain, domain = domain,
web_domain = web_domain, web_domain = web_domain,
@ -171,9 +170,9 @@ class Connection(bsql.Connection):
def put_instance_data(self, def put_instance_data(self,
nodeinfo: aputils.Nodeinfo, nodeinfo: Nodeinfo,
webfinger: aputils.Webfinger, webfinger: Webfinger,
actor: aputils.Message) -> Instance: actor: Message) -> Instance:
if self.get_instance(actor.domain) is not None: if self.get_instance(actor.domain) is not None:
raise KeyError("Instance already exists") raise KeyError("Instance already exists")
@ -193,7 +192,7 @@ class Connection(bsql.Connection):
except KeyError: except KeyError:
description = None description = None
current = aputils.HttpDate.new_utc() current = HttpDate.new_utc()
instance = Instance( instance = Instance(
id = 0 if has_instance else 1, id = 0 if has_instance else 1,
domain = webfinger.domain, domain = webfinger.domain,
@ -229,8 +228,8 @@ class Connection(bsql.Connection):
raise ValueError(TRANS.fetch("error", "empty-row")) raise ValueError(TRANS.fetch("error", "empty-row"))
TRANS.print("setup", "create-key", username = username) TRANS.print("setup", "create-key", username = username)
signer = aputils.Signer.new(f"https://{instance.web_domain}/user/{username}#main-key") signer = Signer.new(f"https://{instance.web_domain}/user/{username}#main-key")
date = aputils.HttpDate.new_utc() date = HttpDate.new_utc()
data = User( data = User(
id = 0 if has_user else 1, id = 0 if has_user else 1,
username = username, username = username,
@ -260,7 +259,7 @@ class Connection(bsql.Connection):
return self.insert_row(data) return self.insert_row(data)
def put_remote_user(self, actor: aputils.Message) -> User: def put_remote_user(self, actor: Message) -> User:
with self.run("get-at-least-1-user") as cur: with self.run("get-at-least-1-user") as cur:
has_user = cur.one() is not None has_user = cur.one() is not None
@ -274,7 +273,7 @@ class Connection(bsql.Connection):
date = actor.published date = actor.published
except AttributeError: except AttributeError:
date = aputils.MessageDate.now() date = MessageDate.now()
data = User( data = User(
id = 0 if has_user else 1, id = 0 if has_user else 1,
@ -288,7 +287,7 @@ class Connection(bsql.Connection):
password = None, password = None,
permission = PermissionLevel.REMOTE, permission = PermissionLevel.REMOTE,
locked = actor.manually_approves_followers, locked = actor.manually_approves_followers,
is_bot = actor.type != aputils.ObjectType.PERSON, is_bot = actor.type != ObjectType.PERSON,
private_key = None, private_key = None,
public_key = actor.pubkey, public_key = actor.pubkey,
bio = actor.get("summary", ""), bio = actor.get("summary", ""),
@ -348,7 +347,7 @@ class Connection(bsql.Connection):
def put_cookie(self, user: User, user_agent: str | None) -> Cookie: def put_cookie(self, user: User, user_agent: str | None) -> Cookie:
date = aputils.HttpDate.new_utc() date = HttpDate.new_utc()
params = { params = {
"code": secrets.token_hex(16), "code": secrets.token_hex(16),
"userid": user.id, "userid": user.id,
@ -369,7 +368,7 @@ class Connection(bsql.Connection):
pass pass
def get_count(row: bsql.Row | None) -> int: def get_count(row: Row | None) -> int:
if not row: if not row:
return 0 return 0

View file

@ -1,9 +1,10 @@
from __future__ import annotations from __future__ import annotations
import aputils
import bsql
import typing import typing
from aputils import MessageDate, Signer
from blib import HttpDate
from bsql import Row
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from typing import Any, Self from typing import Any, Self
@ -29,7 +30,7 @@ class ObjectMixin:
@classmethod @classmethod
def from_row(cls: type[Self], row: bsql.Row | dict[str, Any]) -> Self: def from_row(cls: type[Self], row: Row | dict[str, Any]) -> Self:
data = [] data = []
for key in cls.__dataclass_fields__: for key in cls.__dataclass_fields__:
@ -40,7 +41,7 @@ class ObjectMixin:
value = row[key] value = row[key]
if isinstance(value, datetime): if isinstance(value, datetime):
value = aputils.MessageDate.parse(value) value = MessageDate.parse(value)
data.append(value) data.append(value)
@ -51,7 +52,7 @@ class ObjectMixin:
@classmethod @classmethod
def from_possible_row(cls: type[Self], row: bsql.Row | dict[str, Any] | None) -> Self | None: def from_possible_row(cls: type[Self], row: Row | dict[str, Any] | None) -> Self | None:
if row is None: if row is None:
return None return None
@ -108,7 +109,7 @@ class Follow(ObjectMixin):
target.get_instance(connection).id, target.get_instance(connection).id,
followid, followid,
accept, accept,
aputils.HttpDate.new_utc() HttpDate.new_utc()
) )
@ -177,11 +178,11 @@ class User(ObjectMixin):
@property @property
def signer(self) -> aputils.Signer: def signer(self) -> Signer:
if self.private_key: if self.private_key:
return aputils.Signer(self.private_key, self.actor + "#main-key") return Signer(self.private_key, self.actor + "#main-key")
return aputils.Signer(self.public_key, self.actor + "#main-key") return Signer(self.public_key, self.actor + "#main-key")
# auth middleware stuff # auth middleware stuff

View file

@ -1,4 +1,4 @@
from aputils import IntEnum, StrEnum from blib import IntEnum, StrEnum
class PermissionLevel(IntEnum): class PermissionLevel(IntEnum):

View file

@ -9,7 +9,7 @@
#info #info
.avatar .avatar
%div %div
-if user.domain != state.config.host -if user.domain != app.config.host
%p.handle %p.handle
%a(href="{{user.page_url}}" target="_new") << @{{user.handle}} %a(href="{{user.page_url}}" target="_new") << @{{user.handle}}

View file

@ -4,8 +4,9 @@ import httpx
import logging import logging
import traceback import traceback
from aputils import JsonBase, Message, Signature, SignatureFailureError from aputils import Message, Signature, SignatureFailureError
from basgi import Client, HttpError, Request from basgi import Client, HttpError, Request
from blib import JsonBase
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from .database.connection import Connection from .database.connection import Connection

View file

@ -1,6 +1,5 @@
from importlib.resources import files as PackageFiles from importlib.resources import files as PackageFiles
from pathlib import Path from pathlib import Path
from typing import Any
def get_resource(path: str) -> Path: def get_resource(path: str) -> Path:
@ -10,31 +9,3 @@ def get_resource(path: str) -> Path:
def read_resource_text(path: str) -> str: def read_resource_text(path: str) -> str:
with get_resource(path).open("r", encoding = "utf-8") as fd: with get_resource(path).open("r", encoding = "utf-8") as fd:
return fd.read() return fd.read()
class DictProperty:
"Provide attribute access to a dict key."
def __init__(self, key: str):
self.key = key
def __get__(self, obj: dict[str, Any] | None, objtype: type[Any] | None = None) -> Any:
if obj is None:
return self
try:
return obj[self.key]
except KeyError:
objname = object.__class__.__name__
raise AttributeError(f"'{objname}' has no attribute '{self.key}'") from None
def __set__(self, obj: dict[str, Any], value: Any) -> None:
obj[self.key] = value
def __delete__(self, obj: dict[str, Any]) -> None:
del obj[self.key]

View file

@ -76,6 +76,10 @@ async def handle_undo(request: Request, message: Message, user: User) -> None:
else: else:
obj = Message.parse(message.object) obj = Message.parse(message.object)
if obj.type not in ObjectType.Actor:
print(repr(obj.type))
return
if obj.object_id != user.actor: if obj.object_id != user.actor:
logging.warning( logging.warning(
"Follow object does not match actor: msg=%s, actor=%s", "Follow object does not match actor: msg=%s, actor=%s",
@ -104,6 +108,8 @@ async def handle_undo(request: Request, message: Message, user: User) -> None:
async def process_message(request: Request, message: Message, user: User) -> None: async def process_message(request: Request, message: Message, user: User) -> None:
print(f"process_message: {user.handle}, message:", message.to_json(4), flush = True)
if request.state.user.actor != message.actor_id: if request.state.user.actor != message.actor_id:
logging.warning( logging.warning(
"Message actor and signature keyid do not match: msg=%s, actor=%s", "Message actor and signature keyid do not match: msg=%s, actor=%s",
@ -120,7 +126,7 @@ async def process_message(request: Request, message: Message, user: User) -> Non
logging.error(error.response.read()) logging.error(error.response.read())
except KeyError: except KeyError:
logging.debug("Unhandled message type: %s", message.type.name) logging.debug("Unhandled message type: %s", message.type)
except Exception: except Exception:
traceback.print_exc() traceback.print_exc()

View file

@ -59,15 +59,19 @@ async def handle_instance_actor_get(request: Request) -> Response:
async def handle_instance_actor_post(request: Request) -> Response: async def handle_instance_actor_post(request: Request) -> Response:
ensure_signed(request) ensure_signed(request)
return Response(202, "UvU") with request.app.state.database.session(False) as s:
if (user := s.get_user(request.app.state.config.host)) is None:
raise HttpError(404, "User not found")
message = Message.parse(await request.body())
asyncio.create_task(process_message(request, message, user))
return Response(200, "uvu")
@router.get("BarksharkSocial", "/relay") @router.get("BarksharkSocial", "/relay")
async def handle_relay_get(request: Request) -> Response: async def handle_relay_get(request: Request) -> Response:
config = request.app.state.config
with request.app.state.database.session(False) as s: with request.app.state.database.session(False) as s:
if (user := s.get_user("relay", config.host)) is None: if (user := s.get_user("relay")) is None:
raise HttpError(404, "User not found") raise HttpError(404, "User not found")
data = Message.new_actor( data = Message.new_actor(
@ -87,7 +91,13 @@ async def handle_relay_get(request: Request) -> Response:
async def handle_relay_post(request: Request) -> Response: async def handle_relay_post(request: Request) -> Response:
ensure_signed(request) ensure_signed(request)
return Response(202, "UvU") with request.app.state.database.session(False) as s:
if (user := s.get_user("relay")) is None:
raise HttpError(404, "User not found")
message = Message.parse(await request.body())
asyncio.create_task(process_message(request, message, user))
return Response(200, "uvu")
@router.get("BarksharkSocial", "/user/{username}") @router.get("BarksharkSocial", "/user/{username}")

View file

@ -1,6 +1,6 @@
from aputils import HttpDate
from argon2.exceptions import VerifyMismatchError from argon2.exceptions import VerifyMismatchError
from basgi import HttpError, FileResponse, Request, Response, TemplateResponse, router from basgi import HttpError, FileResponse, Request, Response, TemplateResponse, router
from blib import HttpDate
from datetime import timedelta from datetime import timedelta
from ..misc import get_resource from ..misc import get_resource

View file

@ -1,10 +1,10 @@
from __future__ import annotations from __future__ import annotations
import aputils
import os import os
import subprocess import subprocess
import sys import sys
from aputils import Signer, register_validator
from argon2 import PasswordHasher from argon2 import PasswordHasher
from bsql import Database from bsql import Database
from pathlib import Path from pathlib import Path
@ -126,8 +126,8 @@ class Application(App[Request, RequestState, AppState]):
return context return context
@aputils.register_validator(Request) @register_validator(Request)
async def handle_validate_request(signer: aputils.Signer, request: Request) -> bool: async def handle_validate_request(signer: Signer, request: Request) -> bool:
return signer.validate_signature( return signer.validate_signature(
request.method, request.method,
request.path, request.path,

5
dev.py
View file

@ -130,13 +130,8 @@ def cli_update_files() -> None:
@cli.command("exec", context_settings = {"allow_extra_args": True}) @cli.command("exec", context_settings = {"allow_extra_args": True})
@option("--watch", "-w", is_flag = True)
@pass_context @pass_context
def cli_run(ctx: Context, watch: bool = False) -> None: def cli_run(ctx: Context, watch: bool = False) -> None:
if watch:
handle_run_watcher("-m", "barkshark_social", *ctx.args)
return
run_python("-m", "barkshark_social", *ctx.args) run_python("-m", "barkshark_social", *ctx.args)

View file

@ -39,10 +39,12 @@ dependencies = [
"argon2-cffi == 23.1.0", "argon2-cffi == 23.1.0",
"barkshark-sql == 0.1.3", "barkshark-sql == 0.1.3",
"click == 8.1.7", "click == 8.1.7",
"itsdangerous == 2.1.2",
"pymemcache == 4.0.0", "pymemcache == 4.0.0",
"pyyaml == 6.0.1", "pyyaml == 6.0.1",
"uvicorn[standard] == 0.29.0" "uvicorn[standard] == 0.29.0",
"barkshark-asgi @ https://git.barkshark.xyz/barkshark/basgi/archive/main.tar.gz",
"barkshark-lib @ https://git.barkshark.xyz/barkshark/blib/archive/main.tar.gz"
] ]
[project.urls] [project.urls]