auth code & client secret verification
This commit is contained in:
parent
34a12ec226
commit
23a5a93509
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user