Authentication included in scaffolded site
This commit is contained in:
parent
465366766b
commit
7e4ec40779
@ -35,7 +35,9 @@ module Yesod.Helpers.Auth
|
|||||||
, EmailSettings (..)
|
, EmailSettings (..)
|
||||||
, FacebookSettings (..)
|
, FacebookSettings (..)
|
||||||
-- * Functions
|
-- * Functions
|
||||||
|
, maybeAuth
|
||||||
, maybeAuthId
|
, maybeAuthId
|
||||||
|
, requireAuth
|
||||||
, requireAuthId
|
, requireAuthId
|
||||||
) where
|
) where
|
||||||
|
|
||||||
@ -47,6 +49,7 @@ import Yesod
|
|||||||
import Yesod.Mail (randomString)
|
import Yesod.Mail (randomString)
|
||||||
|
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
|
import Data.Int (Int64)
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import System.Random
|
import System.Random
|
||||||
import Data.Digest.Pure.MD5
|
import Data.Digest.Pure.MD5
|
||||||
@ -56,19 +59,15 @@ import Data.ByteString.Lazy.UTF8 (fromString)
|
|||||||
import Data.Object
|
import Data.Object
|
||||||
import Language.Haskell.TH.Syntax
|
import Language.Haskell.TH.Syntax
|
||||||
|
|
||||||
class (Integral (AuthEmailId master), Yesod master,
|
type AuthId m = Key (AuthEntity m)
|
||||||
Show (AuthId master), Read (AuthId master), Eq (AuthId master)
|
type AuthEmailId m = Key (AuthEmailEntity m)
|
||||||
) => YesodAuth master where
|
|
||||||
type AuthId master
|
|
||||||
type AuthEmailId master
|
|
||||||
|
|
||||||
showAuthId :: AuthId master -> GHandler s master String
|
class ( Yesod master
|
||||||
showAuthId = return . show
|
, PersistEntity (AuthEntity master)
|
||||||
|
, PersistEntity (AuthEmailEntity master)
|
||||||
readAuthId :: String -> GHandler s master (Maybe (AuthId master))
|
) => YesodAuth master where
|
||||||
readAuthId s = return $ case reads s of
|
type AuthEntity master
|
||||||
[] -> Nothing
|
type AuthEmailEntity master
|
||||||
((x, _):_) -> Just x
|
|
||||||
|
|
||||||
-- | Default destination on successful login or logout, if no other
|
-- | Default destination on successful login or logout, if no other
|
||||||
-- destination exists.
|
-- destination exists.
|
||||||
@ -163,7 +162,7 @@ setCreds creds extra = do
|
|||||||
maid <- getAuthId creds extra
|
maid <- getAuthId creds extra
|
||||||
case maid of
|
case maid of
|
||||||
Nothing -> return ()
|
Nothing -> return ()
|
||||||
Just aid -> showAuthId aid >>= setSession credsKey
|
Just aid -> setSession credsKey $ show $ fromPersistKey aid
|
||||||
|
|
||||||
-- | Retrieves user credentials, if user is authenticated.
|
-- | Retrieves user credentials, if user is authenticated.
|
||||||
maybeAuthId :: YesodAuth m => GHandler s m (Maybe (AuthId m))
|
maybeAuthId :: YesodAuth m => GHandler s m (Maybe (AuthId m))
|
||||||
@ -171,7 +170,23 @@ maybeAuthId = do
|
|||||||
ms <- lookupSession credsKey
|
ms <- lookupSession credsKey
|
||||||
case ms of
|
case ms of
|
||||||
Nothing -> return Nothing
|
Nothing -> return Nothing
|
||||||
Just s -> readAuthId s
|
Just s -> case reads s of
|
||||||
|
[] -> return Nothing
|
||||||
|
(i, _):_ -> return $ Just $ toPersistKey i
|
||||||
|
|
||||||
|
maybeAuth :: ( PersistBackend (YesodDB m (GHandler s m))
|
||||||
|
, YesodPersist m
|
||||||
|
, YesodAuth m
|
||||||
|
) => GHandler s m (Maybe (AuthId m, AuthEntity m))
|
||||||
|
maybeAuth = do
|
||||||
|
maid <- maybeAuthId
|
||||||
|
case maid of
|
||||||
|
Nothing -> return Nothing
|
||||||
|
Just aid -> do
|
||||||
|
ma <- runDB $ get aid
|
||||||
|
case ma of
|
||||||
|
Nothing -> return Nothing
|
||||||
|
Just a -> return $ Just (aid, a)
|
||||||
|
|
||||||
mkYesodSub "Auth"
|
mkYesodSub "Auth"
|
||||||
[ ClassP ''YesodAuth [VarT $ mkName "master"]
|
[ ClassP ''YesodAuth [VarT $ mkName "master"]
|
||||||
@ -186,7 +201,7 @@ mkYesodSub "Auth"
|
|||||||
/facebook FacebookR GET
|
/facebook FacebookR GET
|
||||||
|
|
||||||
/register EmailRegisterR GET POST
|
/register EmailRegisterR GET POST
|
||||||
/verify/#Integer/#String EmailVerifyR GET
|
/verify/#Int64/#String EmailVerifyR GET
|
||||||
/email-login EmailLoginR POST
|
/email-login EmailLoginR POST
|
||||||
/set-password EmailPasswordR GET POST
|
/set-password EmailPasswordR GET POST
|
||||||
|
|
||||||
@ -233,7 +248,7 @@ handleRpxnowR :: YesodAuth master => GHandler Auth master ()
|
|||||||
handleRpxnowR = do
|
handleRpxnowR = do
|
||||||
ay <- getYesod
|
ay <- getYesod
|
||||||
auth <- getYesod
|
auth <- getYesod
|
||||||
apiKey <- case rpxnowApp <$> rpxnowSettings auth of
|
apiKey <- case rpxnowKey <$> rpxnowSettings auth of
|
||||||
Just x -> return x
|
Just x -> return x
|
||||||
Nothing -> notFound
|
Nothing -> notFound
|
||||||
token1 <- lookupGetParam "token"
|
token1 <- lookupGetParam "token"
|
||||||
@ -295,15 +310,21 @@ getLogoutR = do
|
|||||||
-- 'authRoute'. Sets ultimate destination to current route, so user
|
-- 'authRoute'. Sets ultimate destination to current route, so user
|
||||||
-- should be sent back here after authenticating.
|
-- should be sent back here after authenticating.
|
||||||
requireAuthId :: YesodAuth m => GHandler sub m (AuthId m)
|
requireAuthId :: YesodAuth m => GHandler sub m (AuthId m)
|
||||||
requireAuthId =
|
requireAuthId = maybeAuthId >>= maybe redirectLogin return
|
||||||
maybeAuthId >>= maybe redirectLogin return
|
|
||||||
where
|
requireAuth :: ( PersistBackend (YesodDB m (GHandler s m))
|
||||||
redirectLogin = do
|
, YesodPersist m
|
||||||
y <- getYesod
|
, YesodAuth m
|
||||||
setUltDest'
|
) => GHandler s m (AuthId m, AuthEntity m)
|
||||||
case authRoute y of
|
requireAuth = maybeAuth >>= maybe redirectLogin return
|
||||||
Just z -> redirect RedirectTemporary z
|
|
||||||
Nothing -> permissionDenied "Please configure authRoute"
|
redirectLogin :: Yesod m => GHandler s m a
|
||||||
|
redirectLogin = do
|
||||||
|
y <- getYesod
|
||||||
|
setUltDest'
|
||||||
|
case authRoute y of
|
||||||
|
Just z -> redirect RedirectTemporary z
|
||||||
|
Nothing -> permissionDenied "Please configure authRoute"
|
||||||
|
|
||||||
getEmailSettings :: YesodAuth master
|
getEmailSettings :: YesodAuth master
|
||||||
=> GHandler Auth master (EmailSettings master)
|
=> GHandler Auth master (EmailSettings master)
|
||||||
@ -341,16 +362,16 @@ postEmailRegisterR = do
|
|||||||
return (lid, key)
|
return (lid, key)
|
||||||
render <- getUrlRender
|
render <- getUrlRender
|
||||||
tm <- getRouteToMaster
|
tm <- getRouteToMaster
|
||||||
let verUrl = render $ tm $ EmailVerifyR (fromIntegral lid) verKey
|
let verUrl = render $ tm $ EmailVerifyR (fromPersistKey lid) verKey
|
||||||
sendVerifyEmail ae email verKey verUrl
|
sendVerifyEmail ae email verKey verUrl
|
||||||
defaultLayout $ setTitle "Confirmation e-mail sent" >> addBody [$hamlet|
|
defaultLayout $ setTitle "Confirmation e-mail sent" >> addBody [$hamlet|
|
||||||
%p A confirmation e-mail has been sent to $email$.
|
%p A confirmation e-mail has been sent to $email$.
|
||||||
|]
|
|]
|
||||||
|
|
||||||
getEmailVerifyR :: YesodAuth master
|
getEmailVerifyR :: YesodAuth master
|
||||||
=> Integer -> String -> GHandler Auth master RepHtml
|
=> Int64 -> String -> GHandler Auth master RepHtml
|
||||||
getEmailVerifyR lid' key = do
|
getEmailVerifyR lid' key = do
|
||||||
let lid = fromInteger lid'
|
let lid = toPersistKey lid'
|
||||||
ae <- getEmailSettings
|
ae <- getEmailSettings
|
||||||
realKey <- getVerifyKey ae lid
|
realKey <- getVerifyKey ae lid
|
||||||
memail <- getEmail ae lid
|
memail <- getEmail ae lid
|
||||||
|
|||||||
148
scaffold.hs
148
scaffold.hs
@ -173,13 +173,19 @@ module ~sitearg~
|
|||||||
, ~sitearg~Route (..)
|
, ~sitearg~Route (..)
|
||||||
, resources~sitearg~
|
, resources~sitearg~
|
||||||
, Handler
|
, Handler
|
||||||
|
, maybeAuth
|
||||||
|
, requireAuth
|
||||||
, module Yesod
|
, module Yesod
|
||||||
, module Settings
|
, module Settings
|
||||||
, module Model
|
, module Model
|
||||||
|
, StaticRoute (..)
|
||||||
|
, AuthRoute (..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Yesod
|
import Yesod
|
||||||
|
import Yesod.Mail
|
||||||
import Yesod.Helpers.Static
|
import Yesod.Helpers.Static
|
||||||
|
import Yesod.Helpers.Auth
|
||||||
import qualified Settings
|
import qualified Settings
|
||||||
import System.Directory
|
import System.Directory
|
||||||
import qualified Data.ByteString.Lazy as L
|
import qualified Data.ByteString.Lazy as L
|
||||||
@ -187,6 +193,8 @@ import Yesod.WebRoutes
|
|||||||
import Database.Persist.GenericSql
|
import Database.Persist.GenericSql
|
||||||
import Settings (hamletFile, cassiusFile, juliusFile)
|
import Settings (hamletFile, cassiusFile, juliusFile)
|
||||||
import Model
|
import Model
|
||||||
|
import Control.Monad (join)
|
||||||
|
import Data.Maybe (isJust)
|
||||||
|
|
||||||
data ~sitearg~ = ~sitearg~
|
data ~sitearg~ = ~sitearg~
|
||||||
{ getStatic :: Static
|
{ getStatic :: Static
|
||||||
@ -196,10 +204,13 @@ data ~sitearg~ = ~sitearg~
|
|||||||
type Handler = GHandler ~sitearg~ ~sitearg~
|
type Handler = GHandler ~sitearg~ ~sitearg~
|
||||||
|
|
||||||
mkYesodData "~sitearg~" [$parseRoutes|
|
mkYesodData "~sitearg~" [$parseRoutes|
|
||||||
/ RootR GET POST
|
|
||||||
/static StaticR Static getStatic
|
/static StaticR Static getStatic
|
||||||
|
/auth AuthR Auth getAuth
|
||||||
|
|
||||||
/favicon.ico FaviconR GET
|
/favicon.ico FaviconR GET
|
||||||
/robots.txt RobotsR GET
|
/robots.txt RobotsR GET
|
||||||
|
|
||||||
|
/ RootR GET
|
||||||
|~~]
|
|~~]
|
||||||
|
|
||||||
instance Yesod ~sitearg~ where
|
instance Yesod ~sitearg~ where
|
||||||
@ -217,6 +228,7 @@ instance Yesod ~sitearg~ where
|
|||||||
ss :: Site StaticRoute (String -> Maybe (GHandler Static ~sitearg~ ChooseRep))
|
ss :: Site StaticRoute (String -> Maybe (GHandler Static ~sitearg~ ChooseRep))
|
||||||
ss = getSubSite
|
ss = getSubSite
|
||||||
urlRenderOverride _ _ = Nothing
|
urlRenderOverride _ _ = Nothing
|
||||||
|
authRoute _ = Just $ AuthR LoginR
|
||||||
addStaticContent ext' _ content = do
|
addStaticContent ext' _ content = do
|
||||||
let fn = base64md5 content ++ '.' : ext'
|
let fn = base64md5 content ++ '.' : ext'
|
||||||
let statictmp = Settings.staticdir ++ "/tmp/"
|
let statictmp = Settings.staticdir ++ "/tmp/"
|
||||||
@ -227,6 +239,76 @@ instance Yesod ~sitearg~ where
|
|||||||
instance YesodPersist ~sitearg~ where
|
instance YesodPersist ~sitearg~ where
|
||||||
type YesodDB ~sitearg~ = SqlPersist
|
type YesodDB ~sitearg~ = SqlPersist
|
||||||
runDB db = fmap connPool getYesod >>= Settings.runConnectionPool db
|
runDB db = fmap connPool getYesod >>= Settings.runConnectionPool db
|
||||||
|
|
||||||
|
instance YesodAuth ~sitearg~ where
|
||||||
|
type AuthEntity ~sitearg~ = User
|
||||||
|
type AuthEmailEntity ~sitearg~ = Email
|
||||||
|
|
||||||
|
defaultDest _ = RootR
|
||||||
|
|
||||||
|
getAuthId creds _extra = runDB $ do
|
||||||
|
x <- getBy $ UniqueUser $ credsIdent creds
|
||||||
|
case x of
|
||||||
|
Just (uid, _) -> return $ Just uid
|
||||||
|
Nothing -> do
|
||||||
|
fmap Just $ insert $ User (credsIdent creds) Nothing
|
||||||
|
|
||||||
|
openIdEnabled _ = True
|
||||||
|
|
||||||
|
emailSettings _ = Just EmailSettings
|
||||||
|
{ addUnverified = \email verkey ->
|
||||||
|
runDB $ insert $ Email email Nothing (Just verkey)
|
||||||
|
, sendVerifyEmail = sendVerifyEmail'
|
||||||
|
, getVerifyKey = runDB . fmap (join . fmap emailVerkey) . get
|
||||||
|
, setVerifyKey = \eid key -> runDB $ update eid [EmailVerkey $ Just key]
|
||||||
|
, verifyAccount = \eid -> runDB $ do
|
||||||
|
me <- get eid
|
||||||
|
case me of
|
||||||
|
Nothing -> return Nothing
|
||||||
|
Just e -> do
|
||||||
|
let email = emailEmail e
|
||||||
|
case emailUser e of
|
||||||
|
Just uid -> return $ Just uid
|
||||||
|
Nothing -> do
|
||||||
|
uid <- insert $ User email Nothing
|
||||||
|
update eid [EmailUser $ Just uid]
|
||||||
|
return $ Just uid
|
||||||
|
, getPassword = runDB . fmap (join . fmap userPassword) . get
|
||||||
|
, setPassword = \uid pass -> runDB $ update uid [UserPassword $ Just pass]
|
||||||
|
, getEmailCreds = \email -> runDB $ do
|
||||||
|
me <- getBy $ UniqueEmail email
|
||||||
|
case me of
|
||||||
|
Nothing -> return Nothing
|
||||||
|
Just (eid, e) -> return $ Just EmailCreds
|
||||||
|
{ emailCredsId = eid
|
||||||
|
, emailCredsAuthId = emailUser e
|
||||||
|
, emailCredsStatus = isJust $ emailUser e
|
||||||
|
, emailCredsVerkey = emailVerkey e
|
||||||
|
}
|
||||||
|
, getEmail = runDB . fmap (fmap emailEmail) . get
|
||||||
|
}
|
||||||
|
|
||||||
|
sendVerifyEmail' :: String -> String -> String -> GHandler Auth m ()
|
||||||
|
sendVerifyEmail' email _ verurl =
|
||||||
|
liftIO $ renderSendMail Mail
|
||||||
|
{ mailHeaders =
|
||||||
|
[ ("From", "noreply")
|
||||||
|
, ("To", email)
|
||||||
|
, ("Subject", "Verify your email address")
|
||||||
|
]
|
||||||
|
, mailPlain = verurl
|
||||||
|
, mailParts = return Part
|
||||||
|
{ partType = "text/html; charset=utf-8"
|
||||||
|
, partEncoding = None
|
||||||
|
, partDisposition = Inline
|
||||||
|
, partContent = renderHamlet id [$hamlet|
|
||||||
|
%p Please confirm your email address by clicking on the link below.
|
||||||
|
%p
|
||||||
|
%a!href=$verurl$ $verurl$
|
||||||
|
%p Thank you
|
||||||
|
|~~]
|
||||||
|
}
|
||||||
|
}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
writeFile' "Controller.hs" [$codegen|
|
writeFile' "Controller.hs" [$codegen|
|
||||||
@ -240,6 +322,7 @@ module Controller
|
|||||||
import ~sitearg~
|
import ~sitearg~
|
||||||
import Settings
|
import Settings
|
||||||
import Yesod.Helpers.Static
|
import Yesod.Helpers.Static
|
||||||
|
import Yesod.Helpers.Auth
|
||||||
import Database.Persist.GenericSql
|
import Database.Persist.GenericSql
|
||||||
|
|
||||||
import Handler.Root
|
import Handler.Root
|
||||||
@ -255,7 +338,8 @@ getRobotsR = return $ RepPlain $ toContent "User-agent: *"
|
|||||||
with~sitearg~ :: (Application -> IO a) -> IO a
|
with~sitearg~ :: (Application -> IO a) -> IO a
|
||||||
with~sitearg~ f = Settings.withConnectionPool $ \p -> do
|
with~sitearg~ f = Settings.withConnectionPool $ \p -> do
|
||||||
flip runConnectionPool p $ runMigration $ do
|
flip runConnectionPool p $ runMigration $ do
|
||||||
migrate (undefined :: Message)
|
migrate (undefined :: User)
|
||||||
|
migrate (undefined :: Email)
|
||||||
let h = ~sitearg~ s p
|
let h = ~sitearg~ s p
|
||||||
toWaiApp h >>= f
|
toWaiApp h >>= f
|
||||||
where
|
where
|
||||||
@ -269,30 +353,15 @@ module Handler.Root where
|
|||||||
import ~sitearg~
|
import ~sitearg~
|
||||||
import Control.Applicative
|
import Control.Applicative
|
||||||
|
|
||||||
messageFormlet :: Formlet sub master Message
|
|
||||||
messageFormlet x = fieldsToTable
|
|
||||||
$ Message <$> textareaField "Message"
|
|
||||||
(fmap messageContent x)
|
|
||||||
|
|
||||||
getRootR :: Handler RepHtml
|
getRootR :: Handler RepHtml
|
||||||
getRootR = do
|
getRootR = do
|
||||||
messages <- runDB $ selectList [] [] 10 0
|
mu <- maybeAuth
|
||||||
(_, wform, _) <- runFormGet $ messageFormlet Nothing
|
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
|
h2id <- newIdent
|
||||||
setTitle "~project~ homepage"
|
setTitle "~project~ homepage"
|
||||||
ident <- newIdent
|
|
||||||
form <- extractBody wform
|
|
||||||
addBody $(hamletFile "homepage")
|
addBody $(hamletFile "homepage")
|
||||||
addStyle $(cassiusFile "homepage")
|
addStyle $(cassiusFile "homepage")
|
||||||
addJavascript $(juliusFile "homepage")
|
addJavascript $(juliusFile "homepage")
|
||||||
|
|
||||||
postRootR :: Handler ()
|
|
||||||
postRootR = do
|
|
||||||
(res, _, _) <- runFormPost $ messageFormlet Nothing
|
|
||||||
case res of
|
|
||||||
FormSuccess message -> runDB (insert message) >> return ()
|
|
||||||
_ -> return ()
|
|
||||||
redirect RedirectTemporary RootR
|
|
||||||
|]
|
|]
|
||||||
|
|
||||||
writeFile' "Model.hs" [$codegen|
|
writeFile' "Model.hs" [$codegen|
|
||||||
@ -302,8 +371,15 @@ module Model where
|
|||||||
import Yesod
|
import Yesod
|
||||||
|
|
||||||
mkPersist [$persist|
|
mkPersist [$persist|
|
||||||
Message
|
User
|
||||||
content Textarea
|
ident String
|
||||||
|
password String null update
|
||||||
|
UniqueUser ident
|
||||||
|
Email
|
||||||
|
email String
|
||||||
|
user UserId null update
|
||||||
|
verkey String null update
|
||||||
|
UniqueEmail email
|
||||||
|~~]
|
|~~]
|
||||||
|]
|
|]
|
||||||
|
|
||||||
@ -399,21 +475,17 @@ body
|
|||||||
|
|
||||||
writeFile' "hamlet/homepage.hamlet" [$codegen|
|
writeFile' "hamlet/homepage.hamlet" [$codegen|
|
||||||
%h1 Hello
|
%h1 Hello
|
||||||
%p#$ident$ Welcome.
|
%h2#$h2id$ You do not have Javascript enabled.
|
||||||
%h3 Messages
|
$maybe mu u
|
||||||
$if null.messages
|
%p
|
||||||
%p No messages.
|
You are logged in as $userIdent.snd.u$. $
|
||||||
$else
|
%a!href=@AuthR.LogoutR@ Logout
|
||||||
%ul
|
\.
|
||||||
$forall messages m
|
$nothing
|
||||||
%li $messageContent.snd.m$
|
%p
|
||||||
%h3 Add Message
|
You are not logged in. $
|
||||||
%form!method=post!action=@RootR@
|
%a!href=@AuthR.LoginR@ Login now
|
||||||
%table
|
\.
|
||||||
^form^
|
|
||||||
%tr
|
|
||||||
%td!colspan=2
|
|
||||||
%input!type=submit!value="Add Message"
|
|
||||||
|]
|
|]
|
||||||
|
|
||||||
writeFile' "cassius/homepage.cassius" [$codegen|
|
writeFile' "cassius/homepage.cassius" [$codegen|
|
||||||
@ -421,11 +493,13 @@ body
|
|||||||
font-family: sans-serif
|
font-family: sans-serif
|
||||||
h1
|
h1
|
||||||
text-align: center
|
text-align: center
|
||||||
|
h2#$h2id$
|
||||||
|
color: #990
|
||||||
|]
|
|]
|
||||||
|
|
||||||
writeFile' "julius/homepage.julius" [$codegen|
|
writeFile' "julius/homepage.julius" [$codegen|
|
||||||
window.onload = function(){
|
window.onload = function(){
|
||||||
document.getElementById("%ident%").innerHTML = "<i>Added from JavaScript.</i>";
|
document.getElementById("%h2id%").innerHTML = "<i>Added from JavaScript.</i>";
|
||||||
}
|
}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user