Create some quick and dirty admin endpoints
This commit is contained in:
parent
35a37aab8a
commit
e5b9b133fc
7 changed files with 90 additions and 13 deletions
|
@ -4,4 +4,4 @@ cd "$(realpath $(dirname $0))"
|
||||||
|
|
||||||
bazel build ...
|
bazel build ...
|
||||||
|
|
||||||
exec ../../bazel-bin/projects/activitypub_relay/activitypub_relay -c $(realpath ./relay.yaml) run
|
exec ../../bazel-bin/projects/activitypub_relay/activitypub_relay -c $(realpath ./relay_prod.yaml) run
|
||||||
|
|
|
@ -357,6 +357,7 @@ def relay_setup(obj: Application):
|
||||||
obj.config.listen = os.getenv("LISTEN_ADDRESS", obj.config.listen)
|
obj.config.listen = os.getenv("LISTEN_ADDRESS", obj.config.listen)
|
||||||
obj.config.port = int(os.getenv("LISTEN_PORT", obj.config.port))
|
obj.config.port = int(os.getenv("LISTEN_PORT", obj.config.port))
|
||||||
obj.config.host = os.getenv("RELAY_HOSTNAME")
|
obj.config.host = os.getenv("RELAY_HOSTNAME")
|
||||||
|
obj.config.admin_token = os.getenv("RELAY_ADMIN_TOKEN")
|
||||||
if not obj.config.host:
|
if not obj.config.host:
|
||||||
click.echo("Error: No relay host configured! Set $RELAY_HOSTNAME")
|
click.echo("Error: No relay host configured! Set $RELAY_HOSTNAME")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
|
@ -78,6 +78,7 @@ class RelayConfig(DotDict):
|
||||||
"json": 1024,
|
"json": 1024,
|
||||||
"objects": 1024,
|
"objects": 1024,
|
||||||
"digests": 1024,
|
"digests": 1024,
|
||||||
|
"admin_token": None,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
||||||
import socket
|
import socket
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
import uuid
|
import uuid
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from Crypto.Hash import SHA, SHA256, SHA512
|
from Crypto.Hash import SHA, SHA256, SHA512
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
|
@ -453,7 +454,7 @@ class Message(DotDict):
|
||||||
|
|
||||||
class Response(AiohttpResponse):
|
class Response(AiohttpResponse):
|
||||||
@classmethod
|
@classmethod
|
||||||
def new(cls, body="", status=200, headers=None, ctype="text"):
|
def new(cls, body: Union[dict, str, bytes] = "", status=200, headers=None, ctype="text"):
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"status": status,
|
"status": status,
|
||||||
"headers": headers,
|
"headers": headers,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiohttp.web import HTTPUnauthorized
|
from aiohttp.web import HTTPUnauthorized, Request
|
||||||
from relay import __version__, misc
|
from relay import __version__, misc
|
||||||
from relay.http_debug import STATS
|
from relay.http_debug import STATS
|
||||||
from relay.misc import (
|
from relay.misc import (
|
||||||
|
@ -148,7 +148,8 @@ async def inbox(request):
|
||||||
|
|
||||||
@register_route("GET", "/.well-known/webfinger")
|
@register_route("GET", "/.well-known/webfinger")
|
||||||
async def webfinger(request):
|
async def webfinger(request):
|
||||||
subject = request.query["resource"]
|
if not (subject := request.query.get("resource")):
|
||||||
|
return Response.new_error(404, "no resource specified", "json")
|
||||||
|
|
||||||
if subject != f"acct:relay@{request.app.config.host}":
|
if subject != f"acct:relay@{request.app.config.host}":
|
||||||
return Response.new_error(404, "user not found", "json")
|
return Response.new_error(404, "user not found", "json")
|
||||||
|
@ -203,5 +204,73 @@ async def nodeinfo_wellknown(request):
|
||||||
|
|
||||||
|
|
||||||
@register_route("GET", "/stats")
|
@register_route("GET", "/stats")
|
||||||
async def stats(*args, **kwargs):
|
async def stats(request):
|
||||||
return Response.new(STATS, ctype="json")
|
stats = STATS.copy()
|
||||||
|
stats["pending_requests"] = len(request.app.database.get("follow-requests", {}))
|
||||||
|
return Response.new(stats, ctype="json")
|
||||||
|
|
||||||
|
|
||||||
|
@register_route("POST", "/admin/config")
|
||||||
|
async def set_config(request: Request):
|
||||||
|
if not (auth := request.headers.get("Authorization")):
|
||||||
|
return Response.new_error(403, "access denied", "json")
|
||||||
|
|
||||||
|
if not auth == f"Bearer {request.app.config.admin_token}":
|
||||||
|
return Response.new_error(403, "access denied", "json")
|
||||||
|
|
||||||
|
# FIXME: config doesn't have a way to go from JSON or update, using dict stuff
|
||||||
|
new_config = await request.json()
|
||||||
|
|
||||||
|
request.app.config.update(new_config)
|
||||||
|
|
||||||
|
# If there are pending follows which are NOW whitelisted, allow them
|
||||||
|
if request.app.config.whitelist_enabled:
|
||||||
|
for domain in request.app.config.whitelist:
|
||||||
|
if (pending_follow := request.app.database.get_request(domain, False)):
|
||||||
|
await misc.request(
|
||||||
|
actor.shared_inbox,
|
||||||
|
misc.Message.new_response(
|
||||||
|
host=request.app.config.host,
|
||||||
|
actor=pending_follow["actor"],
|
||||||
|
followid=pending_follow["followid"],
|
||||||
|
accept=True
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
await misc.request(
|
||||||
|
actor.shared_inbox,
|
||||||
|
misc.Message.new_follow(
|
||||||
|
host=request.app.config.host,
|
||||||
|
actor=pending_follow["actor"]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
request.app.database.del_request(domain)
|
||||||
|
|
||||||
|
request.app.database.save()
|
||||||
|
|
||||||
|
request.app.config.save()
|
||||||
|
|
||||||
|
return Response.new(status=202)
|
||||||
|
|
||||||
|
|
||||||
|
@register_route("GET", "/admin/config")
|
||||||
|
def set_config(request: Request):
|
||||||
|
if not (auth := request.headers.get("Authorization")):
|
||||||
|
return Response.new_error(403, "access denied", "json")
|
||||||
|
|
||||||
|
if not auth == f"Bearer {request.app.config.admin_token}":
|
||||||
|
return Response.new_error(403, "access denied", "json")
|
||||||
|
|
||||||
|
return Response.new(request.app.config, status=200, ctype="json")
|
||||||
|
|
||||||
|
|
||||||
|
@register_route("GET", "/admin/pending")
|
||||||
|
def get_pending(request):
|
||||||
|
if not (auth := request.headers.get("Authorization")):
|
||||||
|
return Response.new_error(403, "access denied", "json")
|
||||||
|
|
||||||
|
if not auth == f"Bearer {request.app.config.admin_token}":
|
||||||
|
return Response.new_error(403, "access denied", "json")
|
||||||
|
|
||||||
|
return Response.new(request.app.database["follow-requests"], status=200, ctype="json")
|
||||||
|
|
|
@ -4,12 +4,8 @@
|
||||||
ichor entrypoint
|
ichor entrypoint
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from ichor.bootstrap import (
|
|
||||||
BOOTSTRAP,
|
|
||||||
NOT1,
|
|
||||||
TRUE,
|
|
||||||
)
|
|
||||||
from ichor import isa
|
from ichor import isa
|
||||||
|
from ichor.bootstrap import BOOTSTRAP, NOT1, TRUE
|
||||||
from ichor.interpreter import Interpreter
|
from ichor.interpreter import Interpreter
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import typing as t
|
import typing as t
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from shoggoth.types import Keyword, List, Symbol
|
from shoggoth.types import Keyword, List, Symbol
|
||||||
|
|
||||||
|
@ -85,12 +86,20 @@ class Multiverse:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
soup: t.Dict[]
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Namespace:
|
class Namespace:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Entry:
|
||||||
|
ns: "Namespace"
|
||||||
|
expr: t.Any
|
||||||
|
value: t.Any
|
||||||
|
|
||||||
|
soup: t.Dict[UUID, Entry] = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
BOOTSTRAP = "lang.shoggoth.v0.bootstrap"
|
BOOTSTRAP = "lang.shoggoth.v0.bootstrap"
|
||||||
SPECIALS = Multiverse.Namespace(Symbol(BOOTSTRAP), {
|
SPECIALS = Multiverse.Namespace(Symbol(BOOTSTRAP), {
|
||||||
|
|
Loading…
Reference in a new issue