Add HTTP Signature helper; add url/photo to AP profile
Committed 89e328
--- a/pyproject.toml
+++ b/pyproject.toml
[tool.poetry.dependencies]
python = ">=3.10,<3.11"
webint = ">=0.0"
+httpsig = "^1.3.0"
[tool.poetry.group.dev.dependencies]
gmpg = {path="../gmpg", develop=true}
--- a/webint_owner/__init__.py
+++ b/webint_owner/__init__.py
import os
import pathlib
import subprocess
+from base64 import b64encode
+from hashlib import sha256
+from urllib.parse import urlparse
+import httpsig
+import requests
+import requests.auth
import web
from web import tx
)
+def ap_request(url, data=None):
+ headers = {
+ "Accept": "application/activity+json",
+ "Date": web.now().strftime("%a, %d %b %Y %H:%M:%S GMT"),
+ "Digest": f"SHA-256={b64encode(sha256(data or b'').digest()).decode()}",
+ }
+ auth = HTTPSignatureAuth(
+ f"{tx.origin}/owner/actor#main-key",
+ ap_pvtkey.open("r").read(),
+ algorithm="rsa-sha256",
+ headers=("Date", "Host", "Digest", "(request-target)"),
+ sign_header="signature",
+ )
+ return requests.get(url, headers=headers, auth=auth).json()
+
+
+class HTTPSignatureAuth(requests.auth.AuthBase):
+ def __init__(
+ self,
+ key_id="",
+ secret="",
+ algorithm=None,
+ headers=None,
+ sign_header="authorization",
+ ):
+ headers = headers or []
+ self.header_signer = httpsig.sign.HeaderSigner(
+ key_id=key_id,
+ secret=secret,
+ algorithm=algorithm,
+ headers=headers,
+ sign_header=sign_header,
+ )
+ self.uses_host = "host" in [h.lower() for h in headers]
+
+ def __call__(self, r):
+ headers = self.header_signer.sign(
+ r.headers,
+ host=urlparse(r.url).netloc if self.uses_host else None,
+ method=r.method,
+ path=r.path_url,
+ )
+ r.headers.update(headers)
+ return r
+
+
@app.query
def get_identities(db):
"""Return identity with given `uid`."""
return "Passphrase must be manually deleted first."
+ap_pubkey = pathlib.Path("ap_public.pem")
+ap_pvtkey = pathlib.Path("ap_private.pem")
+
+
@app.control("actor")
class ActivityPubActor:
"""."""
def get(self):
"""."""
- ap_pubkey = pathlib.Path("ap_public.pem")
- ap_pvtkey = pathlib.Path("ap_private.pem")
if not ap_pubkey.exists():
subprocess.run(["openssl", "genrsa", "-out", ap_pvtkey, "2048"])
subprocess.run(
with ap_pubkey.open() as fp:
pubkey = fp.read().strip()
web.header("Content-Type", "application/ld+json")
- return {
+ profile = {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
],
"id": f"{tx.origin}/owner/actor",
+ "url": [f"{tx.origin}"],
"type": "Person",
"preferredUsername": tx.host.owner["nickname"][0],
"name": tx.host.owner["name"][0],
"publicKeyPem": pubkey,
},
}
+ card = app.model.get_identity()
+ if photo := card.get("photo"):
+ profile["icon"] = [{"type": "Image", "url": f"{tx.origin}/{photo}"}]
+ return profile
@app.control(".well-known/webfinger", prefixed=False)