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
|
open Wormhole
let (fake_post : Post.t) =
{
link = "https://mccd.space";
summary = "My personal blog";
tags = [ "cool"; "article" ];
published = "2020-01-01T00:00:00Z";
author = "Marc";
}
let (fake_post_2 : Post.t) =
{
link = "https://google.com";
summary = "Some other cool article that I just made";
tags = [ "cool"; "something" ];
published = "2020-01-02T00:00:00Z";
author = "Bob";
}
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_whitelist =
[
"https://fosstodon.org/users/marcc";
"https://universeodon.com/users/icecreambook";
"https://mastodon.social/users/ronent";
"https://graphics.social/users/theohonohan";
]
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 "./static");
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 signature = Dream.headers request "signature" in
Dream.log "Got signature: %s" (String.concat " " signature);
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
match actor with
| Error e ->
Dream.log "User not found %s" (Printexc.to_string e);
let code = Some 400 in
Dream.json ?code "User not found"
| Ok actor ->
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
(match valid_request with
| Error e ->
Dream.log "Error verifying request %s"
Printexc.(to_string e);
let code = Some 500 in
Dream.json ?code "Invalid request"
| Ok false ->
Dream.log "Unauthorized request";
let code = Some 501 in
Dream.json ?code "Unauthorized"
| Ok true ->
message_object
|> Post.post_of_mastodon_post (User.name actor)
|> Post.add;
message_object |> Post.yojson_of_mastodon_post
|> Yojson.Safe.to_string |> Dream.log "Added post %s";
Dream.json "Added user"));
]
|