refactor: flatten POST handler with Result pipeline, simplify IP lookup
This commit is contained in:
@@ -10,14 +10,9 @@ import storage
|
|||||||
import wisp
|
import wisp
|
||||||
|
|
||||||
fn get_client_ip(request: wisp.Request) -> String {
|
fn get_client_ip(request: wisp.Request) -> String {
|
||||||
case list.key_find(request.headers, "x-forwarded-for") {
|
list.key_find(request.headers, "x-forwarded-for")
|
||||||
Ok(ip) -> ip
|
|> result.try_recover(fn(_) { list.key_find(request.headers, "x-real-ip") })
|
||||||
Error(_) ->
|
|> result.unwrap("unknown")
|
||||||
case list.key_find(request.headers, "x-real-ip") {
|
|
||||||
Ok(ip) -> ip
|
|
||||||
Error(_) -> "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle(
|
pub fn handle(
|
||||||
@@ -57,54 +52,81 @@ fn handle_home(
|
|||||||
}
|
}
|
||||||
http.Post -> {
|
http.Post -> {
|
||||||
use form <- wisp.require_form(request)
|
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")
|
let result = {
|
||||||
case csrf_cookie, csrf_form {
|
use _ <- result.try(verify_csrf(request, form))
|
||||||
Ok(cookie_token), Ok(form_token) if cookie_token == form_token -> {
|
use _ <- result.try(check_rate_limit(storage, get_client_ip(request)))
|
||||||
let ip = get_client_ip(request)
|
use key <- result.try(create_paste(storage, form))
|
||||||
let rate_reply = process.new_subject()
|
Ok(key)
|
||||||
process.send(storage, storage.CheckRateLimit(ip, rate_reply))
|
}
|
||||||
case process.receive(rate_reply, 1000) {
|
|
||||||
Ok(True) -> {
|
case result {
|
||||||
let encrypted_content =
|
Ok(paste_key) -> {
|
||||||
list.key_find(form.values, "encrypted_content")
|
wisp.ok()
|
||||||
|> result.unwrap("")
|
|> wisp.html_body(html.created(paste_key))
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_, _ -> wisp.bad_request("Invalid CSRF token")
|
Error(response) -> response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ -> wisp.method_not_allowed([http.Get, http.Post])
|
_ -> 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(
|
fn handle_paste(
|
||||||
request: wisp.Request,
|
request: wisp.Request,
|
||||||
storage: process.Subject(storage.StorageMsg),
|
storage: process.Subject(storage.StorageMsg),
|
||||||
|
|||||||
Reference in New Issue
Block a user