yesod/Yesod/Helpers/Auth.hs
2009-12-13 01:38:20 +02:00

174 lines
5.2 KiB
Haskell

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
---------------------------------------------------------
--
-- Module : Yesod.Helpers.Auth
-- Copyright : Michael Snoyman
-- License : BSD3
--
-- Maintainer : Michael Snoyman <michael@snoyman.com>
-- Stability : Stable
-- Portability : portable
--
-- Authentication through the authentication package.
--
---------------------------------------------------------
module Yesod.Helpers.Auth
( AuthResource
, authHandler
, authResourcePattern
, RpxnowApiKey (..)
) where
import qualified Hack
import Web.Encodings
import qualified Web.Authenticate.Rpxnow as Rpxnow
import qualified Web.Authenticate.OpenId as OpenId
import Data.Enumerable
import Data.Object.Html
import Data.Convertible.Text (cs)
import Yesod
import Yesod.Constants
import Control.Applicative ((<$>), Applicative (..))
import Control.Monad.Reader
import Control.Monad.Attempt
import Data.Maybe (fromMaybe)
import Control.Monad.Attempt
data AuthResource =
Check
| Logout
| Openid
| OpenidForward
| OpenidComplete
| LoginRpxnow
deriving Show
instance Enumerable AuthResource where
enumerate =
[ Check
, Logout
, Openid
, OpenidForward
, OpenidComplete
, LoginRpxnow
]
newtype RpxnowApiKey = RpxnowApiKey String
authHandler :: Maybe RpxnowApiKey -> AuthResource -> Verb -> Handler HtmlObject
authHandler _ Check Get = authCheck
authHandler _ Logout Get = authLogout
authHandler _ Openid Get = authOpenidForm
authHandler _ OpenidForward Get = authOpenidForward
authHandler _ OpenidComplete Get = authOpenidComplete
-- two different versions of RPX protocol apparently...
authHandler (Just (RpxnowApiKey key)) LoginRpxnow Get = rpxnowLogin key
authHandler (Just (RpxnowApiKey key)) LoginRpxnow Post = rpxnowLogin key
authHandler _ _ _ = notFound
authResourcePattern :: AuthResource -> String -- FIXME supply prefix as well
authResourcePattern Check = "/auth/check/"
authResourcePattern Logout = "/auth/logout/"
authResourcePattern Openid = "/auth/openid/"
authResourcePattern OpenidForward = "/auth/openid/forward/"
authResourcePattern OpenidComplete = "/auth/openid/complete/"
authResourcePattern LoginRpxnow = "/auth/login/rpxnow/"
data OIDFormReq = OIDFormReq (Maybe String) (Maybe String)
instance Request OIDFormReq where
parseRequest = OIDFormReq <$> getParam "message" <*> getParam "dest"
instance Show OIDFormReq where
show (OIDFormReq Nothing _) = ""
show (OIDFormReq (Just s) _) = "<p class='message'>" ++ encodeHtml s ++
"</p>"
authOpenidForm :: Handler HtmlObject
authOpenidForm = do
m@(OIDFormReq _ dest) <- parseRequest
let html =
show m ++
"<form method='get' action='forward/'>" ++
"OpenID: <input type='text' name='openid'>" ++
"<input type='submit' value='Login'>" ++
"</form>"
case dest of
Just dest' -> addCookie 120 "DEST" dest'
Nothing -> return ()
return $ toHtmlObject $ Html $ cs html
authOpenidForward :: Handler HtmlObject
authOpenidForward = do
oid <- getParam "openid"
env <- parseEnv
let complete = "http://" ++ Hack.serverName env ++ ":" ++
show (Hack.serverPort env) ++
"/auth/openid/complete/"
res <- runAttemptT $ OpenId.getForwardUrl oid complete
attempt
(\err -> redirect $ "/auth/openid/?message=" ++ encodeUrl (show err))
redirect
res
authOpenidComplete :: Handler HtmlObject
authOpenidComplete = do
gets' <- rawGetParams <$> askRawRequest
dest <- cookieParam "DEST"
res <- runAttemptT $ OpenId.authenticate gets'
let onFailure err = redirect $ "/auth/openid/?message="
++ encodeUrl (show err)
let onSuccess (OpenId.Identifier ident) = do
deleteCookie "DEST"
header authCookieName ident
redirect $ fromMaybe "/" dest
attempt onFailure onSuccess res
-- | token dest
data RpxnowRequest = RpxnowRequest String (Maybe String)
instance Request RpxnowRequest where
parseRequest = do
token <- anyParam "token"
dest <- anyParam "dest"
return $! RpxnowRequest token $ chopHash `fmap` dest
chopHash :: String -> String
chopHash ('#':rest) = rest
chopHash x = x
rpxnowLogin :: String -- ^ api key
-> Handler HtmlObject
rpxnowLogin apiKey = do
token <- anyParam "token"
postDest <- postParam "dest"
dest' <- case postDest of
Nothing -> getParam "dest"
Just d -> return d
let dest = case dest' of
Nothing -> "/"
Just "" -> "/"
Just ('#':rest) -> rest
Just s -> s
ident <- Rpxnow.authenticate apiKey token
header authCookieName $ Rpxnow.identifier ident
redirect dest
authCheck :: Handler HtmlObject
authCheck = do
ident <- maybeIdentifier
case ident of
Nothing -> return $ toHtmlObject [("status", "notloggedin")]
Just i -> return $ toHtmlObject
[ ("status", "loggedin")
, ("ident", i)
]
authLogout :: Handler HtmlObject
authLogout = do
deleteCookie authCookieName
return $ toHtmlObject [("status", "loggedout")]