tsweb-0.1.0.0: src/TsWeb/Routing/Auth.hs
{-|
Description: Auth hook for routing
A generic authentication hook for TsWeb routing. The goal here is to be able
to specify authentication requirements in a view's type, so that the routing
statically guarantees that a view can only be entered when session
preconditions are met. A full example of this is under the Example module of
this source tree, but a synopsis follows. Given a session/user definition like
so:
@
data UserT f = User
{ _userId :: C f Int
, _userLogin :: C f Text
} deriving (Generic)
...
data SessionDataT f = SessionData
{ _sdUser :: PrimaryKey UserT (Nullable f)
, _sdRemember :: C f Bool
} deriving (Generic)
...
userP :: 'Data.Proxy.Proxy' User
userP = Proxy
@
then we can define a logged-in 'Authorize' check as
@
instance Authorize SessionData User where
checkAuth _ =
_sdUser \<$\> 'Web.Spock.readSession' >>= \case
UserId Nothing -> pure Nothing
UserId (Just uid) ->
'TsWeb.Db.queryMaybe' ('Database.Beam.select' $ q uid) >>= \case
QSimply (Just user) -> pure $ Just user
_ -> pure Nothing
where
q uid = do
u <- 'Database.Beam.all_' $ _dbUser db
'Database.Beam.guard_' $ _userId u ==. 'Database.Beam.val_' uid
pure u
@
A view requiring an authenticated user would have a signature like
@
authd :: ListContains n User xs => TsActionCtxT lts xs SessionData a
authd = do
user :: User <- 'TsWeb.Action.getExtra'
...
@
Finally, the route for only allowing logged-in users would look like
@
'TsWeb.Routing.runroute' ro rw $ 'TsWeb.Routing.path' #authd "authd" $ `TsWeb.Routing.get' $ 'auth' userP authd
@
That view is statically defined to only be accessible to logged-in users; any
anonymous session will either go to an alternate (non-auth) view, or get a
404.
-}
module TsWeb.Routing.Auth where
import TsWeb.Types (Context(..), TsActionCtxT)
import TsWeb.Types.Db (ReadOnlyPool)
import Data.HVect (HVect((:&:)), ListContains)
import Data.Proxy (Proxy)
import Web.Spock (getContext)
import Web.Spock.Action (jumpNext, runInContext)
-- | A class for session data that needs to be statically verified against
-- routes. This could be checks for optional session info, or to validate the
-- value of that session information.
class Authorize sess perm where
-- | Load a value out of the session or return Nothing. Used in the context
-- of 'auth', the wrapped view will only be called when this returns Just; a
-- Nothing value will cause the wrapped view to be skipped.
checkAuth ::
ListContains n ReadOnlyPool xs
=> Proxy perm
-> TsActionCtxT lts xs sess (Maybe perm)
-- | Guarantee that the Spock session hold some verified piece of data. If the
-- requisite data can be loaded, then the view is called with the data in its
-- ctxExtras; otherwise 'Web.Spock.Action.jumpNext' is called and the view is
-- skipped.
auth ::
(Authorize sess perm, ListContains n ReadOnlyPool xs, Authorize sess perm)
=> Proxy perm
-> TsActionCtxT lts (perm ': xs) sess ()
-> TsActionCtxT lts xs sess ()
auth proxy action =
checkAuth proxy >>= \case
Nothing -> jumpNext
Just a -> do
ctx <- getContext
runInContext (ctx {ctxExtras = a :&: ctxExtras ctx}) action