refactor: flatten POST handler with Result pipeline, simplify IP lookup
This commit is contained in:
@@ -10,14 +10,9 @@ import storage
|
||||
import wisp
|
||||
|
||||
fn get_client_ip(request: wisp.Request) -> String {
|
||||
case list.key_find(request.headers, "x-forwarded-for") {
|
||||
Ok(ip) -> ip
|
||||
Error(_) ->
|
||||
case list.key_find(request.headers, "x-real-ip") {
|
||||
Ok(ip) -> ip
|
||||
Error(_) -> "unknown"
|
||||
}
|
||||
}
|
||||
list.key_find(request.headers, "x-forwarded-for")
|
||||
|> result.try_recover(fn(_) { list.key_find(request.headers, "x-real-ip") })
|
||||
|> result.unwrap("unknown")
|
||||
}
|
||||
|
||||
pub fn handle(
|
||||
@@ -57,54 +52,81 @@ fn handle_home(
|
||||
}
|
||||
http.Post -> {
|
||||
use form <- wisp.require_form(request)
|
||||
let csrf_cookie = wisp.get_cookie(request, "csrf_token", wisp.Signed)
|
||||
let csrf_form = list.key_find(form.values, "csrf_token")
|
||||
case csrf_cookie, csrf_form {
|
||||
Ok(cookie_token), Ok(form_token) if cookie_token == form_token -> {
|
||||
let ip = get_client_ip(request)
|
||||
let rate_reply = process.new_subject()
|
||||
process.send(storage, storage.CheckRateLimit(ip, rate_reply))
|
||||
case process.receive(rate_reply, 1000) {
|
||||
Ok(True) -> {
|
||||
let encrypted_content =
|
||||
list.key_find(form.values, "encrypted_content")
|
||||
|> result.unwrap("")
|
||||
case string.length(encrypted_content) {
|
||||
0 -> wisp.bad_request("Missing content")
|
||||
n if n > 10_000_000 -> wisp.bad_request("Content too large")
|
||||
_ -> {
|
||||
let new_key = key.generate()
|
||||
let paste_reply = process.new_subject()
|
||||
process.send(
|
||||
storage,
|
||||
storage.CreatePaste(new_key, encrypted_content, paste_reply),
|
||||
)
|
||||
case process.receive(paste_reply, 1000) {
|
||||
Ok(True) -> {
|
||||
wisp.ok()
|
||||
|> wisp.html_body(html.created(new_key))
|
||||
}
|
||||
_ -> {
|
||||
wisp.internal_server_error()
|
||||
|> wisp.html_body("Failed to create paste")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ -> {
|
||||
wisp.response(429)
|
||||
|> wisp.html_body("Rate limit exceeded")
|
||||
}
|
||||
}
|
||||
|
||||
let result = {
|
||||
use _ <- result.try(verify_csrf(request, form))
|
||||
use _ <- result.try(check_rate_limit(storage, get_client_ip(request)))
|
||||
use key <- result.try(create_paste(storage, form))
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
case result {
|
||||
Ok(paste_key) -> {
|
||||
wisp.ok()
|
||||
|> wisp.html_body(html.created(paste_key))
|
||||
}
|
||||
_, _ -> wisp.bad_request("Invalid CSRF token")
|
||||
Error(response) -> response
|
||||
}
|
||||
}
|
||||
_ -> wisp.method_not_allowed([http.Get, http.Post])
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_csrf(
|
||||
request: wisp.Request,
|
||||
form: wisp.FormData,
|
||||
) -> Result(Nil, wisp.Response) {
|
||||
let csrf_cookie = wisp.get_cookie(request, "csrf_token", wisp.Signed)
|
||||
let csrf_form = list.key_find(form.values, "csrf_token")
|
||||
|
||||
case csrf_cookie, csrf_form {
|
||||
Ok(cookie_token), Ok(form_token) if cookie_token == form_token -> Ok(Nil)
|
||||
_, _ -> Error(wisp.bad_request("Invalid CSRF token"))
|
||||
}
|
||||
}
|
||||
|
||||
fn check_rate_limit(storage, ip: String) -> Result(Nil, wisp.Response) {
|
||||
let rate_reply = process.new_subject()
|
||||
process.send(storage, storage.CheckRateLimit(ip, rate_reply))
|
||||
|
||||
case process.receive(rate_reply, 1000) {
|
||||
Ok(True) -> Ok(Nil)
|
||||
_ ->
|
||||
Error(
|
||||
wisp.response(429)
|
||||
|> wisp.html_body("Rate limit exceeded"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_paste(storage, form: wisp.FormData) -> Result(String, wisp.Response) {
|
||||
let encrypted_content =
|
||||
list.key_find(form.values, "encrypted_content")
|
||||
|> result.unwrap("")
|
||||
|
||||
case string.length(encrypted_content) {
|
||||
0 -> Error(wisp.bad_request("Missing content"))
|
||||
n if n > 10_000_000 -> Error(wisp.bad_request("Content too large"))
|
||||
_ -> {
|
||||
let new_key = key.generate()
|
||||
let paste_reply = process.new_subject()
|
||||
process.send(
|
||||
storage,
|
||||
storage.CreatePaste(new_key, encrypted_content, paste_reply),
|
||||
)
|
||||
|
||||
case process.receive(paste_reply, 1000) {
|
||||
Ok(True) -> Ok(new_key)
|
||||
_ ->
|
||||
Error(
|
||||
wisp.internal_server_error()
|
||||
|> wisp.html_body("Failed to create paste"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_paste(
|
||||
request: wisp.Request,
|
||||
storage: process.Subject(storage.StorageMsg),
|
||||
|
||||
Reference in New Issue
Block a user