diff --git a/doc/Per-User Database b/doc/Per-User Database new file mode 100644 index 0000000..e69de29 diff --git a/src/clj_activitypub/README.md b/src/clj_activitypub/README.md deleted file mode 100644 index ff3d8f9..0000000 --- a/src/clj_activitypub/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# NOTE - -Files in this directory are copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub). If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project. \ No newline at end of file diff --git a/src/clj_activitypub/core.clj b/src/clj_activitypub/core.clj deleted file mode 100644 index 9385002..0000000 --- a/src/clj_activitypub/core.clj +++ /dev/null @@ -1,150 +0,0 @@ -(ns clj-activitypub.core - "copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub). - If and when Jahfer issues a release of that library, this directory will be deleted and a - dependency on that library will be added to the project." - (:require [clj-activitypub.internal.crypto :as crypto] - [clj-activitypub.internal.thread-cache :as thread-cache] - [clj-activitypub.internal.http-util :as http] - [clj-http.client :as client] - [clojure.string :as str])) - -(defn config - "Creates hash of computed data relevant for most ActivityPub utilities." - [{:keys [domain username username-route public-key private-key] - :or {username-route "/users/" - public-key nil - private-key nil}}] - (let [base-url (str "https://" domain)] - {:domain domain - :base-url base-url - :username username - :user-id (str base-url username-route username) - :public-key public-key - :private-key (when private-key - (crypto/private-key private-key))})) - -(defn parse-account - "Given an ActivityPub handle (e.g. @jahfer@mastodon.social), produces - a map containing {:domain ... :username ...}." - [handle] - (let [[username domain] (filter #(not (str/blank? %)) - (str/split handle #"@"))] - {:domain domain :username username})) - -(def ^:private user-cache (thread-cache/make)) -(defn fetch-user - "Fetches the customer account details located at user-id from a remote - server. Will return cached results if they exist in memory." - [user-id] - ((:get-v user-cache) - user-id - #(:body - (client/get user-id {:as :json-string-keys - :throw-exceptions false - :ignore-unknown-host? true - :headers {"Accept" "application/activity+json"}})))) - -(defn actor - "Accepts a config, and returns a map in the form expected by the ActivityPub - spec. See https://www.w3.org/TR/activitypub/#actor-objects for reference." - [{:keys [user-id username public-key]}] - {"@context" ["https://www.w3.org/ns/activitystreams" - "https://w3id.org/security/v1"] - :id user-id - :type "Person" - :preferredUsername username - :inbox (str user-id "/inbox") - :outbox (str user-id "/outbox") - :publicKey {:id (str user-id "#main-key") - :owner user-id - :publicKeyPem (or public-key "")}}) - -(def signature-headers ["(request-target)" "host" "date" "digest"]) - -(defn- str-for-signature [headers] - (let [headers-xf (reduce-kv - (fn [m k v] - (assoc m (str/lower-case k) v)) {} headers)] - (->> signature-headers - (select-keys headers-xf) - (reduce-kv (fn [coll k v] (conj coll (str k ": " v))) []) - (interpose "\n") - (apply str)))) - -(defn gen-signature-header - "Generates a HTTP Signature string based on the provided map of headers." - [config headers] - (let [{:keys [user-id private-key]} config - string-to-sign (str-for-signature headers) - signature (crypto/base64-encode (crypto/sign string-to-sign private-key)) - sig-header-keys {"keyId" user-id - "headers" (str/join " " signature-headers) - "signature" signature}] - (->> sig-header-keys - (reduce-kv (fn [m k v] - (conj m (str k "=" "\"" v "\""))) []) - (interpose ",") - (apply str)))) - -(defn auth-headers - "Given a config and request map of {:body ... :headers ...}, returns the - original set of headers with Signature and Digest attributes appended." - [config {:keys [body headers]}] - (let [digest (http/digest body) - h (-> headers - (assoc "Digest" digest) - (assoc "(request-target)" "post /inbox"))] - (assoc headers - "Signature" (gen-signature-header config h) - "Digest" digest))) - -(defmulti obj - "Produces a map representing an ActivityPub object which can be serialized - directly to JSON in the form expected by the ActivityStreams 2.0 spec. - See https://www.w3.org/TR/activitystreams-vocabulary/ for reference." - (fn [_config object-data] (:type object-data))) - -(defmethod obj :note - [{:keys [user-id]} - {:keys [id published inReplyTo content to] - :or {published (http/date) - inReplyTo "" - to "https://www.w3.org/ns/activitystreams#Public"}}] - {"id" (str user-id "/notes/" id) - "type" "Note" - "published" published - "attributedTo" user-id - "inReplyTo" inReplyTo - "content" content - "to" to}) - -(defmulti activity - "Produces a map representing an ActivityPub activity which can be serialized - directly to JSON in the form expected by the ActivityStreams 2.0 spec. - See https://www.w3.org/TR/activitystreams-vocabulary/ for reference." - (fn [_config activity-type _data] activity-type)) - -(defmethod activity :create [{:keys [user-id]} _ data] - {"@context" ["https://www.w3.org/ns/activitystreams" - "https://w3id.org/security/v1"] - "type" "Create" - "actor" user-id - "object" data}) - -(defmethod activity :delete [{:keys [user-id]} _ data] - {"@context" ["https://www.w3.org/ns/activitystreams" - "https://w3id.org/security/v1"] - "type" "Delete" - "actor" user-id - "object" data}) - -(defn with-config - "Returns curried forms of the #activity and #obj multimethods in the form - {:activity ... :obj ...}, with the initial parameter set to config." - [config] - (let [f (juxt - #(partial activity %) - #(partial obj %)) - [activity-fn obj-fn] (f config)] - {:activity activity-fn - :obj obj-fn})) \ No newline at end of file diff --git a/src/clj_activitypub/internal/crypto.clj b/src/clj_activitypub/internal/crypto.clj deleted file mode 100644 index 0de519d..0000000 --- a/src/clj_activitypub/internal/crypto.clj +++ /dev/null @@ -1,39 +0,0 @@ -(ns clj-activitypub.internal.crypto - "copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub). - If and when Jahfer issues a release of that library, this directory will be deleted and a - dependency on that library will be added to the project." - (:require [clojure.java.io :as io]) - (:import (java.util Base64) - (java.security MessageDigest SecureRandom Signature))) - -(java.security.Security/addProvider - (org.bouncycastle.jce.provider.BouncyCastleProvider.)) - -(defn- keydata [reader] - (->> reader - (org.bouncycastle.openssl.PEMParser.) - (.readObject))) - -(defn- pem-string->key-pair [string] - (let [kd (keydata (io/reader (.getBytes string)))] - (.getKeyPair (org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.) kd))) - -(defn private-key [private-pem-str] - (-> private-pem-str - (pem-string->key-pair) - (.getPrivate))) - -(defn base64-encode [bytes] - (.encodeToString (Base64/getEncoder) bytes)) - -(defn sha256-base64 [data] - (let [digest (.digest (MessageDigest/getInstance "SHA-256") (.getBytes data))] - (base64-encode digest))) - -(defn sign [data private-key] - (let [bytes (.getBytes data) - signer (doto (Signature/getInstance "SHA256withRSA") - (.initSign private-key (SecureRandom.)) - (.update bytes))] - (.sign signer))) - diff --git a/src/clj_activitypub/internal/http_util.clj b/src/clj_activitypub/internal/http_util.clj deleted file mode 100644 index 5832884..0000000 --- a/src/clj_activitypub/internal/http_util.clj +++ /dev/null @@ -1,28 +0,0 @@ -(ns clj-activitypub.internal.http-util - "copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub). - If and when Jahfer issues a release of that library, this directory will be deleted and a - dependency on that library will be added to the project." - (:require [clj-activitypub.internal.crypto :as crypto]) - (:import (java.net URLEncoder) - (java.time OffsetDateTime ZoneOffset) - (java.time.format DateTimeFormatter))) - -(defn encode-url-params [params] - (->> params - (reduce-kv - (fn [coll k v] - (conj coll - (str (URLEncoder/encode (name k)) "=" (URLEncoder/encode (str v))))) - []) - (interpose "&") - (apply str))) - -(defn date [] - (-> (OffsetDateTime/now (ZoneOffset/UTC)) - (.format DateTimeFormatter/RFC_1123_DATE_TIME))) - -(defn digest - "Accepts body from HTTP request and generates string - for use in HTTP `Digest` request header." - [body] - (str "sha-256=" (crypto/sha256-base64 body))) \ No newline at end of file diff --git a/src/clj_activitypub/internal/thread_cache.clj b/src/clj_activitypub/internal/thread_cache.clj deleted file mode 100644 index 3f79264..0000000 --- a/src/clj_activitypub/internal/thread_cache.clj +++ /dev/null @@ -1,47 +0,0 @@ -(ns clj-activitypub.internal.thread-cache - "copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub). - If and when Jahfer issues a release of that library, this directory will be deleted and a - dependency on that library will be added to the project.") - -(defn- current-time - "Returns current time using UNIX epoch." - [] - (System/currentTimeMillis)) - -(defn- update-read-at [store k v] - (dosync - (commute store assoc k - (merge v {:read-at (current-time)})))) - -(defn make - "Creates a thread-local cache." - ([] (make false)) - ([cache-if-nil] - (let [store (ref {})] - (letfn [(cache-kv ([k v] - (dosync - (commute store assoc k - {:write-at (current-time) - :read-at (current-time) - :value v}) - v))) - (get-v ([k] - (when-let [data (get @store k)] - (update-read-at store k data) - (:value data))) - ([k compute-fn] - (let [storage @store] - (if (contains? storage k) - (get-v k) - (let [v (compute-fn)] - (when (or (not (nil? v)) cache-if-nil) - (cache-kv k v) - (get-v k))))))) - (lru ([] - (mapv - (fn [[k v]] [k (:value v)]) - (sort-by #(-> % val :read-at) < @store))))] - {:cache-kv cache-kv - :get-v get-v - :cache-if-nil cache-if-nil - :lru lru})))) \ No newline at end of file diff --git a/src/clj_activitypub/webfinger.clj b/src/clj_activitypub/webfinger.clj deleted file mode 100644 index c9aa700..0000000 --- a/src/clj_activitypub/webfinger.clj +++ /dev/null @@ -1,35 +0,0 @@ -(ns clj-activitypub.webfinger - "copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub). - If and when Jahfer issues a release of that library, this directory will be deleted and a - dependency on that library will be added to the project." - (:require [clj-http.client :as client] - [clj-activitypub.internal.http-util :as http] - [clj-activitypub.internal.thread-cache :as thread-cache])) - -(def remote-uri-path "/.well-known/webfinger") - -(defn- resource-str [domain username] - (str "acct:" username "@" domain)) - -(defn resource-url - "Builds a URL pointing to the user's account on the remote server." - [domain username & [params]] - (let [resource (resource-str domain username) - query-str (http/encode-url-params (merge params {:resource resource}))] - (str "https://" domain remote-uri-path "?" query-str))) - -(def ^:private user-id-cache - (thread-cache/make)) - -(defn fetch-user-id - "Follows the webfinger request to a remote domain, retrieving the ID of the requested - account. Typically returns a string in the form of a URL." - [domain username] - ((:get-v user-id-cache) - (str domain "@" username) ;; cache key - (fn [] - (let [response (some-> (resource-url domain username {:rel "self"}) - (client/get {:as :json :throw-exceptions false :ignore-unknown-host? true}))] - (some->> response :body :links - (some #(when (= (:type %) "application/activity+json") %)) - :href)))))