diff --git a/Yesod/Auth/OAuth2.hs b/Yesod/Auth/OAuth2.hs index fe58afa..0e42848 100644 --- a/Yesod/Auth/OAuth2.hs +++ b/Yesod/Auth/OAuth2.hs @@ -11,6 +11,7 @@ -- module Yesod.Auth.OAuth2 ( authOAuth2 + , authOAuth2Image , oauth2Url , fromProfileURL , YesodOAuth2Exception(..) @@ -57,9 +58,22 @@ authOAuth2 :: YesodAuth m -- second authorized request to @api/me.json@. -- -- See @'fromProfileURL'@ for an example. - -- -> AuthPlugin m -authOAuth2 name oauth getCreds = AuthPlugin name dispatch login +authOAuth2 name oauth getCreds = authOAuth2Image name oauth getCreds Nothing + +authOAuth2Image :: YesodAuth m + => Text -- ^ Service name + -> OAuth2 -- ^ Service details + -> (Manager -> AccessToken -> IO (Creds m)) + -- ^ This function defines how to take an @'AccessToken'@ and + -- retrieve additional information about the user, to be + -- set in the session as @'Creds'@. Usually this means a + -- second authorized request to @api/me.json@. + -- + -- See @'fromProfileURL'@ for an example. + -> Maybe Text -- ^ URL to image shown instead of "Login with xxx"-Text + -> AuthPlugin m +authOAuth2Image name oauth getCreds im = AuthPlugin name dispatch login where url = PluginR name ["callback"] @@ -105,7 +119,11 @@ authOAuth2 name oauth getCreds = AuthPlugin name dispatch login tokenSessionKey = "_yesod_oauth2_" <> name login tm = [whamlet| - Login via #{name} + + $maybe image <- im + + $nothing + Login via #{name} |] -- | Handle the common case of fetching Profile information a JSON endpoint diff --git a/Yesod/Auth/OAuth2/EveOnline.hs b/Yesod/Auth/OAuth2/EveOnline.hs new file mode 100644 index 0000000..dbf1a85 --- /dev/null +++ b/Yesod/Auth/OAuth2/EveOnline.hs @@ -0,0 +1,116 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} +-- | +-- +-- OAuth2 plugin for http://eveonline.com +-- +-- * Authenticates against eveonline +-- * Uses EVEs unique account-user-char-hash as credentials identifier +-- * Returns charName, tokenType and expires as extras +-- +module Yesod.Auth.OAuth2.EveOnline + ( oauth2Eve + , oauth2EveImage + , oauth2EveScoped + , ImageType(..) + , module Yesod.Auth.OAuth2 + ) where + +#if __GLASGOW_HASKELL__ < 710 +import Control.Applicative ((<$>), (<*>)) +#endif + +import Control.Exception.Lifted +import Control.Monad (mzero) +import Data.Aeson +import Data.Monoid ((<>)) +import Data.Text as T (Text,unwords) +import Data.ByteString as B (ByteString) +import Data.Text.Encoding (encodeUtf8, decodeUtf8) +import Data.Time.Clock (UTCTime) +import Network.HTTP.Conduit (Manager) +import Yesod.Auth +import Yesod.Auth.OAuth2 + +import qualified Data.Text as T + +data ImageType = BigWhite + | SmallWhite + | BigBlack + | SmallBlack + | Custom Text + +data EveUser = EveUser + { eveUserId :: Int + , eveUserName :: Text + , eveUserExpire :: Text + , eveScopes :: [Text] + , eveTokenType :: Text + , eveCharOwnerHash :: Text + } + +instance FromJSON EveUser where + parseJSON (Object o) = EveUser + <$> o .: "CharacterID" + <*> o .: "CharacterName" + <*> o .: "ExpiresOn" + <*> (T.words <$> o .: "Scopes") + <*> o .: "TokenType" + <*> o .: "CharacterOwnerHash" + + parseJSON _ = mzero + +oauth2Eve :: YesodAuth m + => Text -- ^ Client ID + -> Text -- ^ Client Secret + -> AuthPlugin m +oauth2Eve clientId clientSecret = oauth2EveScoped clientId clientSecret ["publicData"] Nothing + +oauth2EveImage :: YesodAuth m + => Text -- ^ Client ID + -> Text -- ^ Client Secret + -> ImageType + -> AuthPlugin m +oauth2EveImage clientId clientSecret im = oauth2EveScoped clientId clientSecret ["publicData"] (Just . toURI $ im) + where + toURI :: ImageType -> Text + toURI BigWhite = "https://images.contentful.com/idjq7aai9ylm/4PTzeiAshqiM8osU2giO0Y/5cc4cb60bac52422da2e45db87b6819c/EVE_SSO_Login_Buttons_Large_White.png?w=270&h=45" + toURI BigBlack = "https://images.contentful.com/idjq7aai9ylm/4fSjj56uD6CYwYyus4KmES/4f6385c91e6de56274d99496e6adebab/EVE_SSO_Login_Buttons_Large_Black.png?w=270&h=45" + toURI SmallWhite = "https://images.contentful.com/idjq7aai9ylm/18BxKSXCymyqY4QKo8KwKe/c2bdded6118472dd587c8107f24104d7/EVE_SSO_Login_Buttons_Small_White.png?w=195&h=30" + toURI SmallBlack = "https://images.contentful.com/idjq7aai9ylm/12vrPsIMBQi28QwCGOAqGk/33234da7672c6b0cdca394fc8e0b1c2b/EVE_SSO_Login_Buttons_Small_Black.png?w=195&h=30" + toURI (Custom a) = a + +oauth2EveScoped :: YesodAuth m + => Text -- ^ Client ID + -> Text -- ^ Client Secret + -> [Text] -- ^ List of scopes to request + -> Maybe Text -- ^ Login-Image + -> AuthPlugin m +oauth2EveScoped clientId clientSecret scopes = authOAuth2Image "eveonline" oauth fetchEveProfile + where + oauth = OAuth2 + { oauthClientId = encodeUtf8 clientId + , oauthClientSecret = encodeUtf8 clientSecret + , oauthOAuthorizeEndpoint = encodeUtf8 $ "https://login.eveonline.com/oauth/authorize?response_type=code&scope=" <> T.intercalate " " scopes + , oauthAccessTokenEndpoint = "https://login.eveonline.com/oauth/token" + , oauthCallback = Nothing + } + +fetchEveProfile :: Manager -> AccessToken -> IO (Creds m) +fetchEveProfile manager token = do + userResult <- authGetJSON manager token "https://login.eveonline.com/oauth/verify" + + case userResult of + Right user -> return $ toCreds user token + Left err-> throwIO $ InvalidProfileResponse "eveonline" err + +toCreds :: EveUser -> AccessToken -> Creds m +toCreds user token = Creds + { credsPlugin = "eveonline" + , credsIdent = T.pack $ show $ eveCharOwnerHash user + , credsExtra = + [ ("charName", eveUserName user) + , ("tokenType", eveTokenType user) + , ("expires", eveUserExpire user) + ] + } diff --git a/yesod-auth-oauth2.cabal b/yesod-auth-oauth2.cabal index fa1a452..822c83d 100644 --- a/yesod-auth-oauth2.cabal +++ b/yesod-auth-oauth2.cabal @@ -1,5 +1,5 @@ name: yesod-auth-oauth2 -version: 0.1.2 +version: 0.1.3 license: BSD3 license-file: LICENSE author: Tom Streller @@ -36,6 +36,7 @@ library , transformers >= 0.2.2 && < 0.5 , hoauth2 >= 0.4.7 && < 0.5 , lifted-base >= 0.2 && < 0.4 + , time --dependency of yesod-core. Let cabal choose version. exposed-modules: Yesod.Auth.OAuth2 Yesod.Auth.OAuth2.Github @@ -43,6 +44,7 @@ library Yesod.Auth.OAuth2.Spotify Yesod.Auth.OAuth2.Twitter Yesod.Auth.OAuth2.Upcase + Yesod.Auth.OAuth2.EveOnline ghc-options: -Wall