module Ecluse.Core.Server.Pipeline.Shared (
recognisedButUnserved,
edgeTokenMatches,
edgeUnauthorised,
serveOverloaded,
forwardedToken,
jsonResponse,
renderedResponse,
bodiless,
integrityMissing,
integrityBelowFloor,
trustedIntegrityMissing,
trustedIntegrityBelowFloor,
hRetryAfter,
) where
import Data.Text qualified as T
import Network.HTTP.Types (HeaderName, ResponseHeaders, Status, hAuthorization, hContentType, status401, status501, status503)
import Network.Wai (Request, Response, requestHeaders, responseHeaders, responseLBS, responseStatus)
import Ecluse.Core.Credential (Secret, mkSecret)
import Ecluse.Core.Server.Response (
MountRenderer,
RejectReason (BelowIntegrityFloor, MissingIntegrity),
Rejection (Rejection),
RenderedBody (RenderedBody),
ServeDecision (Reject),
renderError,
)
hRetryAfter :: HeaderName
hRetryAfter :: HeaderName
hRetryAfter = HeaderName
"Retry-After"
recognisedButUnserved :: MountRenderer -> Response
recognisedButUnserved :: MountRenderer -> Response
recognisedButUnserved MountRenderer
renderer =
Status -> ResponseHeaders -> RenderedBody -> Response
renderedResponse Status
status501 [] (MountRenderer -> Maybe HelpMessage -> Text -> RenderedBody
renderError MountRenderer
renderer Maybe HelpMessage
forall a. Maybe a
Nothing Text
"this route is recognised but not yet served by this proxy")
edgeTokenMatches :: Maybe Secret -> Maybe Secret -> Bool
edgeTokenMatches :: Maybe Secret -> Maybe Secret -> Bool
edgeTokenMatches Maybe Secret
expected Maybe Secret
forwarded = case Maybe Secret
expected of
Maybe Secret
Nothing -> Bool
True
Just Secret
want -> Maybe Secret
forwarded Maybe Secret -> Maybe Secret -> Bool
forall a. Eq a => a -> a -> Bool
== Secret -> Maybe Secret
forall a. a -> Maybe a
Just Secret
want
edgeUnauthorised :: MountRenderer -> Response
edgeUnauthorised :: MountRenderer -> Response
edgeUnauthorised MountRenderer
renderer =
Status -> ResponseHeaders -> RenderedBody -> Response
renderedResponse Status
status401 [] (MountRenderer -> Maybe HelpMessage -> Text -> RenderedBody
renderError MountRenderer
renderer Maybe HelpMessage
forall a. Maybe a
Nothing Text
"authentication required")
serveOverloaded :: MountRenderer -> Response
serveOverloaded :: MountRenderer -> Response
serveOverloaded MountRenderer
renderer =
Status -> ResponseHeaders -> RenderedBody -> Response
renderedResponse Status
status503 [(HeaderName
hRetryAfter, ByteString
"1")] (MountRenderer -> Maybe HelpMessage -> Text -> RenderedBody
renderError MountRenderer
renderer Maybe HelpMessage
forall a. Maybe a
Nothing Text
"server is busy; retry later")
forwardedToken :: Request -> Maybe Secret
forwardedToken :: Request -> Maybe Secret
forwardedToken Request
request = do
(_, raw) <- ((HeaderName, ByteString) -> Bool)
-> ResponseHeaders -> Maybe (HeaderName, ByteString)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((HeaderName -> HeaderName -> Bool
forall a. Eq a => a -> a -> Bool
== HeaderName
hAuthorization) (HeaderName -> Bool)
-> ((HeaderName, ByteString) -> HeaderName)
-> (HeaderName, ByteString)
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (HeaderName, ByteString) -> HeaderName
forall a b. (a, b) -> a
fst) (Request -> ResponseHeaders
requestHeaders Request
request)
let value = ByteString -> Text
forall a b. ConvertUtf8 a b => b -> a
decodeUtf8 ByteString
raw
(scheme, rest) = T.break (== ' ') value
guard (T.toLower scheme == "bearer")
let token = (Char -> Bool) -> Text -> Text
T.dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
' ') Text
rest
guard (not (T.null token))
pure (mkSecret token)
jsonResponse :: Status -> ResponseHeaders -> LByteString -> Response
jsonResponse :: Status -> ResponseHeaders -> ByteString -> Response
jsonResponse Status
status ResponseHeaders
extra =
Status -> ResponseHeaders -> ByteString -> Response
responseLBS Status
status ((HeaderName
hContentType, ByteString
"application/json") (HeaderName, ByteString) -> ResponseHeaders -> ResponseHeaders
forall a. a -> [a] -> [a]
: ResponseHeaders
extra)
renderedResponse :: Status -> ResponseHeaders -> RenderedBody -> Response
renderedResponse :: Status -> ResponseHeaders -> RenderedBody -> Response
renderedResponse Status
status ResponseHeaders
extra (RenderedBody ByteString
contentType ByteString
body) =
Status -> ResponseHeaders -> ByteString -> Response
responseLBS Status
status ((HeaderName
hContentType, ByteString
contentType) (HeaderName, ByteString) -> ResponseHeaders -> ResponseHeaders
forall a. a -> [a] -> [a]
: ResponseHeaders
extra) ByteString
body
bodiless :: Response -> Response
bodiless :: Response -> Response
bodiless Response
response = Status -> ResponseHeaders -> ByteString -> Response
responseLBS (Response -> Status
responseStatus Response
response) (Response -> ResponseHeaders
responseHeaders Response
response) ByteString
""
integrityMissing :: ServeDecision
integrityMissing :: ServeDecision
integrityMissing =
Rejection -> ServeDecision
Reject (RejectReason -> Text -> Rejection
Rejection RejectReason
MissingIntegrity Text
"this version carries no integrity digest and cannot be served from a public upstream")
integrityBelowFloor :: ServeDecision
integrityBelowFloor :: ServeDecision
integrityBelowFloor =
Rejection -> ServeDecision
Reject (RejectReason -> Text -> Rejection
Rejection RejectReason
BelowIntegrityFloor Text
"this version's integrity digest is weaker than the configured minimum and cannot be served from a public upstream")
trustedIntegrityMissing :: ServeDecision
trustedIntegrityMissing :: ServeDecision
trustedIntegrityMissing =
Rejection -> ServeDecision
Reject (RejectReason -> Text -> Rejection
Rejection RejectReason
MissingIntegrity Text
"this private version carries no integrity digest and was not served")
trustedIntegrityBelowFloor :: ServeDecision
trustedIntegrityBelowFloor :: ServeDecision
trustedIntegrityBelowFloor =
Rejection -> ServeDecision
Reject (RejectReason -> Text -> Rejection
Rejection RejectReason
BelowIntegrityFloor Text
"this private version's integrity digest is weaker than the configured trusted minimum and was not served")