From 1472d92a8d36bf1900c8f2887ecd4519649b8193 Mon Sep 17 00:00:00 2001
From: tobi <31960611+tsmethurst@users.noreply.github.com>
Date: Thu, 23 Jan 2025 14:42:31 +0000
Subject: [PATCH 1/3] [feature] Add `published` property to outgoing AP Actor
representations (#3671)
---
internal/typeutils/internaltoas.go | 6 ++++++
internal/typeutils/internaltoas_test.go | 6 ++++++
2 files changed, 12 insertions(+)
diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go
index 644d832f5..de1badb5c 100644
--- a/internal/typeutils/internaltoas.go
+++ b/internal/typeutils/internaltoas.go
@@ -53,6 +53,12 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
idProp.SetIRI(profileIDURI)
person.SetJSONLDId(idProp)
+ // published
+ // The moment when the account was created.
+ publishedProp := streams.NewActivityStreamsPublishedProperty()
+ publishedProp.Set(a.CreatedAt)
+ person.SetActivityStreamsPublished(publishedProp)
+
// following
// The URI for retrieving a list of accounts this user is following
followingURI, err := url.Parse(a.FollowingURI)
diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go
index c847cfc93..344a42798 100644
--- a/internal/typeutils/internaltoas_test.go
+++ b/internal/typeutils/internaltoas_test.go
@@ -86,6 +86,7 @@ func (suite *InternalToASTestSuite) TestAccountToAS() {
"owner": "http://localhost:8080/users/the_mighty_zork",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXTcOAvM1Jiw5Ffpk0qn\nr0cwbNvFe/5zQ+Tp7tumK/ZnT37o7X0FUEXrxNi+dkhmeJ0gsaiN+JQGNUewvpSk\nPIAXKvi908aSfCGjs7bGlJCJCuDuL5d6m7hZnP9rt9fJc70GElPpG0jc9fXwlz7T\nlsPb2ecatmG05Y4jPwdC+oN4MNCv9yQzEvCVMzl76EJaM602kIHC1CISn0rDFmYd\n9rSN7XPlNJw1F6PbpJ/BWQ+pXHKw3OEwNTETAUNYiVGnZU+B7a7bZC9f6/aPbJuV\nt8Qmg+UnDvW1Y8gmfHnxaWG2f5TDBvCHmcYtucIZPLQD4trAozC4ryqlmCWQNKbt\n0wIDAQAB\n-----END PUBLIC KEY-----\n"
},
+ "published": "2022-05-20T11:09:18Z",
"summary": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"tag": [],
"type": "Person",
@@ -150,6 +151,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithFields() {
"owner": "http://localhost:8080/users/1happyturtle",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtTc6Jpg6LrRPhVQG4KLz\n2+YqEUUtZPd4YR+TKXuCnwEG9ZNGhgP046xa9h3EWzrZXaOhXvkUQgJuRqPrAcfN\nvc8jBHV2xrUeD8pu/MWKEabAsA/tgCv3nUC47HQ3/c12aHfYoPz3ufWsGGnrkhci\nv8PaveJ3LohO5vjCn1yZ00v6osMJMViEZvZQaazyE9A8FwraIexXabDpoy7tkHRg\nA1fvSkg4FeSG1XMcIz2NN7xyUuFACD+XkuOk7UqzRd4cjPUPLxiDwIsTlcgGOd3E\nUFMWVlPxSGjY2hIKa3lEHytaYK9IMYdSuyCsJshd3/yYC9LqxZY2KdlKJ80VOVyh\nyQIDAQAB\n-----END PUBLIC KEY-----\n"
},
+ "published": "2022-06-04T13:12:00Z",
"summary": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
"tag": [],
"type": "Person",
@@ -231,6 +233,7 @@ func (suite *InternalToASTestSuite) TestAccountToASAliasedAndMoved() {
"owner": "http://localhost:8080/users/the_mighty_zork",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXTcOAvM1Jiw5Ffpk0qn\nr0cwbNvFe/5zQ+Tp7tumK/ZnT37o7X0FUEXrxNi+dkhmeJ0gsaiN+JQGNUewvpSk\nPIAXKvi908aSfCGjs7bGlJCJCuDuL5d6m7hZnP9rt9fJc70GElPpG0jc9fXwlz7T\nlsPb2ecatmG05Y4jPwdC+oN4MNCv9yQzEvCVMzl76EJaM602kIHC1CISn0rDFmYd\n9rSN7XPlNJw1F6PbpJ/BWQ+pXHKw3OEwNTETAUNYiVGnZU+B7a7bZC9f6/aPbJuV\nt8Qmg+UnDvW1Y8gmfHnxaWG2f5TDBvCHmcYtucIZPLQD4trAozC4ryqlmCWQNKbt\n0wIDAQAB\n-----END PUBLIC KEY-----\n"
},
+ "published": "2022-05-20T11:09:18Z",
"summary": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"tag": [],
"type": "Person",
@@ -292,6 +295,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithOneField() {
"owner": "http://localhost:8080/users/1happyturtle",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtTc6Jpg6LrRPhVQG4KLz\n2+YqEUUtZPd4YR+TKXuCnwEG9ZNGhgP046xa9h3EWzrZXaOhXvkUQgJuRqPrAcfN\nvc8jBHV2xrUeD8pu/MWKEabAsA/tgCv3nUC47HQ3/c12aHfYoPz3ufWsGGnrkhci\nv8PaveJ3LohO5vjCn1yZ00v6osMJMViEZvZQaazyE9A8FwraIexXabDpoy7tkHRg\nA1fvSkg4FeSG1XMcIz2NN7xyUuFACD+XkuOk7UqzRd4cjPUPLxiDwIsTlcgGOd3E\nUFMWVlPxSGjY2hIKa3lEHytaYK9IMYdSuyCsJshd3/yYC9LqxZY2KdlKJ80VOVyh\nyQIDAQAB\n-----END PUBLIC KEY-----\n"
},
+ "published": "2022-06-04T13:12:00Z",
"summary": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
"tag": [],
"type": "Person",
@@ -353,6 +357,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithEmoji() {
"owner": "http://localhost:8080/users/the_mighty_zork",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXTcOAvM1Jiw5Ffpk0qn\nr0cwbNvFe/5zQ+Tp7tumK/ZnT37o7X0FUEXrxNi+dkhmeJ0gsaiN+JQGNUewvpSk\nPIAXKvi908aSfCGjs7bGlJCJCuDuL5d6m7hZnP9rt9fJc70GElPpG0jc9fXwlz7T\nlsPb2ecatmG05Y4jPwdC+oN4MNCv9yQzEvCVMzl76EJaM602kIHC1CISn0rDFmYd\n9rSN7XPlNJw1F6PbpJ/BWQ+pXHKw3OEwNTETAUNYiVGnZU+B7a7bZC9f6/aPbJuV\nt8Qmg+UnDvW1Y8gmfHnxaWG2f5TDBvCHmcYtucIZPLQD4trAozC4ryqlmCWQNKbt\n0wIDAQAB\n-----END PUBLIC KEY-----\n"
},
+ "published": "2022-05-20T11:09:18Z",
"summary": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"tag": {
"icon": {
@@ -427,6 +432,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithSharedInbox() {
"owner": "http://localhost:8080/users/the_mighty_zork",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXTcOAvM1Jiw5Ffpk0qn\nr0cwbNvFe/5zQ+Tp7tumK/ZnT37o7X0FUEXrxNi+dkhmeJ0gsaiN+JQGNUewvpSk\nPIAXKvi908aSfCGjs7bGlJCJCuDuL5d6m7hZnP9rt9fJc70GElPpG0jc9fXwlz7T\nlsPb2ecatmG05Y4jPwdC+oN4MNCv9yQzEvCVMzl76EJaM602kIHC1CISn0rDFmYd\n9rSN7XPlNJw1F6PbpJ/BWQ+pXHKw3OEwNTETAUNYiVGnZU+B7a7bZC9f6/aPbJuV\nt8Qmg+UnDvW1Y8gmfHnxaWG2f5TDBvCHmcYtucIZPLQD4trAozC4ryqlmCWQNKbt\n0wIDAQAB\n-----END PUBLIC KEY-----\n"
},
+ "published": "2022-05-20T11:09:18Z",
"summary": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"tag": [],
"type": "Person",
From b42cb7a802096762cbffb0fa1177c8355898cc1c Mon Sep 17 00:00:00 2001
From: tobi <31960611+tsmethurst@users.noreply.github.com>
Date: Thu, 23 Jan 2025 14:48:09 +0000
Subject: [PATCH 2/3] [feature] Add warning about `trusted-proxies` to make
config easier (#3675)
* [feature] Add warning about `trusted-proxies` to make config easier
* thank you linter, hugs and kisses to you
---
docs/api/ratelimiting.md | 4 +-
docs/configuration/general.md | 4 +-
docs/configuration/trusted_proxies.md | 71 ++++++++++++++++++
docs/getting_started/reverse_proxy/index.md | 4 ++
example/docker-compose/docker-compose.yaml | 7 +-
internal/api/util/template.go | 79 +++++++++++++++++++++
mkdocs.yml | 1 +
web/source/css/page.css | 21 +++++-
web/template/page_header.tmpl | 26 +++++++
9 files changed, 208 insertions(+), 9 deletions(-)
create mode 100644 docs/configuration/trusted_proxies.md
diff --git a/docs/api/ratelimiting.md b/docs/api/ratelimiting.md
index d99f4d379..a3ee7baca 100644
--- a/docs/api/ratelimiting.md
+++ b/docs/api/ratelimiting.md
@@ -24,11 +24,11 @@ In case the rate limit is exceeded, an [HTTP 429 Too Many Requests](https://deve
### My rate limit keeps being exceeded! Why?
-If you find that your rate limit is regularly being exceeded (both for yourself and other callers) during normal use of your instance, it may be that GoToSocial can't tell the clients apart by IP address. You can investigate this by viewing the logs of your instance. If (almost) all logged IP addresses appear to be the same IP address (something like `172.x.x.x`), then the rate limiting will cause problems.
+If you find that your rate limit is regularly being exceeded (both for yourself and other callers) during normal use of your instance, it may be that GoToSocial can't tell the clients apart by IP address. You can investigate this by viewing the logs of your instance. If (almost) all logged client IP addresses appear to be the same IP address (something like `172.x.x.x`), then the rate limiting will cause problems.
This happens when your server is running inside NAT (port forwarding), or behind an HTTP proxy without the correct configuration, causing your instance to see all incoming IP addresses as the same address: namely, the IP address of your reverse proxy or gateway. This means that all incoming requests are *sharing the same rate limit*, rather than being split correctly per IP.
-If you are using an HTTP proxy then it's likely that your `trusted-proxies` is not correctly configured. If this is the case, try adding the IP address of your reverse proxy to the list of `trusted-proxies`, and restarting your instance.
+If you are using an HTTP proxy then it's likely that your `trusted-proxies` is not correctly configured. See the [trusted-proxies](../configuration/trusted_proxies.md) documentation for more info on how to resolve this.
If you don't have an HTTP proxy, then it's likely caused by NAT. In this case you should disable rate limiting altogether.
diff --git a/docs/configuration/general.md b/docs/configuration/general.md
index 50b04bbe1..16589c842 100644
--- a/docs/configuration/general.md
+++ b/docs/configuration/general.md
@@ -1,8 +1,6 @@
# General
-The top-level configuration for GoToSocial, including basic things like host, port, bind address and transport protocol.
-
-The only things you *really* need to set here are `host`, which should be the hostname where your instance is reachable, and probably `port`.
+The top-level configuration for GoToSocial, including basic things like host, port, bind address, and trusted-proxies.
## Settings
diff --git a/docs/configuration/trusted_proxies.md b/docs/configuration/trusted_proxies.md
new file mode 100644
index 000000000..6852e22e1
--- /dev/null
+++ b/docs/configuration/trusted_proxies.md
@@ -0,0 +1,71 @@
+# Trusted Proxies
+
+To correctly enforce [rate limiting](../api/ratelimiting.md), GoToSocial relies on the concept of "trusted proxies" in order to accurately determine the IP address of clients accessing your server.
+
+A "trusted proxy" is an intermediate network hop that GoToSocial can be instructed to trust to provide a correct client IP address.
+
+For example, if you are running in a reverse proxy configuration with Docker + Nginx, then the Docker network address of Nginx should be configured as a trusted proxy, since all traffic from the wider internet will come into GoToSocial via Nginx.
+
+Without setting `trusted-proxies` correctly, GoToSocial will see all incoming client IP addresses as the same address, which leads to rate limiting issues, since GoToSocial uses client IP addresses to bucket rate limits.
+
+## tl;dr: How to set `trusted-proxies` correctly
+
+If your `trusted-proxies` setting is not correctly configured, you may see the following warning on the web view of your instance (v0.18.0 and above):
+
+> Warning! It looks like trusted-proxies is not set correctly in this instance's configuration. This may cause rate-limiting issues and, by extension, federation issues.
+>
+> If you are the instance admin, you should fix this by adding `SUGGESTED_IP_RANGE` to your trusted-proxies.
+
+To resolve this, copy the IP range in the message, and edit your `config.yaml` file to add the IP range to your `trusted-proxies`.
+
+!!! tip "You may be getting rate limited even if you don't see the above warning!"
+ If you're on a version of GoToSocial below v0.18.0, or you're running behind a CDN such as Cloudflare (not recommended), you won't see a warning message. Instead, you'll see in your GoToSocial logs that all client IPs are the same address. In this case, take the recurring client IP value as `SUGGESTED_IP_RANGE`.
+
+In this example, we assume `SUGGESTED_IP_RANGE` to be `172.17.0.1/16` (the default Docker bridge network subnet).
+
+Before (default config):
+
+```yaml
+trusted-proxies:
+ - "127.0.0.1/32"
+ - "::1"
+```
+
+After (new config):
+
+```yaml
+trusted-proxies:
+ - "172.17.0.1/16"
+ - "127.0.0.1/32"
+ - "::1"
+```
+
+If you are using [environment variables](../configuration/index.md#environment-variables) to configure your instance, you can configure `trusted-proxies` by setting the environment variable `GTS_TRUSTED_PROXIES` to a comma-separated list of IP ranges, like so:
+
+```env
+GTS_TRUSTED_PROXIES="172.17.0.1/16,127.0.0.1/32,::1"
+```
+
+If you are using docker compose, your docker-compose.yaml file should look something like this after the change (note that yaml uses `: ` and not `=`):
+
+```yaml
+################################
+# BLAH BLAH OTHER CONFIG STUFF #
+################################
+ environment:
+ ############################
+ # BLAH BLAH OTHER ENV VARS #
+ ############################
+ ## For reverse proxy setups:
+ GTS_TRUSTED_PROXIES: "172.17.0.1/16,127.0.0.1/32,::1"
+################################
+# BLAH BLAH OTHER CONFIG STUFF #
+################################
+```
+
+Once you have made the necessary configuration changes, restart your instance and refresh the home page. If the message is gone, then the problem is resolved!
+
+If you still see the warning message but with a different suggested IP range to add to `trusted-proxies`, then follow the same steps as above again, including the new suggested IP range in your config in addition to the one you just added.
+
+!!! tip "Cloudflare IP Addresses"
+ If you are running with a CDN/proxy such as Cloudflare in front of your GoToSocial instance (not recommended), then you may need to add one or more of the Cloudflare IP addresses to your `trusted-proxies` in order to have rate limiting work properly. You can find a list of Cloudflare IP addresses here: https://www.cloudflare.com/ips/
diff --git a/docs/getting_started/reverse_proxy/index.md b/docs/getting_started/reverse_proxy/index.md
index 927968df0..8b11f937a 100644
--- a/docs/getting_started/reverse_proxy/index.md
+++ b/docs/getting_started/reverse_proxy/index.md
@@ -41,3 +41,7 @@ We have guides available for the following servers:
When using a reverse-proxy, special care must be taken to allow WebSockets to work too. This is necessary as many client applications use WebSockets to stream your timeline. WebSockets is not used as part of federation.
Make sure you read the [WebSocket](websocket.md) documentation and configure your reverse proxy accordingly.
+
+## Trusted Proxies
+
+When using a reverse-proxy, you may run into issues with rate limiting and `trusted-proxies`. Check the [trusted proxies](../../configuration/trusted_proxies.md) documentation if you have any problems.
diff --git a/example/docker-compose/docker-compose.yaml b/example/docker-compose/docker-compose.yaml
index 05896d724..fbd9af8dd 100644
--- a/example/docker-compose/docker-compose.yaml
+++ b/example/docker-compose/docker-compose.yaml
@@ -1,5 +1,3 @@
-version: "3.3"
-
services:
gotosocial:
image: superseriousbusiness/gotosocial:latest
@@ -24,7 +22,7 @@ services:
# Wazero compilation cache will be stored.
GTS_WAZERO_COMPILATION_CACHE: /gotosocial/.cache
## For reverse proxy setups:
- # GTS_TRUSTED_PROXIES: "172.x.x.x"
+ GTS_TRUSTED_PROXIES: "172.18.0.1/16"
## Set the timezone of your server:
#TZ: UTC
ports:
@@ -47,3 +45,6 @@ networks:
gotosocial:
ipam:
driver: default
+ config:
+ - subnet: "172.18.0.0/16"
+ gateway: "172.18.0.1"
diff --git a/internal/api/util/template.go b/internal/api/util/template.go
index b8c710c3c..fcfd80956 100644
--- a/internal/api/util/template.go
+++ b/internal/api/util/template.go
@@ -18,6 +18,7 @@
package util
import (
+ "net"
"net/http"
"github.com/gin-gonic/gin"
@@ -63,6 +64,11 @@ type WebPage struct {
// ogMeta, stylesheets, javascript, and any extra
// properties will be provided to the template if
// set, but can all be nil.
+//
+// TemplateWebPage also checks whether the requesting
+// clientIP is 127.0.0.1 or within a private IP range.
+// If so, it injects a suggestion into the page header
+// about setting trusted-proxies correctly.
func TemplateWebPage(
c *gin.Context,
page WebPage,
@@ -74,13 +80,86 @@ func TemplateWebPage(
"javascript": page.Javascript,
}
+ // Add extras to template object.
for k, v := range page.Extra {
obj[k] = v
}
+ // Inject trustedProxiesRec to template
+ // object (or noop if not necessary).
+ injectTrustedProxiesRec(c, obj)
+
templatePage(c, page.Template, http.StatusOK, obj)
}
+func injectTrustedProxiesRec(
+ c *gin.Context,
+ obj map[string]any,
+) {
+ clientIP := c.ClientIP()
+ if clientIP == "127.0.0.1" {
+ // Suggest precise 127.0.0.1/32.
+ trustedProxiesRec := clientIP + "/32"
+ obj["trustedProxiesRec"] = trustedProxiesRec
+ return
+ }
+
+ // True if "X-Forwarded-For"
+ // or "X-Real-IP" were set.
+ var hasRemoteIPHeader bool
+ for _, k := range []string{
+ "X-Forwarded-For",
+ "X-Real-IP",
+ } {
+ if v := c.GetHeader(k); v != "" {
+ hasRemoteIPHeader = true
+ break
+ }
+ }
+
+ if !hasRemoteIPHeader {
+ // Upstream hasn't set a
+ // remote IP header, bail.
+ return
+ }
+
+ ip := net.ParseIP(clientIP)
+ if !ip.IsPrivate() {
+ // Upstream set a remote IP
+ // header but final clientIP
+ // isn't private, so upstream
+ // is probably already trusted.
+ // Don't inject suggestion.
+ return
+ }
+
+ // Private IP, guess if Docker.
+ if dockerSubnet.Contains(ip) {
+ // Suggest a CIDR that likely
+ // covers this Docker subnet,
+ // eg., 172.17.0.0 -> 172.17.255.255.
+ trustedProxiesRec := clientIP + "/16"
+ obj["trustedProxiesRec"] = trustedProxiesRec
+ return
+ }
+
+ // Private IP but we don't know
+ // what it is. Suggest precise CIDR.
+ trustedProxiesRec := clientIP + "/32"
+ obj["trustedProxiesRec"] = trustedProxiesRec
+}
+
+// dockerSubnet is a CIDR that lets one make hazy guesses
+// as to whether an address is within the ranges Docker
+// uses for subnets, ie., 172.16.0.0 -> 172.31.255.255.
+var dockerSubnet = func() *net.IPNet {
+ _, subnet, err := net.ParseCIDR("172.16.0.0/12")
+ if err != nil {
+ panic(err)
+ }
+ return subnet
+}()
+
// templateErrorPage renders the given
// HTTP code, error, and request ID
// within the standard error template.
diff --git a/mkdocs.yml b/mkdocs.yml
index 82bb03791..15c0a56f7 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -98,6 +98,7 @@ nav:
- "Configuration":
- "configuration/index.md"
- "configuration/general.md"
+ - "configuration/trusted_proxies.md"
- "configuration/database.md"
- "configuration/web.md"
- "configuration/instance.md"
diff --git a/web/source/css/page.css b/web/source/css/page.css
index 642586048..752b264ee 100644
--- a/web/source/css/page.css
+++ b/web/source/css/page.css
@@ -42,7 +42,26 @@
padding: 1.5rem;
gap: 1rem;
- a {
+ .trusted-proxies-rec {
+ color: $info-fg;
+ background: $info-bg;
+ max-width: fit-content;
+ padding-left: 1rem;
+ padding-right: 1rem;
+ border-radius: $br;
+ text-align: center;
+ align-self: center;
+
+ code {
+ background: $info-bg;
+ }
+
+ a {
+ color: $info-fg;
+ }
+ }
+
+ & > a {
display: flex;
flex-wrap: wrap;
gap: 1rem;
diff --git a/web/template/page_header.tmpl b/web/template/page_header.tmpl
index 388587aaf..f2349581e 100644
--- a/web/template/page_header.tmpl
+++ b/web/template/page_header.tmpl
@@ -17,6 +17,29 @@
// along with this program. If not, see .
*/ -}}
+{{- define "trustedProxiesRec" -}}
+{{- .with }}
+
+
+ Warning! It looks like trusted-proxies is not set correctly in this instance's configuration.
+ This may cause rate-limiting issues and, by extension, federation issues.
+
+
+ If you are the instance admin, you should fix this by adding {{- .trustedProxiesRec -}} to your trusted-proxies.
+
+
+ For more information, see
+
+ the documentation
+ .
+
+
+{{- end -}}
+
{{- define "thumbnailDescription" -}}
{{- if .instance.ThumbnailDescription -}}
{{- .instance.ThumbnailDescription -}}
@@ -56,6 +79,9 @@ Instance Logo
{{- end -}}
{{- with . }}
+{{- if .trustedProxiesRec }}
+{{- template "trustedProxiesRec" . }}
+{{- end }}
{{- if .instance.ThumbnailStatic }}
From 9333bbc4d0d5ae46c72fca1f5b1aacb3c0a7653e Mon Sep 17 00:00:00 2001
From: tobi <31960611+tsmethurst@users.noreply.github.com>
Date: Thu, 23 Jan 2025 17:18:23 +0000
Subject: [PATCH 3/3] [feature] Serve bot accounts over AP as Service instead
of Person (#3672)
* pepis
* oopsie doopsie
* bollocks
---
docs/federation/actors.md | 8 ++
internal/ap/activitystreams.go | 16 ++++
internal/ap/ap_test.go | 11 +--
internal/ap/interfaces.go | 2 +
.../api/activitypub/users/inboxpost_test.go | 60 ++++--------
internal/federation/federator_test.go | 4 +-
internal/processing/fedi/user.go | 13 ++-
internal/processing/workers/federate.go | 23 ++---
internal/typeutils/internaltoas.go | 94 ++++++++++++-------
internal/typeutils/internaltoas_test.go | 91 +++++++++++++++---
internal/typeutils/wrap.go | 71 +++++---------
internal/typeutils/wrap_test.go | 80 ++++++++++++++++
testrig/testmodels.go | 14 +--
testrig/transportcontroller.go | 3 +-
14 files changed, 315 insertions(+), 175 deletions(-)
diff --git a/docs/federation/actors.md b/docs/federation/actors.md
index d5dc61e19..ba2283ee9 100644
--- a/docs/federation/actors.md
+++ b/docs/federation/actors.md
@@ -1,5 +1,13 @@
# Actors and Actor Properties
+## `Service` vs `Person` actors
+
+GoToSocial serves most accounts as the ActivityStreams `Person` type described [here](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-person).
+
+Accounts that users have selected to mark as bot accounts, however, will use the `Service` type described [here](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-service).
+
+This type distinction can be used by remote servers to distinguish between bot accounts and "regular" user accounts.
+
## Inbox
GoToSocial implements Inboxes for Actors following the ActivityPub specification [here](https://www.w3.org/TR/activitypub/#inbox).
diff --git a/internal/ap/activitystreams.go b/internal/ap/activitystreams.go
index 8c53ae501..50955ce2c 100644
--- a/internal/ap/activitystreams.go
+++ b/internal/ap/activitystreams.go
@@ -17,6 +17,22 @@
package ap
+import (
+ "net/url"
+
+ "github.com/superseriousbusiness/activity/pub"
+)
+
+// PublicURI returns a fresh copy of the *url.URL version of the
+// magic ActivityPub URI https://www.w3.org/ns/activitystreams#Public
+func PublicURI() *url.URL {
+ publicURI, err := url.Parse(pub.PublicActivityPubIRI)
+ if err != nil {
+ panic(err)
+ }
+ return publicURI
+}
+
// https://www.w3.org/TR/activitystreams-vocabulary
const (
ActivityAccept = "Accept" // ActivityStreamsAccept https://www.w3.org/TR/activitystreams-vocabulary/#dfn-accept
diff --git a/internal/ap/ap_test.go b/internal/ap/ap_test.go
index f982e4443..3738c8c9c 100644
--- a/internal/ap/ap_test.go
+++ b/internal/ap/ap_test.go
@@ -24,7 +24,6 @@ import (
"io"
"github.com/stretchr/testify/suite"
- "github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
@@ -111,7 +110,7 @@ func noteWithMentions1() vocab.ActivityStreamsNote {
// Anyone can like.
canLikeAlwaysProp := streams.NewGoToSocialAlwaysProperty()
- canLikeAlwaysProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
+ canLikeAlwaysProp.AppendIRI(ap.PublicURI())
canLike.SetGoToSocialAlways(canLikeAlwaysProp)
// Empty approvalRequired.
@@ -128,7 +127,7 @@ func noteWithMentions1() vocab.ActivityStreamsNote {
// Anyone can reply.
canReplyAlwaysProp := streams.NewGoToSocialAlwaysProperty()
- canReplyAlwaysProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
+ canReplyAlwaysProp.AppendIRI(ap.PublicURI())
canReply.SetGoToSocialAlways(canReplyAlwaysProp)
// Set empty approvalRequired.
@@ -151,7 +150,7 @@ func noteWithMentions1() vocab.ActivityStreamsNote {
// Public requires approval to announce.
canAnnounceApprovalRequiredProp := streams.NewGoToSocialApprovalRequiredProperty()
- canAnnounceApprovalRequiredProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
+ canAnnounceApprovalRequiredProp.AppendIRI(ap.PublicURI())
canAnnounce.SetGoToSocialApprovalRequired(canAnnounceApprovalRequiredProp)
// Set canAnnounce on the policy.
@@ -266,7 +265,7 @@ func addressable1() ap.Addressable {
note := streams.NewActivityStreamsNote()
toProp := streams.NewActivityStreamsToProperty()
- toProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
+ toProp.AppendIRI(ap.PublicURI())
note.SetActivityStreamsTo(toProp)
@@ -288,7 +287,7 @@ func addressable2() ap.Addressable {
note.SetActivityStreamsTo(toProp)
ccProp := streams.NewActivityStreamsCcProperty()
- ccProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
+ ccProp.AppendIRI(ap.PublicURI())
note.SetActivityStreamsCc(ccProp)
diff --git a/internal/ap/interfaces.go b/internal/ap/interfaces.go
index 1f08fde37..fdd5e4a0b 100644
--- a/internal/ap/interfaces.go
+++ b/internal/ap/interfaces.go
@@ -188,6 +188,7 @@ type Accountable interface {
WithTag
WithPublished
WithUpdated
+ WithImage
}
// Statusable represents the minimum activitypub interface for representing a 'status'.
@@ -439,6 +440,7 @@ type WithValue interface {
// WithImage represents an activity with ActivityStreamsImageProperty
type WithImage interface {
GetActivityStreamsImage() vocab.ActivityStreamsImageProperty
+ SetActivityStreamsImage(vocab.ActivityStreamsImageProperty)
}
// WithSummary represents an activity with ActivityStreamsSummaryProperty
diff --git a/internal/api/activitypub/users/inboxpost_test.go b/internal/api/activitypub/users/inboxpost_test.go
index 64c9f7e6c..f87224d65 100644
--- a/internal/api/activitypub/users/inboxpost_test.go
+++ b/internal/api/activitypub/users/inboxpost_test.go
@@ -177,38 +177,6 @@ func (suite *InboxPostTestSuite) newUndo(
return undo
}
-func (suite *InboxPostTestSuite) newUpdatePerson(person vocab.ActivityStreamsPerson, cc string, updateIRI string) vocab.ActivityStreamsUpdate {
- // create an update
- update := streams.NewActivityStreamsUpdate()
-
- // set the appropriate actor on it
- updateActor := streams.NewActivityStreamsActorProperty()
- updateActor.AppendIRI(person.GetJSONLDId().Get())
- update.SetActivityStreamsActor(updateActor)
-
- // Set the person as the 'object' property.
- updateObject := streams.NewActivityStreamsObjectProperty()
- updateObject.AppendActivityStreamsPerson(person)
- update.SetActivityStreamsObject(updateObject)
-
- // Set the To of the update as public
- updateTo := streams.NewActivityStreamsToProperty()
- updateTo.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
- update.SetActivityStreamsTo(updateTo)
-
- // set the cc of the update to the receivingAccount
- updateCC := streams.NewActivityStreamsCcProperty()
- updateCC.AppendIRI(testrig.URLMustParse(cc))
- update.SetActivityStreamsCc(updateCC)
-
- // set some random-ass ID for the activity
- updateID := streams.NewJSONLDIdProperty()
- updateID.SetIRI(testrig.URLMustParse(updateIRI))
- update.SetJSONLDId(updateID)
-
- return update
-}
-
func (suite *InboxPostTestSuite) newDelete(actorIRI string, objectIRI string, deleteIRI string) vocab.ActivityStreamsDelete {
// create a delete
delete := streams.NewActivityStreamsDelete()
@@ -225,7 +193,7 @@ func (suite *InboxPostTestSuite) newDelete(actorIRI string, objectIRI string, de
// Set the To of the delete as public
deleteTo := streams.NewActivityStreamsToProperty()
- deleteTo.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
+ deleteTo.AppendIRI(ap.PublicURI())
delete.SetActivityStreamsTo(deleteTo)
// set some random-ass ID for the activity
@@ -329,7 +297,6 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
var (
requestingAccount = new(gtsmodel.Account)
targetAccount = suite.testAccounts["local_account_1"]
- activityID = "http://fossbros-anonymous.io/72cc96a3-f742-4daf-b9f5-3407667260c5"
updatedDisplayName = "updated display name!"
)
@@ -348,11 +315,19 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
requestingAccount.Emojis = []*gtsmodel.Emoji{testEmoji}
// Create an update from the account.
- asAccount, err := suite.tc.AccountToAS(context.Background(), requestingAccount)
+ accountable, err := suite.tc.AccountToAS(context.Background(), requestingAccount)
if err != nil {
suite.FailNow(err.Error())
}
- update := suite.newUpdatePerson(asAccount, targetAccount.URI, activityID)
+ update, err := suite.tc.WrapAccountableInUpdate(accountable)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ // Set the ID to something from fossbros anonymous.
+ idProp := streams.NewJSONLDIdProperty()
+ idProp.SetIRI(testrig.URLMustParse("https://fossbros-anonymous.io/updates/waaaaaaaaaaaaaaaaa"))
+ update.SetJSONLDId(idProp)
// Update.
suite.inboxPost(
@@ -540,17 +515,20 @@ func (suite *InboxPostTestSuite) TestPostFromBlockedAccount() {
var (
requestingAccount = suite.testAccounts["remote_account_1"]
targetAccount = suite.testAccounts["local_account_2"]
- activityID = requestingAccount.URI + "/some-new-activity/01FG9C441MCTW3R2W117V2PQK3"
)
- person, err := suite.tc.AccountToAS(context.Background(), requestingAccount)
+ // Create an update from the account.
+ accountable, err := suite.tc.AccountToAS(context.Background(), requestingAccount)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+ update, err := suite.tc.WrapAccountableInUpdate(accountable)
if err != nil {
suite.FailNow(err.Error())
}
- // Post an update from foss satan to turtle, who blocks him.
- update := suite.newUpdatePerson(person, targetAccount.URI, activityID)
-
+ // Post an update from foss satan
+ // to turtle, who blocks him.
suite.inboxPost(
update,
requestingAccount,
diff --git a/internal/federation/federator_test.go b/internal/federation/federator_test.go
index e10c7576c..a4f8c4683 100644
--- a/internal/federation/federator_test.go
+++ b/internal/federation/federator_test.go
@@ -75,12 +75,12 @@ func (suite *FederatorStandardTestSuite) SetupTest() {
// Ensure it's possible to deref
// main key of foss satan.
- fossSatanPerson, err := suite.typeconverter.AccountToAS(context.Background(), suite.testAccounts["remote_account_1"])
+ fossSatanAS, err := suite.typeconverter.AccountToAS(context.Background(), suite.testAccounts["remote_account_1"])
if err != nil {
suite.FailNow(err.Error())
}
- suite.httpClient = testrig.NewMockHTTPClient(nil, "../../testrig/media", fossSatanPerson)
+ suite.httpClient = testrig.NewMockHTTPClient(nil, "../../testrig/media", fossSatanAS)
suite.httpClient.TestRemotePeople = testrig.NewTestFediPeople()
suite.httpClient.TestRemoteStatuses = testrig.NewTestFediStatuses()
diff --git a/internal/processing/fedi/user.go b/internal/processing/fedi/user.go
index bf14554cf..79c1b4fdb 100644
--- a/internal/processing/fedi/user.go
+++ b/internal/processing/fedi/user.go
@@ -23,7 +23,6 @@ import (
"fmt"
"net/url"
- "github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
@@ -72,7 +71,7 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque
}
// Auth passed, generate the proper AP representation.
- person, err := p.converter.AccountToAS(ctx, receiver)
+ accountable, err := p.converter.AccountToAS(ctx, receiver)
if err != nil {
err := gtserror.Newf("error converting account: %w", err)
return nil, gtserror.NewErrorInternalError(err)
@@ -91,7 +90,7 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque
// Instead, we end up in an 'I'll show you mine if you show me
// yours' situation, where we sort of agree to reveal each
// other's profiles at the same time.
- return data(person)
+ return data(accountable)
}
// Get requester from auth.
@@ -107,13 +106,13 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque
return nil, gtserror.NewErrorForbidden(errors.New(text))
}
- return data(person)
+ return data(accountable)
}
-func data(requestedPerson vocab.ActivityStreamsPerson) (interface{}, gtserror.WithCode) {
- data, err := ap.Serialize(requestedPerson)
+func data(accountable ap.Accountable) (interface{}, gtserror.WithCode) {
+ data, err := ap.Serialize(accountable)
if err != nil {
- err := gtserror.Newf("error serializing person: %w", err)
+ err := gtserror.Newf("error serializing accountable: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
diff --git a/internal/processing/workers/federate.go b/internal/processing/workers/federate.go
index 8c08c42b7..d6dec6691 100644
--- a/internal/processing/workers/federate.go
+++ b/internal/processing/workers/federate.go
@@ -21,7 +21,6 @@ import (
"context"
"net/url"
- "github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
@@ -93,11 +92,6 @@ func (f *federate) DeleteAccount(ctx context.Context, account *gtsmodel.Account)
return err
}
- publicIRI, err := parseURI(pub.PublicActivityPubIRI)
- if err != nil {
- return err
- }
-
// Create a new delete.
// todo: tc.AccountToASDelete
delete := streams.NewActivityStreamsDelete()
@@ -121,7 +115,7 @@ func (f *federate) DeleteAccount(ctx context.Context, account *gtsmodel.Account)
// Address the delete CC public.
deleteCC := streams.NewActivityStreamsCcProperty()
- deleteCC.AppendIRI(publicIRI)
+ deleteCC.AppendIRI(ap.PublicURI())
delete.SetActivityStreamsCc(deleteCC)
// Send the Delete via the Actor's outbox.
@@ -877,14 +871,14 @@ func (f *federate) UpdateAccount(ctx context.Context, account *gtsmodel.Account)
return err
}
- // Convert account to ActivityStreams Person.
- person, err := f.converter.AccountToAS(ctx, account)
+ // Convert account to Accountable.
+ accountable, err := f.converter.AccountToAS(ctx, account)
if err != nil {
return gtserror.Newf("error converting account to Person: %w", err)
}
- // Use ActivityStreams Person as Object of Update.
- update, err := f.converter.WrapPersonInUpdate(person, account)
+ // Use Accountable as Object of Update.
+ update, err := f.converter.WrapAccountableInUpdate(accountable)
if err != nil {
return gtserror.Newf("error wrapping Person in Update: %w", err)
}
@@ -1089,11 +1083,6 @@ func (f *federate) MoveAccount(ctx context.Context, account *gtsmodel.Account) e
return err
}
- publicIRI, err := parseURI(pub.PublicActivityPubIRI)
- if err != nil {
- return err
- }
-
// Create a new move.
move := streams.NewActivityStreamsMove()
@@ -1115,7 +1104,7 @@ func (f *federate) MoveAccount(ctx context.Context, account *gtsmodel.Account) e
ap.AppendTo(move, followersIRI)
// Address the move CC public.
- ap.AppendCc(move, publicIRI)
+ ap.AppendCc(move, ap.PublicURI())
// Send the Move via the Actor's outbox.
if _, err := f.FederatingActor().Send(
diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go
index de1badb5c..ce5187bde 100644
--- a/internal/typeutils/internaltoas.go
+++ b/internal/typeutils/internaltoas.go
@@ -36,12 +36,24 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/uris"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/superseriousbusiness/gotosocial/internal/util/xslices"
)
-// AccountToAS converts a gts model account into an activity streams person, suitable for federation
-func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) {
- person := streams.NewActivityStreamsPerson()
+// AccountToAS converts a gts model account
+// into an activity streams person or service.
+func (c *Converter) AccountToAS(
+ ctx context.Context,
+ a *gtsmodel.Account,
+) (ap.Accountable, error) {
+ // accountable is a service if this
+ // is a bot account, otherwise a person.
+ var accountable ap.Accountable
+ if util.PtrOrZero(a.Bot) {
+ accountable = streams.NewActivityStreamsService()
+ } else {
+ accountable = streams.NewActivityStreamsPerson()
+ }
// id should be the activitypub URI of this user
// something like https://example.org/users/example_user
@@ -51,13 +63,13 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
}
idProp := streams.NewJSONLDIdProperty()
idProp.SetIRI(profileIDURI)
- person.SetJSONLDId(idProp)
+ accountable.SetJSONLDId(idProp)
// published
// The moment when the account was created.
publishedProp := streams.NewActivityStreamsPublishedProperty()
publishedProp.Set(a.CreatedAt)
- person.SetActivityStreamsPublished(publishedProp)
+ accountable.SetActivityStreamsPublished(publishedProp)
// following
// The URI for retrieving a list of accounts this user is following
@@ -67,7 +79,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
}
followingProp := streams.NewActivityStreamsFollowingProperty()
followingProp.SetIRI(followingURI)
- person.SetActivityStreamsFollowing(followingProp)
+ accountable.SetActivityStreamsFollowing(followingProp)
// followers
// The URI for retrieving a list of this user's followers
@@ -77,7 +89,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
}
followersProp := streams.NewActivityStreamsFollowersProperty()
followersProp.SetIRI(followersURI)
- person.SetActivityStreamsFollowers(followersProp)
+ accountable.SetActivityStreamsFollowers(followersProp)
// inbox
// the activitypub inbox of this user for accepting messages
@@ -87,7 +99,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
}
inboxProp := streams.NewActivityStreamsInboxProperty()
inboxProp.SetIRI(inboxURI)
- person.SetActivityStreamsInbox(inboxProp)
+ accountable.SetActivityStreamsInbox(inboxProp)
// shared inbox -- only add this if we know for sure it has one
if a.SharedInboxURI != nil && *a.SharedInboxURI != "" {
@@ -101,7 +113,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
sharedInboxProp.SetIRI(sharedInboxURI)
endpoints.SetActivityStreamsSharedInbox(sharedInboxProp)
endpointsProp.AppendActivityStreamsEndpoints(endpoints)
- person.SetActivityStreamsEndpoints(endpointsProp)
+ accountable.SetActivityStreamsEndpoints(endpointsProp)
}
// outbox
@@ -112,7 +124,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
}
outboxProp := streams.NewActivityStreamsOutboxProperty()
outboxProp.SetIRI(outboxURI)
- person.SetActivityStreamsOutbox(outboxProp)
+ accountable.SetActivityStreamsOutbox(outboxProp)
// featured posts
// Pinned posts.
@@ -122,7 +134,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
}
featuredProp := streams.NewTootFeaturedProperty()
featuredProp.SetIRI(featuredURI)
- person.SetTootFeatured(featuredProp)
+ accountable.SetTootFeatured(featuredProp)
// featuredTags
// NOT IMPLEMENTED
@@ -131,7 +143,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
// Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI.
preferredUsernameProp := streams.NewActivityStreamsPreferredUsernameProperty()
preferredUsernameProp.SetXMLSchemaString(a.Username)
- person.SetActivityStreamsPreferredUsername(preferredUsernameProp)
+ accountable.SetActivityStreamsPreferredUsername(preferredUsernameProp)
// name
// Used as profile display name.
@@ -141,14 +153,14 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
} else {
nameProp.AppendXMLSchemaString(a.Username)
}
- person.SetActivityStreamsName(nameProp)
+ accountable.SetActivityStreamsName(nameProp)
// summary
// Used as profile bio.
if a.Note != "" {
summaryProp := streams.NewActivityStreamsSummaryProperty()
summaryProp.AppendXMLSchemaString(a.Note)
- person.SetActivityStreamsSummary(summaryProp)
+ accountable.SetActivityStreamsSummary(summaryProp)
}
// url
@@ -159,19 +171,19 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
}
urlProp := streams.NewActivityStreamsUrlProperty()
urlProp.AppendIRI(profileURL)
- person.SetActivityStreamsUrl(urlProp)
+ accountable.SetActivityStreamsUrl(urlProp)
// manuallyApprovesFollowers
// Will be shown as a locked account.
manuallyApprovesFollowersProp := streams.NewActivityStreamsManuallyApprovesFollowersProperty()
manuallyApprovesFollowersProp.Set(*a.Locked)
- person.SetActivityStreamsManuallyApprovesFollowers(manuallyApprovesFollowersProp)
+ accountable.SetActivityStreamsManuallyApprovesFollowers(manuallyApprovesFollowersProp)
// discoverable
// Will be shown in the profile directory.
discoverableProp := streams.NewTootDiscoverableProperty()
discoverableProp.Set(*a.Discoverable)
- person.SetTootDiscoverable(discoverableProp)
+ accountable.SetTootDiscoverable(discoverableProp)
// devices
// NOT IMPLEMENTED, probably won't implement
@@ -189,7 +201,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
alsoKnownAsURIs[i] = uri
}
- ap.SetAlsoKnownAs(person, alsoKnownAsURIs)
+ ap.SetAlsoKnownAs(accountable, alsoKnownAsURIs)
}
// movedTo
@@ -200,7 +212,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
return nil, err
}
- ap.SetMovedTo(person, movedTo)
+ ap.SetMovedTo(accountable, movedTo)
}
// publicKey
@@ -241,7 +253,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKey)
// set the public key property on the Person
- person.SetW3IDSecurityV1PublicKey(publicKeyProp)
+ accountable.SetW3IDSecurityV1PublicKey(publicKeyProp)
// tags
tagProp := streams.NewActivityStreamsTagProperty()
@@ -269,7 +281,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
// tag -- hashtags
// TODO
- person.SetActivityStreamsTag(tagProp)
+ accountable.SetActivityStreamsTag(tagProp)
// attachment
// Used for profile fields.
@@ -290,7 +302,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
attachmentProp.AppendSchemaPropertyValue(propertyValue)
}
- person.SetActivityStreamsAttachment(attachmentProp)
+ accountable.SetActivityStreamsAttachment(attachmentProp)
}
// endpoints
@@ -326,7 +338,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
iconImage.SetActivityStreamsUrl(avatarURLProperty)
iconProperty.AppendActivityStreamsImage(iconImage)
- person.SetActivityStreamsIcon(iconProperty)
+ accountable.SetActivityStreamsIcon(iconProperty)
}
}
@@ -360,20 +372,32 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
headerImage.SetActivityStreamsUrl(headerURLProperty)
headerProperty.AppendActivityStreamsImage(headerImage)
- person.SetActivityStreamsImage(headerProperty)
+ accountable.SetActivityStreamsImage(headerProperty)
}
}
- return person, nil
+ return accountable, nil
}
-// AccountToASMinimal converts a gts model account into an activity streams person, suitable for federation.
+// AccountToASMinimal converts a gts model account
+// into an activity streams person or service.
//
-// The returned account will just have the Type, Username, PublicKey, and ID properties set. This is
-// suitable for serving to requesters to whom we want to give as little information as possible because
-// we don't trust them (yet).
-func (c *Converter) AccountToASMinimal(ctx context.Context, a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) {
- person := streams.NewActivityStreamsPerson()
+// The returned account will just have the Type, Username,
+// PublicKey, and ID properties set. This is suitable for
+// serving to requesters to whom we want to give as little
+// information as possible because we don't trust them (yet).
+func (c *Converter) AccountToASMinimal(
+ ctx context.Context,
+ a *gtsmodel.Account,
+) (ap.Accountable, error) {
+ // accountable is a service if this
+ // is a bot account, otherwise a person.
+ var accountable ap.Accountable
+ if util.PtrOrZero(a.Bot) {
+ accountable = streams.NewActivityStreamsService()
+ } else {
+ accountable = streams.NewActivityStreamsPerson()
+ }
// id should be the activitypub URI of this user
// something like https://example.org/users/example_user
@@ -383,13 +407,13 @@ func (c *Converter) AccountToASMinimal(ctx context.Context, a *gtsmodel.Account)
}
idProp := streams.NewJSONLDIdProperty()
idProp.SetIRI(profileIDURI)
- person.SetJSONLDId(idProp)
+ accountable.SetJSONLDId(idProp)
// preferredUsername
// Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI.
preferredUsernameProp := streams.NewActivityStreamsPreferredUsernameProperty()
preferredUsernameProp.SetXMLSchemaString(a.Username)
- person.SetActivityStreamsPreferredUsername(preferredUsernameProp)
+ accountable.SetActivityStreamsPreferredUsername(preferredUsernameProp)
// publicKey
// Required for signatures.
@@ -429,9 +453,9 @@ func (c *Converter) AccountToASMinimal(ctx context.Context, a *gtsmodel.Account)
publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKey)
// set the public key property on the Person
- person.SetW3IDSecurityV1PublicKey(publicKeyProp)
+ accountable.SetW3IDSecurityV1PublicKey(publicKeyProp)
- return person, nil
+ return accountable, nil
}
// StatusToAS converts a gts model status into an ActivityStreams Statusable implementation, suitable for federation
diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go
index 344a42798..4d0d95641 100644
--- a/internal/typeutils/internaltoas_test.go
+++ b/internal/typeutils/internaltoas_test.go
@@ -27,6 +27,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/superseriousbusiness/gotosocial/testrig"
)
@@ -38,10 +39,10 @@ func (suite *InternalToASTestSuite) TestAccountToAS() {
testAccount := >smodel.Account{}
*testAccount = *suite.testAccounts["local_account_1"] // take zork for this test
- asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
+ accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
- ser, err := ap.Serialize(asPerson)
+ ser, err := ap.Serialize(accountable)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@@ -94,14 +95,80 @@ func (suite *InternalToASTestSuite) TestAccountToAS() {
}`, string(bytes))
}
+func (suite *InternalToASTestSuite) TestAccountToASBot() {
+ testAccount := >smodel.Account{}
+ *testAccount = *suite.testAccounts["local_account_1"] // take zork for this test
+
+ // Update zork to be a bot.
+ testAccount.Bot = util.Ptr(true)
+ if err := suite.state.DB.UpdateAccount(context.Background(), testAccount); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
+ suite.NoError(err)
+
+ ser, err := ap.Serialize(accountable)
+ suite.NoError(err)
+
+ bytes, err := json.MarshalIndent(ser, "", " ")
+ suite.NoError(err)
+
+ suite.Equal(`{
+ "@context": [
+ "https://w3id.org/security/v1",
+ "https://www.w3.org/ns/activitystreams",
+ {
+ "discoverable": "toot:discoverable",
+ "featured": {
+ "@id": "toot:featured",
+ "@type": "@id"
+ },
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "toot": "http://joinmastodon.org/ns#"
+ }
+ ],
+ "discoverable": true,
+ "featured": "http://localhost:8080/users/the_mighty_zork/collections/featured",
+ "followers": "http://localhost:8080/users/the_mighty_zork/followers",
+ "following": "http://localhost:8080/users/the_mighty_zork/following",
+ "icon": {
+ "mediaType": "image/jpeg",
+ "type": "Image",
+ "url": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg"
+ },
+ "id": "http://localhost:8080/users/the_mighty_zork",
+ "image": {
+ "mediaType": "image/jpeg",
+ "type": "Image",
+ "url": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg"
+ },
+ "inbox": "http://localhost:8080/users/the_mighty_zork/inbox",
+ "manuallyApprovesFollowers": false,
+ "name": "original zork (he/they)",
+ "outbox": "http://localhost:8080/users/the_mighty_zork/outbox",
+ "preferredUsername": "the_mighty_zork",
+ "publicKey": {
+ "id": "http://localhost:8080/users/the_mighty_zork/main-key",
+ "owner": "http://localhost:8080/users/the_mighty_zork",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXTcOAvM1Jiw5Ffpk0qn\nr0cwbNvFe/5zQ+Tp7tumK/ZnT37o7X0FUEXrxNi+dkhmeJ0gsaiN+JQGNUewvpSk\nPIAXKvi908aSfCGjs7bGlJCJCuDuL5d6m7hZnP9rt9fJc70GElPpG0jc9fXwlz7T\nlsPb2ecatmG05Y4jPwdC+oN4MNCv9yQzEvCVMzl76EJaM602kIHC1CISn0rDFmYd\n9rSN7XPlNJw1F6PbpJ/BWQ+pXHKw3OEwNTETAUNYiVGnZU+B7a7bZC9f6/aPbJuV\nt8Qmg+UnDvW1Y8gmfHnxaWG2f5TDBvCHmcYtucIZPLQD4trAozC4ryqlmCWQNKbt\n0wIDAQAB\n-----END PUBLIC KEY-----\n"
+ },
+ "published": "2022-05-20T11:09:18Z",
+ "summary": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
+ "tag": [],
+ "type": "Service",
+ "url": "http://localhost:8080/@the_mighty_zork"
+}`, string(bytes))
+}
+
func (suite *InternalToASTestSuite) TestAccountToASWithFields() {
testAccount := >smodel.Account{}
*testAccount = *suite.testAccounts["local_account_2"]
- asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
+ accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
- ser, err := ap.Serialize(asPerson)
+ ser, err := ap.Serialize(accountable)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@@ -176,10 +243,10 @@ func (suite *InternalToASTestSuite) TestAccountToASAliasedAndMoved() {
suite.FailNow(err.Error())
}
- asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
+ accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
- ser, err := ap.Serialize(asPerson)
+ ser, err := ap.Serialize(accountable)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@@ -246,10 +313,10 @@ func (suite *InternalToASTestSuite) TestAccountToASWithOneField() {
*testAccount = *suite.testAccounts["local_account_2"]
testAccount.Fields = testAccount.Fields[0:1] // Take only one field.
- asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
+ accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
- ser, err := ap.Serialize(asPerson)
+ ser, err := ap.Serialize(accountable)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@@ -308,10 +375,10 @@ func (suite *InternalToASTestSuite) TestAccountToASWithEmoji() {
*testAccount = *suite.testAccounts["local_account_1"] // take zork for this test
testAccount.Emojis = []*gtsmodel.Emoji{suite.testEmojis["rainbow"]}
- asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
+ accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
- ser, err := ap.Serialize(asPerson)
+ ser, err := ap.Serialize(accountable)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@@ -381,10 +448,10 @@ func (suite *InternalToASTestSuite) TestAccountToASWithSharedInbox() {
sharedInbox := "http://localhost:8080/sharedInbox"
testAccount.SharedInboxURI = &sharedInbox
- asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
+ accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
- ser, err := ap.Serialize(asPerson)
+ ser, err := ap.Serialize(accountable)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
diff --git a/internal/typeutils/wrap.go b/internal/typeutils/wrap.go
index 89bcdfc09..1230981d4 100644
--- a/internal/typeutils/wrap.go
+++ b/internal/typeutils/wrap.go
@@ -18,68 +18,45 @@
package typeutils
import (
- "net/url"
-
- "github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
- "github.com/superseriousbusiness/gotosocial/internal/gtserror"
- "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
+ "github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/uris"
)
-// WrapPersonInUpdate ...
-func (c *Converter) WrapPersonInUpdate(person vocab.ActivityStreamsPerson, originAccount *gtsmodel.Account) (vocab.ActivityStreamsUpdate, error) {
+// WrapAccountableInUpdate wraps the given accountable
+// in an Update activity with the accountable as the object.
+//
+// The Update will be addressed to Public and bcc followers.
+func (c *Converter) WrapAccountableInUpdate(accountable ap.Accountable) (vocab.ActivityStreamsUpdate, error) {
update := streams.NewActivityStreamsUpdate()
- // set the actor
- actorURI, err := url.Parse(originAccount.URI)
- if err != nil {
- return nil, gtserror.Newf("error parsing url %s: %w", originAccount.URI, err)
- }
- actorProp := streams.NewActivityStreamsActorProperty()
- actorProp.AppendIRI(actorURI)
- update.SetActivityStreamsActor(actorProp)
+ // Set actor IRI to this accountable's IRI.
+ ap.AppendActorIRIs(update, ap.GetJSONLDId(accountable))
- // set the ID
- newID, err := id.NewRandomULID()
- if err != nil {
- return nil, err
- }
+ // Set the update ID
+ updateURI := uris.GenerateURIForUpdate(ap.ExtractPreferredUsername(accountable), id.NewULID())
+ ap.MustSet(ap.SetJSONLDIdStr, ap.WithJSONLDId(update), updateURI)
- idString := uris.GenerateURIForUpdate(originAccount.Username, newID)
- idURI, err := url.Parse(idString)
- if err != nil {
- return nil, gtserror.Newf("error parsing url %s: %w", idString, err)
- }
- idProp := streams.NewJSONLDIdProperty()
- idProp.SetIRI(idURI)
- update.SetJSONLDId(idProp)
-
- // set the person as the object here
+ // Set the accountable as the object of the update.
objectProp := streams.NewActivityStreamsObjectProperty()
- objectProp.AppendActivityStreamsPerson(person)
+ switch t := accountable.(type) {
+ case vocab.ActivityStreamsPerson:
+ objectProp.AppendActivityStreamsPerson(t)
+ case vocab.ActivityStreamsService:
+ objectProp.AppendActivityStreamsService(t)
+ default:
+ log.Panicf(nil, "%T was neither person nor service", t)
+ }
update.SetActivityStreamsObject(objectProp)
- // to should be public
- toURI, err := url.Parse(pub.PublicActivityPubIRI)
- if err != nil {
- return nil, gtserror.Newf("error parsing url %s: %w", pub.PublicActivityPubIRI, err)
- }
- toProp := streams.NewActivityStreamsToProperty()
- toProp.AppendIRI(toURI)
- update.SetActivityStreamsTo(toProp)
+ // to should be public.
+ ap.AppendTo(update, ap.PublicURI())
- // bcc followers
- followersURI, err := url.Parse(originAccount.FollowersURI)
- if err != nil {
- return nil, gtserror.Newf("error parsing url %s: %w", originAccount.FollowersURI, err)
- }
- bccProp := streams.NewActivityStreamsBccProperty()
- bccProp.AppendIRI(followersURI)
- update.SetActivityStreamsBcc(bccProp)
+ // bcc should be followers.
+ ap.AppendBcc(update, ap.GetFollowers(accountable))
return update, nil
}
diff --git a/internal/typeutils/wrap_test.go b/internal/typeutils/wrap_test.go
index 1085c8c66..8c8af7506 100644
--- a/internal/typeutils/wrap_test.go
+++ b/internal/typeutils/wrap_test.go
@@ -139,6 +139,86 @@ func (suite *WrapTestSuite) TestWrapNoteInCreate() {
}`, string(bytes))
}
+func (suite *WrapTestSuite) TestWrapAccountableInUpdate() {
+ testAccount := suite.testAccounts["local_account_1"]
+
+ accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ create, err := suite.typeconverter.WrapAccountableInUpdate(accountable)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ createI, err := ap.Serialize(create)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ // Get the ID as it's not determinate.
+ createID := ap.GetJSONLDId(create)
+
+ bytes, err := json.MarshalIndent(createI, "", " ")
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.Equal(`{
+ "@context": [
+ "https://w3id.org/security/v1",
+ "https://www.w3.org/ns/activitystreams",
+ {
+ "discoverable": "toot:discoverable",
+ "featured": {
+ "@id": "toot:featured",
+ "@type": "@id"
+ },
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "toot": "http://joinmastodon.org/ns#"
+ }
+ ],
+ "actor": "http://localhost:8080/users/the_mighty_zork",
+ "bcc": "http://localhost:8080/users/the_mighty_zork/followers",
+ "id": "`+createID.String()+`",
+ "object": {
+ "discoverable": true,
+ "featured": "http://localhost:8080/users/the_mighty_zork/collections/featured",
+ "followers": "http://localhost:8080/users/the_mighty_zork/followers",
+ "following": "http://localhost:8080/users/the_mighty_zork/following",
+ "icon": {
+ "mediaType": "image/jpeg",
+ "type": "Image",
+ "url": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg"
+ },
+ "id": "http://localhost:8080/users/the_mighty_zork",
+ "image": {
+ "mediaType": "image/jpeg",
+ "type": "Image",
+ "url": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg"
+ },
+ "inbox": "http://localhost:8080/users/the_mighty_zork/inbox",
+ "manuallyApprovesFollowers": false,
+ "name": "original zork (he/they)",
+ "outbox": "http://localhost:8080/users/the_mighty_zork/outbox",
+ "preferredUsername": "the_mighty_zork",
+ "publicKey": {
+ "id": "http://localhost:8080/users/the_mighty_zork/main-key",
+ "owner": "http://localhost:8080/users/the_mighty_zork",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXTcOAvM1Jiw5Ffpk0qn\nr0cwbNvFe/5zQ+Tp7tumK/ZnT37o7X0FUEXrxNi+dkhmeJ0gsaiN+JQGNUewvpSk\nPIAXKvi908aSfCGjs7bGlJCJCuDuL5d6m7hZnP9rt9fJc70GElPpG0jc9fXwlz7T\nlsPb2ecatmG05Y4jPwdC+oN4MNCv9yQzEvCVMzl76EJaM602kIHC1CISn0rDFmYd\n9rSN7XPlNJw1F6PbpJ/BWQ+pXHKw3OEwNTETAUNYiVGnZU+B7a7bZC9f6/aPbJuV\nt8Qmg+UnDvW1Y8gmfHnxaWG2f5TDBvCHmcYtucIZPLQD4trAozC4ryqlmCWQNKbt\n0wIDAQAB\n-----END PUBLIC KEY-----\n"
+ },
+ "published": "2022-05-20T11:09:18Z",
+ "summary": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
+ "tag": [],
+ "type": "Person",
+ "url": "http://localhost:8080/@the_mighty_zork"
+ },
+ "to": "https://www.w3.org/ns/activitystreams#Public",
+ "type": "Update"
+}`, string(bytes))
+}
+
func TestWrapTestSuite(t *testing.T) {
suite.Run(t, new(WrapTestSuite))
}
diff --git a/testrig/testmodels.go b/testrig/testmodels.go
index 81c3a85c5..da4202eed 100644
--- a/testrig/testmodels.go
+++ b/testrig/testmodels.go
@@ -2853,7 +2853,7 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit
"this is a public status, please forward it!",
"",
URLMustParse("http://example.org/users/Some_User"),
- []*url.URL{URLMustParse(pub.PublicActivityPubIRI)},
+ []*url.URL{ap.PublicURI()},
nil,
false,
[]vocab.ActivityStreamsMention{},
@@ -3207,7 +3207,7 @@ func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote {
"this is a public status, please forward it!",
"",
URLMustParse("http://example.org/users/Some_User"),
- []*url.URL{URLMustParse(pub.PublicActivityPubIRI)},
+ []*url.URL{ap.PublicURI()},
nil,
false,
[]vocab.ActivityStreamsMention{},
@@ -3228,7 +3228,7 @@ func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote {
"",
URLMustParse("https://unknown-instance.com/users/brand_new_person"),
[]*url.URL{
- URLMustParse(pub.PublicActivityPubIRI),
+ ap.PublicURI(),
},
[]*url.URL{},
false,
@@ -3244,7 +3244,7 @@ func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote {
"",
URLMustParse("https://unknown-instance.com/users/brand_new_person"),
[]*url.URL{
- URLMustParse(pub.PublicActivityPubIRI),
+ ap.PublicURI(),
},
[]*url.URL{},
false,
@@ -3265,7 +3265,7 @@ func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote {
"",
URLMustParse("https://unknown-instance.com/users/brand_new_person"),
[]*url.URL{
- URLMustParse(pub.PublicActivityPubIRI),
+ ap.PublicURI(),
},
[]*url.URL{},
false,
@@ -3286,7 +3286,7 @@ func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote {
"",
URLMustParse("https://turnip.farm/users/turniplover6969"),
[]*url.URL{
- URLMustParse(pub.PublicActivityPubIRI),
+ ap.PublicURI(),
},
[]*url.URL{},
false,
@@ -3309,7 +3309,7 @@ func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote {
"",
URLMustParse("http://fossbros-anonymous.io/users/foss_satan"),
[]*url.URL{
- URLMustParse(pub.PublicActivityPubIRI),
+ ap.PublicURI(),
},
[]*url.URL{},
false,
diff --git a/testrig/transportcontroller.go b/testrig/transportcontroller.go
index 3bc8752e0..b886e5c40 100644
--- a/testrig/transportcontroller.go
+++ b/testrig/transportcontroller.go
@@ -29,6 +29,7 @@ import (
"github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
+ "github.com/superseriousbusiness/gotosocial/internal/ap"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/federation"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
@@ -81,7 +82,7 @@ type MockHTTPClient struct {
// to customize how the client is mocked.
//
// Note that you should never ever make ACTUAL http calls with this thing.
-func NewMockHTTPClient(do func(req *http.Request) (*http.Response, error), relativeMediaPath string, extraPeople ...vocab.ActivityStreamsPerson) *MockHTTPClient {
+func NewMockHTTPClient(do func(req *http.Request) (*http.Response, error), relativeMediaPath string, extraPeople ...ap.Accountable) *MockHTTPClient {
mockHTTPClient := &MockHTTPClient{}
if do != nil {