my eye

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)