1
votes

completely rewritten to include improved understanding

The Yesod typeclass contains the function isAuthorized which you can adapt so that different routes are only accessible for different user groups. The scaffolded site shows examples of how to do that, including making the authorization subsite available to everyone:

isAuthorized (AuthR _) _ = return Authorized

The scaffolded site also helpfully includes a subsite for static content. But: that static subsite does not honor what you do in isAuthorized. You can check that by adding a pattern match like

isAuthorized (StaticR _) _ = error "this error is never reached"

You can still access all static content (including newly created one) and you will never encounter this pattern match.

It does make a bit of sense to give everyone access to content like bootstrap or jquery. Still, the same result could be achieved by honouring isAuthorized and always returning Authorized, the same way it's done with the authorization subsite or the favicon handler.

I personally would like to go one step further with dispatch like

isAuthorized (StaticR (StaticRoute ("public":_) _)) _     = pure Authorized
isAuthorized (StaticR (StaticRoute ("admin" :_) _)) _     = checkIsAdmin
isAuthorized (StaticR (StaticRoute ("cats"  :_) _)) False = checkIsAllowedToViewCats
:

It seems the only missing bit is to make the static subsite honour the check or to add a shim that does.

Sadly the subsite is a huge amount of complicated code with template haskell and a lot of magic doing complicated things like embedding files in the executable. The way it's included in the scaffolding has yet more magic. I'm also just learning about subsites and my training to see types as documentation fails in contexts like type families or Q Def. For these reasons I couldn't figure out how to add the check. Any pointers would be appreciated.

1

1 Answers

1
votes

So I found an imperfect answer that's good enough for my purposes for now.

I couldn't figure out how to make the static site behave differently. It seems it somehow uses the underlying capabilities of the wai server, so it never even touches the Yesod part of it. And as subsites seem to do the dispatch before the core system does, that was the only place where I could have changed something. Strange choice to have a core system be last in line, but whatever, there's probably a reason for this.

The solution, then, is to duplicate what the static subsite does. But with as little work as possible, please. So here's the most basic handler you can create:

getStaticCatContentHtmlR :: Text -> Handler Value
getStaticCatContentHtmlR path = do
  let filePath = "static/cats/html" </> unpack path
  sendFile "text/html" filePath

Just point your static subsite at static/public instead, create three new subfolders for html, css and js per subsite, add three corresponding handlers with suitable permissions and be done with it. The routing system makes sure that users can't request a path like /static/cats/html/../../../.

There are several drawbacks, though.

  • Yesod cannot do any optimizations.
  • Errors from missing files or wrong routes do not crash the server, but the error response is horrible. Of course you could catch the error yourself.
  • You don't get any routing tools inside Yesod, so you must do it manually and unchecked.
  • All routes in this subsite should be relative so you get less errors when you move the server from development to deployment. That might not be a drawback, but it limits your choices.

Is it worth the effort to create a subsite to bundle these three handlers? Well, maybe as a training exercise...