feat(webpush): add Web Push (VAPID) provider - #30
Merged
Merged
Conversation
Ports Charon's orchestration model (.claude/agents/planning, supervisor, go-dev, qa-security, devops, docs-writer) scaled to this repo's surface — no frontend-dev/playwright-dev since there's no frontend or E2E here. CLAUDE.md's Orchestration Model section replaces the prior "no multi-agent pipeline" stance. Also installs lefthook (scaled down from Charon's lefthook.yml: file hygiene, shellcheck, actionlint, go vet, staticcheck) so the Definition of Done's lint requirements are enforced automatically on every commit instead of relying on remembering to run them by hand. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JdcrJhvqk42UL3b1mEKFMs
Records the approved technical spec for providers/webpush (RFC 8291/8292 Web Push) so the design that drove the implementation is tracked in repo history alongside it, matching Charon's docs/plans/*.md convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zpLzMxyw465hYA4WjoQVW
Webpush's Config.TTL needs an int-typed config field; regconfig only had StringField/StringSliceField before this. Accepts int/int64/float64 to cover both native Go construction and a generic JSON-style decode. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zpLzMxyw465hYA4WjoQVW
…aders Web Push requires these four RFC 8030/8188 headers on every request, none of which were in the outbound header allowlist. Additive only — every existing provider's tests still pass unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zpLzMxyw465hYA4WjoQVW
Implements the Web Push message encryption content-coding: ECDH between a fresh ephemeral sender keypair and the subscriber's p256dh key, two-step HKDF-SHA256 derivation of the content-encryption key/nonce, and RFC 8188 single-record aes128gcm framing. encryptAES128GCMWithKeys takes the ephemeral keypair/salt as parameters (rather than generating them) so tests can force RFC 8291 Appendix A's exact published example and assert byte-for-byte equality against its ciphertext — the highest-value correctness gate in this feature, since a round-trip-only test can't catch a bug that's symmetric on both the encrypt and decrypt sides. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zpLzMxyw465hYA4WjoQVW
buildVAPIDHeader signs an ES256 JWT (raw r||s signature, not ASN.1 DER) and formats it as the RFC 8292 "vapid t=<jwt>, k=<key>" Authorization header value; it takes the three VAPID strings directly rather than a Config, since Config isn't defined until a later commit. GenerateVAPIDKeyPair is a small convenience for hosts that don't already have an externally generated keypair, using the non-deprecated ecdsa.ParseRawPrivateKey/ ParseUncompressedPublicKey/Bytes APIs to avoid crypto/elliptic's deprecated low-level Marshal/ScalarBaseMult surface. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zpLzMxyw465hYA4WjoQVW
Wires the RFC 8291 encryption and RFC 8292 VAPID signing pieces from the previous two commits into a full notify.Sender: fail-fast validation of all six required Config fields plus a VAPID keypair-consistency check (catches a mismatched public/private key pair locally instead of an opaque remote 401), Urgency/Topic validation, template rendering, encryption, and dispatch via transport.Wrapper with the aes128gcm/TTL/ Authorization headers set. Unlike ntfy/pushover, the rendered payload is not required to carry a "message" field — the payload shape is entirely up to the receiving service worker, which this module has no visibility into. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zpLzMxyw465hYA4WjoQVW
Registers the webpush Factory under "webpush" (mirroring every other provider's register.go convention) and adds it to providers/all's blank imports so it's available via notify.New without an explicit per-provider import. Bumps providers/all's wantProviderCount safety-net test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zpLzMxyw465hYA4WjoQVW
Adds a providers/webpush row to README's provider table, updates the "Project status" paragraph to explain why webpush is a deliberate, maintainer-approved exception to the "no new providers without an explicit ask" policy rather than a violation of it, adds a webpush.New construction example (with the one-Client-per-subscriber fan-out pattern) to docs/INTEGRATION.md, and flags webpush.Config's app-wide/per-recipient field split as a documented precedent in ARCHITECTURE.md §3.2. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zpLzMxyw465hYA4WjoQVW
… add QA report QA/Security final gate for issue #14 (Web Push). Closes real (non-fault-injection) coverage gaps: invalid-base64 (not merely wrong-length) p256dh/auth in encryptAES128GCMWithKeys, and malformed VAPID public/private keys reached through the real Client.Send path rather than only the lower-level buildVAPIDHeader seam. providers/webpush package coverage 87.7% -> 89.6%. Full gate results, SSRF/retry/crypto review, and two non-blocking findings (raw Endpoint echoed into a vapid.go error on malformed-URL input; the INTEGRATION.md example logs the raw Endpoint on send failure) are in docs/reports/qa_report.md, with concrete remediations for both. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zpLzMxyw465hYA4WjoQVW
buildVAPIDHeader wrapped net/url.Parse's error with %w, which embeds the full raw input — leaking a malformed Endpoint's bearer-token-equivalent path segment (e.g. FCM) into Send's returned error. Return a generic message instead, matching transport/wrapper.go's buildSafeRequestURL convention for the same class of failure. Fixes QA report Finding 1 (MEDIUM, docs/reports/qa_report.md). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zpLzMxyw465hYA4WjoQVW
…example Fixes QA report Finding 2 (LOW): docs/INTEGRATION.md's webpush fan-out example logged sub.Endpoint verbatim on every Send failure, which for push services like FCM commonly embeds a bearer-token-equivalent path segment; it now logs only the endpoint's host via neturl.Parse. Also fixes stale provider-count/list omissions of webpush found during the final docs pass (README.md intro, ARCHITECTURE.md's providers/* list and §3.8, docs/INTEGRATION.md's "eight providers" bullets) so all three docs correctly reflect nine built-in providers including webpush. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zpLzMxyw465hYA4WjoQVW
Integrates the 0.2.3 release-please automation (version bump + changelog) that landed upstream while the webpush provider work was in progress locally. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zpLzMxyw465hYA4WjoQVW
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
providers/webpush, a newnotify.Senderfor direct browser Web Push delivery (RFC 8291 payload encryption + RFC 8292 VAPID auth) — no third-party relay involved, stdlib-only crypto (crypto/ecdh,crypto/hkdf,crypto/ecdsa,crypto/aes/crypto/cipher). Closes Add provider: Web Push (Push API / VAPID) #14.GenerateVAPIDKeyPair()for setup ergonomics, and a documentedConfig.TTL == 0 → 4-week defaultpolicy (a deliberate, permanent simplification vs. RFC 8030's literal zero-TTL "attempt-only" semantics — flagged explicitly in the spec and doc comments).providers/internal/regconfig.IntFieldhelper, and an additive-onlytransport.sanitizeOutboundHeadersallowlist addition (Content-Encoding/TTL/Urgency/Topic) needed for Web Push requests.docs/plans/current_spec.md(design), two rounds of implementation review, anddocs/reports/qa_report.md(final QA/security pass — 2 non-blocking findings, both fixed).Test plan
go build ./...go vet ./.../staticcheck ./...cleango test ./...— all packages passbash scripts/test-coverage.sh— 94.6% repo-wide (floor 85%)grep -r "Wikid82/charon" --include=*.go .— zero hitsproviders/all/all_test.go'swantProviderCountbumped 8→9, passing