generate encrypted tokens
This commit is contained in:
parent
23a5a93509
commit
751f3a463a
@ -35,6 +35,7 @@ library
|
|||||||
, bytestring
|
, bytestring
|
||||||
, containers
|
, containers
|
||||||
, http-client
|
, http-client
|
||||||
|
, jose-jwt
|
||||||
, servant
|
, servant
|
||||||
, servant-client
|
, servant-client
|
||||||
, servant-server
|
, servant-server
|
||||||
@ -60,6 +61,7 @@ executable oauth2-mock-server-exe
|
|||||||
, bytestring
|
, bytestring
|
||||||
, containers
|
, containers
|
||||||
, http-client
|
, http-client
|
||||||
|
, jose-jwt
|
||||||
, oauth2-mock-server
|
, oauth2-mock-server
|
||||||
, servant
|
, servant
|
||||||
, servant-client
|
, servant-client
|
||||||
@ -87,6 +89,7 @@ test-suite oauth2-mock-server-test
|
|||||||
, bytestring
|
, bytestring
|
||||||
, containers
|
, containers
|
||||||
, http-client
|
, http-client
|
||||||
|
, jose-jwt
|
||||||
, oauth2-mock-server
|
, oauth2-mock-server
|
||||||
, servant
|
, servant
|
||||||
, servant-client
|
, servant-client
|
||||||
|
|||||||
@ -33,6 +33,7 @@ dependencies:
|
|||||||
- time
|
- time
|
||||||
- transformers
|
- transformers
|
||||||
- bytestring
|
- bytestring
|
||||||
|
- jose-jwt
|
||||||
|
|
||||||
ghc-options:
|
ghc-options:
|
||||||
- -Wall
|
- -Wall
|
||||||
|
|||||||
@ -18,10 +18,16 @@ import Control.Concurrent.STM.TVar
|
|||||||
import Control.Monad (void, (>=>))
|
import Control.Monad (void, (>=>))
|
||||||
import Control.Monad.STM
|
import Control.Monad.STM
|
||||||
|
|
||||||
|
import Jose.Jwk (Jwk(..))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
newtype State = State { activeCodes :: Map String (String, UTCTime) } deriving Show -- ^ maps auth codes to (client ID, expiration time)
|
|
||||||
|
data State = State
|
||||||
|
{ activeCodes :: Map String (String, UTCTime) -- ^ maps auth codes to (client ID, expiration time)
|
||||||
|
, publicKey :: Jwk
|
||||||
|
, privateKey :: Jwk
|
||||||
|
} deriving Show
|
||||||
|
|
||||||
type AuthState = TVar State
|
type AuthState = TVar State
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{-# LANGUAGE DataKinds, TypeOperators, OverloadedStrings, ScopedTypeVariables, TypeApplications #-}
|
{-# LANGUAGE DataKinds, TypeOperators, OverloadedStrings, ScopedTypeVariables, TypeApplications, RecordWildCards #-}
|
||||||
|
|
||||||
module Server
|
module Server
|
||||||
( insecureOAuthMock'
|
( insecureOAuthMock'
|
||||||
@ -11,23 +11,30 @@ import User
|
|||||||
|
|
||||||
import Control.Applicative ((<|>))
|
import Control.Applicative ((<|>))
|
||||||
import Control.Concurrent
|
import Control.Concurrent
|
||||||
import Control.Concurrent.STM.TVar (newTVarIO)
|
import Control.Concurrent.STM (atomically)
|
||||||
|
import Control.Concurrent.STM.TVar (newTVarIO, readTVar)
|
||||||
import Control.Exception (bracket)
|
import Control.Exception (bracket)
|
||||||
import Control.Monad (unless)
|
import Control.Monad (unless)
|
||||||
import Control.Monad.IO.Class
|
import Control.Monad.IO.Class
|
||||||
import Control.Monad.Trans.Reader
|
import Control.Monad.Trans.Reader
|
||||||
|
|
||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
import Data.ByteString (ByteString (..))
|
import Data.ByteString (ByteString (..), toStrict)
|
||||||
import Data.List (find, elemIndex)
|
import Data.List (find, elemIndex)
|
||||||
import Data.Maybe (fromMaybe, isJust)
|
import Data.Maybe (fromMaybe, isJust)
|
||||||
import Data.String (IsString (..))
|
import Data.String (IsString (..))
|
||||||
import Data.Text hiding (elem, find, head, length, map, null, splitAt, tail, words)
|
import Data.Text hiding (elem, find, head, length, map, null, splitAt, tail, words)
|
||||||
import Data.Text.Encoding (decodeUtf8)
|
import Data.Text.Encoding (decodeUtf8)
|
||||||
import Data.Time.Clock (NominalDiffTime (..), nominalDay)
|
import Data.Time.Clock (NominalDiffTime (..), nominalDay, UTCTime(..), getCurrentTime, addUTCTime)
|
||||||
|
|
||||||
|
import qualified Data.ByteString.Char8 as BS
|
||||||
import qualified Data.Map.Strict as Map
|
import qualified Data.Map.Strict as Map
|
||||||
|
|
||||||
|
import Jose.Jwa
|
||||||
|
import Jose.Jwe
|
||||||
|
import Jose.Jwk (generateRsaKeyPair, generateSymmetricKey, KeyUse(Enc), KeyId)
|
||||||
|
import Jose.Jwt hiding (decode, encode)
|
||||||
|
|
||||||
import Network.HTTP.Client (newManager, defaultManagerSettings)
|
import Network.HTTP.Client (newManager, defaultManagerSettings)
|
||||||
import Network.Wai.Handler.Warp
|
import Network.Wai.Handler.Warp
|
||||||
|
|
||||||
@ -148,17 +155,23 @@ frontend ba = client authAPI ba "[ID]" "42" "code" ""
|
|||||||
|
|
||||||
runMockServer :: Int -> IO ()
|
runMockServer :: Int -> IO ()
|
||||||
runMockServer port = do
|
runMockServer port = do
|
||||||
state <- newTVarIO $ State { activeCodes = Map.empty }
|
state <- mkState
|
||||||
run port $ insecureOAuthMock' testUsers state
|
run port $ insecureOAuthMock' testUsers state
|
||||||
|
|
||||||
runMockServer' :: Int -> IO ()
|
runMockServer' :: Int -> IO ()
|
||||||
runMockServer' port = do
|
runMockServer' port = do
|
||||||
mgr <- newManager defaultManagerSettings
|
mgr <- newManager defaultManagerSettings
|
||||||
state <- newTVarIO $ State { activeCodes = Map.empty }
|
state <- mkState
|
||||||
bracket (forkIO . run port $ insecureOAuthMock' testUsers state) killThread $ \_ ->
|
bracket (forkIO . run port $ insecureOAuthMock' testUsers state) killThread $ \_ ->
|
||||||
runClientM (frontend $ BasicAuthData "foo@bar.com" "0000") (mkClientEnv mgr (BaseUrl Http "localhost" port ""))
|
runClientM (frontend $ BasicAuthData "foo@bar.com" "0000") (mkClientEnv mgr (BaseUrl Http "localhost" port ""))
|
||||||
>>= print
|
>>= print
|
||||||
|
|
||||||
|
mkState :: IO AuthState
|
||||||
|
mkState = do
|
||||||
|
(publicKey, privateKey) <- generateRsaKeyPair 256 (KeyId "Oauth2MockKey") Enc Nothing
|
||||||
|
let activeCodes = Map.empty
|
||||||
|
newTVarIO State{..}
|
||||||
|
|
||||||
|
|
||||||
------
|
------
|
||||||
------ Token
|
------ Token
|
||||||
@ -200,26 +213,51 @@ instance FromJSON GrantType where
|
|||||||
| otherwise = error $ show s ++ " grant type not supported yet"
|
| otherwise = error $ show s ++ " grant type not supported yet"
|
||||||
|
|
||||||
data JWT = JWT
|
data JWT = JWT
|
||||||
{ token :: Text -- TODO should be JWT
|
{ issuer :: Text
|
||||||
, tokenType :: Text -- TODO enum
|
, expiration :: UTCTime
|
||||||
, expiration :: NominalDiffTime
|
} deriving (Show, Eq)
|
||||||
}
|
|
||||||
|
instance ToJSON JWT where
|
||||||
|
toJSON (JWT i e) = object ["iss" .= i, "exp" .= e]
|
||||||
|
|
||||||
|
data JWTWrapper = JWTW
|
||||||
|
{ token :: String
|
||||||
|
, expiresIn :: NominalDiffTime
|
||||||
|
} deriving (Show)
|
||||||
|
|
||||||
|
instance ToJSON JWTWrapper where
|
||||||
|
toJSON (JWTW t e) = object ["access_token" .= t, "token_type" .= ("JWT" :: Text), "expires_in" .= e]
|
||||||
|
|
||||||
type Token = "token"
|
type Token = "token"
|
||||||
:> ReqBody '[JSON] ClientData
|
:> ReqBody '[JSON] ClientData
|
||||||
:> Post '[JSON] JWT
|
:> Post '[JSON] JWTWrapper
|
||||||
|
|
||||||
tokenEndpoint :: AuthServer Token
|
tokenEndpoint :: AuthServer Token
|
||||||
tokenEndpoint = provideToken
|
tokenEndpoint = provideToken
|
||||||
where
|
where
|
||||||
provideToken :: ClientData -> AuthHandler JWT
|
provideToken :: ClientData -> AuthHandler JWTWrapper
|
||||||
provideToken client = case (grantType client) of
|
provideToken client = case (grantType client) of
|
||||||
AuthCode -> do
|
AuthCode -> do
|
||||||
--TODO validate everything
|
|
||||||
unless (Client (pack $ clientID client) (pack $ clientSecret client) `elem` trustedClients) .
|
unless (Client (pack $ clientID client) (pack $ clientSecret client) `elem` trustedClients) .
|
||||||
throwError $ err500 { errBody = "Invalid client" }
|
throwError $ err500 { errBody = "Invalid client" }
|
||||||
valid <- asks (verify (grant client) (clientID client)) >>= liftIO
|
valid <- asks (verify (grant client) (clientID client)) >>= liftIO
|
||||||
unless valid . throwError $ err500 { errBody = "Invalid authorisation code" }
|
unless valid . throwError $ err500 { errBody = "Invalid authorisation code" }
|
||||||
return JWT {token = "", tokenType = "JWT", expiration = 0.25 * nominalDay}
|
-- return JWT {token = "", tokenType = "JWT", expiration = 0.25 * nominalDay}
|
||||||
|
token <- asks mkToken >>= liftIO
|
||||||
|
return token
|
||||||
x -> error $ show x ++ " not supported yet"
|
x -> error $ show x ++ " not supported yet"
|
||||||
|
|
||||||
|
|
||||||
|
mkToken :: AuthState -> IO JWTWrapper
|
||||||
|
mkToken state = do
|
||||||
|
privateKey <- atomically $ readTVar state >>= return . privateKey
|
||||||
|
now <- getCurrentTime
|
||||||
|
let
|
||||||
|
lifetime = nominalDay / 4 -- TODO make configurable
|
||||||
|
jwt = JWT "Oauth2MockServer" (lifetime `addUTCTime` now)
|
||||||
|
encoded <- jwkEncode RSA_OAEP_256 A128GCM privateKey (Nested . Jwt . toStrict $ encode jwt)
|
||||||
|
case encoded of
|
||||||
|
Right (Jwt token) -> return $ JWTW (BS.unpack token) lifetime
|
||||||
|
Left e -> error $ show e
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user