Repository navigation
Add webhook subscriptions with allow-listed URLs, a secret shown once and the admin page - #2608
Merged
Merged
Conversation
… and the admin page
# Conflicts: # src/FluentCMS.Infrastructure/InfrastructureServiceCollectionExtensions.cs # src/FluentCMS.Infrastructure/Persistence/FluentCmsDbContext.cs
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.
Closes #2567
What
The first slice of F-WHK (#2431): webhook subscriptions with allow-listed URLs and a signing secret shown once, plus the admin page that adds, lists and disables them (API-05, SEC-05, SEC-07).
FluentCMS.Core.Webhooks):IWebhookDirectory:ListAsync,AddAsync(NewWebhookSubscription)andDisableAsync(id), each returning aResult. Only a signed-in person may call them. An agent, an engine or no one getswebhook_people_only(SEC-02).WebhookSubscriptionhas a URL, its events (change_set.published,change_set.reverted), when and by whom it was added, and when it was disabled. It is never edited; it is disabled and added again.https, orhttpto a loopback host for a local endpoint;Each failed rule returns its own code with a message that says what to change.
WebhookSecrets.Generate(): 32 random bytes in the Standard Webhooks form,whsec_and base64.ISecretProtector(FluentCMS.Core.Security):ProtectandUnprotectunder a purpose. The host implements it with its Data Protection key ring, which both the web and stdio hosts share.WebhookSubscriptionstable, with migrations for both providers. The secret is stored only as ciphertext.webhook.addedandwebhook.disabledare appended in the write's unit of work. They name the subscription and never its secret./webhooks, with a link in the header.Why
API-05 asks for an outbound webhook on publish and revert, and SEC-05 for outbound access only to hosts the administrator allow-lists. Delivery needs somewhere to send and a secret to sign with, so subscriptions come first.
Choices worth a look:
Webhooks__AllowedHostswith*.example.comentries. The model gateway already hasOutbound__AllowedHostsbehindIOutboundHosts, where a host also allows its subdomains, so webhooks use that one. An administrator manages one list for SEC-05. I updated the issue to match.FluentCMS.Webhooks.SigningSecret, so no other protected value opens it. Losing the key ring loses the secrets as it loses sign-ins, and CONTRIBUTING says so./agents(Admin revoke and disable: a repeated post shows a blank page, and a lost race fails inside the unit of work #2610): a second disable post for the same webhook shows a blank page rather than "already disabled", and a disable that loses a race reports it inside its unit of work.Tests
Integration, each on SQLite and PostgreSQL:
webhook.addednames the subscription and the person.example.comandlocalhost:5005allowed,example.com, a deeper subdomain andhttp://localhost:5005are accepted.example.com.evil.test, another host, plain http on a public host, another local port, a user name and password, a URL without a scheme,ftp, a URL over 2,048 characters, no events and an unknown event. A URL that breaks several rules reports each.webhook.disabled. A second disable and an unknown id are refused.Downdrops the table andUpadds it again.Integration, admin page:
/webhookssends an anonymous request to sign-in.The full suite ran locally against a
postgres:17container as well as SQLite, and markdownlint passes. No new package and no new project reference.Checklist
v2