-
Notifications
You must be signed in to change notification settings - Fork 1
/
redirect_server.gleam
150 lines (125 loc) · 3.8 KB
/
redirect_server.gleam
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
import gleam/bytes_builder
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/result
import gleam/uri.{type Uri}
import gleam/http.{Get}
import gleam/http/request.{type Request, Request}
import gleam/http/response.{type Response}
import gleam/erlang/process.{type Subject}
import gleam/otp/supervisor.{type Message as SupervisorMessage}
import gleam/otp/actor.{type StartError}
import mist.{type Connection, type ResponseData}
pub opaque type RedirectServer {
State(
csrf_state: String,
mailbox: Subject(String),
mist: Option(Subject(SupervisorMessage)),
redirect_uri: Uri,
status: Status,
)
}
pub type Status {
Running
Stopped
}
pub type Message {
Shutdown
Start
}
pub fn new(
csrf_state: String,
mailbox: Subject(String),
redirect_uri: Uri,
) -> Result(Subject(Message), StartError) {
let state = State(csrf_state, mailbox, None, redirect_uri, Stopped)
actor.start(state, handle_message)
}
fn handle_message(
message: Message,
state: RedirectServer,
) -> actor.Next(Message, RedirectServer) {
case message {
Shutdown -> {
let assert Some(Nil) =
state.mist
|> option.map(process.subject_owner)
|> option.map(process.kill)
actor.Stop(process.Normal)
}
Start -> {
let port = option.unwrap(state.redirect_uri.port, 3000)
let assert Ok(mist_subject) =
mist.new(new_router(state))
|> mist.port(port)
|> mist.start_http
actor.continue(State(..state, mist: Some(mist_subject), status: Running))
}
}
}
pub fn start(server: Subject(Message)) {
actor.send(server, Start)
}
pub fn shutdown(server: Subject(Message)) {
actor.send(server, Shutdown)
}
fn new_router(server: RedirectServer) {
let redirect_path = server.redirect_uri.path
let router = fn(req: Request(Connection)) -> Response(ResponseData) {
case req.method, request.path_segments(req) {
Get, [path] if path == redirect_path -> make_redirect_handler(server)(req)
_, _ ->
response.new(404)
|> response.set_body(mist.Bytes(bytes_builder.new()))
}
}
router
}
/// 1024 bytes * 1024 bytes * 10
const ten_megabytes_in_bytes = 10_485_760
fn make_mist_body(body: Option(String)) {
body
|> option.unwrap("")
|> bytes_builder.from_string
|> mist.Bytes
}
fn get_code_and_csrf_token_query_params(req) {
let query_params =
request.get_query(req)
|> result.unwrap([])
let code_result = list.key_find(query_params, "code")
let csrf_state_result = list.key_find(query_params, "state")
#(option.from_result(code_result), option.from_result(csrf_state_result))
}
fn bad_request_response(message: Option(String)) -> Response(ResponseData) {
response.new(400)
|> response.set_body(make_mist_body(message))
}
fn generic_bad_request_response() -> Response(ResponseData) {
bad_request_response(None)
}
fn ok_response(body: Option(String)) {
response.new(200)
|> response.set_body(make_mist_body(body))
}
fn make_redirect_handler(server: RedirectServer) {
let handle_redirect = fn(req: Request(Connection)) -> Response(ResponseData) {
case get_code_and_csrf_token_query_params(req) {
#(Some(code), Some(state)) if state == server.csrf_state -> {
mist.read_body(req, ten_megabytes_in_bytes)
|> result.map(send_response(server, code))
|> result.lazy_unwrap(generic_bad_request_response)
}
#(Some(_), Some(_)) -> bad_request_response(Some("Invalid csrf state"))
#(Some(_), None) -> bad_request_response(Some("No csrf state received"))
#(None, _) -> bad_request_response(Some("No code received"))
}
}
handle_redirect
}
fn send_response(server: RedirectServer, code: String) {
fn(_) {
process.send_after(server.mailbox, 250, code)
ok_response(Some("Successfully received redirect payload from Twitch."))
}
}