removed specific user types from library
This commit is contained in:
parent
a0c5ed71de
commit
4ccd17d357
@ -1 +1,5 @@
|
|||||||
# oauth2-mock-server
|
# oauth2-mock-server
|
||||||
|
Windows:
|
||||||
|
```
|
||||||
|
ghcup run --mingw-path -- stack run
|
||||||
|
```
|
||||||
@ -7,14 +7,15 @@ import Server
|
|||||||
import Control.Applicative ((<|>))
|
import Control.Applicative ((<|>))
|
||||||
import System.Environment (lookupEnv)
|
import System.Environment (lookupEnv)
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
port <- determinePort
|
port <- determinePort
|
||||||
putStrLn $ "Try: http://localhost:" ++ show port ++ "/auth?scope=ID%20Profile&client_id=42&response_type=code&redirect_uri=http:%2F%2Flocalhost:0000%2F"
|
putStrLn $ "Try: http://localhost:" ++ show port ++ "/auth?scope=ID%20Profile&client_id=42&response_type=code&redirect_uri=http:%2F%2Flocalhost:0000%2F"
|
||||||
runMockServer port testUsers
|
runMockServer @User @(M.Map T.Text T.Text) port
|
||||||
where
|
where
|
||||||
determinePort :: IO Int
|
determinePort :: IO Int
|
||||||
determinePort = do
|
determinePort = do
|
||||||
Just port <- (read @Int <$> lookupEnv "OAUTH2_SERVER_PORT") <|> Just 9443
|
Just port <- lookupEnv "OAUTH2_SERVER_PORT" >>= \p -> return $ p <|> Just "9443"
|
||||||
return port
|
return $ read @Int port
|
||||||
|
|||||||
@ -1,4 +1,17 @@
|
|||||||
{-# Language GADTs, GeneralizedNewtypeDeriving, OverloadedStrings, QuasiQuotes, TemplateHaskell, TypeFamilies #-}
|
{-# Language GADTs,
|
||||||
|
GeneralizedNewtypeDeriving,
|
||||||
|
OverloadedStrings,
|
||||||
|
QuasiQuotes,
|
||||||
|
TemplateHaskell,
|
||||||
|
TypeFamilies,
|
||||||
|
TypeApplications,
|
||||||
|
DerivingStrategies,
|
||||||
|
StandaloneDeriving,
|
||||||
|
UndecidableInstances,
|
||||||
|
DataKinds,
|
||||||
|
FlexibleInstances,
|
||||||
|
MultiParamTypeClasses,
|
||||||
|
RecordWildCards #-}
|
||||||
|
|
||||||
module UniWorX (User(..), initDB, testUsers) where
|
module UniWorX (User(..), initDB, testUsers) where
|
||||||
|
|
||||||
@ -6,6 +19,17 @@ import User
|
|||||||
|
|
||||||
import Control.Applicative ((<|>))
|
import Control.Applicative ((<|>))
|
||||||
import Control.Monad (void, forM_)
|
import Control.Monad (void, forM_)
|
||||||
|
import Control.Monad.IO.Class (liftIO)
|
||||||
|
import Control.Monad.Logger (runStderrLoggingT, NoLoggingT)
|
||||||
|
import Control.Monad.Reader (ReaderT)
|
||||||
|
|
||||||
|
import Conduit (ResourceT)
|
||||||
|
|
||||||
|
import Data.Map (Map(..))
|
||||||
|
import Data.String (IsString(..))
|
||||||
|
import Data.Text (Text(..))
|
||||||
|
import qualified Data.Map as M
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
import Database.Persist
|
import Database.Persist
|
||||||
import Database.Persist.TH
|
import Database.Persist.TH
|
||||||
@ -23,15 +47,15 @@ share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
|
|||||||
|
|
||||||
testUsers :: [User] -- TODO move to db
|
testUsers :: [User] -- TODO move to db
|
||||||
testUsers =
|
testUsers =
|
||||||
[ User {name = "Fallback User", email = "foo@bar.com"}
|
[ User "Fallback User" "foo@bar.com"
|
||||||
, User {name = "Tina Tester", email = "t@t.tt"}
|
, User "Tina Tester" "t@t.tt"
|
||||||
, User {name = "Max Muster", email = "m@m.mm"}]
|
, User "Max Muster" "m@m.mm"]
|
||||||
|
|
||||||
runDB :: IO a -> IO a
|
runDB :: ReaderT SqlBackend (NoLoggingT (ResourceT IO)) a -> IO a
|
||||||
runDB action = do
|
runDB action = do
|
||||||
Just port <- lookupEnv "OAUTH2_DB_PORT" <|> Just "9444"
|
Just port <- lookupEnv "OAUTH2_DB_PORT" >>= \p -> return $ p <|> Just "9444"
|
||||||
let connStr = "host=localhost dbname=test_users user=oauth2mock password=0000 port=" ++ port
|
let connStr = fromString @ConnectionString $ "host=localhost dbname=test_users user=oauth2mock password=0000 port=" ++ port
|
||||||
withPostgresqlPool connStr 10 $ \pool -> flip runSqlPersistMPool pool action
|
runStderrLoggingT $ withPostgresqlPool connStr 10 $ \pool -> liftIO $ flip runSqlPersistMPool pool action
|
||||||
|
|
||||||
initDB :: IO ()
|
initDB :: IO ()
|
||||||
initDB = runDB $ do
|
initDB = runDB $ do
|
||||||
@ -42,11 +66,11 @@ instance UserData User (Map Text Text) where
|
|||||||
data Scope User = ID | Profile deriving (Show, Read, Eq)
|
data Scope User = ID | Profile deriving (Show, Read, Eq)
|
||||||
readScope = read
|
readScope = read
|
||||||
showScope = show
|
showScope = show
|
||||||
userScope (Entity uID _) ID = singleton "id" uID
|
userScope (Entity uID _) ID = M.singleton "id" . T.pack $ show uID
|
||||||
userScope (Entity _ User{..}) Profile = fromList [("name", name), ("email", email)]
|
userScope (Entity _ User{..}) Profile = M.fromList [("name", userName), ("email", userEmail)]
|
||||||
lookupUser email _ = runDB $ do
|
lookupUser email _ = runDB $ do
|
||||||
user <- selectList [UserEmail ==. e] []
|
user <- selectList [UserEmail ==. email] []
|
||||||
case user of
|
case user of
|
||||||
[Entity _ u] -> return $ Just u
|
[entity] -> return $ Just entity
|
||||||
[] -> Nothing
|
[] -> return Nothing
|
||||||
_ -> error "Ambiguous User."
|
_ -> error "Ambiguous User."
|
||||||
|
|||||||
@ -37,11 +37,14 @@ library
|
|||||||
, base64
|
, base64
|
||||||
, blaze-html
|
, blaze-html
|
||||||
, bytestring
|
, bytestring
|
||||||
|
, conduit
|
||||||
, containers
|
, containers
|
||||||
, http-api-data
|
, http-api-data
|
||||||
, http-client
|
, http-client
|
||||||
, http-media
|
, http-media
|
||||||
, jose-jwt
|
, jose-jwt
|
||||||
|
, monad-logger
|
||||||
|
, mtl
|
||||||
, persistent
|
, persistent
|
||||||
, persistent-postgresql
|
, persistent-postgresql
|
||||||
, persistent-template
|
, persistent-template
|
||||||
@ -73,11 +76,14 @@ executable oauth2-mock-server-exe
|
|||||||
, base64
|
, base64
|
||||||
, blaze-html
|
, blaze-html
|
||||||
, bytestring
|
, bytestring
|
||||||
|
, conduit
|
||||||
, containers
|
, containers
|
||||||
, http-api-data
|
, http-api-data
|
||||||
, http-client
|
, http-client
|
||||||
, http-media
|
, http-media
|
||||||
, jose-jwt
|
, jose-jwt
|
||||||
|
, monad-logger
|
||||||
|
, mtl
|
||||||
, oauth2-mock-server
|
, oauth2-mock-server
|
||||||
, persistent
|
, persistent
|
||||||
, persistent-postgresql
|
, persistent-postgresql
|
||||||
@ -110,11 +116,14 @@ test-suite oauth2-mock-server-test
|
|||||||
, base64
|
, base64
|
||||||
, blaze-html
|
, blaze-html
|
||||||
, bytestring
|
, bytestring
|
||||||
|
, conduit
|
||||||
, containers
|
, containers
|
||||||
, http-api-data
|
, http-api-data
|
||||||
, http-client
|
, http-client
|
||||||
, http-media
|
, http-media
|
||||||
, jose-jwt
|
, jose-jwt
|
||||||
|
, monad-logger
|
||||||
|
, mtl
|
||||||
, oauth2-mock-server
|
, oauth2-mock-server
|
||||||
, persistent
|
, persistent
|
||||||
, persistent-postgresql
|
, persistent-postgresql
|
||||||
|
|||||||
@ -43,6 +43,9 @@ dependencies:
|
|||||||
- persistent
|
- persistent
|
||||||
- persistent-postgresql
|
- persistent-postgresql
|
||||||
- persistent-template
|
- persistent-template
|
||||||
|
- monad-logger
|
||||||
|
- conduit
|
||||||
|
- mtl
|
||||||
|
|
||||||
ghc-options:
|
ghc-options:
|
||||||
- -Wall
|
- -Wall
|
||||||
|
|||||||
@ -21,6 +21,8 @@ import Data.UUID
|
|||||||
|
|
||||||
import qualified Data.Map.Strict as M
|
import qualified Data.Map.Strict as M
|
||||||
|
|
||||||
|
import Database.Persist (Entity(..))
|
||||||
|
|
||||||
import Control.Concurrent (forkIO, threadDelay)
|
import Control.Concurrent (forkIO, threadDelay)
|
||||||
import Control.Concurrent.STM.TVar
|
import Control.Concurrent.STM.TVar
|
||||||
import Control.Monad (void, (>=>))
|
import Control.Monad (void, (>=>))
|
||||||
@ -45,7 +47,7 @@ instance FromJSON JWT where
|
|||||||
data AuthRequest user = AuthRequest
|
data AuthRequest user = AuthRequest
|
||||||
{ client :: String
|
{ client :: String
|
||||||
, codeExpiration :: NominalDiffTime
|
, codeExpiration :: NominalDiffTime
|
||||||
, user :: user
|
, user :: Entity user
|
||||||
, scopes :: [Scope user]
|
, scopes :: [Scope user]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +55,7 @@ data AuthRequest user = AuthRequest
|
|||||||
|
|
||||||
data State user = State
|
data State user = State
|
||||||
{ activeCodes :: Map Text (AuthRequest user)
|
{ activeCodes :: Map Text (AuthRequest user)
|
||||||
, activeTokens :: Map UUID (user, [Scope user])
|
, activeTokens :: Map UUID (Entity user, [Scope user])
|
||||||
, publicKey :: Jwk
|
, publicKey :: Jwk
|
||||||
, privateKey :: Jwk
|
, privateKey :: Jwk
|
||||||
}
|
}
|
||||||
@ -84,7 +86,7 @@ expire code time state = void . forkIO $ do
|
|||||||
atomically . modifyTVar state $ \s -> s{ activeCodes = M.delete code s.activeCodes }
|
atomically . modifyTVar state $ \s -> s{ activeCodes = M.delete code s.activeCodes }
|
||||||
|
|
||||||
|
|
||||||
verify :: Text -> Maybe String -> AuthState user -> IO (Maybe (user, [Scope user]))
|
verify :: Text -> Maybe String -> AuthState user -> IO (Maybe (Entity user, [Scope user]))
|
||||||
verify code mClientID state = do
|
verify code mClientID state = do
|
||||||
now <- getCurrentTime
|
now <- getCurrentTime
|
||||||
mData <- atomically $ do
|
mData <- atomically $ do
|
||||||
|
|||||||
@ -34,6 +34,8 @@ import Data.UUID.V4
|
|||||||
import qualified Data.ByteString.Char8 as BS
|
import qualified Data.ByteString.Char8 as BS
|
||||||
import qualified Data.Map.Strict as Map
|
import qualified Data.Map.Strict as Map
|
||||||
|
|
||||||
|
import Database.Persist (Entity(..))
|
||||||
|
|
||||||
import GHC.Read (readPrec, lexP)
|
import GHC.Read (readPrec, lexP)
|
||||||
|
|
||||||
import Jose.Jwa
|
import Jose.Jwa
|
||||||
@ -99,7 +101,6 @@ type QParam = QueryParam' [Required, Strict]
|
|||||||
|
|
||||||
-- type ProtectedAuth user = BasicAuth "login" user :> "auth" :> Auth -- Prompts for username & password
|
-- type ProtectedAuth user = BasicAuth "login" user :> "auth" :> Auth -- Prompts for username & password
|
||||||
-- type QuickAuth = "qauth" :> Auth -- Prompts for username only
|
-- type QuickAuth = "qauth" :> Auth -- Prompts for username only
|
||||||
type Foo user userData = BasicAuth "login" user :> "foo" :> Get '[JSON] userData
|
|
||||||
type Auth = "auth"
|
type Auth = "auth"
|
||||||
:> QParam "scope" QScope
|
:> QParam "scope" QScope
|
||||||
:> QParam "client_id" QClient
|
:> QParam "client_id" QClient
|
||||||
@ -246,7 +247,7 @@ tokenEndpoint = provideToken
|
|||||||
|
|
||||||
|
|
||||||
mkToken :: forall user userData . UserData user userData
|
mkToken :: forall user userData . UserData user userData
|
||||||
=> user -> [Scope user] -> AuthState user -> IO JWTWrapper
|
=> Entity user -> [Scope user] -> AuthState user -> IO JWTWrapper
|
||||||
mkToken u scopes state = do
|
mkToken u scopes state = do
|
||||||
pubKey <- atomically $ readTVar state >>= return . publicKey
|
pubKey <- atomically $ readTVar state >>= return . publicKey
|
||||||
now <- getCurrentTime
|
now <- getCurrentTime
|
||||||
@ -273,7 +274,7 @@ type HeaderR = Header' [Strict, Required]
|
|||||||
type Me userData = Users
|
type Me userData = Users
|
||||||
:> "me"
|
:> "me"
|
||||||
:> HeaderR "Authorization" Text
|
:> HeaderR "Authorization" Text
|
||||||
:> Get '[JSON] userData
|
:> Get '[JSON] (Maybe userData)
|
||||||
|
|
||||||
type UserList userData = Users
|
type UserList userData = Users
|
||||||
:> "query"
|
:> "query"
|
||||||
@ -284,7 +285,7 @@ type UserList userData = Users
|
|||||||
userEndpoint :: forall user userData . UserData user userData => AuthServer user (Me userData)
|
userEndpoint :: forall user userData . UserData user userData => AuthServer user (Me userData)
|
||||||
userEndpoint = handleUserData
|
userEndpoint = handleUserData
|
||||||
where
|
where
|
||||||
handleUserData :: Text -> AuthHandler user userData
|
handleUserData :: Text -> AuthHandler user (Maybe userData)
|
||||||
handleUserData jwtw = do
|
handleUserData jwtw = do
|
||||||
let mToken = stripPrefix "Bearer " jwtw
|
let mToken = stripPrefix "Bearer " jwtw
|
||||||
unless (isJust mToken) . throwError $ err500 { errBody = "Invalid token format"}
|
unless (isJust mToken) . throwError $ err500 { errBody = "Invalid token format"}
|
||||||
@ -298,7 +299,7 @@ userEndpoint = handleUserData
|
|||||||
liftIO $ print jwt
|
liftIO $ print jwt
|
||||||
mUser <- ask >>= liftIO . (atomically . readTVar >=> return . Map.lookup (jti jwt) . activeTokens)
|
mUser <- ask >>= liftIO . (atomically . readTVar >=> return . Map.lookup (jti jwt) . activeTokens)
|
||||||
case mUser of
|
case mUser of
|
||||||
Just (u, scopes) -> return . mconcat $ map (userScope @user @userData u) scopes
|
Just (u, scopes) -> return . Just . mconcat $ map (userScope @user @userData u) scopes
|
||||||
Nothing -> throwError $ err500 { errBody = "Unknown token" }
|
Nothing -> throwError $ err500 { errBody = "Unknown token" }
|
||||||
|
|
||||||
|
|
||||||
@ -324,7 +325,6 @@ type Routing user userData = Auth
|
|||||||
:<|> Token
|
:<|> Token
|
||||||
:<|> Me userData
|
:<|> Me userData
|
||||||
:<|> UserList userData
|
:<|> UserList userData
|
||||||
:<|> Foo user userData
|
|
||||||
-- :<|> "qauth" :> Get '[HTML] Html
|
-- :<|> "qauth" :> Get '[HTML] Html
|
||||||
|
|
||||||
routing :: forall user userData . UserData user userData => AuthServer user (Routing user userData)
|
routing :: forall user userData . UserData user userData => AuthServer user (Routing user userData)
|
||||||
@ -333,40 +333,33 @@ routing = loginServer @user @userData
|
|||||||
:<|> tokenEndpoint @user @userData
|
:<|> tokenEndpoint @user @userData
|
||||||
:<|> userEndpoint @user @userData
|
:<|> userEndpoint @user @userData
|
||||||
:<|> userListEndpoint @user @userData
|
:<|> userListEndpoint @user @userData
|
||||||
:<|> undefined
|
|
||||||
-- :<|> return (loginPage "/foobar")
|
-- :<|> return (loginPage "/foobar")
|
||||||
|
|
||||||
exampleAuthServer :: AuthServer User (Routing User (Map.Map Text Text))
|
|
||||||
exampleAuthServer = routing
|
|
||||||
|
|
||||||
|
|
||||||
authAPI :: Proxy (Routing User (Map.Map Text Text))
|
|
||||||
authAPI = Proxy
|
|
||||||
|
|
||||||
-- insecureOAuthMock :: Application
|
-- insecureOAuthMock :: Application
|
||||||
-- insecureOAuthMock = authAPI `serve` exampleAuthServer
|
-- insecureOAuthMock = authAPI `serve` exampleAuthServer
|
||||||
|
|
||||||
insecureOAuthMock' :: [User] -> AuthState User -> Application
|
insecureOAuthMock' :: forall user userData . UserData user userData => AuthState user -> Application
|
||||||
insecureOAuthMock' testUsers s = serveWithContext authAPI c $ hoistServerWithContext authAPI p (toHandler @User @(Map.Map Text Text) s) exampleAuthServer
|
insecureOAuthMock' s = serve authAPI $ hoistServer authAPI (toHandler @user @userData s) (routing @user @userData)
|
||||||
where
|
where
|
||||||
c = authenticate testUsers :. EmptyContext
|
authAPI = Proxy @(Routing user userData)
|
||||||
p = Proxy :: Proxy '[BasicAuthCheck User]
|
|
||||||
|
|
||||||
authenticate :: [User] -> BasicAuthCheck User
|
-- authenticate :: [User] -> BasicAuthCheck User
|
||||||
authenticate users = BasicAuthCheck $ \authData -> do
|
-- authenticate users = BasicAuthCheck $ \authData -> do
|
||||||
let
|
-- let
|
||||||
(uEmail, uPass) = (,) <$> (decodeUtf8 . basicAuthUsername) <*> (decodeUtf8 . basicAuthPassword) $ authData
|
-- (uEmail, uPass) = (,) <$> (decodeUtf8 . basicAuthUsername) <*> (decodeUtf8 . basicAuthPassword) $ authData
|
||||||
case (find (\u -> email u == uEmail) users) of
|
-- case (find (\u -> email u == uEmail) users) of
|
||||||
Nothing -> return NoSuchUser
|
-- Nothing -> return NoSuchUser
|
||||||
Just u -> return $ if uPass == password u then Authorized u else BadPassword
|
-- Just u -> return $ if uPass == password u then Authorized u else BadPassword
|
||||||
|
|
||||||
-- frontend :: BasicAuthData -> ClientM (Map.Map Text Text)
|
-- frontend :: BasicAuthData -> ClientM (Map.Map Text Text)
|
||||||
-- frontend ba = client authAPI ba "[ID]" "42" "code" ""
|
-- frontend ba = client authAPI ba "[ID]" "42" "code" ""
|
||||||
|
|
||||||
runMockServer :: Int -> [User] -> IO ()
|
runMockServer :: forall user userData . UserData user userData => Int -> IO ()
|
||||||
runMockServer port testUsers = do
|
runMockServer port = do
|
||||||
state <- mkState @User @(Map.Map Text Text)
|
state <- mkState @user @userData
|
||||||
run port $ insecureOAuthMock' testUsers state
|
run port $ insecureOAuthMock' @user @userData state
|
||||||
|
|
||||||
-- runMockServer' :: Int -> IO ()
|
-- runMockServer' :: Int -> IO ()
|
||||||
-- runMockServer' port = do
|
-- runMockServer' port = do
|
||||||
|
|||||||
@ -24,7 +24,7 @@ class (PersistEntity u, Show u, ToJSON a, Monoid a) => UserData u a where -- TOD
|
|||||||
readScope :: String -> Scope u
|
readScope :: String -> Scope u
|
||||||
showScope :: Scope u -> String
|
showScope :: Scope u -> String
|
||||||
userScope :: Entity u -> Scope u -> a
|
userScope :: Entity u -> Scope u -> a
|
||||||
lookupUser :: UserName -> Password -> IO (Maybe u)
|
lookupUser :: UserName -> Password -> IO (Maybe (Entity u))
|
||||||
|
|
||||||
|
|
||||||
-- data User = User
|
-- data User = User
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user