mirror of
https://github.com/freckle/yesod-auth-oauth2.git
synced 2026-02-02 14:20:26 +01:00
163 lines
4.6 KiB
Haskell
163 lines
4.6 KiB
Haskell
{-# LANGUAGE DeriveDataTypeable #-}
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE TupleSections #-}
|
|
-- |
|
|
--
|
|
-- Modules and support functions required by most or all provider
|
|
-- implementations. May also be useful for writing local providers.
|
|
--
|
|
module Yesod.Auth.OAuth2.Prelude
|
|
( YesodOAuth2Exception(..)
|
|
, invalidProfileResponse
|
|
|
|
-- * Helpers
|
|
, authGetProfile
|
|
, setExtra
|
|
, scopeParam
|
|
|
|
-- * Text
|
|
, Text
|
|
, decodeUtf8
|
|
, encodeUtf8
|
|
|
|
-- * JSON
|
|
, (.:)
|
|
, (.:?)
|
|
, (.=)
|
|
, (<>)
|
|
, FromJSON(..)
|
|
, ToJSON(..)
|
|
, eitherDecode
|
|
, withObject
|
|
|
|
-- * Exceptions
|
|
, throwIO
|
|
, tryIO
|
|
|
|
-- * OAuth2
|
|
, OAuth2(..)
|
|
, OAuth2Token(..)
|
|
, AccessToken(..)
|
|
, RefreshToken(..)
|
|
|
|
-- * HTTP
|
|
, Manager
|
|
|
|
-- * Yesod
|
|
, YesodAuth(..)
|
|
, AuthPlugin(..)
|
|
, Creds(..)
|
|
|
|
-- * Bytestring URI types
|
|
, URI
|
|
, Host(..)
|
|
|
|
-- * Bytestring URI extensions
|
|
, module URI.ByteString.Extension
|
|
|
|
-- * Temporary, until I finish re-structuring modules
|
|
, authOAuth2
|
|
, authOAuth2Widget
|
|
|
|
-- * Deprecated, until everything's moved over to @'authGetProfile'@
|
|
, authGetJSON
|
|
, fromProfileURL
|
|
, maybeExtra
|
|
) where
|
|
|
|
import Control.Exception.Safe
|
|
import Data.Aeson
|
|
import Data.ByteString (ByteString)
|
|
import qualified Data.ByteString.Lazy as BL
|
|
import qualified Data.ByteString.Lazy.Char8 as BL8
|
|
import Data.Semigroup ((<>))
|
|
import Data.Text (Text)
|
|
import qualified Data.Text as T
|
|
import Data.Text.Encoding
|
|
import Network.HTTP.Conduit
|
|
import Network.OAuth.OAuth2
|
|
import URI.ByteString
|
|
import URI.ByteString.Extension
|
|
import Yesod.Auth
|
|
import Yesod.Auth.OAuth2
|
|
|
|
-- | Provider name and error
|
|
--
|
|
-- The error is a lazy bytestring because it's most often encoded JSON.
|
|
--
|
|
-- Deprecated. Eventually, we'll return @Either@s all the way up.
|
|
--
|
|
data YesodOAuth2Exception = InvalidProfileResponse Text BL.ByteString
|
|
deriving (Show, Typeable)
|
|
instance Exception YesodOAuth2Exception
|
|
|
|
-- | Retrieve a user's profile as JSON
|
|
--
|
|
-- The response should be parsed only far enough to read the required
|
|
-- @'credsIdent'@. The raw response is returned as well, to be set in
|
|
-- @'credsExtra'@for consumers to re-use if information they seek is in the
|
|
-- response already.
|
|
--
|
|
-- Information requiring other requests should use the access token (also in
|
|
-- @'credsExtra'@ to make subsequent requests themselves.
|
|
--
|
|
authGetProfile
|
|
:: FromJSON a
|
|
=> Text -- ^ Plugin name
|
|
-> Manager
|
|
-> OAuth2Token
|
|
-> URI
|
|
-> IO (a, BL.ByteString)
|
|
authGetProfile name manager token url = do
|
|
resp <- fromAuthGet name =<< authGetBS manager (accessToken token) url
|
|
decoded <- fromAuthJSON name resp
|
|
pure (decoded, resp)
|
|
|
|
-- | Throws a @Left@ result as an @'InvalidProfileResponse'@
|
|
fromAuthGet :: Text -> Either (OAuth2Error Value) BL.ByteString -> IO BL.ByteString
|
|
fromAuthGet _ (Right bs) = pure bs -- nice
|
|
fromAuthGet name (Left err) = throwIO $ invalidProfileResponse name err
|
|
|
|
-- | Throws a decoding error as an @'InvalidProfileResponse'@
|
|
fromAuthJSON :: FromJSON a => Text -> BL.ByteString -> IO a
|
|
fromAuthJSON name =
|
|
-- FIXME: unique exception constructors
|
|
either (throwIO . InvalidProfileResponse name . BL8.pack) pure . eitherDecode
|
|
|
|
-- | Construct (part of) @'credsExtra'@ container the token and user response
|
|
setExtra :: OAuth2Token -> BL.ByteString -> [(Text, Text)]
|
|
setExtra token userResponseJSON =
|
|
[ ("accessToken", atoken $ accessToken token)
|
|
, ("userResponseJSON", decodeUtf8 $ BL.toStrict userResponseJSON)
|
|
]
|
|
|
|
-- | Construct an @'InvalidProfileResponse'@ exception from an @'OAuth2Error'@
|
|
--
|
|
-- This forces the @e@ in @'OAuth2Error' e@ to parse as a JSON @'Value'@ which
|
|
-- is then re-encoded for the exception message.
|
|
--
|
|
-- Deprecated.
|
|
--
|
|
invalidProfileResponse :: Text -> OAuth2Error Value -> YesodOAuth2Exception
|
|
invalidProfileResponse name = InvalidProfileResponse name . encode
|
|
|
|
-- | Handle the common case of fetching Profile information from a JSON endpoint
|
|
--
|
|
-- Throws @'InvalidProfileResponse'@ if JSON parsing fails
|
|
--
|
|
-- Deprecated.
|
|
--
|
|
fromProfileURL :: FromJSON a => Text -> URI -> (a -> Creds m) -> FetchCreds m
|
|
fromProfileURL name url toCreds manager token =
|
|
toCreds . fst <$> authGetProfile name manager token url
|
|
|
|
-- | A tuple of @scope@ and the given scopes separated by a delimiter
|
|
scopeParam :: Text -> [Text] -> (ByteString, ByteString)
|
|
scopeParam d = ("scope",) . encodeUtf8 . T.intercalate d
|
|
|
|
-- | A helper for providing an optional value to credsExtra
|
|
maybeExtra :: Text -> Maybe Text -> [(Text, Text)]
|
|
maybeExtra k (Just v) = [(k, v)]
|
|
maybeExtra _ Nothing = []
|