feat(auth): tokens can be stored & refreshed
This commit is contained in:
parent
5a023a9e32
commit
c8fa509ace
@ -62,7 +62,7 @@ import Jobs
|
|||||||
import qualified Data.Text.Encoding as Text
|
import qualified Data.Text.Encoding as Text
|
||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
|
|
||||||
import Yesod.Auth.OAuth2.AzureADv2 (oauth2AzureADv2)
|
import Yesod.Auth.OAuth2.AzureADv2 (oauth2AzureADv2Scoped)
|
||||||
import Yesod.Auth.Util.PasswordStore
|
import Yesod.Auth.Util.PasswordStore
|
||||||
|
|
||||||
import qualified Data.ByteString.Lazy as LBS
|
import qualified Data.ByteString.Lazy as LBS
|
||||||
@ -338,7 +338,7 @@ makeFoundation appSettings''@AppSettings{..} = do
|
|||||||
|
|
||||||
appAuthPlugins <- liftIO $ sequence [
|
appAuthPlugins <- liftIO $ sequence [
|
||||||
(oauth2MockServer . fromJust) <$> lookupEnv "OAUTH2_SERVER_PORT"
|
(oauth2MockServer . fromJust) <$> lookupEnv "OAUTH2_SERVER_PORT"
|
||||||
, loadPlugin (oauth2AzureADv2 tenantID) "AZURE_ADV2"
|
, loadPlugin (oauth2AzureADv2Scoped ["openid", "profile", "offline_access"] tenantID) "AZURE_ADV2"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -9,9 +9,10 @@ module Auth.OAuth2
|
|||||||
, azurePluginName
|
, azurePluginName
|
||||||
, oauth2MockServer
|
, oauth2MockServer
|
||||||
, mockPluginName
|
, mockPluginName
|
||||||
, queryOauth2User
|
, queryOAuth2User
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
import Data.Maybe (fromJust)
|
||||||
import Data.Text
|
import Data.Text
|
||||||
|
|
||||||
import Import.NoFoundation hiding (unpack)
|
import Import.NoFoundation hiding (unpack)
|
||||||
@ -21,7 +22,7 @@ import Network.HTTP.Simple (httpJSONEither, getResponseBody, JSONException)
|
|||||||
import System.Environment (lookupEnv)
|
import System.Environment (lookupEnv)
|
||||||
|
|
||||||
import Yesod.Auth.OAuth2
|
import Yesod.Auth.OAuth2
|
||||||
import Yesod.Auth.OAuth2.Prelude
|
import Yesod.Auth.OAuth2.Prelude hiding (encodeUtf8)
|
||||||
|
|
||||||
|
|
||||||
data AzureUserException = AzureUserError
|
data AzureUserException = AzureUserError
|
||||||
@ -73,21 +74,58 @@ data UserData = UD
|
|||||||
instance FromJSON UserData where
|
instance FromJSON UserData where
|
||||||
parseJSON _ = pure UD
|
parseJSON _ = pure UD
|
||||||
|
|
||||||
queryOauth2User :: forall m . (MonadIO m, MonadThrow m)
|
queryOAuth2User :: forall m . (MonadIO m, MonadThrow m, MonadHandler m, MonadFail m)
|
||||||
=> Text
|
=> Text
|
||||||
-> Text
|
-> Text
|
||||||
-> m (Either JSONException UserData)
|
-> m (Either JSONException UserData)
|
||||||
queryOauth2User authPlugin userID = do
|
queryOAuth2User authPlugin userID = do
|
||||||
baseUrl <- liftIO mkBaseUrl
|
(queryUrl, tokenUrl) <- liftIO $ mkBaseUrls authPlugin
|
||||||
req <- parseRequest $ "GET " ++ baseUrl ++ unpack userID
|
req <- parseRequest $ "GET " ++ queryUrl ++ unpack userID
|
||||||
-- TODO get new token & put token in auth header
|
mTokens <- lookupSessionJson SessionOAuth2Token
|
||||||
getResponseBody <$> httpJSONEither @m @UserData req
|
unless (isJust mTokens) . fail $ "Tried to load sesion Oauth2 tokens, but there are none"
|
||||||
where
|
eNewToken <- refreshOAuth2Token @m (fromJust mTokens) tokenUrl (authPlugin == azurePluginName)
|
||||||
mkBaseUrl :: IO String
|
case eNewToken of
|
||||||
mkBaseUrl
|
Left e -> return $ Left e
|
||||||
| authPlugin == azurePluginName = return "https://graph.microsoft.com/v1.0/users/"
|
Right newTokens -> do
|
||||||
| authPlugin == mockPluginName = do
|
setSessionJson SessionOAuth2Token newTokens
|
||||||
Just port <- lookupEnv "OAUTH2_SERVER_PORT"
|
getResponseBody <$> httpJSONEither @m @UserData (req
|
||||||
return $ "http://localhost:" ++ port ++ "/users/query?id="
|
{ secure = authPlugin == azurePluginName
|
||||||
| otherwise = fail $ unpack authPlugin
|
, requestHeaders = [("Authorization", encodeUtf8 . atoken . fromJust $ fst newTokens)] })
|
||||||
|
|
||||||
|
mkBaseUrls :: Text -> IO (String, String)
|
||||||
|
mkBaseUrls authPlugin
|
||||||
|
| authPlugin == azurePluginName = do
|
||||||
|
Just tenantID <- lookupEnv "AZURE_TENANT_ID"
|
||||||
|
return ( "https://graph.microsoft.com/v1.0/users/"
|
||||||
|
, "https://login.microsoftonline.com/" ++ tenantID ++ "/oauth2/v2.0" )
|
||||||
|
| authPlugin == mockPluginName = do
|
||||||
|
Just port <- lookupEnv "OAUTH2_SERVER_PORT"
|
||||||
|
let base = "http://localhost:" ++ port
|
||||||
|
return ( base ++ "/users/query?id="
|
||||||
|
, base ++ "/token" )
|
||||||
|
| otherwise = fail $ "Unsupported auth plugin: " ++ unpack authPlugin
|
||||||
|
|
||||||
|
|
||||||
|
refreshOAuth2Token :: forall m x. (MonadIO m, MonadThrow m, MonadHandler m, MonadFail m, x ~ (Maybe AccessToken, Maybe RefreshToken))
|
||||||
|
=> x
|
||||||
|
-> String
|
||||||
|
-> Bool
|
||||||
|
-> m (Either JSONException x)
|
||||||
|
refreshOAuth2Token (_, refreshToken) url secure
|
||||||
|
| isJust refreshToken = do
|
||||||
|
req <- parseRequest $ "POST " ++ url
|
||||||
|
let
|
||||||
|
body =
|
||||||
|
[ ("grant_type", "refresh_token")
|
||||||
|
, ("refresh_token", encodeUtf8 . rtoken $ fromJust refreshToken)
|
||||||
|
, ("scope", "") -- TODO must be subset of previously requested scopes. space separated list
|
||||||
|
]
|
||||||
|
body' <- if secure then do
|
||||||
|
Just clientID <- liftIO $ lookupEnv "CLIENT_ID"
|
||||||
|
Just clientSecret <- liftIO $ lookupEnv "CLIENT_SECRET"
|
||||||
|
return $ body ++ [("client_id", fromString clientID), ("client_secret", fromString clientSecret)]
|
||||||
|
else return body
|
||||||
|
getResponseBody <$> httpJSONEither @m @x (urlEncodedBody body' req{ secure = secure })
|
||||||
|
| otherwise = fail "Could not refresh access token. Refresh token is missing."
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
|
-- SPDX-FileCopyrightText: 2022-2024 Gregor Kleen <gregor.kleen@ifi.lmu.de>, David Mosbach <david.mosbach@uniworx.de>
|
||||||
--
|
--
|
||||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -20,6 +20,7 @@ data SessionKey = SessionActiveAuthTags | SessionInactiveAuthTags
|
|||||||
| SessionLang
|
| SessionLang
|
||||||
| SessionError
|
| SessionError
|
||||||
| SessionFiles
|
| SessionFiles
|
||||||
|
| SessionOAuth2Token
|
||||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
||||||
deriving anyclass (Universe, Finite)
|
deriving anyclass (Universe, Finite)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user