packages feed

servant-auth-token-persistent (empty) → 0.4.0.0

raw patch · 7 files changed

+335/−0 lines, 7 filesdep +aeson-injectordep +basedep +bytestringsetup-changed

Dependencies added: aeson-injector, base, bytestring, containers, ghc-prim, monad-control, mtl, persistent, persistent-postgresql, persistent-template, servant-auth-token, servant-auth-token-api, servant-server, text, time, transformers, transformers-base, uuid

Files

+ CHANGELOG.md view
@@ -0,0 +1,4 @@+0.3.2.0+=======++* Initial factor out from parent package.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Anton Gushcha (c) 2016-2017++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of NCrashed nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,40 @@+# servant-auth-token-persistent++Storage backend on persistent for [servant-auth-token](https://github.com/NCrashed/servant-auth-token) server.++An itegration of the backend is simple:+``` haskell+-- | Special monad for authorisation actions+newtype AuthM a = AuthM { unAuthM :: PersistentBackendT IO a }+  deriving (Functor, Applicative, Monad, MonadIO, MonadError ServantErr, HasStorage, HasAuthConfig)++-- | Execution of authorisation actions that require 'AuthHandler' context+runAuth :: AuthM a -> ServerM a+runAuth m = do+  cfg <- asks envAuthConfig+  pool <- asks envPool+  liftHandler $ ExceptT $ runPersistentBackendT cfg pool $ unAuthM m+```++Don't forget to add migration to your server startup (if you actually want automatic migrations):+``` haskell+-- | Create new server environment+newServerEnv :: MonadIO m => ServerConfig -> m ServerEnv+newServerEnv cfg = do+  let authConfig = defaultAuthConfig+  pool <- liftIO $ do+    pool <- createPool cfg+    -- run migrations+    flip runSqlPool pool $ runMigration S.migrateAll+    -- create default admin if missing one+    _ <- runPersistentBackendT authConfig pool $ ensureAdmin 17 "admin" "123456" "admin@localhost"+    return pool+  let env = ServerEnv {+        envConfig = cfg+      , envAuthConfig = authConfig+      , envPool = pool+      }+  return env+```++See a full example in [servant-auth-token-example-persistent](https://github.com/NCrashed/servant-auth-token/tree/master/example/persistent).
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ servant-auth-token-persistent.cabal view
@@ -0,0 +1,65 @@+name:                servant-auth-token-persistent+version:             0.4.0.0+synopsis:            Persistent backend for servant-auth-token server+description:         Please see README.md+homepage:            https://github.com/ncrashed/servant-auth-token#readme+license:             BSD3+license-file:        LICENSE+author:              NCrashed+maintainer:          ncrashed@gmail.com+copyright:           2016 Anton Gushcha+category:            Web+build-type:          Simple+extra-source-files:+  README.md+  CHANGELOG.md+cabal-version:       >=1.10++library+  hs-source-dirs:      src+  exposed-modules:+    Servant.Server.Auth.Token.Persistent+    Servant.Server.Auth.Token.Persistent.Schema+  build-depends:+      base                    >= 4.7    && < 5+    , aeson-injector          >= 1.0    && < 1.1+    , bytestring              >= 0.10   && < 0.11+    , containers              >= 0.5    && < 0.6+    , ghc-prim                >= 0.5    && < 0.6+    , monad-control           >= 1.0    && < 1.1+    , mtl                     >= 2.2    && < 2.3+    , persistent              >= 2.2    && < 2.7+    , persistent-postgresql   >= 2.2    && < 2.7+    , persistent-template     >= 2.1    && < 2.7+    , servant-server          >= 0.9    && < 0.10+    , servant-auth-token      >= 0.4    && < 0.5+    , servant-auth-token-api  >= 0.4    && < 0.5+    , text                    >= 1.2    && < 1.3+    , time                    >= 1.5    && < 1.7+    , transformers            >= 0.4    && < 0.6+    , transformers-base       >= 0.4    && < 0.5+    , uuid                    >= 1.3    && < 1.4+  default-language:    Haskell2010+  default-extensions:+    BangPatterns+    DataKinds+    DeriveGeneric+    FlexibleContexts+    FlexibleInstances+    FunctionalDependencies+    GADTs+    GeneralizedNewtypeDeriving+    KindSignatures+    MultiParamTypeClasses+    OverloadedStrings+    QuasiQuotes+    RecordWildCards+    ScopedTypeVariables+    TemplateHaskell+    TupleSections+    TypeFamilies+    TypeOperators++source-repository head+  type:     git+  location: https://github.com/ncrashed/servant-auth-token
+ src/Servant/Server/Auth/Token/Persistent.hs view
@@ -0,0 +1,109 @@+module Servant.Server.Auth.Token.Persistent(+    PersistentBackendT+  , runPersistentBackendT+  ) where++import Control.Monad.Reader+import Control.Monad.Trans.Control+import Control.Monad.Except+import Data.Aeson.WithField+import Database.Persist+import Database.Persist.Sql+import Servant.Server+import Servant.Server.Auth.Token.Monad+import Servant.Server.Auth.Token.Model+import Servant.Server.Auth.Token.Config++import qualified Servant.Server.Auth.Token.Persistent.Schema as S++-- | Monad transformer that implements storage backend+newtype PersistentBackendT m a = PersistentBackendT { unPersistentBackendT :: ReaderT (AuthConfig, ConnectionPool) (ExceptT ServantErr (SqlPersistT m)) a }+  deriving (Functor, Applicative, Monad, MonadIO, MonadError ServantErr)++instance Monad m => MonadReader SqlBackend (PersistentBackendT m) where+  ask = PersistentBackendT $ lift . lift $ ask+  local f m = do+    (cfg, pool) <- PersistentBackendT ask+    em <- PersistentBackendT . lift . lift $ local f $ runExceptT $ runReaderT (unPersistentBackendT m) (cfg, pool)+    case em of+      Left er -> PersistentBackendT . lift $ throwError er+      Right m' -> return m'++instance Monad m => HasAuthConfig (PersistentBackendT m) where+  getAuthConfig = fmap fst $ PersistentBackendT ask++-- | Execute backend action with given connection pool.+runPersistentBackendT :: MonadBaseControl IO m => AuthConfig -> ConnectionPool -> PersistentBackendT m a -> m (Either ServantErr a)+runPersistentBackendT cfg pool ma = runSqlPool (runExceptT $ runReaderT (unPersistentBackendT ma) (cfg, pool)) pool++-- | Convert entity struct to 'WithId' version+toWithId :: (S.ConvertStorage a' a, S.ConvertStorage (Key a') i) => Entity a' -> WithId i a+toWithId (Entity k v) = WithField (S.convertFrom k) (S.convertFrom v)++-- | Helper to execute DB actions in backend monad+liftDB :: Monad m => SqlPersistT m a -> PersistentBackendT m a+liftDB = PersistentBackendT . lift . lift++instance (MonadIO m) => HasStorage (PersistentBackendT m) where+  getUserImpl = liftDB . fmap (fmap S.convertFrom) . get . S.convertTo+  getUserImplByLogin = liftDB . fmap (fmap toWithId) . getBy . S.UniqueLogin+  listUsersPaged page size = liftDB $ do+    users <- selectList [] [Asc S.UserImplId, OffsetBy (fromIntegral $ page * size), LimitTo (fromIntegral size)]+    total <- count ([] :: [Filter S.UserImpl])+    return (fmap toWithId users, fromIntegral total)+  getUserImplPermissions uid = liftDB . fmap (fmap toWithId) $ selectList [S.UserPermUser ==. S.convertTo uid] [Asc S.UserPermPermission]+  deleteUserPermissions uid = liftDB $ deleteWhere [S.UserPermUser ==. S.convertTo uid]+  insertUserPerm = liftDB . fmap S.convertFrom . insert . S.convertTo+  insertUserImpl = liftDB . fmap S.convertFrom . insert . S.convertTo+  replaceUserImpl i v = liftDB $ replace (S.convertTo i) (S.convertTo v)+  deleteUserImpl = liftDB . delete . S.convertTo+  hasPerm i p = liftDB $ do+    c <- count [S.UserPermUser ==. S.convertTo i, S.UserPermPermission ==. p]+    return $ c > 0+  getFirstUserByPerm p = liftDB . fmap (fmap toWithId) $ do+    mp <- selectFirst [S.UserPermPermission ==. p] []+    case mp of+      Nothing -> return Nothing+      Just (Entity _ S.UserPerm{..}) -> fmap (Entity userPermUser) <$> get userPermUser+  selectUserImplGroups i = liftDB . fmap (fmap toWithId) $ selectList [S.AuthUserGroupUsersUser ==. S.convertTo i] [Asc S.AuthUserGroupUsersGroup]+  clearUserImplGroups i = liftDB $ deleteWhere [S.AuthUserGroupUsersUser ==. S.convertTo i]+  insertAuthUserGroup = liftDB . fmap S.convertFrom . insert . S.convertTo+  insertAuthUserGroupUsers = liftDB . fmap S.convertFrom . insert . S.convertTo+  insertAuthUserGroupPerms = liftDB . fmap S.convertFrom . insert . S.convertTo+  getAuthUserGroup = liftDB . fmap (fmap S.convertFrom) . get . S.convertTo+  listAuthUserGroupPermissions i = liftDB . fmap (fmap toWithId) $ selectList [S.AuthUserGroupPermsGroup ==. S.convertTo i] [Asc S.AuthUserGroupPermsPermission]+  listAuthUserGroupUsers i = liftDB . fmap (fmap toWithId) $ selectList [S.AuthUserGroupUsersGroup ==. S.convertTo i] [Asc S.AuthUserGroupUsersUser]+  replaceAuthUserGroup i v = liftDB $ replace (S.convertTo i) (S.convertTo v)+  clearAuthUserGroupUsers i = liftDB $ deleteWhere [S.AuthUserGroupUsersGroup ==. S.convertTo i]+  clearAuthUserGroupPerms i = liftDB $ deleteWhere [S.AuthUserGroupPermsGroup ==. S.convertTo i]+  deleteAuthUserGroup = liftDB . delete . S.convertTo+  listGroupsPaged page size = liftDB $ do+    groups <- selectList [] [Asc S.AuthUserGroupId, OffsetBy (fromIntegral $ page * size), LimitTo (fromIntegral size)]+    total <- count ([] :: [Filter S.AuthUserGroup])+    return (fmap toWithId groups, fromIntegral total)+  setAuthUserGroupName i n = liftDB $ update (S.convertTo i) [S.AuthUserGroupName =. n]+  setAuthUserGroupParent i mp = liftDB $ update (S.convertTo i) [S.AuthUserGroupParent =. fmap S.convertTo mp]+  insertSingleUseCode = liftDB . fmap S.convertFrom . insert . S.convertTo+  setSingleUseCodeUsed i mt = liftDB $ update (S.convertTo i) [S.UserSingleUseCodeUsed =. mt]+  getUnusedCode c i t = liftDB . fmap (fmap toWithId) $ selectFirst ([+          S.UserSingleUseCodeValue ==. c+        , S.UserSingleUseCodeUser ==. S.convertTo i+        , S.UserSingleUseCodeUsed ==. Nothing+        ] ++ (+            [S.UserSingleUseCodeExpire ==. Nothing]+        ||. [S.UserSingleUseCodeExpire >=. Just t]+    )) [Desc S.UserSingleUseCodeExpire]+  invalidatePermamentCodes i t = liftDB $ updateWhere [+      S.UserSingleUseCodeUser ==. S.convertTo i+    , S.UserSingleUseCodeUsed ==. Nothing+    , S.UserSingleUseCodeExpire ==. Nothing+    ]+    [S.UserSingleUseCodeUsed =. Just t]+  selectLastRestoreCode i t = liftDB . fmap (fmap toWithId) $ selectFirst [S.UserRestoreUser ==. S.convertTo i, S.UserRestoreExpire >. t] [Desc S.UserRestoreExpire]+  insertUserRestore = liftDB . fmap S.convertFrom . insert . S.convertTo+  findRestoreCode i rc t = liftDB . fmap (fmap toWithId) $ selectFirst [S.UserRestoreUser ==. S.convertTo i, S.UserRestoreValue ==. rc, S.UserRestoreExpire >. t] [Desc S.UserRestoreExpire]+  replaceRestoreCode i v = liftDB $ replace (S.convertTo i) (S.convertTo v)+  findAuthToken i t = liftDB . fmap (fmap toWithId) $ selectFirst [S.AuthTokenUser ==. S.convertTo i, S.AuthTokenExpire >. t] []+  findAuthTokenByValue t = liftDB . fmap (fmap toWithId) $ selectFirst [S.AuthTokenValue ==. t] []+  insertAuthToken = liftDB . fmap S.convertFrom . insert . S.convertTo+  replaceAuthToken i v = liftDB $ replace (S.convertTo i) (S.convertTo v)
+ src/Servant/Server/Auth/Token/Persistent/Schema.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE MagicHash #-}+module Servant.Server.Auth.Token.Persistent.Schema where++import Data.Text+import Data.Time+import Database.Persist.TH+import GHC.Generics+import GHC.Prim++import Servant.API.Auth.Token++import qualified Servant.Server.Auth.Token.Model as M++share [mkPersist sqlSettings+     , mkDeleteCascade sqlSettings+     , mkMigrate "migrateAll"] [persistLowerCase|+UserImpl+  login       Login+  password    Password     -- encrypted with salt+  email       Email+  UniqueLogin login+  deriving Generic Show+UserPerm+  user        UserImplId+  permission  Permission+  deriving Generic Show+AuthToken+  value       SimpleToken+  user        UserImplId+  expire      UTCTime+  deriving Generic Show+UserRestore+  value       RestoreCode+  user        UserImplId+  expire      UTCTime+  deriving Generic Show+UserSingleUseCode+  value     SingleUseCode+  user      UserImplId+  expire    UTCTime Maybe -- Nothing is code that never expires+  used      UTCTime Maybe+  deriving Generic Show+AuthUserGroup+  name        Text+  parent      AuthUserGroupId Maybe+  deriving Generic Show+AuthUserGroupUsers+  group       AuthUserGroupId+  user        UserImplId+  deriving Generic Show+AuthUserGroupPerms+  group       AuthUserGroupId+  permission  Permission+  deriving Generic Show+|]++-- | Defines way to convert from persistent struct to model struct and vice versa.+--+-- Warning: default implementation is done via 'unsafeCoerce#', so make sure that+-- structure of 'a' and 'b' is completely identical.+class ConvertStorage a b | a -> b, b -> a where+  -- | Convert to internal representation+  convertTo   :: b -> a+  convertTo = unsafeCoerce#+  -- | Convert from internal representation+  convertFrom :: a -> b+  convertFrom = unsafeCoerce#++instance ConvertStorage UserImpl M.UserImpl+instance ConvertStorage UserPerm M.UserPerm+instance ConvertStorage AuthToken M.AuthToken+instance ConvertStorage UserRestore M.UserRestore+instance ConvertStorage UserSingleUseCode M.UserSingleUseCode+instance ConvertStorage AuthUserGroup M.AuthUserGroup+instance ConvertStorage AuthUserGroupUsers M.AuthUserGroupUsers+instance ConvertStorage AuthUserGroupPerms M.AuthUserGroupPerms++instance ConvertStorage UserImplId M.UserImplId+instance ConvertStorage UserPermId M.UserPermId+instance ConvertStorage AuthTokenId M.AuthTokenId+instance ConvertStorage UserRestoreId M.UserRestoreId+instance ConvertStorage UserSingleUseCodeId M.UserSingleUseCodeId+instance ConvertStorage AuthUserGroupId M.AuthUserGroupId+instance ConvertStorage AuthUserGroupUsersId M.AuthUserGroupUsersId+instance ConvertStorage AuthUserGroupPermsId M.AuthUserGroupPermsId