# HG changeset patch # User Kim Alvefur # Date 1535115129 -7200 # Node ID 1df139b157fb3a4678c69a5ca1a2c41a5b205352 # Parent e86315c9b5c46680aa846a85825138995aa3e31b mod_pubsub_post: Add support for WebSub authentication diff -r e86315c9b5c4 -r 1df139b157fb mod_pubsub_post/README.markdown --- a/mod_pubsub_post/README.markdown Fri Mar 29 17:06:18 2019 +0100 +++ b/mod_pubsub_post/README.markdown Fri Aug 24 14:52:09 2018 +0200 @@ -43,6 +43,24 @@ to attempt to publish to a non-existant node becomes owner of it, which includes publishing rights. +## WebSub + +``` {.lua} +-- Per node secrets +pubsub_post_secrets = { + my_node = "shared secret" +} + +-- Same secret for all nodes +pubsub_post_secret = "shared secret" +``` + +This enables the +[WebSub](https://www.w3.org/TR/2018/REC-websub-20180123/) [Authenticated +Content +Distribution](https://www.w3.org/TR/2018/REC-websub-20180123/#authenticated-content-distribution) +authentication method, where payloads are signed using a shared secret. + ## Setting up affiliations Prosodys PubSub module supports [setting affiliations via diff -r e86315c9b5c4 -r 1df139b157fb mod_pubsub_post/mod_pubsub_post.lua --- a/mod_pubsub_post/mod_pubsub_post.lua Fri Mar 29 17:06:18 2019 +0100 +++ b/mod_pubsub_post/mod_pubsub_post.lua Fri Aug 24 14:52:09 2018 +0200 @@ -5,6 +5,14 @@ local xml = require "util.xml"; local uuid_generate = require "util.uuid".generate; local timestamp_generate = require "util.datetime".datetime; +local hashes = require "util.hashes"; +local from_hex = require "util.hex".from; +local hmacs = { + sha1 = hashes.hmac_sha1; + sha256 = hashes.hmac_sha256; + sha384 = hashes.hmac_sha384; + sha512 = hashes.hmac_sha512; +}; local pubsub_service = module:depends("pubsub").service; @@ -68,6 +76,17 @@ end local actor_source = module:get_option_string("pubsub_post_actor", "superuser"); +local actor_secret = module:get_option_string("pubsub_post_secret"); +local actor_secrets = module:get_option("pubsub_post_secrets"); + +local function verify_signature(secret, body, signature) + if not signature then return false; end + local algo, digest = signature:match("^([^=]+)=(%x+)"); + if not algo then return false; end + local hmac = hmacs[algo]; + if not algo then return false; end + return hmac(secret, body) == from_hex(digest); +end function handle_POST(event, path) local request = event.request; @@ -76,6 +95,11 @@ local content_type = request.headers.content_type or "application/octet-stream"; local actor; + local secret = actor_secrets and actor_secrets[path] or actor_secret; + if secret and not verify_signature(secret, request.body, request.headers.x_hub_signature) then + return 401; + end + if actor_source == "request.ip" then actor = request.ip or request.conn:ip(); elseif actor_source == "superuser" then