5.3 KiB
title | date | draft | image | categrories | tags | |||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
I did something naughty: Circumventing Authorized-Fetch as implemented by GoToSocial | 2024-12-11T06:10:10+02:00 | false | uploads/fediproxy/fediproxy.png |
|
|
Yes the title is correct, but I had nothing malicious in mind!
What this is about
For @qzt@queereszentrumtuebingen.de we include the public feed in a sidbar on the homepage. Initially this was done using the standard API to fetch statuses /api/v1/accounts/{account_id}/statuses
and worked like a charm. The problem started when GoToSocial (the fediverse server we use, similar to mastodon) implemented authorized fetch. This is a a good thing! Authorized fetch means, that every call to a endpoint needs to be authorized by an access_token
. You get an access token from a fedi account. It's what fediverse clients like Tusky or Phanpy do on your behalf to get the posts that make up you timeline.
Authorized fetch has major advantages as
- data scraping can only be done by other fediaccounts
- blocking can not be circumvented by using the public API
and much more. Sadly it also broke our website integration.
Possible Solutions
So what now? I initially wanted to turn of authorized fetch for @qzt@queereszentrumtuebingen.de by messing with the GoToSocial code and turning it off for the whole server. This would have been possible as this is the only user on the server. The GoToSocial devs helped me manage to find where to do that. But it's not ideal and would make me build a custom docker image fore each update.
Next idea: The whole point of authorized fetch is, that only fedi-accounts (and apps they authorized) can access the API. So lets do that! Set up a new account, add app and authorize it as described in the GoToSocial documentation. I used #Bruno for that, that was much more comfortable than using curl for me. With that authorization code you can now get an access token for your app. Put that in the Javascript that loads posts and we are good right? Sadly no. It would totally work. But it would also allow anyone to read and post on behalf of the account. That calls for malicious actors using this for scraping or spamming.
So instead, we need a proxy that stores the access token securely and restricts the actions.
The proxy
Such a proxie must
- offer the endpoint that provides the same data as the FediverseAPI
- authorize itself to the FediverseAPI via
access_token
- restrict to read access of consenting accounts
The last point is really important, as we don't want to allow others to use this endpoint to scrape data unauthorized.
I wrote a short FastAPI server that offers this. It only implements one method
@app.get("/api/v1/accounts/{account_id}/statuses")
async def fetch_data(account_id):
if account_id not in ALLOWED_ACCOUNTS:
raise HTTPException(status_code=401, detail="You can only use this proxy to access configured accounts")
headers = {"Authorization": f"Bearer {ACCESS_TOKEN}"}
response = requests.get(f"{EXTERNAL_API_BASE_URL}/api/v1/accounts/{account_id}/statuses", headers=headers)
return response.json()
Basically this is the whole API code, I only trimmed a few checks and error handling.
Deployment
To deploy, I put it in a docker container and started it via docker-compose. Reverse proxing is handled by Traefik, I won't go into detail here.
services:
fediproxy.example.org:
image: docker.io/moanos/fediproxy
container_name: "fediproxy.example.org"
restart: unless-stopped
environment:
EXTERNAL_API_BASE_URL: ${EXTERNAL_API_BASE_URL}
ACCESS_TOKEN: ${ACCESS_TOKEN}
ALLOWED_ACCOUNTS: ${ALLOWED_ACCOUNTS}
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik"
- "traefik.http.routers.fediproxy.rule=Host(`fediproxy.example.org`)"
- "traefik.http.routers.fediproxy.service=fediproxy-service"
- "traefik.http.routers.fediproxy.entrypoints=web-secure"
- "traefik.http.routers.fediproxy.tls=true"
- "traefik.http.routers.fediproxy.tls.certResolver=default"
- "traefik.http.services.fediproxy-service.loadbalancer.server.port=8000"
networks:
- traefik
networks:
traefik:
name: "traefik"
external: true
I added a short .env
to configure:
ACCESS_TOKEN=VERYSECRETTOKENTHATISDEFINETLYREAL
EXTERNAL_API_BASE_URL=https://gay-pirate-assassins.de
ALLOWED_ACCOUNTS=ZGGZF4G8NNOTREAL81Z8G7RTC
Results
Now I can again use something like the wordpress plugin Include Mastodon Feed just by pointing to the proxy: [include-mastodon-feed instance="fediproxy.example.org.de" account="ZGGZF4G8NNOTREAL81Z8G7RTC"]
Hope you enjoyed the read. Source code for the proxy can be found here: https://git.hyteck.de/moanos/FediProxy If you want to play around a bit you can use https://git.hyteck.de/moanos/include-fedi
Sloth logo of GTS by Anna Abramek, Creative Commons BY-SA license.