This repository has been archived on 2026-02-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
federation/bp/wellknown/routes.py
giles 41e9670975
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 41s
Initial federation app — ActivityPub server for Rose-Ash
Phase 0+1 of AP integration. New 5th Quart microservice:

Blueprints:
- wellknown: WebFinger, NodeInfo 2.0, host-meta
- actors: AP actor profiles (JSON-LD + HTML), outbox, inbox, followers
- identity: username selection flow (creates ActorProfile + RSA keypair)
- auth: magic link login/logout (ported from blog, self-contained)

Services:
- Registers SqlFederationService (real impl) for federation domain
- Registers real impls for blog, calendar, market, cart
- All cross-domain via shared service contracts

Templates:
- Actor profiles, username selection, platform home
- Auth login/check-email (ported from blog)

Infrastructure:
- Dockerfile + entrypoint.sh (matches other apps)
- CI/CD via Gitea Actions
- shared/ as git submodule

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 15:11:52 +00:00

115 lines
3.7 KiB
Python

"""Well-known federation endpoints: WebFinger, NodeInfo, host-meta.
Ported from ~/art-dag/activity-pub/app/routers/federation.py.
"""
from __future__ import annotations
import os
from quart import Blueprint, request, abort, Response, g
from shared.services.registry import services
def _domain() -> str:
return os.getenv("AP_DOMAIN", "rose-ash.com")
def register(url_prefix=""):
bp = Blueprint("wellknown", __name__, url_prefix=url_prefix)
@bp.get("/.well-known/webfinger")
async def webfinger():
resource = request.args.get("resource", "")
if not resource.startswith("acct:"):
abort(400, "Invalid resource format")
parts = resource[5:].split("@")
if len(parts) != 2:
abort(400, "Invalid resource format")
username, domain = parts
if domain != _domain():
abort(404, "User not on this server")
actor = await services.federation.get_actor_by_username(g.s, username)
if not actor:
abort(404, "User not found")
domain = _domain()
return Response(
response=__import__("json").dumps({
"subject": resource,
"aliases": [f"https://{domain}/users/{username}"],
"links": [
{
"rel": "self",
"type": "application/activity+json",
"href": f"https://{domain}/users/{username}",
},
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": f"https://{domain}/users/{username}",
},
],
}),
content_type="application/jrd+json",
)
@bp.get("/.well-known/nodeinfo")
async def nodeinfo_index():
domain = _domain()
return Response(
response=__import__("json").dumps({
"links": [
{
"rel": "http://nodeinfo.diaspora.software/ns/schema/2.0",
"href": f"https://{domain}/nodeinfo/2.0",
}
]
}),
content_type="application/json",
)
@bp.get("/nodeinfo/2.0")
async def nodeinfo():
stats = await services.federation.get_stats(g.s)
return Response(
response=__import__("json").dumps({
"version": "2.0",
"software": {
"name": "rose-ash",
"version": "1.0.0",
},
"protocols": ["activitypub"],
"usage": {
"users": {
"total": stats.get("actors", 0),
"activeMonth": stats.get("actors", 0),
},
"localPosts": stats.get("activities", 0),
},
"openRegistrations": False,
"metadata": {
"nodeName": "Rose Ash",
"nodeDescription": "Cooperative platform with ActivityPub federation",
},
}),
content_type="application/json",
)
@bp.get("/.well-known/host-meta")
async def host_meta():
domain = _domain()
xml = (
'<?xml version="1.0" encoding="UTF-8"?>\n'
'<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">\n'
f' <Link rel="lrdd" type="application/xrd+xml" '
f'template="https://{domain}/.well-known/webfinger?resource={{uri}}"/>\n'
'</XRD>'
)
return Response(response=xml, content_type="application/xrd+xml")
return bp