Skip to content

ActivityPub Web Handler

The ActivityPub (activitypub.js) provides ActivityPub support currently compatible with Mastodon and perhaps similar ActivityPub systems within the Fediverse allowing direct and public messaging, following/followers, etc. to be integrated with the ENiGMA½ BBS system.

  • ActivityPub with Mastodon support
  • Users are generated a random avatar once enabled with the ability to change them within their ActivityPub configuration
  • Social Manager
  • Actor search (that is, other users within the Fediverse)
  • Private and public messaging to/from the Fediverse

Full ActivityPub support requires the Web Server module be enabled as well as a number of Web Handlers. Each handler configured within the contentServers.web.handlers block keys below of your config.hjson must be set to enabled: true:

HandlerKeyDescriptionDefault
WebFingerwebFingerAllows other servers to discover your user/Actorsdisabled
System GeneralsystemGeneralServes avatar imagesenabled
NodeInfo2nodeInfo2Allows other systems to query information about your nodeenabled
ActivityPubactivityPubDescribed within this filedisabled

Example

contentServers: {
web: {
handlers: {
webFinger: {
enabled: true
}
// ...
activityPub: {
enabled: true
}
}
}
}
KeyDescription
enabledBoolean. Set to true to enable ActivityPub services
selfTemplateString. Provide a fully qualified, or relative to static root path to a template file for fetching profile information. Defaults to the same file used for WebFinger queries; See WebFinger for more information.
allowInsecureHttpBoolean. Allow plain http:// requests to remote servers. For local development and testing only; see Outbound Requests and Limits. Defaults to false.
allowedPrivateAddressesArray of strings. IPv4 or IPv6 addresses or CIDRs (10.0.0.0/8, fd00::/8; a bare address is just that address) that remote servers may be reached at although they are private, such as a fediverse server on your own network; see Servers on your own network. Link-local, unspecified, multicast and IPv6 transition addresses are never allowed. Defaults to [].
maxInboxBodyBytesNumber. Largest inbox request body accepted, in bytes. A larger request is refused with 413. Defaults to 1048576 (1 MiB).
maxResponseBodyBytesNumber. Largest response accepted from a remote server (an actor, a Note, a WebFinger lookup, a delivery’s answer), in bytes. Defaults to 524288 (512 KiB).
outboundFetchLimitsObject. Limits on the fetches an incoming inbox request may cause; see Outbound Requests and Limits. Members: perHostConcurrency (fetches from one remote host at once, default 4), perHostPerMinute (fetches from one remote host per minute, default 60), globalConcurrency (fetches at once, board wide, default 32) and globalPerMinute (fetches per minute, board wide, default 600). A value below 1, or not a number, uses its default.

General ActivityPub configuration can be found within the activityPub block:

KeyDescriptionDefault
autoSignaturesInclude auto-signatures in ActivityPub outgoing message/Notes?false
maxMessageLengthMax single message/Note length in characters. Note that longer lengths are generally allowed by remote systems.500

Settings applied to new users or users first enabling ActivityPub are found within users.activityPub with the following members:

KeyDescriptionDefault
enabledEnabled for users by default?false
manuallyApproveFollowersDo users need to manually approve followers?false
hideSocialGraphHide users social graph? (followers, following, …)false
showRealNameShow real name?true

ENiGMA½ contacts remote servers to look up actors (including the key that signed an incoming request), to resolve handles with WebFinger, to fetch Notes, and to deliver activities.

ENiGMA½ will not connect to a remote server whose name resolves to a loopback, private, link-local (including cloud metadata), shared (CGNAT), multicast, documentation or otherwise reserved address, IPv4 or IPv6. The same applies to the IPv4-mapped and NAT64 forms of those addresses, to addresses given literally in a URL, and to names such as localhost or anything under .local, .internal or .lan. Every address a name resolves to is checked, at the moment of connecting, and again for each redirect; at most 3 redirects are followed. A name refused for the address it resolves to is logged as a warning, with the host and the address.

Your board’s own address is the exception. Behind a reverse proxy, the board’s public name often resolves, on the machine ENiGMA½ runs on, to a private address (split-horizon DNS), and ENiGMA½ fetches its own URLs too: its local users’ actors, WebFinger for its own users, deliveries to its own inbox. A URL with exactly the scheme, host name and port of your board’s base URL (overrideUrlPrefix if set, otherwise built from domain and the web server’s port) is therefore not checked. The same host name on any other port or scheme is refused outright, so the exception reaches nothing but the board itself. The http:///https:// rules still apply: an http:// base URL is only fetched with allowInsecureHttp. Nothing needs configuring for this, but the board’s own address is never taken from the placeholder domain a new install ships with.

A fediverse server on your own network whose name resolves, for ENiGMA½, to a private address is refused like any other. List its address, or a range that holds it, in allowedPrivateAddresses:

contentServers: {
web: {
handlers: {
activityPub: {
enabled: true
// a GoToSocial box on the LAN, and an IPv6 ULA prefix
allowedPrivateAddresses: [
"192.168.1.20"
"fd12:3456:789a::/48"
]
}
}
}
}

This applies to the addresses a name resolves to and to addresses written in a URL; names such as localhost or anything under .local, .internal or .lan are still refused, so use a name that resolves to the address. Link-local addresses (169.254.0.0/16 and fe80::/10, where cloud metadata services live), unspecified (0.0.0.0/8, ::) and multicast addresses, and their IPv4-mapped and NAT64 forms, can never be allowed, nor can IPv6 transition addresses (IPv4-compatible ::/96 other than ::1, IPv4-translated, 6to4, Teredo and local-use NAT64): an entry that covers them, even 0.0.0.0/0, leaves them refused. An entry that is not an address or CIDR is ignored with a warning in the log. When a connection is refused for an address that listing would allow, the warning names allowedPrivateAddresses.

A refused lookup is remembered for 15 minutes (see Failed Lookups); saving config.hjson forgets every remembered failure, so a change here takes effect at once.

allowInsecureHttp: true is for local development and testing only, such as federating with a test server on your LAN that has no TLS. It allows http:// URLs and skips the address checks for them, and a WebFinger lookup whose https:// attempt fails falls back to http:// only while it is set. https:// URLs are always checked. While it is set, ENiGMA½ logs a warning when ActivityPub starts.

The fetches an incoming inbox request causes (the sender’s actor and key, a forwarded or boosted Note, a handle lookup) are limited per remote host and board wide by outboundFetchLimits, and to 16 per request. A request that goes over a limit is answered 503 Service Unavailable with Retry-After: 60; Mastodon, GoToSocial and similar servers deliver it again later. Outbound deliveries, and lookups a local user makes (an actor search, for example), are not limited.

contentServers: {
web: {
handlers: {
activityPub: {
enabled: true
outboundFetchLimits: {
perHostConcurrency: 4
perHostPerMinute: 60
globalConcurrency: 32
globalPerMinute: 600
}
}
}
}
}

The limits are kept per remote host, not per sender. Heavy traffic that names one host can therefore use up that host’s allowance, and activities from that host are delayed (answered 503) until it subsides. Senders retry, so they arrive later rather than being lost.

A failed actor or WebFinger lookup is remembered and not repeated for a while: 15 minutes when the remote server refused or answered with something unusable (a 4xx other than 408 or 429, an invalid document, a refused address), and 2 minutes for anything that may clear up by itself (timeouts, 5xx, 408, 429, connection errors). A lookup refused by the fetch limits is not remembered.