diff --git a/content/post/public-posts-with-authorized-fetch/index.md b/content/post/public-posts-with-authorized-fetch/index.md new file mode 100644 index 0000000..180a8db --- /dev/null +++ b/content/post/public-posts-with-authorized-fetch/index.md @@ -0,0 +1,96 @@ +--- +title: "I did something naughty: Circumventing Authorized-Fetch as implemented by GoToSocial" +date: 2024-12-11T06:10:10+02:00 +draft: false +image: "uploads/fediproxy/fediproxy.png" +categrories: ['English'] +tags: ['gotosocial', 'fediverse', 'mastodon', 'authorized fetch', 'rss', 'FastAPI',] +--- + +Yes the title is correct, but I had nothing malicious in mind! + +## What this is about + +For [@qzt@queereszentrumtuebingen.de](https://social.queereszentrumtuebingen.de/@qzt) we include the public feed [in a sidbar on the homepage](https://queereszentrumtuebingen.de/). 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](https://gotosocial.org/) (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](https://social.queereszentrumtuebingen.de/@qzt) 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](https://docs.gotosocial.org/en/latest/api/authentication/). 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 + +I wrote a short #FastAPI server for that. 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}"} + try: + response = requests.get(f"{EXTERNAL_API_BASE_URL}/api/v1/accounts/{account_id}/statuses", headers=headers) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + raise HTTPException(status_code=502, detail=f"Error fetching data from API: {e}") +``` + +Basically this is the whole API. I trimmed a few error checks and such. To deploy, I put it in a docker container and started it via docker-compose +``` +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 + +``` + +and 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](https://wordpress.org/plugins/include-mastodon-feed/#installation) 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](https://abramek.art/), [Creative Commons BY-SA license](http://creativecommons.org/licenses/by-sa/4.0/). \ No newline at end of file diff --git a/static/uploads/fediproxy/fediproxy.png b/static/uploads/fediproxy/fediproxy.png new file mode 100644 index 0000000..a7741a6 Binary files /dev/null and b/static/uploads/fediproxy/fediproxy.png differ