Skip to content

necessary-nu/misotext-apns

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

misotext-apns

A small service that relays WebPush (RFC 8030) notifications to Apple's Push Notification service. It is wire-compatible with the original Ruby feditext-apns (itself a fork of Metabolist's metatext-apns), so existing Mastodon push subscriptions and the Feditext iOS notification service extension keep working.

This is a Rust port of feditext-apns. The endpoints, URL shape, and APNS payload JSON are unchanged. What's different is what's inside: a single Tokio process multiplexing requests over one HTTP/2 connection per environment instead of a multi-process / multi-thread Ruby server with a connection pool.

Endpoints

  • GET /200. Health check.
  • POST /push/{device_token}/{id}?sandbox={true|false} — relay a WebPush message.
    • device_token: Base16 device token from application(_:didRegisterForRemoteNotificationsWithDeviceToken:).
    • id: opaque account identifier (echoed back to the client as i in the payload).
    • sandbox=true routes to APNS development; otherwise production.
    • Request body: the encrypted WebPush ciphertext, opaque to this service.
    • Content-Encoding must be aes128gcm or aesgcm; anything else → 415.
    • For aesgcm, the Encryption: salt=... and Crypto-Key: dh=...;... headers are parsed and forwarded as s and k.
    • Always returns 202 immediately. APNS errors are logged, not surfaced.

The APNS payload sent to Apple includes:

  • aps.alert = { "loc-key": "apns-default-message" }
  • aps.mutable-content = 1 (so the iOS notification service extension fires)
  • Custom keys at the top level: e (encoding), i (account id), m (URL-safe Base64 of the ciphertext), and for aesgcm also s (salt) and k (server public key from the dh= parameter).
  • HTTP header apns-push-type: alert.

Configuration

All configuration is via environment variables. The service exits at startup if any required variable is missing.

Variable Required Notes
APNS_AUTH_KEY yes PEM contents of the .p8 private key from the Apple Developer portal (literal newlines, not \n escapes).
APNS_KEY_ID yes Key ID from the Apple Developer portal.
APPLE_TEAM_ID yes Team ID from the Apple Developer account.
TOPIC yes The app's bundle ID, sent as the apns-topic header.
PORT no TCP port to bind. Default 3000.
RUST_LOG no tracing-subscriber filter directive. Default info.

The Ruby-era variables CONNECTION_POOL_SIZE, WEB_CONCURRENCY, MAX_THREADS, and RACK_ENV are not used. HTTP/2 multiplexes many concurrent notifications over a single connection per environment, so there is nothing equivalent to tune.

Operational notes

  • APNS root CA rotated to USERTrust RSA (sandbox 2025-01-21, production 2025-02-24). This binary uses rustls with webpki-roots, which already trusts USERTrust, so no action is required. If you switch the TLS backend, make sure your trust store includes the new root.
  • Team-scoped and topic-specific keys (introduced February 2025) work transparently — generate them in the developer portal and drop them into APNS_AUTH_KEY.

Running

cargo run --release

The binary listens on 0.0.0.0:$PORT. Put it behind a reverse proxy if you need TLS termination or rate limiting. The service has no authentication of its own — it assumes whoever can reach it is trusted to send pushes (the same trust model as the original Ruby version).

Graceful shutdown on SIGINT (Ctrl-C) drains the listener.

License

Licensed under the GNU Affero General Public License, version 3 or later (AGPL-3.0-or-later). See LICENSE for the full text.

This is a derivative work of feditext-apns, which is itself derived from Metabolist's metatext-apns (Copyright © 2021 Metabolist). The AGPL terms are inherited from those upstream projects.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages