Help us shape the future of Go! Take the 2026 Go Developer Survey

webpush

package
v0.56.0 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package webpush sends Web Push messages: RFC 8030 delivery, the payload encrypted for the receiving browser (RFC 8291, "aes128gcm") and every request signed with the application server's VAPID key (RFC 8292).

It is the transport and nothing else. Who is told what, and when, is internal/notify's (push.go): a push is one more reader of a person's bell, never a channel with rules of its own.

Standard library only: the key is ECDSA P-256 (crypto/ecdsa for the VAPID signature, crypto/ecdh for the payload's key agreement), the key schedule is crypto/hkdf, the record is AES-128-GCM.

⚠ An endpoint is written by the browser but reaches the server in a person's request, so it is an address a person chose. Policy.Check (an allow-list of the browsers' push services) and SafeClient (no address on this machine or a private network, no proxy, no redirect) are what keep the sender from being a way to make the server POST into its own network.

Index

Constants

View Source
const MaxEndpoint = 2048

MaxEndpoint bounds an endpoint's length. The longest real ones (Windows Push Notification Services) are a few hundred characters.

View Source
const MaxPayload = recordSize - headerSize - 1 - 16

MaxPayload is the most plaintext one push carries: a push service must take a 4096-byte body (RFC 8291 §4), which holds the header, the plaintext, its one-byte delimiter and the 16-byte tag.

Variables

View Source
var DefaultHosts = []string{

	"fcm.googleapis.com",
	"android.googleapis.com",

	"updates.push.services.mozilla.com",
	"push.services.mozilla.com",

	"web.push.apple.com",

	"notify.windows.com",
}

DefaultHosts are the push services of the browsers filex supports. An endpoint's host is accepted when it is one of them or below one of them.

View Source
var ErrEndpoint = errors.New("webpush: endpoint refused")

ErrEndpoint is what Policy.Check answers an endpoint it refuses with.

View Source
var ErrKeys = errors.New("webpush: subscription keys unusable")

ErrKeys is what CheckKeys answers a subscription's unusable keys with.

View Source
var ErrTooLarge = errors.New("webpush: payload too large")

ErrTooLarge is a payload over MaxPayload.

Functions

func CheckKeys

func CheckKeys(p256dh, auth string) error

CheckKeys reports whether a subscription's keys are what RFC 8291 needs: a P-256 point (65 bytes, uncompressed) and a 16-byte auth secret.

func Decrypt

func Decrypt(body []byte, ua *ecdh.PrivateKey, authSecret []byte) ([]byte, error)

Decrypt is the browser's half of Encrypt: what a subscription whose private key is ua and whose auth secret is authSecret reads out of body. filex never receives a push; this is how its tests read what it sent.

func Encrypt

func Encrypt(plaintext []byte, p256dh, auth string) ([]byte, error)

Encrypt seals plaintext for one browser subscription (RFC 8291): a fresh key agreed with the browser's p256dh key, the auth secret mixed in, a random salt, one aes128gcm record. The result is the request's body.

func EndpointHash

func EndpointHash(endpoint string) string

EndpointHash is the lower-hex SHA-256 of an endpoint, as given. It is how a device is named on the wire (GET /api/notifications/push): the client hashes its own subscription's endpoint the same way to find itself in the list, and the endpoint itself - an address anybody holding it may push to, given the key - never leaves the server.

func PublicAddr

func PublicAddr(ip netip.Addr) bool

PublicAddr reports whether ip is an address on the public internet: not this machine, not a private, link-local, shared or multicast network.

func SafeClient

func SafeClient(timeout time.Duration) *http.Client

SafeClient is the client pushes go out on: direct connections only (no proxy from the environment - a proxy would dial the address for us, past the check), never to an address that is not public, and no redirects.

func ServiceOf

func ServiceOf(endpoint string) string

ServiceOf is the push service an endpoint belongs to (its host), for a person reading their device list.

Types

type Key

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

Key is the application server's VAPID key pair (RFC 8292): ECDSA on P-256. Its public half is the `applicationServerKey` every browser subscribes with, and a push service accepts a push for a subscription only when it is signed by the private half - so a leaked endpoint alone pushes nothing.

func GenerateKey

func GenerateKey() (*Key, error)

GenerateKey makes a new key pair.

func ParseKey

func ParseKey(text string) (*Key, error)

ParseKey reads MarshalPrivate's text back.

func (*Key) Authorization

func (k *Key) Authorization(endpoint, subject string, exp time.Time) (string, error)

Authorization is the VAPID header of a request to endpoint, valid until exp (RFC 8292 §2-3): `vapid t=<JWT>, k=<public key>`. The token's audience is the endpoint's origin; subject is the contact a push service may write to about this server (a mailto: or an https: address - Apple refuses others).

⚠ exp no later than 24 hours ahead: a push service refuses a longer one.

func (*Key) MarshalPrivate

func (k *Key) MarshalPrivate() (string, error)

MarshalPrivate is the key as text (PKCS #8, base64) - what is sealed and stored. ⚠ A secret: it never leaves the server unsealed.

func (*Key) Public

func (k *Key) Public() string

Public is the public key as a browser takes it (`applicationServerKey`) and as every request names it (`k=`): the uncompressed point, base64url.

type Message

type Message struct {
	Payload []byte
	// TTL is how long the push service keeps it for a device that is offline.
	TTL time.Duration
	// Urgency is very-low, low, normal or high (RFC 8030 §5.3); "" is normal.
	Urgency string
}

Message is one push.

type Policy

type Policy struct {
	// Hosts are the push services accepted. Empty: DefaultHosts.
	Hosts []string
	// AnyHost accepts any https host that is a name, not an address
	// (FILEX_PUSH_HOSTS=*): a browser whose push service the list does not
	// know. SafeClient still refuses one that resolves into a private network.
	AnyHost bool
	// Insecure accepts http:// and any host and port - tests only, where the
	// push service is an httptest server on this machine.
	Insecure bool
}

Policy decides which endpoints a push may be sent to.

func (Policy) Check

func (p Policy) Check(endpoint string) error

Check answers nil for an endpoint a push may go to, an ErrEndpoint saying why otherwise: an https address of a known push service, on the default port, with no credentials in it.

type Result

type Result struct {
	Status int
	// Gone: the subscription no longer exists (404, 410) and is to be
	// forgotten - the browser unsubscribed, or the person cleared the site.
	Gone bool
}

Result is the push service's answer.

type Sender

type Sender struct {
	// Client is the HTTP client. Nil: SafeClient (10 s).
	Client *http.Client
	// Subject is the VAPID contact (mailto: or https:).
	Subject string
	// Now is the clock the tokens' expiry is read from. Nil: time.Now.
	Now func() time.Time
}

Sender sends pushes.

func (*Sender) Send

func (s *Sender) Send(ctx context.Context, key *Key, sub Subscription, msg Message) (Result, error)

Send encrypts msg for sub, signs it with key and posts it. A push service that took it answers 201 (any 2xx is taken); every other answer is an error, with Result.Gone set for a subscription that no longer exists.

type Subscription

type Subscription struct {
	Endpoint string
	P256dh   string
	Auth     string
}

Subscription is where one browser takes its pushes: what its PushSubscription.toJSON() gives.

Jump to

Keyboard shortcuts

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