foxhole/app/main.py

213 lines
5.7 KiB
Python
Raw Normal View History

2022-11-16 10:29:44 +01:00
#!/usr/bin/env python3
2023-07-29 12:22:16 +02:00
"""Some application path."""
from typing import Any
import sys
2022-11-16 10:29:44 +01:00
import fastapi
from fastapi import FastAPI
from fastapi import Depends
from fastapi import Request
from fastapi import Response
from fastapi.exceptions import HTTPException
from starlette.responses import JSONResponse
from loguru import logger
2023-07-29 12:22:16 +02:00
from sqlalchemy import select
2023-07-29 12:22:16 +02:00
from app import models
from app.utils import precheck
from app.database import get_db_session
2023-03-17 10:59:29 +01:00
from app.config import DEBUG
from app.activitypub import ME
2023-04-02 09:14:37 +02:00
from app.config import BASE_URL, POST_TOKEN
from app.config import DOMAIN
from app.config import ID
from app.config import USERNAME
2023-03-18 06:08:17 +01:00
from app.config import VERSION
from app.database import AsyncSession
2023-03-17 10:59:29 +01:00
from app.boxes import save_incoming
2023-07-29 12:22:16 +02:00
from app.activitypub import VisibilityEnum
from app.boxes import _send_create
from app.orgpython import to_html
2022-11-16 10:29:44 +01:00
2022-11-16 10:29:44 +01:00
def _check_0rtt_early_data(request: Request) -> None:
"""Disable TLS1.3 0-RTT requests for non-GET."""
if request.headers.get("Early-Data", None) == "1" and request.method != "GET":
raise fastapi.HTTPException(status_code=425, detail="Too early")
app = FastAPI(
docs_url=None, redoc_url=None, dependencies=[Depends(_check_0rtt_early_data)]
)
logger.remove()
logger.add(sys.stdout, level="DEBUG" if DEBUG else "INFO")
2023-03-18 15:39:46 +01:00
logger.add("output.log", level="DEBUG")
2023-07-29 12:22:16 +02:00
class ActivityPubResponse(JSONResponse): #pylint: disable=too-few-public-methods
"""Simple wrap JSONresponse return ActivityPub response."""
media_type = "application/activity+json"
2023-04-03 09:16:46 +02:00
def is_ap_requested(req: Request) -> bool:
2023-07-29 12:22:16 +02:00
"""Check resquest is ActivityPub request."""
2023-04-03 09:16:46 +02:00
accept_value = req.headers.get("accept")
if not accept_value:
return False
2023-07-29 12:22:16 +02:00
for i in [
2023-04-03 09:16:46 +02:00
"application/activity+json",
"application/ld+json",
2023-07-29 12:22:16 +02:00
]:
2023-04-03 09:16:46 +02:00
if accept_value.startswith(i):
return True
return False
2022-11-16 10:29:44 +01:00
@app.get("/")
2023-04-03 09:16:46 +02:00
async def index(
request: Request
):
2023-07-29 12:22:16 +02:00
"""Return index page."""
2023-04-03 09:16:46 +02:00
if is_ap_requested(request):
return ActivityPubResponse(ME)
return ME
2023-07-29 12:22:16 +02:00
@app.post("/inbox")
async def inbox(
request: Request,
db_session: AsyncSession = Depends(get_db_session),
httpsig_checker = Depends(precheck.inbox_prechecker),
) -> Response:
2023-07-29 12:22:16 +02:00
"""ActivityPub inbox endpoint."""
payload = await request.json()
2023-03-17 09:49:09 +01:00
2023-07-29 12:22:16 +02:00
if not httpsig_checker:
2023-04-06 11:36:49 +02:00
return Response(
status_code=406,
content="invalid http-sig"
)
2023-07-29 12:22:16 +02:00
if not await save_incoming(db_session, payload):
return Response(
status_code=406,
content="invalid activitypub object"
)
return Response(status_code=202)
2023-04-02 09:14:37 +02:00
@app.post("/outbox")
async def outbox(
request: Request,
db_session: AsyncSession = Depends(get_db_session),
) -> Response:
2023-07-29 12:22:16 +02:00
"""ActivityPub outbox endpoint, now only process client post request."""
2023-04-02 09:14:37 +02:00
payload = await request.body()
2023-04-02 09:48:17 +02:00
content = payload.decode("utf-8")
logger.info(content)
post_token = request.headers.get("Authorization")
2023-04-02 09:14:37 +02:00
2023-07-29 12:22:16 +02:00
if POST_TOKEN is None \
and POST_TOKEN != post_token.split(" ")[1]: # type: ignore
logger.warning("Non-valid post token!")
return Response(status_code=406)
2023-04-02 09:14:37 +02:00
2023-07-29 12:22:16 +02:00
logger.info("True token")
2023-04-02 09:14:37 +02:00
2023-07-29 12:22:16 +02:00
content = to_html(content).replace("\n", "")
2023-04-02 09:14:37 +02:00
2023-07-29 12:22:16 +02:00
await _send_create(
db_session,
"Note",
content,
VisibilityEnum.PUBLIC
)
return Response(status_code=200)
2023-04-02 09:14:37 +02:00
@app.get("/tail/{public_id}")
async def outbox_activity_by_public_id(
public_id: str,
db_session: AsyncSession = Depends(get_db_session),
) -> ActivityPubResponse:
2023-07-29 12:22:16 +02:00
"""Return note page."""
outbox_object = (
await db_session.execute(
select(models.OutboxObject).where(
models.OutboxObject.public_id == public_id,
models.OutboxObject.is_deleted.is_(False),
)
)
).scalar_one_or_none()
if not outbox_object:
raise HTTPException(status_code=404)
return ActivityPubResponse(outbox_object.ap_object)
@app.get("/.well-known/webfinger")
async def wellknown_webfinger(resource: str) -> JSONResponse:
"""Exposes/servers WebFinger data."""
if resource not in [f"acct:{USERNAME}@{DOMAIN}", ID]:
logger.info(f"Got invalid req for {resource}")
raise HTTPException(status_code=404)
out = {
"subject": f"acct:{USERNAME}@{DOMAIN}",
"aliases": [ID],
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": ID + "/",
},
{"rel": "self", "type": "application/activity+json", "href": ID},
{
"rel": "http://ostatus.org/schema/1.0/subscribe",
"template": BASE_URL + "/admin/lookup?query={uri}",
},
],
}
return JSONResponse(
out,
media_type="application/jrd+json; charset=utf-8",
headers={"Access-Control-Allow-Origin": "*"},
)
2023-03-18 05:56:54 +01:00
2023-07-29 12:22:16 +02:00
2023-03-18 05:56:54 +01:00
@app.get("/.well-known/nodeinfo")
async def well_known_nodeinfo() -> dict[str, Any]:
2023-07-29 12:22:16 +02:00
"""Exposes nodeinfo path."""
2023-03-18 05:56:54 +01:00
return {
"links": [
{
"rel": "http://nodeinfo.diaspora.software/ns/schema/2.0",
"href": f"{BASE_URL}/nodeinfo/2.0",
}
]
}
@app.get("/nodeinfo/2.0")
2023-07-29 12:22:16 +02:00
async def nodeinfo():
"""Return site nodeinfo."""
2023-03-18 05:56:54 +01:00
return JSONResponse(
{
"version": "2.0",
"software": {
"name": "foxhole",
2023-03-18 06:08:17 +01:00
"version": VERSION,
2023-03-18 05:56:54 +01:00
},
"protocols": ["activitypub"],
"services": {"inbound": [], "outbound": []},
"usage": {"users": {"total": 1}, "localPosts": 0}, #TODO
"openRegistrations": False,
"metadata": {},
},
)