WIP refresh tokens

This commit is contained in:
David Mosbach 2024-01-21 17:47:24 +01:00
parent 64d7e07f35
commit 3ad50a0ba1

View File

@ -17,10 +17,12 @@ import Control.Concurrent.STM.TVar (newTVarIO, readTVar, modifyTVar)
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.Error (Error(..))
import Control.Monad.Trans.Reader import Control.Monad.Trans.Reader
import Data.Aeson import Data.Aeson
import Data.ByteString (ByteString (..), fromStrict, toStrict) import Data.ByteString (ByteString (..), fromStrict, toStrict)
import Data.Either (fromRight)
import Data.List (find, elemIndex) import Data.List (find, elemIndex)
import Data.Maybe (fromMaybe, fromJust, isJust, isNothing) import Data.Maybe (fromMaybe, fromJust, isJust, isNothing)
import Data.String (IsString (..)) import Data.String (IsString (..))
@ -183,9 +185,11 @@ codeServer = handleCreds
---- Token Endpoint ---- ---- Token Endpoint ----
---------------------- ----------------------
newtype ACode = ACode String deriving (Show)
newtype RToken = RToken String deriving (Show)
data ClientData = ClientData --TODO support other flows data ClientData = ClientData --TODO support other flows
{ authCode :: String { authID :: Either ACode RToken
, clientID :: Maybe String , clientID :: Maybe String
, clientSecret :: Maybe String , clientSecret :: Maybe String
, redirect :: Maybe String , redirect :: Maybe String
@ -198,24 +202,33 @@ instance FromHttpApiData AuthFlow where
instance FromForm ClientData where instance FromForm ClientData where
fromForm f = ClientData fromForm f = ClientData
<$> ((parseUnique @AuthFlow "grant_type" f) *> parseUnique "code" f) <$> ((parseUnique @AuthFlow "grant_type" f) *> ((Left . ACode <$> parseUnique "code" f) <|> (Right . RToken <$> parseUnique "refresh_token" f)))
<*> parseMaybe "client_id" f <*> parseMaybe "client_id" f
<*> parseMaybe "client_secret" f <*> parseMaybe "client_secret" f
<*> parseMaybe "redirect_uri" f <*> parseMaybe "redirect_uri" f
instance Error Text where
strMsg = pack
data JWTWrapper = JWTW data JWTWrapper = JWTW
{ token :: String { acessToken :: String
, expiresIn :: NominalDiffTime , expiresIn :: NominalDiffTime
, refreshToken :: Maybe String
} deriving (Show) } deriving (Show)
instance ToJSON JWTWrapper where instance ToJSON JWTWrapper where
toJSON (JWTW t e) = object ["access_token" .= t, "token_type" .= ("JWT" :: Text), "expires_in" .= e] toJSON (JWTW a e r) = object
[ "access_token" .= a
, "token_type" .= ("JWT" :: Text)
, "expires_in" .= fromEnum e
, "refresh_token" .= r ]
instance FromJSON JWTWrapper where instance FromJSON JWTWrapper where
parseJSON (Object o) = JWTW parseJSON (Object o) = JWTW
<$> o .: "access_token" <$> o .: "access_token"
<*> o .: "expires_in" <*> o .: "expires_in"
<*> o .:? "refresh_token"
instance FromHttpApiData JWTWrapper where instance FromHttpApiData JWTWrapper where
parseHeader bs = case decode (fromStrict bs) of parseHeader bs = case decode (fromStrict bs) of
@ -235,13 +248,16 @@ tokenEndpoint = provideToken
unless (isNothing (clientID client >> clientSecret client) unless (isNothing (clientID client >> clientSecret client)
|| Client (pack . fromJust $ clientID client) (pack . fromJust $ clientSecret client) `elem` trustedClients) . || Client (pack . fromJust $ clientID client) (pack . fromJust $ clientSecret client) `elem` trustedClients) .
throwError $ err500 { errBody = "Invalid client" } throwError $ err500 { errBody = "Invalid client" }
mUser <- asks (verify (pack $ authCode client) (clientID client)) >>= liftIO -- TODO verify redirect url here case authID client of
unless (isJust mUser) . throwError $ err500 { errBody = "Invalid authorisation code" } Left (ACode authCode) -> do
-- return JWT {token = "", tokenType = "JWT", expiration = 0.25 * nominalDay} mUser <- asks (verify (pack authCode) (clientID client)) >>= liftIO -- TODO verify redirect url here
let (user, scopes) = fromJust mUser unless (isJust mUser) . throwError $ err500 { errBody = "Invalid authorisation code" }
token <- asks (mkToken @user @userData user scopes) >>= liftIO -- return JWT {token = "", tokenType = "JWT", expiration = 0.25 * nominalDay}
liftIO . putStrLn $ "token: " ++ show token let (user, scopes) = fromJust mUser
return token token <- asks (mkToken @user @userData user scopes) >>= liftIO
liftIO . putStrLn $ "token: " ++ show token
return token
Right (RToken rToken) -> undefined
mkToken :: forall user userData . UserData user userData mkToken :: forall user userData . UserData user userData
@ -251,13 +267,18 @@ mkToken u scopes state = do
now <- getCurrentTime now <- getCurrentTime
uuid <- nextRandom uuid <- nextRandom
let let
lifetime = nominalDay / 24 -- TODO make configurable lifetimeAT = 120 :: NominalDiffTime -- TODO make configurable
jwt = JWT "Oauth2MockServer" (lifetime `addUTCTime` now) uuid lifetimeRT = nominalDay -- TODO make configurable
encoded <- jwkEncode RSA_OAEP_256 A128GCM pubKey (Nested . Jwt . toStrict $ encode jwt) at = JWT "Oauth2MockServer" (lifetimeAT `addUTCTime` now) uuid
case encoded of rt = JWT "Oauth2MockServer" (lifetimeRT `addUTCTime` now) uuid
Right (Jwt token) -> do encodedAT <- jwkEncode RSA_OAEP_256 A128GCM pubKey (Nested . Jwt . toStrict $ encode at)
encodedRT <- jwkEncode RSA_OAEP_256 A128GCM pubKey (Nested . Jwt . toStrict $ encode rt)
case encodedAT >> encodedRT of
Right _ -> do
let Jwt aToken = fromRight undefined encodedAT
Jwt rToken = fromRight undefined encodedRT
atomically . modifyTVar state $ \s -> s { activeTokens = Map.insert uuid (u, scopes) (activeTokens s) } atomically . modifyTVar state $ \s -> s { activeTokens = Map.insert uuid (u, scopes) (activeTokens s) }
return $ JWTW (BS.unpack token) lifetime return $ JWTW (BS.unpack aToken) lifetimeAT (Just $ BS.unpack rToken)
Left e -> error $ show e Left e -> error $ show e
@ -286,7 +307,7 @@ userEndpoint = handleUserData
handleUserData :: Text -> AuthHandler user (Maybe 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" }
token <- asks (decodeToken @user @userData (fromJust mToken)) >>= liftIO token <- asks (decodeToken @user @userData (fromJust mToken)) >>= liftIO
liftIO $ putStrLn "decoded token:" >> print token liftIO $ putStrLn "decoded token:" >> print token
case token of case token of