aboutsummaryrefslogtreecommitdiff
path: root/bin/main.ml
blob: 1bc31cdaabdc457c8ee9fbd7a322a6574dbc8d2f (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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
open Wormhole
open Ppx_inline_test_lib

let (fake_post : Post.t) =
  {
    link = "https://google.com";
    summary =
      "<p><span class=\"h-card\" translate=\"no\"><a \
       href=\"https://galaxy.mccd.space/actor\" class=\"u-url \
       mention\">@<span>wormhole</span></a></span>   </p><p><a \
       href=\"https://google.com\" target=\"_blank\" rel=\"nofollow noopener \
       noreferrer\" translate=\"no\"><span \
       class=\"invisible\">https://</span><span \
       class=\"\">google.com</span><span \
       class=\"invisible\"></span></a></p><p>This is a place on the web where \
       you can search for things</p><p><a \
       href=\"https://fosstodon.org/tags/goodie\" class=\"mention hashtag\" \
       rel=\"tag\">#<span>goodie</span></a> <a \
       href=\"https://fosstodon.org/tags/othergoodie\" class=\"mention \
       hashtag\" rel=\"tag\">#<span>othergoodie</span></a></p>";
    tags = [ "#goodie" ];
    published = "2023-08-23";
    author = "marcc.rooted";
    author_link = "https://www.fosstodon.org/@marcc";
  }

let webfinger =
  {|
{
	"subject": "acct:wormhole@galaxy.mccd.space",

	"links": [
		{
			"rel": "self",
			"type": "application/activity+json",
			"href": "https://galaxy.mccd.space/actor"
		}
	]
}
|}

let actor =
  {|
{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://w3id.org/security/v1"
  ],

  "id": "https://galaxy.mccd.space/actor",
  "type": "Person",
  "preferredUsername": "wormhole",
  "inbox": "https://galaxy.mccd.space/inbox",

  "publicKey": {
    "id": "https://galaxy.mccd.space/actor#main-key",
    "owner": "https://galaxy.mccd.space/actor",
    "publicKeyPem": "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvqa9W2PjNYB6FDuRewSR\nAUyH2TK5iprbfuKWvCGEYq2FRccjmoluMEb0a16AyqqMeZ8J+pI7cPvqpdXm/VVV\niZx3Q5W4H8kIC3I84qAAzVs3wOQWnudk+D+hEE1Il9+yFBFvF7IoyER9axqJEb88\nYQ5okLU/346SMpMrk4wsUFnwaxdVXQPBQ0tVxqVJiLGSMlGXX/1Vl0+lnhgg+5rH\n8rfIbFX4qQC/gYbEU+VS2nzhYjdqn0maL94OrFHYNdrMBgSBOtbBFSJ+kMgojqES\n7Xhf+G9JcuCIqm0T3dHqo50MUSx8lrS78S3uO7WgPAby8qXjcL8sQEfNvJT16sjk\ndwIDAQAB\n-----END PUBLIC KEY-----"
  }
}
  |}

let actor_allowlist =
  [
    "https://fosstodon.org/users/marcc";
    "https://universeodon.com/users/icecreambook";
    "https://mastodon.social/users/ronent";
    "https://graphics.social/users/theohonohan";
  ]

let loader _root path _request =
  match Assets.read path with
  | None -> Dream.empty `Not_Found
  | Some asset -> Dream.respond asset

let () =
  let port =
    Sys.getenv_opt "PORT" |> Option.map int_of_string
    |> Option.value ~default:8080
  in
  let env = Sys.getenv_opt "ENV" |> Option.value ~default:"PROD" in
  let disable_auth =
    Sys.getenv_opt "DISABLE_AUTH" |> Option.value ~default:"false"
  in
  let interface = if env = "DEV" then "localhost" else "0.0.0.0" in
  if env = "DEV" then Post.add fake_post;
  if env = "DEV" then Post.add fake_post;
  if env = "DEV" then Post.add fake_post;
  if env = "DEV" then Post.add fake_post;
  Dream.run ~port ~interface @@ Dream.logger
  @@ Dream.router
       [
         Dream.get "/static/**" (Dream.static ~loader "");
         Dream.get "/feed.xml" (fun _ ->
             let posts = Post.get_all () in
             let maybe_latest_post = Post.latest_post () in
             match maybe_latest_post with
             | Some latest_post ->
                 let rss_posts = posts |> List.map Post.to_rss_entry in
                 let rss =
                   Xml.format_rss "https://galaxy.mccd.space"
                     (Post.published latest_post)
                     rss_posts
                 in
                 Dream.respond rss
                   ~headers:[ ("Content-Type", "application/rss+xml") ]
             | None -> Dream.html "No posts hav been published!");
         Dream.get "/actor" (fun _ ->
             Dream.log "Sending actor";
             Dream.respond actor
               ~headers:[ ("Content-Type", "application/activity+json") ]);
         Dream.get "/.well-known/webfinger" (fun _ ->
             Dream.log "Sending webfinger";
             Dream.respond webfinger
               ~headers:[ ("Content-Type", "application/activity+json") ]);
         Dream.get "/" (fun request ->
             Dream.log "Sending greeting to %s!" (Dream.client request);
             let posts = Post.get_all () in
             Template.render posts |> Dream.html);
         Dream.post "/inbox" (fun request ->
             let%lwt body = Dream.body request in
             Dream.log "Got body: %s" body;
             let message_object =
               Yojson.Safe.from_string body |> Post.mastodon_post_of_yojson
             in
             let%lwt actor =
               User.get_user (Post.mastodon_actor message_object)
             in
             Dream.log "User found";
             let pem = User.get_public_pem actor |> Result.to_option in
             let%lwt valid_request = Sig.verify_request pem request in
             let post =
               message_object |> Post.post_of_mastodon_post (User.name actor)
             in
             let in_whitelist =
               List.mem (Post.mastodon_actor message_object) actor_allowlist
             in
             match (valid_request, disable_auth, in_whitelist) with
             | Error e, "false", _ ->
                 Dream.log "Error verifying request %s" Printexc.(to_string e);
                 let code = Some 500 in
                 Dream.json ?code "Invalid request"
             | Ok false, "false", _ ->
                 Dream.log "Unauthorized request";
                 let code = Some 401 in
                 Dream.json ?code "Unauthorized"
             | _, "false", false ->
                 Dream.log "Unauthorized request";
                 let code = Some 401 in
                 Dream.json ?code "Unauthorized, not in whitelist"
             | _, _, _ ->
                 post |> Post.add;
                 message_object |> Post.yojson_of_mastodon_post
                 |> Yojson.Safe.to_string |> Dream.log "Added post %s";
                 Dream.json "Added user");
       ]