PageRenderTime 63ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Snap/Snaplet/Auth/Backends/MongoDB.hs

https://bitbucket.org/calxi/snaplet-mongodb
Haskell | 152 lines | 116 code | 14 blank | 22 comment | 0 complexity | 6a80a40c202c2a14e53fb0a1a93a4740 MD5 | raw file
  1. {-# LANGUAGE TemplateHaskell, TypeFamilies, QuasiQuotes #-}
  2. -- | This module containes MongoDB backend implementation
  3. -- for Snap.Snaplet.Auth
  4. module Snap.Snaplet.Auth.Backends.MongoDB where
  5. import Control.Monad.Reader
  6. import Control.Monad.Error
  7. import Data.Lens.Lazy hiding (access)
  8. import Data.Lens.Template
  9. import Data.Time
  10. import Web.ClientSession
  11. import Snap
  12. import Snap.Snaplet
  13. import Snap.Snaplet.Auth hiding (UserId)
  14. import qualified Snap.Snaplet.Auth as Auth (UserId)
  15. import Snap.Snaplet.Session
  16. import Snap.Snaplet.MongoDB hiding (get)
  17. import Snap.Snaplet.MongoDB.MongoValue
  18. import Data.HashMap.Strict (HashMap)
  19. import qualified Data.HashMap.Strict as Hash (toList, fromList)
  20. import qualified Data.Bson as Bson
  21. import qualified Data.Text as T
  22. import qualified Data.Aeson as A
  23. import Data.UString (u)
  24. import qualified Data.Attoparsec.Number as AN
  25. import qualified Data.Vector as V
  26. import qualified Database.MongoDB.Connection as MongoDB
  27. import qualified System.IO.Pool as MPool
  28. import Database.MongoDB.Query (Action, Failure(..), Database, master, access, AccessMode(..))
  29. ----------------------------------------------------------------------
  30. -- | Initialize a MongoDB backed 'AuthManager'
  31. initMongoDBAuthManager
  32. :: AuthSettings
  33. -- ^ Authentication settings for your app
  34. -> Lens b (Snaplet SessionManager)
  35. -- ^ Lens into a 'SessionManager' auth snaplet will use
  36. -> Snaplet MongoDBSnaplet
  37. -- ^ Lens into a 'MongoDBSnaplet'
  38. -> Initializer b (AuthManager b) ()
  39. -- ^ Custom action to run with normal snaplet init
  40. -> SnapletInit b (AuthManager b)
  41. initMongoDBAuthManager settings session_lens mongo action =
  42. makeSnaplet "MongoDBAuthManager"
  43. "A snaplet providing user authentication using a MongoDB backend"
  44. Nothing $ do
  45. action
  46. key <- liftIO $ getKey (asSiteKey settings)
  47. return $ AuthManager {
  48. backend = MongoDBAuthManager mongo
  49. , session = session_lens
  50. , activeUser = Nothing
  51. , minPasswdLen = asMinPasswdLen settings
  52. , rememberCookieName = asRememberCookieName settings
  53. , rememberPeriod = asRememberPeriod settings
  54. , siteKey = key
  55. , lockout = asLockout settings
  56. }
  57. ----------------------------------------------------------------------
  58. data MongoDBAuthManager = MongoDBAuthManager {
  59. _mongoDB :: Snaplet MongoDBSnaplet
  60. }
  61. ----------------------------------------------------------------------
  62. withMgr
  63. :: MongoDBAuthManager
  64. -> ReaderT MongoDBAuthManager IO a
  65. -> IO a
  66. withMgr mgr action = runReaderT action mgr
  67. ----------------------------------------------------------------------
  68. makeLens ''MongoDBAuthManager
  69. ----------------------------------------------------------------------
  70. instance MonadMongoDB (ReaderT MongoDBAuthManager IO) where
  71. withDB run = do
  72. (MongoDBAuthManager mongo) <- ask
  73. let mongoDB = getL snapletValue mongo
  74. let pool = connPoll mongoDB
  75. let db = appDatabase mongoDB
  76. epipe <- liftIO $ runErrorT $ MPool.aResource pool
  77. case epipe of
  78. Left err -> return $ Left $ ConnectionFailure err
  79. Right pipe -> do
  80. liftIO (access pipe master db run)
  81. withDBUnsafe run = undefined
  82. ----------------------------------------------------------------------
  83. asMongoEntity ''AuthUser useDefaults
  84. asMongoValue ''Auth.UserId useDefaults
  85. asMongoValue ''Role useDefaults
  86. asMongoValue ''Password useDefaults
  87. asMongoValue ''A.Value useDefaults
  88. asMongoValue ''AN.Number useDefaults
  89. ----------------------------------------------------------------------
  90. instance MongoValue A.Array where
  91. toValue a = toValue $ V.toList a
  92. fromValue v = return . V.fromList =<< fromValue v
  93. ----------------------------------------------------------------------
  94. instance MongoValue (HashMap T.Text A.Value) where
  95. toValue m = Bson.Array $ map toObj $ Hash.toList m
  96. where
  97. toObj :: (T.Text, A.Value) -> Bson.Value
  98. toObj (k, v) = Bson.Doc [(u "key") := (toValue k), (u "value") := (toValue v)]
  99. fromValue (Bson.Array a) = do
  100. el <- mapM toKV a
  101. return $ Hash.fromList el
  102. where
  103. toKV :: (Applicative m, Monad m) => Bson.Value -> ErrorT String m (T.Text, A.Value)
  104. toKV (Bson.Doc d) = do
  105. key <- fromValue =<< Bson.look (u "key") d
  106. value <- fromValue =<< Bson.look (u "value") d
  107. return (key, value)
  108. toKV v = expected "document" v
  109. fromValue v = expected "array" v
  110. ----------------------------------------------------------------------
  111. instance IAuthBackend MongoDBAuthManager where
  112. --save :: MongoDBAuthManager -> AuthUser -> IO AuthUser
  113. save mgr u = withMgr mgr $ do
  114. muser <- withDB' $ selectOne [mongo| { userId: #{userId u} } |] []
  115. case muser of
  116. Nothing -> (withDB' $ insert u) >> return u
  117. Just (_id, user) -> (withDB' $ Snap.Snaplet.MongoDB.save _id u) >> return user
  118. --lookupByUserId :: MongoDBAuthManager -> UserId -> IO (Maybe AuthUser)
  119. lookupByUserId mgr uid = withMgr mgr $ do
  120. mret <- withDB' $ selectOne [mongo| { userId: #{uid} } |] []
  121. case mret of
  122. Nothing -> return Nothing
  123. Just (_, ret) -> return $ Just ret
  124. --lookupByLogin ::MongoDBAuthManager -> Text -> IO (Maybe AuthUser)
  125. lookupByLogin mgr login = withMgr mgr $ do
  126. mret <- withDB' $ selectOne [mongo| { userLogin: #{login} } |] []
  127. case mret of
  128. Nothing -> return Nothing
  129. Just (_, ret) -> return $ Just ret
  130. --lookupByRememberToken :: MongoDBAuthManager -> Text -> IO (Maybe AuthUser)
  131. lookupByRememberToken mgr token = withMgr mgr $ do
  132. mret <- withDB' $ selectOne [mongo| { userRememberToken: #{token} } |] []
  133. case mret of
  134. Nothing -> return Nothing
  135. Just (_, ret) -> return $ Just ret
  136. --destroy :: MongoDBAuthManager -> AuthUser -> IO ()
  137. destroy mgr u = withMgr mgr $ do
  138. withDB' $ deleteWhere ([mongo| { userId: #{userId u}, userLogin: #{userLogin u}, userRememberToken: #{userRememberToken u} } |] :: Document AuthUser)