a (hacky, wip) multi-tenant oidc-terminating reverse proxy, written in anger on top of pingora
1//! http utilities
2
3use http::StatusCode;
4use pingora::prelude::*;
5
6/// closure that returns the given status with no cause
7///
8/// use with [`Option::ok_or_else`]
9pub fn status_error(
10 why: &'static str,
11 src: ErrorSource,
12 status: StatusCode,
13) -> impl FnOnce() -> Box<Error> {
14 move || {
15 Error::create(
16 ErrorType::HTTPStatus(status.into()),
17 src,
18 Some(why.into()),
19 None,
20 )
21 }
22}
23/// closure that returns `500 Internal Server Error`, marked as caused by the error returned to
24/// the given closure
25///
26/// use with [`Result::map_err`]
27pub fn internal_error_from<E>(why: &'static str) -> impl FnOnce(E) -> Box<Error>
28where
29 E: Into<Box<dyn ErrorTrait + Send + Sync>>,
30{
31 move |cause| {
32 Error::create(
33 ErrorType::HTTPStatus(StatusCode::INTERNAL_SERVER_ERROR.into()),
34 ErrorSource::Internal,
35 Some(why.into()),
36 Some(cause.into()),
37 )
38 }
39}
40
41/// closure that returns `500 Internal Server Error` with no cause
42///
43/// use with [`Option::ok_or_else`]
44pub fn internal_error(why: &'static str) -> impl FnOnce() -> Box<Error> {
45 move || {
46 Error::create(
47 ErrorType::HTTPStatus(StatusCode::INTERNAL_SERVER_ERROR.into()),
48 ErrorSource::Internal,
49 Some(why.into()),
50 None,
51 )
52 }
53}
54/// closure that returns the given status, marked as caused by the error returned to the given closure
55///
56/// use with [`Result::map_err`]
57pub fn status_error_from<E>(
58 why: &'static str,
59 src: ErrorSource,
60 status: http::StatusCode,
61) -> impl FnOnce(E) -> Box<Error>
62where
63 E: Into<Box<dyn ErrorTrait + Send + Sync>>,
64{
65 move |cause| {
66 Error::create(
67 ErrorType::HTTPStatus(status.into()),
68 src,
69 Some(why.into()),
70 Some(cause.into()),
71 )
72 }
73}
74
75/// redirect to the given location
76///
77/// the given callback can be used to inject additional headers, like `set-cookie`
78pub async fn redirect_response(
79 session: &mut Session,
80 to: &str,
81 bld_resp_header: impl FnOnce(&mut ResponseHeader, &Session) -> Result<()>,
82) -> Result<()> {
83 session.set_keepalive(None);
84 session
85 .write_response_header(
86 Box::new({
87 // per <rfc:draft-ietf-oauth-v2-1#1.6>, any redirect is fine save 307, but HTTP 302 seems to
88 // be their example.
89 let mut resp = ResponseHeader::build(StatusCode::FOUND, Some(0))?;
90 resp.insert_header(http::header::LOCATION, to)?;
91 bld_resp_header(&mut resp, session)?;
92 resp
93 }),
94 true,
95 )
96 .await?;
97 session.finish_body().await?;
98 Ok(())
99}
100
101/// fetch the cookies for the current request
102pub fn cookie_jar(req: &'_ RequestHeader) -> Result<Option<cookie_rs::CookieJar<'_>>> {
103 use cookie_rs::CookieJar;
104
105 let Some(raw) = req.headers.get(http::header::COOKIE) else {
106 return Ok(None);
107 };
108 Ok(Some(
109 raw.to_str()
110 .map_err(Box::<dyn ErrorTrait + Send + Sync>::from)
111 .and_then(|c| Ok(CookieJar::parse(c)?))
112 .map_err(status_error_from(
113 "bad cookie header",
114 ErrorSource::Downstream,
115 StatusCode::BAD_REQUEST,
116 ))?,
117 ))
118}