show logout success before redirecting

This commit is contained in:
David Mosbach 2024-03-10 19:46:12 +00:00
parent 83d99e5530
commit 2a2813fef2
4 changed files with 76 additions and 16 deletions

View File

@ -38,6 +38,7 @@ library
, bytestring , bytestring
, containers , containers
, cookie , cookie
, ghc
, http-api-data , http-api-data
, http-client , http-client
, http-media , http-media
@ -74,6 +75,7 @@ executable oauth2-mock-server-exe
, conduit , conduit
, containers , containers
, cookie , cookie
, ghc
, http-api-data , http-api-data
, http-client , http-client
, http-media , http-media
@ -115,6 +117,7 @@ test-suite oauth2-mock-server-test
, bytestring , bytestring
, containers , containers
, cookie , cookie
, ghc
, http-api-data , http-api-data
, http-client , http-client
, http-media , http-media

View File

@ -25,6 +25,7 @@ description: Please see the README # on GitHub at <https://github.com/gi
dependencies: dependencies:
- base >= 4.7 && < 5 - base >= 4.7 && < 5
- ghc
- servant - servant
- servant-server - servant-server
- servant-client - servant-client

View File

@ -14,11 +14,13 @@ module LoginForm
import Prelude hiding (head) import Prelude hiding (head)
import qualified Data.Map as M
import Data.Aeson (encode) import Data.Aeson (encode)
import qualified Data.String.Interpolate as I
import Data.String (IsString(..)) import Data.String (IsString(..))
import Data.Text (Text) import Data.Text (Text, unpack)
import qualified Data.Map as M
import qualified Data.String.Interpolate as I
import GHC.Data.Maybe (whenIsJust)
import Network.HTTP.Media ((//), (/:)) import Network.HTTP.Media ((//), (/:))
@ -74,8 +76,8 @@ loginPage uri headers = docTypeHtml $ head' >> body'
|] |]
logoutPage :: Html logoutPage :: Maybe Text -> Html
logoutPage = docTypeHtml $ head' >> body' logoutPage mUri = docTypeHtml $ head' >> body'
where where
head' = head $ do head' = head $ do
meta ! A.charset "UTF-8" meta ! A.charset "UTF-8"
@ -83,4 +85,10 @@ logoutPage = docTypeHtml $ head' >> body'
title "UniWorX Oauth2 Mock Server" title "UniWorX Oauth2 Mock Server"
body' = body $ do body' = body $ do
h1 "UniWorX Oauth2 Mock Server" h1 "UniWorX Oauth2 Mock Server"
p "Logout successful." p "Logout successful."
whenIsJust mUri $ \uri -> do
a ! A.href (fromString $ unpack uri) $ "Continue"
script $
[I.i|
setTimeout(_ => window.location.replace('#{uri}'), 2000); |]

View File

@ -13,6 +13,7 @@
, AllowAmbiguousTypes , AllowAmbiguousTypes
, LambdaCase , LambdaCase
, FlexibleContexts , FlexibleContexts
, KindSignatures
#-} #-}
module Server module Server
@ -35,19 +36,19 @@ import Control.Concurrent
import Control.Concurrent.STM (atomically) import Control.Concurrent.STM (atomically)
import Control.Concurrent.STM.TVar (newTVarIO, readTVar, modifyTVar) import Control.Concurrent.STM.TVar (newTVarIO, readTVar, modifyTVar)
import Control.Exception (bracket) import Control.Exception (bracket)
import Control.Monad (unless, (>=>), foldM) import Control.Monad (unless, (>=>), foldM, void)
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Control.Monad.Trans.Error (Error(..)) 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 (fromStrict, ByteString) import Data.ByteString (fromStrict, toStrict, ByteString)
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 (..))
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 qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8) import Data.Text.Encoding (encodeUtf8, decodeUtf8)
import Data.Text.Encoding.Base64 import Data.Text.Encoding.Base64
import Data.Time.Clock (NominalDiffTime (..), nominalDay, UTCTime(..), getCurrentTime, addUTCTime) import Data.Time.Clock (NominalDiffTime (..), nominalDay, UTCTime(..), getCurrentTime, addUTCTime)
@ -58,20 +59,22 @@ import GHC.Read (readPrec, lexP)
import Jose.Jwk (generateRsaKeyPair, KeyUse(..), KeyId) import Jose.Jwk (generateRsaKeyPair, KeyUse(..), KeyId)
import Jose.Jwt hiding (decode, encode) import Jose.Jwt hiding (decode, encode)
import Network.HTTP.Client (newManager, defaultManagerSettings) import Network.HTTP.Client (newManager, defaultManagerSettings, httpLbs, parseRequest)
import Network.Wai.Handler.Warp import Network.Wai.Handler.Warp
import Servant import Servant
import Servant.Client hiding (client) import Servant.Client hiding (client)
import Servant.API import Servant.API
import System.Environment (getEnv)
import Text.ParserCombinators.ReadPrec (look, pfail) import Text.ParserCombinators.ReadPrec (look, pfail)
import Text.Read (readMaybe) import Text.Read (readMaybe)
import qualified Text.Read.Lex as Lex import qualified Text.Read.Lex as Lex
import Web.Cookie (parseCookiesText) import Web.Cookie (parseCookiesText)
import Web.Internal.FormUrlEncoded (FromForm(..), parseUnique, parseMaybe, Form(..)) import Web.Internal.FormUrlEncoded (FromForm(..), parseUnique, parseMaybe, Form(..), urlEncodeParams)
@ -364,23 +367,66 @@ userListEndpoint = handleUserData
---- Logout ---- ---- Logout ----
-------------- --------------
{-type Redirect (cs :: [*]) (hs :: [*]) a = Verb 'GET 303 cs (Headers (Header "Location" Text : hs) a)
type CookieLogout = "clogout"
:> QParam "redirect" QRedirect
:> Redirect '[HTML] '[] Text
clogoutEndpoint :: forall user userData . UserData user userData => AuthServer user CookieLogout
clogoutEndpoint uri = do
return $ addHeader uri ""-}
type Logout = "logout" type Logout = "logout"
:> QueryParam "post_logout_redirect_uri" QRedirect :> QueryParam "post_logout_redirect_uri" QRedirect
:> HeaderR "Cookie" QCookie :> HeaderR "Cookie" QCookie
:> Get '[HTML] (Headers '[Header "Set-Cookie" Text] Html) :> Get '[HTML] (Headers '[Header "Set-Cookie" Text] Html)
{- :> Redirect '[HTML] '[Header "Set-Cookie" Text] NoContent
:<|> "logout"
:> HeaderR "Cookie" QCookie
:> Get '[HTML] (Headers '[Header "Set-Cookie" Text] Html) -}
logoutEndpoint :: forall user userData . UserData user userData => AuthServer user Logout logoutEndpoint :: forall user userData . UserData user userData => AuthServer user Logout
logoutEndpoint = logout logoutEndpoint = logout -- rLogout :<|> logout
where where
logout :: Maybe QRedirect logout :: Maybe QRedirect
-> QCookie -> QCookie
-> AuthHandler user (Headers '[Header "Set-Cookie" Text] Html) -> AuthHandler user (Headers '[Header "Set-Cookie" Text] Html)
logout mUri cookie = do logout mRedir cookie = do
let mCreds = lookup authCookie . parseCookiesText $ encodeUtf8 cookie let mCreds = lookup authCookie . parseCookiesText $ encodeUtf8 cookie
unless (isJust mCreds) $ throwError err401 { errBody = "Missing auth cookie" } unless (isJust mCreds) $ throwError err401 { errBody = "Missing auth cookie" }
addHeader (authCookie <> "=\"\"") <$> case mUri of liftIO . putStrLn $ "\nLOGOUT\n "
Just uri -> throwError err303 { errHeaders = [("Location", encodeUtf8 uri)]} return . addHeader (authCookie <> "=\"\"") $ logoutPage mRedir
Nothing -> return logoutPage
{- checkCookie :: QCookie -> AuthHandler user ()
checkCookie cookie = do
let mCreds = lookup authCookie . parseCookiesText $ encodeUtf8 cookie
unless (isJust mCreds) $ throwError err401 { errBody = "Missing auth cookie" }
return ()
rLogout :: QRedirect
-> QCookie
-> AuthHandler user (Headers '[ Header "Location" Text
, Header "Set-Cookie" Text
] NoContent)
rLogout uri cookie = do
liftIO . putStrLn $ "\nLOGOUT with uri " <> show uri <> "\n"
checkCookie cookie
let param = decodeUtf8 . toStrict $ urlEncodeParams [("redirect", uri)]
uri' = "../clogout?" <> param
addHeader uri' . addHeader (authCookie <> "=\"\"") <$> return NoContent
logout :: QCookie
-> AuthHandler user (Headers '[Header "Set-Cookie" Text] Html)
logout cookie = do
liftIO . putStrLn $ "\nLOGOUT\n "
checkCookie cookie
return . addHeader (authCookie <> "=\"\"")$ logoutPage Nothing
-- liftIO $ do
-- port <- getEnv "OAUTH2_SERVER_PORT"
-- manager <- newManager defaultManagerSettings
-- req <- parseRequest $ "GET http://localhost:" ++ port ++ "/clogout" -- TODO get root
-- void $ httpLbs req manager -}
------------------- -------------------
@ -392,6 +438,7 @@ type Routing user userData = Auth
:<|> Token :<|> Token
:<|> Me userData :<|> Me userData
:<|> UserList userData :<|> UserList userData
-- :<|> CookieLogout
:<|> Logout :<|> Logout
routing :: forall user userData . UserData user userData => AuthServer user (Routing user userData) routing :: forall user userData . UserData user userData => AuthServer user (Routing user userData)
@ -400,6 +447,7 @@ routing = loginServer @user @userData
:<|> tokenEndpoint @user @userData :<|> tokenEndpoint @user @userData
:<|> userEndpoint @user @userData :<|> userEndpoint @user @userData
:<|> userListEndpoint @user @userData :<|> userListEndpoint @user @userData
-- :<|> clogoutEndpoint @user @userData
:<|> logoutEndpoint @user @userData :<|> logoutEndpoint @user @userData