gap

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 1, 2026 License: MIT Imports: 15 Imported by: 0

README

gap — Go ActivityPub

Go Reference Go Version License

A lightweight, zero-dependency Go library implementing the ActivityPub protocol (ActivityStreams 2.0). Designed for building federated social applications — Mastodon-compatible servers, bots, clients, and tools.

GAP stands for Go ActivityPub.
The name also happens to reflect a small personal coincidence at the time of creation.

Features

  • ActivityStreams 2.0 types — Actor, Note, Activity, Collection, WebFinger, NodeInfo
  • HTTP Signatures — draft-cavage-http-signatures (RSA-SHA256 sign & verify)
  • LD Signatures — RsaSignature2017 for backward compatibility
  • Safe HTTP client — SSRF protection (blocks private/internal IPs)
  • Activity delivery — signed POST to remote inboxes
  • Key generation — RSA-2048 key pair generation

Installation

go get github.com/Guaderxx/gap

Quick Start

Create an ActivityPub Actor
actor := gap.Actor{
    Context: []string{
        gap.ContextActivityStreams,
        gap.ContextSecurity,
    },
    ID:                "https://example.com/users/alice",
    Type:              gap.ActorPerson,  // use constants for type safety
    PreferredUsername: "alice",
    Name:              "Alice",
    Inbox:             "https://example.com/users/alice/inbox",
    Outbox:            "https://example.com/users/alice/outbox",
    Followers:         "https://example.com/users/alice/followers",
    Following:         "https://example.com/users/alice/following",
    PublicKey: &gap.PublicKey{
        ID:           "https://example.com/users/alice#main-key",
        Owner:        "https://example.com/users/alice",
        PublicKeyPem: publicKeyPEM,
    },
}
Sign and Deliver an Activity
// Create a Follow activity
follow := gap.Activity{
    Context: []string{gap.ContextActivityStreams},
    Type:    gap.ActivityFollow,  // use constants for type safety
    Actor:   "https://example.com/users/alice",
    Object:  "https://mastodon.example/users/bob",
}

body, _ := json.Marshal(follow)

// Deliver it to Bob's inbox
err := gap.DeliverActivity(
    "https://mastodon.example/users/bob/inbox",
    body,
    "https://example.com/users/alice#main-key",
    privateKeyPEM,
)
Safe HTTP Client
client := gap.NewSafeClient()

// This will be blocked (private IP)
resp, err := client.Get("http://10.0.0.1/api")
// err: gap: connection blocked by SSRF protection

// This works normally
resp, err := client.Get("https://mastodon.social/.well-known/nodeinfo")
WebFinger Discovery
resp := gap.WebFingerResponse{
    Subject: "acct:alice@example.com",
    Links: []gap.WebFingerLink{
        {
            Rel:  "self",
            Type: "application/activity+json",
            Href: "https://example.com/users/alice",
        },
    },
}

Package Overview

Package Contents
gap All types and functions in a single flat package
Types
Type Description
Actor ActivityPub actor (Person, Application, Service)
Activity ActivityPub activity (Create, Follow, Like, etc.)
Note ActivityPub note (equivalent to a Mastodon status)
Collection Ordered collection (followers, following, outbox)
PublicKey Actor's public key for HTTP Signatures
WebFingerResponse RFC 7033 WebFinger JRD response
NodeInfo NodeInfo 2.0 instance metadata
Image Attached image in ActivityPub documents
LDKeyPair RSA key pair for Linked Data Signatures
Constants

Context URIs:

Constant Value
ContextActivityStreams https://www.w3.org/ns/activitystreams
ContextSecurity https://w3id.org/security/v1
PublicAddress https://www.w3.org/ns/activitystreams#Public

Activity Types (gap.ActivityXxx):

Constant Value Mastodon equivalent
ActivityAccept Accept Accept follow
ActivityAdd Add Add to collection
ActivityAnnounce Announce Boost / reblog
ActivityBlock Block Block user
ActivityCreate Create Post a status
ActivityDelete Delete Delete a status
ActivityFlag Flag Report
ActivityFollow Follow Follow user
ActivityLike Like Favourite
ActivityMove Move Account migration
ActivityReject Reject Reject follow
ActivityRemove Remove Remove from collection
ActivityUndo Undo Undo an action
ActivityUpdate Update Edit a status/profile
ActivityView View View

Actor Types (gap.ActorXxx):

Constant Value
ActorPerson Person
ActorApplication Application
ActorService Service
ActorGroup Group
ActorOrganization Organization

Object Types (gap.ObjectXxx):

Constant Value
ObjectNote Note
ObjectArticle Article
ObjectDocument Document
ObjectImage Image
ObjectVideo Video
ObjectAudio Audio
ObjectPage Page
ObjectEvent Event
ObjectPlace Place
ObjectQuestion Question
ObjectCollectionType OrderedCollection
HTTP Signatures
Function Description
SignRequest(req, keyID, privateKeyPEM) Sign an HTTP request with RSA-SHA256
VerifyRequest(req, publicKeyPEM) Verify an incoming HTTP signature (returns bool)
AddDateHeader(req) Set the Date header for signing
AddDigestHeader(req, body) Set the Digest header with SHA-256
DeliverActivity(inbox, body, keyID, privateKeyPEM) Sign & POST an activity to a remote inbox
GenerateKeyPair() Generate a RSA-2048 key pair (returns PEM strings)
LD Signatures
Function Description
SignJSONLD(doc, keyPair) Sign a JSON-LD document with RsaSignature2017
VerifyJSONLD(doc, sig, publicKeyPEM) Verify a Linked Data Signature
Safe Client
Function/Type Description
NewSafeClient() Create an HTTP client with SSRF protection
SafeClient.Get(url) Safe GET request
SafeClient.Post(url, contentType, body) Safe POST request
ResolveActorURI(uri) Extract username & domain from an actor URI

Architecture

your-app/
├── handlers/      # Gin/Echo/Chi HTTP handlers
├── federation/    # ActivityPub server logic
├── storage/       # Database layer (your choice)
└── ...
    └── imports github.com/Guaderxx/gap  # Protocol types & crypto

Gap is framework-agnostic. It provides:

  • Types — use them in your JSON serialization
  • Crypto — sign and verify requests
  • Client — make safe HTTP calls to remote servers

It does NOT provide:

  • Database storage
  • HTTP routing
  • Queue/worker systems
  • Real-time streaming

These belong in your application layer — gap is the protocol foundation.

Example Application

See example/main.go for a complete working example of a minimal ActivityPub server.

Dependencies

Zero external dependencies. Gap uses only the Go standard library:

  • crypto/* — RSA, SHA-256, ECDH, AES-GCM
  • net/http — HTTP client with custom transport
  • encoding/json, encoding/pem, encoding/base64

License

MIT. See LICENSE.

Documentation

Overview

Package gap implements the ActivityPub protocol (ActivityStreams 2.0) for Go.

GAP — Go ActivityPub. A lightweight, zero-dependency, framework-agnostic library for building federated social applications in Go.

What gap provides

ActivityPub types that follow the ActivityStreams 2.0 vocabulary:

  • Actor (Person, Application, Service, etc.)
  • Activity (Create, Follow, Like, Announce, Undo, Delete, Accept, Reject)
  • Note (equivalent to a Mastodon status/toot)
  • Collection (OrderedCollection with totalItems)
  • PublicKey (actor's public key for HTTP Signatures)

Discovery types:

  • WebFingerResponse / WebFingerLink (RFC 7033)
  • NodeInfo (NodeInfo 2.0 for instance discovery)

HTTP Signatures (draft-cavage-http-signatures-12):

  • SignRequest — sign an outbound HTTP request with RSA-SHA256
  • VerifyRequest — verify an incoming HTTP Signature
  • DeliverActivity — sign and POST an ActivityPub activity to a remote inbox
  • AddDateHeader / AddDigestHeader — set required signature headers
  • GenerateKeyPair — generate RSA-2048 key pair (returns PEM strings)

Linked Data Signatures:

  • SignJSONLD — sign a JSON-LD document with RsaSignature2017
  • VerifyJSONLD — verify a Linked Data Signature on a document

Safe HTTP client:

  • SafeClient / NewSafeClient — HTTP client with SSRF protection
  • ResolveActorURI — extract username and domain from an actor URI

What gap does NOT provide

Gap is intentionally minimal. It does NOT include:

  • Database storage
  • HTTP routing (use Gin, Echo, Chi, net/http, etc.)
  • Queue/worker systems
  • Real-time streaming / WebSockets
  • OAuth2 / authentication
  • Content formatting / HTML rendering

These belong in your application layer. Gap is the protocol foundation.

Quick start

import "github.com/Guaderxx/gap"

// Generate keys
privKey, pubKey, _ := gap.GenerateKeyPair()

// Create an actor
actor := gap.Actor{
    ID:   "https://example.com/users/alice",
    Type: "Person",
    Inbox: "https://example.com/users/alice/inbox",
    PublicKey: &gap.PublicKey{
        ID:           "https://example.com/users/alice#main-key",
        Owner:        "https://example.com/users/alice",
        PublicKeyPem: pubKey,
    },
}

// Deliver a Follow activity
activity := gap.Activity{
    Type:   "Follow",
    Actor:  "https://example.com/users/alice",
    Object: "https://mastodon.example/users/bob",
}
body, _ := json.Marshal(activity)
gap.DeliverActivity(inboxURL, body, keyID, privKey)

See example/main.go for a complete working server.

Index

Constants

View Source
const (
	ContextActivityStreams = "https://www.w3.org/ns/activitystreams"
	ContextSecurity        = "https://w3id.org/security/v1"
	PublicAddress          = "https://www.w3.org/ns/activitystreams#Public"
)

ActivityStreams 2.0 context URIs used in all ActivityPub documents.

View Source
const (
	ActivityAccept   = "Accept"
	ActivityAdd      = "Add"
	ActivityAnnounce = "Announce" // equivalent to a Mastodon boost/reblog
	ActivityBlock    = "Block"
	ActivityCreate   = "Create"
	ActivityDelete   = "Delete"
	ActivityFlag     = "Flag" // report
	ActivityFollow   = "Follow"
	ActivityLike     = "Like" // equivalent to a Mastodon favourite
	ActivityMove     = "Move" // account migration
	ActivityReject   = "Reject"
	ActivityRemove   = "Remove"
	ActivityUndo     = "Undo"
	ActivityUpdate   = "Update"
	ActivityView     = "View"
)

Activity types as defined by ActivityStreams 2.0. Use these constants instead of raw strings for type safety.

View Source
const (
	ActorPerson       = "Person"
	ActorApplication  = "Application"
	ActorService      = "Service"
	ActorGroup        = "Group"
	ActorOrganization = "Organization"
)

Actor types as defined by ActivityStreams 2.0.

View Source
const (
	ObjectNote           = "Note"
	ObjectArticle        = "Article"
	ObjectDocument       = "Document"
	ObjectImage          = "Image"
	ObjectVideo          = "Video"
	ObjectAudio          = "Audio"
	ObjectPage           = "Page"
	ObjectEvent          = "Event"
	ObjectPlace          = "Place"
	ObjectQuestion       = "Question"
	ObjectCollectionType = "OrderedCollection"
)

Object types as defined by ActivityStreams 2.0.

Variables

This section is empty.

Functions

func AddDateHeader

func AddDateHeader(req *http.Request)

AddDateHeader sets the Date header for HTTP Signature purposes.

func AddDigestHeader

func AddDigestHeader(req *http.Request, body []byte)

AddDigestHeader computes and sets the Digest header for the request body.

func DeliverActivity

func DeliverActivity(inboxURL string, activityJSON []byte, keyID string, privateKeyPEM string) error

DeliverActivity sends an ActivityPub activity JSON to a remote inbox. It sets the proper headers, signs the request, and handles the HTTP delivery.

func GenerateKeyPair

func GenerateKeyPair() (privatePEM, publicPEM string, err error)

GenerateKeyPair creates a new RSA 2048-bit key pair for HTTP Signatures. Returns the private key PEM and public key PEM.

func ResolveActorURI

func ResolveActorURI(uri string) (username, domain string)

ResolveActorURI extracts username and domain from an ActivityPub actor URI. For example, "https://mastodon.example/users/alice" returns ("alice", "mastodon.example").

func SignJSONLD

func SignJSONLD(document map[string]interface{}, keyPair LDKeyPair) (string, error)

SignJSONLD signs a JSON-LD document using RSA-SHA256 Linked Data Signatures. This is primarily for backward compatibility with older ActivityPub implementations. Mastodon 4.x primarily uses HTTP Signatures (see signature.go).

func SignRequest

func SignRequest(req *http.Request, keyID string, privateKeyPEM string) error

SignRequest signs an HTTP request with an RSA-SHA256 HTTP Signature (draft-cavage-http-signatures). It adds the Signature header to the request. The keyID should be the URL of the actor's public key.

func VerifyJSONLD

func VerifyJSONLD(document map[string]interface{}, signature map[string]interface{}, publicKeyPEM string) (bool, error)

VerifyJSONLD verifies a Linked Data Signature on a JSON-LD document.

func VerifyRequest

func VerifyRequest(req *http.Request, publicKeyPEM string) (bool, error)

VerifyRequest verifies an HTTP Signature on an incoming request. Returns true if the signature is valid for the given public key PEM.

Types

type Activity

type Activity struct {
	Context []string    `json:"@context"`
	ID      string      `json:"id"`
	Type    string      `json:"type"`
	Actor   string      `json:"actor"`
	Object  interface{} `json:"object,omitempty"`
	To      []string    `json:"to,omitempty"`
	CC      []string    `json:"cc,omitempty"`
}

Activity represents an ActivityPub activity (Create, Follow, Like, etc.).

type Actor

type Actor struct {
	Context           []string          `json:"@context"`
	Type              string            `json:"type"`
	ID                string            `json:"id"`
	Name              string            `json:"name,omitempty"`
	PreferredUsername string            `json:"preferredUsername"`
	Summary           string            `json:"summary,omitempty"`
	Inbox             string            `json:"inbox"`
	Outbox            string            `json:"outbox"`
	Followers         string            `json:"followers,omitempty"`
	Following         string            `json:"following,omitempty"`
	URL               string            `json:"url,omitempty"`
	PublicKey         *PublicKey        `json:"publicKey,omitempty"`
	Icon              *Image            `json:"icon,omitempty"`
	Image             *Image            `json:"image,omitempty"`
	Endpoints         map[string]string `json:"endpoints,omitempty"`
}

Actor represents an ActivityPub actor (Person, Application, Service, etc.).

type Collection

type Collection struct {
	Context      []string      `json:"@context"`
	ID           string        `json:"id"`
	Type         string        `json:"type"`
	TotalItems   int           `json:"totalItems"`
	First        string        `json:"first,omitempty"`
	OrderedItems []interface{} `json:"orderedItems,omitempty"`
}

Collection represents an ActivityPub ordered collection (followers, following, outbox).

type Image

type Image struct {
	Type      string `json:"type"`
	MediaType string `json:"mediaType"`
	URL       string `json:"url"`
}

Image represents an attached image in ActivityPub documents.

type LDKeyPair

type LDKeyPair struct {
	ID         string `json:"id"`
	Owner      string `json:"owner"`
	PrivateKey string `json:"privateKeyPem"`
	PublicKey  string `json:"publicKeyPem"`
}

LDKeyPair holds RSA keys for Linked Data Signatures.

type NodeInfo

type NodeInfo struct {
	Version           string           `json:"version"`
	Software          NodeInfoSoftware `json:"software"`
	Protocols         []string         `json:"protocols"`
	Usage             NodeInfoUsage    `json:"usage"`
	OpenRegistrations bool             `json:"openRegistrations"`
}

NodeInfo is the standard node information response (NodeInfo 2.0).

type NodeInfoSoftware

type NodeInfoSoftware struct {
	Name    string `json:"name"`
	Version string `json:"version"`
}

NodeInfoSoftware describes the server software.

type NodeInfoUsage

type NodeInfoUsage struct {
	Users NodeInfoUsers `json:"users"`
}

NodeInfoUsage contains instance statistics.

type NodeInfoUsers

type NodeInfoUsers struct {
	Total int `json:"total"`
}

NodeInfoUsers contains user counts.

type Note

type Note struct {
	ID           string   `json:"id"`
	Type         string   `json:"type"`
	AttributedTo string   `json:"attributedTo"`
	Content      string   `json:"content"`
	Published    string   `json:"published"`
	To           []string `json:"to,omitempty"`
	CC           []string `json:"cc,omitempty"`
	InReplyTo    string   `json:"inReplyTo,omitempty"`
	Sensitive    bool     `json:"sensitive,omitempty"`
	Summary      string   `json:"summary,omitempty"`
	URL          string   `json:"url,omitempty"`
}

Note represents an ActivityPub Note (equivalent to a Mastodon status/toot).

type PublicKey

type PublicKey struct {
	ID           string `json:"id"`
	Owner        string `json:"owner"`
	PublicKeyPem string `json:"publicKeyPem"`
}

PublicKey represents an actor's public key for HTTP Signatures.

type SafeClient

type SafeClient struct {
	// contains filtered or unexported fields
}

SafeClient is an HTTP client with SSRF protection. It blocks requests to private/internal IP ranges to prevent server-side request forgery.

func NewSafeClient

func NewSafeClient() *SafeClient

NewSafeClient creates an HTTP client with SSRF protection and a 30-second timeout.

func (*SafeClient) Do

func (c *SafeClient) Do(req *http.Request) (*http.Response, error)

Do performs a safe HTTP request with SSRF protection.

func (*SafeClient) Get

func (c *SafeClient) Get(urlStr string) (*http.Response, error)

Get performs a safe GET request.

func (*SafeClient) Post

func (c *SafeClient) Post(urlStr, contentType string, body string) (*http.Response, error)

Post performs a safe POST request with a JSON body.

type WebFingerLink struct {
	Rel  string `json:"rel,omitempty"`
	Type string `json:"type,omitempty"`
	Href string `json:"href,omitempty"`
}

WebFingerLink represents a link in a WebFinger JRD response (RFC 7033).

type WebFingerResponse

type WebFingerResponse struct {
	Subject string          `json:"subject"`
	Aliases []string        `json:"aliases,omitempty"`
	Links   []WebFingerLink `json:"links,omitempty"`
}

WebFingerResponse is the JSON Resource Descriptor for WebFinger.

Directories

Path Synopsis
Example: minimal ActivityPub server using the gap library.
Example: minimal ActivityPub server using the gap library.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL