packages feed

nested-routes-8.0.2: test/Web/Routes/NestedSpec/Basic.hs

{-# LANGUAGE
    OverloadedStrings
  , ScopedTypeVariables
  , FlexibleContexts
  , DeriveGeneric
  #-}


module Web.Routes.NestedSpec.Basic where

import Web.Routes.Nested (o_, p_, l_, r_, (</>), match, matchHere, matchGroup, auth, AuthScope (..), textOnly, routeAuth)
import Network.Wai.Trans (Middleware, Application, Request, catchMiddlewareT)
import Network.HTTP.Types (status401, status404, status200)
import Text.Regex (mkRegex)
import Data.Attoparsec.Text (double)
import Control.Monad.Catch (Exception, MonadThrow (throwM))
import GHC.Generics (Generic)


data AuthRole = AuthRole deriving (Show, Eq)
data AuthErr = NeedsAuth deriving (Show, Eq, Generic)

instance Exception AuthErr

-- | If you fail here and throw an AuthErr, then the user was not authorized to
--   under the conditions set by @ss :: [AuthRole]@, and based on the authentication
--   of that user's session from the @Request@ object. Note that we could have a
--   shared cache of authenticated sessions, by adding more constraints on @m@ like
--   @MonadIO@.
--   For instance, even if there are [] auth roles, we could still include a header/timestamp
--   pair to uniquely identify the guest. Or, we could equally change @Checksum ~ Maybe Token@,
--   so a guest just returns Nothing, and we could handle the case in @putAuth@ to
--   not do anything.
authorize :: ( MonadThrow m
             ) => Request -> [AuthRole] -> m ()
-- authorize _ _ = return id -- uncomment to force constant authorization
authorize req ss | null ss   = return ()
                 | otherwise = throwM NeedsAuth

defApp :: Application
defApp _ respond = respond $
  textOnly "404 :(" status404 []

successMiddleware :: Middleware
successMiddleware _ _ respond = respond $ textOnly "200!" status200 []

app :: Application
app =
  let yoDawgIHeardYouLikeYoDawgsYo =
        (routeAuth authorize routes) `catchMiddlewareT` unauthHandle
      routes = do
        matchHere successMiddleware
        matchGroup fooRoute $ do
          matchHere successMiddleware
          auth AuthRole DontProtectHere
          match barRoute    successMiddleware
        match doubleRoute (\_ -> successMiddleware)
        match emailRoute (\_ -> successMiddleware)
        match bazRoute successMiddleware
  in yoDawgIHeardYouLikeYoDawgsYo defApp
  where
    -- `/foo`
    fooRoute = l_ "foo" </> o_

    -- `/foo/bar`
    barRoute = l_ "bar" </> o_

    -- `/foo/1234e12`, uses attoparsec
    doubleRoute = p_ "double" double </> o_

    -- `/athan@foo.com`
    emailRoute = r_ "email" (mkRegex "(^[-a-zA-Z0-9_.]+@[-a-zA-Z0-9]+\\.[-a-zA-Z0-9.]+$)") </> o_

    -- `/baz`, uses regex-compat
    bazRoute = l_ "baz" </> o_

unauthHandle :: AuthErr -> Middleware
unauthHandle NeedsAuth _ _ respond = respond $
  textOnly "Unauthorized!" status401 []