auth code & client secret verification

This commit is contained in:
David Mosbach 2024-01-09 02:23:40 +01:00
parent 34a12ec226
commit 23a5a93509
2 changed files with 60 additions and 19 deletions

View File

@ -4,6 +4,7 @@ module AuthCode
( State (..) ( State (..)
, AuthState , AuthState
, genUnencryptedCode , genUnencryptedCode
, verify
) where ) where
import Data.Map.Strict (Map) import Data.Map.Strict (Map)
@ -14,7 +15,7 @@ import qualified Data.Map.Strict as M
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, (>=>))
import Control.Monad.STM import Control.Monad.STM
@ -48,3 +49,14 @@ expire code time state = void . forkIO $ do
threadDelay $ fromEnum time threadDelay $ fromEnum time
atomically . modifyTVar state $ \s -> s{ activeCodes = M.delete code s.activeCodes } atomically . modifyTVar state $ \s -> s{ activeCodes = M.delete code s.activeCodes }
verify :: String -> String -> AuthState -> IO Bool
verify code clientID state = do
now <- getCurrentTime
mData <- atomically $ do
result <- (readTVar >=> return . M.lookup code . activeCodes) state
modifyTVar state $ \s -> s{ activeCodes = M.delete code s.activeCodes }
return result
return $ case mData of
Just (clientID', _) -> clientID == clientID'
_ -> False

View File

@ -9,6 +9,7 @@ module Server
import AuthCode import AuthCode
import User import User
import Control.Applicative ((<|>))
import Control.Concurrent import Control.Concurrent
import Control.Concurrent.STM.TVar (newTVarIO) import Control.Concurrent.STM.TVar (newTVarIO)
import Control.Exception (bracket) import Control.Exception (bracket)
@ -19,7 +20,7 @@ import Control.Monad.Trans.Reader
import Data.Aeson import Data.Aeson
import Data.ByteString (ByteString (..)) import Data.ByteString (ByteString (..))
import Data.List (find, elemIndex) import Data.List (find, elemIndex)
import Data.Maybe (fromMaybe) 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)
@ -44,8 +45,13 @@ testUsers =
, User {name = "Tina Tester", email = "t@t.tt", password = "1111", uID = "1"} , User {name = "Tina Tester", email = "t@t.tt", password = "1111", uID = "1"}
, User {name = "Max Muster", email = "m@m.mm", password = "2222", uID = "2"}] , User {name = "Max Muster", email = "m@m.mm", password = "2222", uID = "2"}]
trustedClients :: [Text] -- TODO move to db data AuthClient = Client
trustedClients = ["42"] { ident :: Text
, secret :: Text
} deriving (Show, Eq)
trustedClients :: [AuthClient] -- TODO move to db
trustedClients = [Client "42" "shhh"]
data ResponseType = Code -- ^ authorisation code grant data ResponseType = Code -- ^ authorisation code grant
| Token -- ^ implicit grant via access token | Token -- ^ implicit grant via access token
@ -91,7 +97,7 @@ authServer = handleAuth
-> QRedirect -> QRedirect
-> AuthHandler userData -> AuthHandler userData
handleAuth u scopes client responseType url = do handleAuth u scopes client responseType url = do
unless (pack client `elem` trustedClients) . -- TODO fetch trusted clients from db | TODO also check if the redirect url really belongs to the client unless (isJust $ find (\c -> ident c == pack client) trustedClients) . -- TODO fetch trusted clients from db | TODO also check if the redirect url really belongs to the client
throwError $ err404 { errBody = "Not a trusted client."} throwError $ err404 { errBody = "Not a trusted client."}
let let
scopes' = map (readScope @user @userData) $ words scopes scopes' = map (readScope @user @userData) $ words scopes
@ -160,20 +166,38 @@ runMockServer' port = do
data ClientData = ClientData data ClientData = ClientData
{ grantType :: Text { grantType :: GrantType
, grant :: Text , grant :: String
, clientID :: Text , userName :: Maybe String
, clientSecret :: Text , clientID :: String
, redirect :: Text , clientSecret :: String
, redirect :: String
} deriving Show } deriving Show
instance FromJSON ClientData where instance FromJSON ClientData where
parseJSON (Object o) = ClientData parseJSON (Object o) = ClientData
<$> o .: "grant_type" <$> o .: "grant_type"
<*> o .: "authorization_code" --TODO add alternatives <*> (o .: ps PassCreds --TODO add alternatives
<*> o .: "client_id" <|> o .: ps AuthCode
<*> o .: "client_secret" <|> o .: ps Implicit
<*> o .: "redirect_url" <|> o .: ps ClientCreds)
<*> o .:? "username"
<*> o .: "client_id"
<*> o .: "client_secret"
<*> o .: "redirect_url"
where ps = fromString @Key . show
data GrantType = PassCreds | AuthCode | Implicit | ClientCreds deriving (Eq)
instance Show GrantType where
show PassCreds = "password"
show AuthCode = "authorization_code"
show _ = undefined --TODO support other flows
instance FromJSON GrantType where
parseJSON (String s)
| s == pack (show AuthCode) = pure AuthCode
| otherwise = error $ show s ++ " grant type not supported yet"
data JWT = JWT data JWT = JWT
{ token :: Text -- TODO should be JWT { token :: Text -- TODO should be JWT
@ -189,8 +213,13 @@ tokenEndpoint :: AuthServer Token
tokenEndpoint = provideToken tokenEndpoint = provideToken
where where
provideToken :: ClientData -> AuthHandler JWT provideToken :: ClientData -> AuthHandler JWT
provideToken client = do provideToken client = case (grantType client) of
--TODO validate everything AuthCode -> do
return JWT {token = "", tokenType = "JWT", expiration = 0.25 * nominalDay} --TODO validate everything
unless (Client (pack $ clientID client) (pack $ clientSecret client) `elem` trustedClients) .
throwError $ err500 { errBody = "Invalid client" }
valid <- asks (verify (grant client) (clientID client)) >>= liftIO
unless valid . throwError $ err500 { errBody = "Invalid authorisation code" }
return JWT {token = "", tokenType = "JWT", expiration = 0.25 * nominalDay}
x -> error $ show x ++ " not supported yet"