summaryrefslogtreecommitdiff
path: root/src/weepinbell/webserver/receiver.clj
blob: ab55ccf7ddca27d7ba16dd0ab69052061f8a6d6c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
(ns weepinbell.webserver.receiver
  "Service receiving Webmention request (a source and a target); verify the request; return error message if any; push Webmention into webbmention channel for processor to take."
  (:require [clojure.core.async :refer [put!]]
            [ring.util.http-response :refer [bad-request created]]
            [weepinbell.utils.url :refer [get-domain valid-http-url? normalize url=]]
            [weepinbell.utils.digest :refer [digest]]))

;; validation and verification

(defn- validate-urls [s t]
  (and (valid-http-url? s) (valid-http-url? t)))

(defn- validate-target [t allow-list]
  (let [target-domain (get-domain t)]
    (some #{target-domain} allow-list)))

(defn- check-request
  "Verify source and target based on URLs, return error message (string) if any. Return nil if none."
  [source target opts]
  (cond
    ;; The receiver MUST check that source and target are valid URLs [URL]
    ;; and are of schemes that are supported by the receiver.
    (not (validate-urls source target))
    "Invalid URLs. Source and target have to be valid http(s) urls."
    ;; The receiver MUST reject the request
    ;; if the source URL is the same as the target URL.
    (url= source target)
    "Source and target are identical."
    ;; The receiver only accetps webmentions on behalf of configured domains.
    (not (validate-target target opts))
    "Domain of target is not allowed. This receiver does not receive webmention on its behalf."))

;; receiving logic

(defn receive
  "Receive webmention; call verify-request; if verification passed, push webmention into queue.
  Return JSON response of pending notification, rejection or error."
  [wm wmc config]
  (let [{:keys [s t]} wm
        ;; if params are nil, use empty string 
        ;; empty strings will fail check-request
        source (if s (normalize s) "")
        target (if t (normalize t) "")
        new-wm {:s source :t target}
        allowlist (:allowed-target-domains config)]
     ;; Request verification
    (if-let [error-msg (check-request source target allowlist)]
       ;; respond if request verification failed
      (bad-request {:status 400
                    :message error-msg})
       ;; proceed if request is ok
      (do
         ;; The receiver SHOULD check that target is a valid
         ;; resource for which it can accept Webmentions.
         ;; This check SHOULD happen synchronously to reject
         ;; invalid Webmentions before more in-depth verification begins.
         ;;
         ;; Webmention verification SHOULD be handled asynchronously
         ;; to prevent DoS (Denial of Service) attacks.
        (put! wmc new-wm)
         ;; respond with 201 when Webmention passed checks and
         ;; was sent into queue, include a Location header
        (let [wm-id (digest (str source target))
              wm-url (str (:base-url config) "/api/mention/" wm-id)
              response {:status 201
                        :message "Webmention is accepted and pending for verification."
                        :url wm-url}]
          (created wm-url response))))))