happstack-authenticate 2.3.4.16 → 2.6.1
raw patch · 12 files changed
Files
- Happstack/Authenticate/Core.hs +53/−5
- Happstack/Authenticate/OpenId/Controllers.hs +1/−9
- Happstack/Authenticate/OpenId/Core.hs +4/−2
- Happstack/Authenticate/OpenId/Partials.hs +3/−13
- Happstack/Authenticate/OpenId/PartialsURL.hs +2/−4
- Happstack/Authenticate/OpenId/Route.hs +21/−17
- Happstack/Authenticate/Password/Controllers.hs +21/−6
- Happstack/Authenticate/Password/Core.hs +64/−32
- Happstack/Authenticate/Password/Route.hs +38/−23
- Happstack/Authenticate/Route.hs +7/−4
- happstack-authenticate.cabal +17/−15
- messages/openid/partials/en.msg +0/−1
Happstack/Authenticate/Core.hs view
@@ -37,6 +37,11 @@ , isAuthAdmin , usernameAcceptable , requireEmail+ , systemFromAddress+ , systemReplyToAddress+ , systemSendmailPath+ , postLoginRedirect+ , createUserCallback , HappstackAuthenticateI18N(..) , UserId(..) , unUserId@@ -60,6 +65,7 @@ , IxUser , SharedSecret(..) , unSharedSecret+ , SimpleAddress(..) , genSharedSecret , genSharedSecretDevURandom , genSharedSecretSysRandom@@ -87,6 +93,8 @@ , GetUserByUsername(..) , GetUserByUserId(..) , GetUserByEmail(..)+ , GetUsers(..)+ , GetUsersByEmail(..) , GetAuthenticateState(..) , getOrGenSharedSecret , Token(..)@@ -110,6 +118,9 @@ , AuthenticateURL(..) , rAuthenticationMethods , rControllers+ , systemFromAddress+ , systemReplyToAddress+ , systemSendmailPath , authenticateURL , nestAuthenticationMethod ) where@@ -136,7 +147,7 @@ import qualified Data.Map as Map import Data.Maybe (fromMaybe, maybeToList) import Data.Monoid ((<>), mconcat, mempty)-import Data.SafeCopy (SafeCopy, base, deriveSafeCopy)+import Data.SafeCopy (SafeCopy, Migrate(..), base, deriveSafeCopy, extension) import Data.IxSet.Typed import qualified Data.IxSet.Typed as IxSet import Data.Set (Set)@@ -157,6 +168,7 @@ import Text.Boomerang.TH (makeBoomerangs) import Text.Shakespeare.I18N (RenderMessage(renderMessage), mkMessageFor) import Web.JWT (Algorithm(HS256), JWT, VerifiedJWT, JWTClaimsSet(..), encodeSigned, claims, decode, decodeAndVerifySignature, secondsSinceEpoch, intDate, verify)+import qualified Web.JWT as JWT #if MIN_VERSION_jwt(0,8,0) import Web.JWT (ClaimsMap(..), hmacSecret) #else@@ -335,14 +347,31 @@ (ixFun $ maybeToList . view email) ------------------------------------------------------------------------------+-- SimpleAddress+------------------------------------------------------------------------------++data SimpleAddress = SimpleAddress+ { _saName :: Maybe Text+ , _saEmail :: Email+ }+ deriving (Eq, Ord, Read, Show, Data, Typeable, Generic)+deriveSafeCopy 0 'base ''SimpleAddress+makeLenses ''SimpleAddress++------------------------------------------------------------------------------ -- AuthenticateConfig ------------------------------------------------------------------------------ -- | Various configuration options that apply to all authentication methods data AuthenticateConfig = AuthenticateConfig- { _isAuthAdmin :: UserId -> IO Bool -- ^ can user administrate the authentication system?- , _usernameAcceptable :: Username -> Maybe CoreError -- ^ enforce username policies, valid email, etc. 'Nothing' == ok, 'Just Text' == error message- , _requireEmail :: Bool+ { _isAuthAdmin :: UserId -> IO Bool -- ^ can user administrate the authentication system?+ , _usernameAcceptable :: Username -> Maybe CoreError -- ^ enforce username policies, valid email, etc. 'Nothing' == ok, 'Just Text' == error message+ , _requireEmail :: Bool -- ^ require use to supply an email address when creating an account+ , _systemFromAddress :: Maybe SimpleAddress -- ^ From: line for emails sent by the server+ , _systemReplyToAddress :: Maybe SimpleAddress -- ^ Reply-To: line for emails sent by the server+ , _systemSendmailPath :: Maybe FilePath -- ^ path to sendmail if it is not \/usr\/sbin\/sendmail+ , _postLoginRedirect :: Maybe Text -- ^ path to redirect to after a successful login+ , _createUserCallback :: Maybe (User -> IO ()) -- ^ a function to call when a new user is created. Useful for adding them to mailing lists or other stuff } deriving (Typeable, Generic) makeLenses ''AuthenticateConfig@@ -556,6 +585,13 @@ do us <- view users return $ getOne $ us @= userId +-- | find all 'Users'+--+getUsers :: Query AuthenticateState (Set User)+getUsers =+ do us <- view users+ return $ toSet $ us+ -- | look up a 'User' by their 'Email' -- -- NOTE: if the email is associated with more than one account this will return 'Nothing'@@ -565,6 +601,14 @@ do us <- view users return $ getOne $ us @= email +-- | find all 'Users' which match 'Email'+--+getUsersByEmail :: Email+ -> Query AuthenticateState (Set User)+getUsersByEmail email =+ do us <- view users+ return $ toSet $ us @= email+ -- | get the entire AuthenticateState value getAuthenticateState :: Query AuthenticateState AuthenticateState getAuthenticateState = ask@@ -582,7 +626,9 @@ , 'deleteUser , 'getUserByUsername , 'getUserByUserId+ , 'getUsers , 'getUserByEmail+ , 'getUsersByEmail , 'getAuthenticateState ] @@ -689,7 +735,9 @@ Nothing -> return Nothing (Just ssecret) -> -- finally we can verify all the claims-#if MIN_VERSION_jwt(0,8,0)+#if MIN_VERSION_jwt(0,11,0)+ case verify (JWT.toVerify $ hmacSecret (_unSharedSecret ssecret)) unverified of+#elif MIN_VERSION_jwt(0,8,0) case verify (hmacSecret (_unSharedSecret ssecret)) unverified of #else case verify (secret (_unSharedSecret ssecret)) unverified of
Happstack/Authenticate/OpenId/Controllers.hs view
@@ -11,7 +11,7 @@ import Happstack.Authenticate.Core (AuthenticateState, AuthenticateURL, getToken, tokenIsAuthAdmin) import Happstack.Authenticate.OpenId.Core (GetOpenIdRealm(..), OpenIdState) import Happstack.Authenticate.OpenId.URL (OpenIdURL(BeginDance, Partial, ReturnTo), nestOpenIdURL)-import Happstack.Authenticate.OpenId.PartialsURL (PartialURL(UsingGoogle, UsingYahoo, RealmForm))+import Happstack.Authenticate.OpenId.PartialsURL (PartialURL(UsingYahoo, RealmForm)) import Happstack.Server (Happstack) import Language.Javascript.JMacro import Web.Routes@@ -70,14 +70,6 @@ error(callback); }; }]);-- openId.directive('openidGoogle', ['$rootScope', function ($rootScope) {- return {- restrict: 'E',- replace: true,- templateUrl: `(showURL (Partial UsingGoogle) [])`- };- }]); openId.directive('openidYahoo', ['$rootScope', function ($rootScope) { return {
Happstack/Authenticate/OpenId/Core.hs view
@@ -82,12 +82,14 @@ , _openIdRealm :: Maybe Text } deriving (Eq, Ord, Read, Show, Data, Typeable, Generic)-deriveSafeCopy 2 'extension ''OpenIdState-makeLenses ''OpenIdState instance Migrate OpenIdState where type MigrateFrom OpenIdState = OpenIdState_1 migrate (OpenIdState_1 ids) = OpenIdState ids Nothing++deriveSafeCopy 2 'extension ''OpenIdState+makeLenses ''OpenIdState+ initialOpenIdState :: OpenIdState initialOpenIdState = OpenIdState
Happstack/Authenticate/OpenId/Partials.hs view
@@ -26,7 +26,7 @@ import HSP.JMacro () import Prelude hiding ((.), id) import Text.Shakespeare.I18N (Lang, mkMessageFor, renderMessage)-import Web.Authenticate.OpenId.Providers (google, yahoo)+import Web.Authenticate.OpenId.Providers (yahoo) import Web.Routes import Web.Routes.XMLGenT () import Web.Routes.TH (derivePathInfo)@@ -35,8 +35,7 @@ type Partial m = XMLGenT (RouteT AuthenticateURL (ReaderT [Lang] m)) data PartialMsgs- = UsingGoogleMsg- | UsingYahooMsg+ = UsingYahooMsg | SetRealmMsg | OpenIdRealmMsg @@ -60,24 +59,15 @@ -> Partial m XML routePartial authenticateState openIdState url = case url of- UsingGoogle -> usingGoogle UsingYahoo -> usingYahoo RealmForm -> realmForm openIdState -usingGoogle :: (Functor m, Monad m) =>- Partial m XML-usingGoogle =- do danceURL <- lift $ nestOpenIdURL $ showURL (BeginDance (Text.pack google))- [hsx|- <a ng-click=("openIdWindow('" <> danceURL <> "')")><img src="https://raw.githubusercontent.com/intridea/authbuttons/master/png/google_32.png" alt=UsingGoogleMsg /></a>- |]- usingYahoo :: (Functor m, Monad m) => Partial m XML usingYahoo = do danceURL <- lift $ nestOpenIdURL $ showURL (BeginDance (Text.pack yahoo)) [hsx|- <a ng-click=("openIdWindow('" <> danceURL <> "')")><img src="https://raw.githubusercontent.com/intridea/authbuttons/master/png/yahoo_32.png" alt=UsingYahooMsg /></a>+ <a ng-click=("openIdWindow('" <> danceURL <> "')")><img src="https://raw.githubusercontent.com/Happstack/authbuttons/master/png/yahoo_32.png" alt=UsingYahooMsg /></a> |] realmForm
Happstack/Authenticate/OpenId/PartialsURL.hs view
@@ -10,8 +10,7 @@ import Web.Routes.Boomerang (Router, (:-), (<>), boomerangFromPathSegments, boomerangToPathSegments) data PartialURL- = UsingGoogle- | UsingYahoo+ = UsingYahoo | RealmForm deriving (Eq, Ord, Data, Typeable, Generic, Read, Show) @@ -19,8 +18,7 @@ partialURL :: Router () (PartialURL :- ()) partialURL =- ( "using-google" . rUsingGoogle- <> "using-yahoo" . rUsingYahoo+ ( "using-yahoo" . rUsingYahoo <> "realm" . rRealmForm )
Happstack/Authenticate/OpenId/Route.hs view
@@ -2,6 +2,8 @@ module Happstack.Authenticate.OpenId.Route where import Control.Applicative ((<$>))+import Control.Concurrent.STM (atomically)+import Control.Concurrent.STM.TVar (TVar, readTVar) import Control.Monad.Reader (ReaderT, runReaderT) import Control.Monad.Trans (liftIO) import Data.Acid (AcidState, closeAcidState, makeAcidic)@@ -31,26 +33,28 @@ routeOpenId :: (Happstack m) => AcidState AuthenticateState- -> AuthenticateConfig+ -> TVar AuthenticateConfig -> AcidState OpenIdState -> [Text] -> RouteT AuthenticateURL (ReaderT [Lang] m) Response-routeOpenId authenticateState authenticateConfig openIdState pathSegments =+routeOpenId authenticateState authenticateConfigTV openIdState pathSegments = case parseSegments fromPathSegments pathSegments of (Left _) -> notFound $ toJSONError URLDecodeFailed (Right url) ->- case url of- (Partial u) ->- do xml <- unXMLGenT (routePartial authenticateState openIdState u)- ok $ toResponse (html4StrictFrag, xml)- (BeginDance providerURL) ->- do returnURL <- nestOpenIdURL $ showURL ReturnTo- realm <- query' openIdState GetOpenIdRealm- forwardURL <- liftIO $ do manager <- newManager tlsManagerSettings- getForwardUrl providerURL returnURL realm [] manager -- [("Email", "http://schema.openid.net/contact/email")]- seeOther forwardURL (toResponse ())- ReturnTo -> token authenticateState authenticateConfig openIdState- Realm -> realm authenticateState openIdState+ do case url of+ (Partial u) ->+ do xml <- unXMLGenT (routePartial authenticateState openIdState u)+ ok $ toResponse (html4StrictFrag, xml)+ (BeginDance providerURL) ->+ do returnURL <- nestOpenIdURL $ showURL ReturnTo+ realm <- query' openIdState GetOpenIdRealm+ forwardURL <- liftIO $ do manager <- newManager tlsManagerSettings+ getForwardUrl providerURL returnURL realm [] manager -- [("Email", "http://schema.openid.net/contact/email")]+ seeOther forwardURL (toResponse ())+ ReturnTo ->+ do authenticateConfig <- liftIO $ atomically $ readTVar authenticateConfigTV+ token authenticateState authenticateConfig openIdState+ Realm -> realm authenticateState openIdState ------------------------------------------------------------------------------ -- initOpenId@@ -58,9 +62,9 @@ initOpenId :: FilePath -> AcidState AuthenticateState- -> AuthenticateConfig+ -> TVar AuthenticateConfig -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)-initOpenId basePath authenticateState authenticateConfig =+initOpenId basePath authenticateState authenticateConfigTV = do openIdState <- openLocalStateFrom (combine basePath "openId") initialOpenIdState let shutdown = \normal -> if normal@@ -70,6 +74,6 @@ do langsOveride <- queryString $ lookTexts' "_LANG" langs <- bestLanguage <$> acceptLanguage mapRouteT (flip runReaderT (langsOveride ++ langs)) $- routeOpenId authenticateState authenticateConfig openIdState pathSegments+ routeOpenId authenticateState authenticateConfigTV openIdState pathSegments return (shutdown, (openIdAuthenticationMethod, authenticationHandler), openIdCtrl authenticateState openIdState)
Happstack/Authenticate/Password/Controllers.hs view
@@ -1,22 +1,27 @@ {-# LANGUAGE QuasiQuotes #-} module Happstack.Authenticate.Password.Controllers where +import Control.Concurrent.STM (atomically)+import Control.Concurrent.STM.TVar (TVar, readTVar)+import Control.Monad.Trans (MonadIO(liftIO))+import Data.Maybe (isJust, fromJust) import Data.Text (Text) import qualified Data.Text as T-import Happstack.Authenticate.Core (AuthenticateURL)+import Happstack.Authenticate.Core (AuthenticateConfig(_postLoginRedirect), AuthenticateURL) import Happstack.Authenticate.Password.URL (PasswordURL(Account, Token, Partial, PasswordReset, PasswordRequestReset), nestPasswordURL) import Happstack.Authenticate.Password.PartialsURL (PartialURL(ChangePassword, Logout, Login, LoginInline, SignupPassword, ResetPasswordForm, RequestResetPasswordForm)) import Language.Javascript.JMacro import Web.Routes -usernamePasswordCtrl :: (Monad m) => RouteT AuthenticateURL m JStat-usernamePasswordCtrl =+usernamePasswordCtrl :: (MonadIO m) => TVar AuthenticateConfig -> RouteT AuthenticateURL m JStat+usernamePasswordCtrl authenticateConfigTV = nestPasswordURL $ do fn <- askRouteFn- return $ usernamePasswordCtrlJs fn+ plr <- liftIO (_postLoginRedirect <$> (atomically $ readTVar authenticateConfigTV))+ return $ usernamePasswordCtrlJs plr fn -usernamePasswordCtrlJs :: (PasswordURL -> [(Text, Maybe Text)] -> Text) -> JStat-usernamePasswordCtrlJs showURL = [jmacro|+usernamePasswordCtrlJs :: Maybe Text -> (PasswordURL -> [(Text, Maybe Text)] -> Text) -> JStat+usernamePasswordCtrlJs postLoginRedirect showURL = [jmacro| { var usernamePassword = angular.module('usernamePassword', ['happstackAuthentication']); @@ -38,6 +43,7 @@ if (datum.jrStatus == "Ok") { $scope.username_password_error = ''; userService.updateFromToken(datum.jrData.token);+ `loginRedirect postLoginRedirect` } else { userService.clearUser(); $scope.username_password_error = datum.jrData;@@ -299,3 +305,12 @@ } |]+ where+ loginRedirect (Just plr) =+ [jmacro|+ // console.log(`"loginRedirect - " ++ show plr`);+ window.location.href = `plr`;+ |]+ loginRedirect Nothing =+ BlockStat []+
Happstack/Authenticate/Password/Core.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP, DataKinds, DeriveDataTypeable, DeriveGeneric, FlexibleInstances, MultiParamTypeClasses, RecordWildCards, TemplateHaskell, TypeFamilies, TypeSynonymInstances, OverloadedStrings #-}+{-# LANGUAGE CPP, DataKinds, DeriveDataTypeable, DeriveGeneric, FlexibleInstances, MultiParamTypeClasses, RecordWildCards, TemplateHaskell, TypeFamilies, TypeSynonymInstances, OverloadedStrings, StandaloneDeriving #-} module Happstack.Authenticate.Password.Core where import Control.Applicative ((<$>), optional)@@ -13,6 +13,9 @@ import qualified Data.Aeson as Aeson import Data.Aeson (Value(..), Object(..), Result(..), decode, encode, fromJSON) import Data.Aeson.Types (ToJSON(..), FromJSON(..), Options(fieldLabelModifier), defaultOptions, genericToJSON, genericParseJSON)+#if MIN_VERSION_aeson(2,0,0)+import qualified Data.Aeson.KeyMap as KM+#endif import Data.ByteString (ByteString) import qualified Data.ByteString.Lazy as B import Data.Data (Data, Typeable)@@ -21,7 +24,7 @@ import qualified Data.Map as Map import Data.Maybe (fromMaybe, fromJust) import Data.Monoid ((<>), mempty)-import Data.SafeCopy (SafeCopy, base, deriveSafeCopy)+import Data.SafeCopy (SafeCopy, Migrate(..), base, extension, deriveSafeCopy) import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Text.IO as Text@@ -30,13 +33,13 @@ import Data.Time.Clock.POSIX (getPOSIXTime) import Data.UserId (UserId) import GHC.Generics (Generic)-import Happstack.Authenticate.Core (AuthenticationHandler, AuthenticationMethod(..), AuthenticateState(..), AuthenticateConfig, usernameAcceptable, requireEmail, AuthenticateURL, CoreError(..), CreateUser(..), Email(..), unEmail, GetUserByUsername(..), HappstackAuthenticateI18N(..), SharedSecret(..), User(..), Username(..), GetSharedSecret(..), addTokenCookie, email, getToken, getOrGenSharedSecret, jsonOptions, userId, username, toJSONSuccess, toJSONResponse, toJSONError, tokenUser)+import Happstack.Authenticate.Core (AuthenticationHandler, AuthenticationMethod(..), AuthenticateState(..), AuthenticateConfig, usernameAcceptable, requireEmail, AuthenticateURL, CoreError(..), CreateUser(..), Email(..), unEmail, GetUserByUserId(..), GetUserByUsername(..), HappstackAuthenticateI18N(..), SharedSecret(..), SimpleAddress(..), User(..), Username(..), GetSharedSecret(..), addTokenCookie, createUserCallback, email, getToken, getOrGenSharedSecret, jsonOptions, userId, username, systemFromAddress, systemReplyToAddress, systemSendmailPath, toJSONSuccess, toJSONResponse, toJSONError, tokenUser) import Happstack.Authenticate.Password.URL (AccountURL(..)) import Happstack.Server import HSP.JMacro import Language.Javascript.JMacro import Network.HTTP.Types (toQuery, renderQuery)-import Network.Mail.Mime (Address(..), Mail, simpleMail', renderMail', renderSendMail, sendmail)+import Network.Mail.Mime (Address(..), Mail(..), simpleMail', renderMail', renderSendMail, renderSendMailCustom, sendmail) import System.FilePath (combine) import qualified Text.Email.Validate as Email import Text.Shakespeare.I18N (RenderMessage(..), Lang, mkMessageFor)@@ -127,7 +130,7 @@ initialPasswordState :: PasswordState initialPasswordState = PasswordState- { _passwords = Map.empty+ { _passwords = Map.empty } ------------------------------------------------------------------------------@@ -220,7 +223,11 @@ if not valid then unauthorized $ toJSONError InvalidUsernamePassword else do token <- addTokenCookie authenticateState authenticateConfig u+#if MIN_VERSION_aeson(2,0,0)+ resp 201 $ toJSONSuccess (Object $ KM.fromList [("token", toJSON token)]) -- toResponseBS "application/json" $ encode $ Object $ HashMap.fromList [("token", toJSON token)]+#else resp 201 $ toJSONSuccess (Object $ HashMap.fromList [("token", toJSON token)]) -- toResponseBS "application/json" $ encode $ Object $ HashMap.fromList [("token", toJSON token)]+#endif ------------------------------------------------------------------------------ -- account@@ -281,6 +288,9 @@ (Right user) -> do hashed <- mkHashedPass (_naPassword newAccount) update' passwordState (SetPassword (user ^. userId) hashed)+ case (authenticateConfig ^. createUserCallback) of+ Nothing -> pure ()+ (Just callback) -> liftIO $ callback user ok $ (Right (user ^. userId)) where validEmail :: Bool -> Maybe Email -> Maybe PasswordError@@ -338,11 +348,12 @@ -- | request reset password passwordRequestReset :: (Happstack m) =>- PasswordConfig+ AuthenticateConfig+ -> PasswordConfig -> AcidState AuthenticateState -> AcidState PasswordState -> m (Either PasswordError Text)-passwordRequestReset passwordConfig authenticateState passwordState =+passwordRequestReset authenticateConfig passwordConfig authenticateState passwordState = do method POST ~(Just (Body body)) <- takeRequestBody =<< askRq case Aeson.decode body of@@ -355,28 +366,39 @@ case user ^. email of Nothing -> return $ Left NoEmailAddress (Just toEm) ->- do eResetToken <- issueResetToken authenticateState user- case eResetToken of- (Left err) -> return (Left err)- (Right resetToken) ->- do let resetLink' = (passwordConfig ^. resetLink) <> (Text.decodeUtf8 $ renderQuery True $ toQuery [("reset_token"::Text, resetToken)])- liftIO $ Text.putStrLn resetLink' -- FIXME: don't print to stdout- sendResetEmail toEm (Email ("no-reply@" <> (passwordConfig ^. domain))) resetLink'- return (Right "password reset request email sent.") -- FIXME: I18N+ do resetToken <- issueResetToken authenticateState user+ let resetLink' = resetTokenLink (passwordConfig ^. resetLink) resetToken+ -- liftIO $ Text.putStrLn resetLink' -- FIXME: don't print to stdout+ let from = fromMaybe (SimpleAddress Nothing (Email ("no-reply@" <> (passwordConfig ^. domain)))) (authenticateConfig ^. systemFromAddress)+ sendResetEmail (authenticateConfig ^. systemSendmailPath) toEm from (authenticateConfig ^. systemReplyToAddress) resetLink'+ return (Right "password reset request email sent.") -- FIXME: I18N +-- | generate a reset token for a UserId+resetTokenForUserId :: Text -> AcidState AuthenticateState -> AcidState PasswordState -> UserId -> IO (Either PasswordError Text)+resetTokenForUserId resetLink authenticateState passwordState userId =+ do mUser <- query' authenticateState (GetUserByUserId userId)+ case mUser of+ Nothing -> pure $ Left (CoreError InvalidUserId)+ (Just user) ->+ do resetToken <- issueResetToken authenticateState user+ pure $ Right $ resetTokenLink resetLink resetToken++-- | create a link for a reset token+resetTokenLink :: Text -- ^ base URI+ -> Text -- ^ reset token+ -> Text+resetTokenLink baseURI resetToken = baseURI <> (Text.decodeUtf8 $ renderQuery True $ toQuery [("reset_token"::Text, resetToken)])+ -- | issueResetToken issueResetToken :: (MonadIO m) => AcidState AuthenticateState -> User- -> m (Either PasswordError Text)+ -> m Text issueResetToken authenticateState user =- case user ^. email of- Nothing -> return (Left NoEmailAddress)- (Just addr) ->- do ssecret <- getOrGenSharedSecret authenticateState (user ^. userId)- -- FIXME: add expiration time- now <- liftIO getPOSIXTime- let claims = JWT.JWTClaimsSet+ do ssecret <- getOrGenSharedSecret authenticateState (user ^. userId)+ -- FIXME: add expiration time+ now <- liftIO getPOSIXTime+ let claims = JWT.JWTClaimsSet { JWT.iss = Nothing , JWT.sub = Nothing , JWT.aud = Nothing@@ -391,24 +413,32 @@ Map.singleton "reset-password" (toJSON user) } #if MIN_VERSION_jwt(0,10,0)- return $ Right $ encodeSigned (hmacSecret $ _unSharedSecret ssecret) mempty claims+ return $ encodeSigned (hmacSecret $ _unSharedSecret ssecret) mempty claims #elif MIN_VERSION_jwt(0,9,0)- return $ Right $ encodeSigned (hmacSecret $ _unSharedSecret ssecret) claims+ return $ encodeSigned (hmacSecret $ _unSharedSecret ssecret) claims #else- return $ Right $ encodeSigned HS256 (secret $ _unSharedSecret ssecret) claims+ return $ encodeSigned HS256 (secret $ _unSharedSecret ssecret) claims #endif -- FIXME: I18N -- FIXME: call renderSendMail sendResetEmail :: (MonadIO m) =>- Email+ Maybe FilePath -> Email+ -> SimpleAddress+ -> Maybe SimpleAddress -> Text -> m ()-sendResetEmail (Email toEm) (Email fromEm) resetLink = liftIO $- do mailBS <- renderMail' $ simpleMail' (Address Nothing toEm) (Address (Just "no-reply") fromEm) "Reset Password Request" (LT.fromStrict resetLink)- -- B.putStr mailBS- sendmail mailBS+sendResetEmail mSendmailPath (Email toEm) (SimpleAddress fromNm (Email fromEm)) mReplyTo resetLink = liftIO $+ do let mail = addReplyTo mReplyTo $ simpleMail' (Address Nothing toEm) (Address fromNm fromEm) "Reset Password Request" (LT.fromStrict resetLink)+ case mSendmailPath of+ Nothing -> renderSendMail mail+ (Just sendmailPath) -> renderSendMailCustom sendmailPath ["-t"] mail+ where+ addReplyTo :: Maybe SimpleAddress -> Mail -> Mail+ addReplyTo Nothing m = m+ addReplyTo (Just (SimpleAddress rplyToNm rplyToEm)) m =+ let m' = m { mailHeaders = (mailHeaders m) } in m' -- | JSON record for new account data data ResetPasswordData = ResetPasswordData@@ -487,7 +517,9 @@ case mssecret of Nothing -> return Nothing (Just ssecret) ->-#if MIN_VERSION_jwt(0,8,0)+#if MIN_VERSION_jwt(0,11,0)+ case verify (JWT.toVerify $ hmacSecret (_unSharedSecret ssecret)) unverified of+#elif MIN_VERSION_jwt(0,8,0) case verify (hmacSecret (_unSharedSecret ssecret)) unverified of #else case verify (secret (_unSharedSecret ssecret)) unverified of
Happstack/Authenticate/Password/Route.hs view
@@ -2,6 +2,9 @@ import Control.Applicative ((<$>)) import Control.Monad.Reader (ReaderT, runReaderT)+import Control.Monad.Trans (MonadIO(liftIO))+import Control.Concurrent.STM (atomically)+import Control.Concurrent.STM.TVar (TVar, newTVar, readTVar) import Data.Acid (AcidState, closeAcidState, makeAcidic) import Data.Acid.Local (createCheckpointAndClose, openLocalStateFrom) import Data.Text (Text)@@ -25,24 +28,26 @@ ------------------------------------------------------------------------------ routePassword :: (Happstack m) =>- PasswordConfig+ TVar PasswordConfig -> AcidState AuthenticateState- -> AuthenticateConfig+ -> TVar AuthenticateConfig -> AcidState PasswordState -> [Text] -> RouteT AuthenticateURL (ReaderT [Lang] m) Response-routePassword passwordConfig authenticateState authenticateConfig passwordState pathSegments =+routePassword passwordConfigTV authenticateState authenticateConfigTV passwordState pathSegments = case parseSegments fromPathSegments pathSegments of (Left _) -> notFound $ toJSONError URLDecodeFailed (Right url) ->- case url of- Token -> token authenticateState authenticateConfig passwordState- Account mUrl -> toJSONResponse <$> account authenticateState passwordState authenticateConfig passwordConfig mUrl- (Partial u) -> do xml <- unXMLGenT (routePartial authenticateState u)- return $ toResponse (html4StrictFrag, xml)- PasswordRequestReset -> toJSONResponse <$> passwordRequestReset passwordConfig authenticateState passwordState- PasswordReset -> toJSONResponse <$> passwordReset authenticateState passwordState passwordConfig- UsernamePasswordCtrl -> toResponse <$> usernamePasswordCtrl+ do authenticateConfig <- liftIO $ atomically $ readTVar authenticateConfigTV+ passwordConfig <- liftIO $ atomically $ readTVar passwordConfigTV+ case url of+ Token -> token authenticateState authenticateConfig passwordState+ Account mUrl -> toJSONResponse <$> account authenticateState passwordState authenticateConfig passwordConfig mUrl+ (Partial u) -> do xml <- unXMLGenT (routePartial authenticateState u)+ return $ toResponse (html4StrictFrag, xml)+ PasswordRequestReset -> toJSONResponse <$> passwordRequestReset authenticateConfig passwordConfig authenticateState passwordState+ PasswordReset -> toJSONResponse <$> passwordReset authenticateState passwordState passwordConfig+ UsernamePasswordCtrl -> toResponse <$> usernamePasswordCtrl authenticateConfigTV ------------------------------------------------------------------------------ -- initPassword@@ -51,17 +56,27 @@ initPassword :: PasswordConfig -> FilePath -> AcidState AuthenticateState- -> AuthenticateConfig+ -> TVar AuthenticateConfig -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)-initPassword passwordConfig basePath authenticateState authenticateConfig =+initPassword passwordConfig basePath authenticateState authenticateConfigTV = do passwordState <- openLocalStateFrom (combine basePath "password") initialPasswordState- let shutdown = \normal ->- if normal- then createCheckpointAndClose passwordState- else closeAcidState passwordState- authenticationHandler pathSegments =- do langsOveride <- queryString $ lookTexts' "_LANG"- langs <- bestLanguage <$> acceptLanguage- mapRouteT (flip runReaderT (langsOveride ++ langs)) $- routePassword passwordConfig authenticateState authenticateConfig passwordState pathSegments- return (shutdown, (passwordAuthenticationMethod, authenticationHandler), usernamePasswordCtrl)+ passwordConfigTV <- atomically $ newTVar passwordConfig+ initPassword' passwordConfigTV passwordState basePath authenticateState authenticateConfigTV++initPassword' :: TVar PasswordConfig+ -> AcidState PasswordState+ -> FilePath+ -> AcidState AuthenticateState+ -> TVar AuthenticateConfig+ -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)+initPassword' passwordConfigTV passwordState basePath authenticateState authenticateConfigTV =+ do let shutdown = \normal ->+ if normal+ then createCheckpointAndClose passwordState+ else closeAcidState passwordState+ authenticationHandler pathSegments =+ do langsOveride <- queryString $ lookTexts' "_LANG"+ langs <- bestLanguage <$> acceptLanguage+ mapRouteT (flip runReaderT (langsOveride ++ langs)) $+ routePassword passwordConfigTV authenticateState authenticateConfigTV passwordState pathSegments+ pure (shutdown, (passwordAuthenticationMethod, authenticationHandler), usernamePasswordCtrl authenticateConfigTV)
Happstack/Authenticate/Route.hs view
@@ -2,6 +2,8 @@ module Happstack.Authenticate.Route where import Control.Applicative ((<$>))+import Control.Concurrent.STM (atomically)+import Control.Concurrent.STM.TVar (TVar, newTVar) import Control.Monad.Trans (MonadIO(liftIO)) import Data.Acid (AcidState) import Data.Acid.Local (openLocalStateFrom, createCheckpointAndClose)@@ -47,16 +49,17 @@ initAuthentication :: Maybe FilePath -> AuthenticateConfig- -> [FilePath -> AcidState AuthenticateState -> AuthenticateConfig -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)]- -> IO (IO (), AuthenticateURL -> RouteT AuthenticateURL (ServerPartT IO) Response, AcidState AuthenticateState)+ -> [FilePath -> AcidState AuthenticateState -> TVar AuthenticateConfig -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)]+ -> IO (IO (), AuthenticateURL -> RouteT AuthenticateURL (ServerPartT IO) Response, AcidState AuthenticateState, TVar AuthenticateConfig) initAuthentication mBasePath authenticateConfig initMethods = do let authenticatePath = combine (fromMaybe "state" mBasePath) "authenticate" authenticateState <- openLocalStateFrom (combine authenticatePath "core") initialAuthenticateState+ authenticateConfigTV <- atomically $ newTVar authenticateConfig -- FIXME: need to deal with one of the initMethods throwing an exception- (cleanupPartial, handlers, javascript) <- unzip3 <$> mapM (\initMethod -> initMethod authenticatePath authenticateState authenticateConfig) initMethods+ (cleanupPartial, handlers, javascript) <- unzip3 <$> mapM (\initMethod -> initMethod authenticatePath authenticateState authenticateConfigTV) initMethods let cleanup = sequence_ $ createCheckpointAndClose authenticateState : (map (\c -> c True) cleanupPartial) h = route javascript (Map.fromList handlers)- return (cleanup, h, authenticateState)+ return (cleanup, h, authenticateState, authenticateConfigTV) instance (Functor m, MonadIO m) => IntegerSupply (RouteT AuthenticateURL m) where nextInteger =
happstack-authenticate.cabal view
@@ -1,5 +1,5 @@ Name: happstack-authenticate-Version: 2.3.4.16+Version: 2.6.1 Synopsis: Happstack Authentication Library Description: A themeable authentication library with support for username+password and OpenId. Homepage: http://www.happstack.com/@@ -10,8 +10,8 @@ Copyright: 2011-2015 SeeReason Partners, LLC Category: Web Build-type: Simple-Cabal-version: >=1.8-tested-with: GHC == 7.8.4, GHC == 7.10.3, GHC == 8.0.1, GHC == 8.2.2, GHC == 8.4.1, GHC == 8.6.3+Cabal-version: >=1.10+tested-with: GHC==8.0.2, GHC==8.2.2, GHC==8.4.4, GHC==8.6.5, GHC==8.8.4, GHC==8.10.7, GHC==9.0.2, GHC==9.2.2 data-files: messages/core/en.msg messages/openid/error/en.msg@@ -24,6 +24,7 @@ location: https://github.com/Happstack/happstack-authenticate.git Library+ Default-language: Haskell2010 Exposed-modules: Happstack.Authenticate.Core Happstack.Authenticate.Controller Happstack.Authenticate.Route@@ -42,36 +43,37 @@ Build-depends: base > 4 && < 5,- acid-state >= 0.6 && < 0.16,- aeson (>= 0.4 && < 0.10) || (>= 0.11 && < 1.5),+ acid-state >= 0.6 && < 0.17,+ aeson (>= 0.4 && < 0.10) || (>= 0.11 && < 1.6) || (>= 2.0 && < 2.1), authenticate == 1.3.*,- base64-bytestring >= 1.0 && < 1.1,+ base64-bytestring >= 1.0 && < 1.3, boomerang >= 1.4 && < 1.5,- bytestring >= 0.9 && < 0.11,+ bytestring >= 0.9 && < 0.12, containers >= 0.4 && < 0.7, data-default >= 0.5 && < 0.8, email-validate >= 2.1 && < 2.4, filepath >= 1.3 && < 1.5, hsx2hs >= 0.13 && < 0.15, jmacro >= 0.6.11 && < 0.7,- jwt >= 0.3 && < 0.11,- ixset-typed >= 0.3 && < 0.5,+ jwt >= 0.3 && < 0.12,+ ixset-typed >= 0.3 && < 0.6, happstack-jmacro >= 7.0 && < 7.1,- happstack-server >= 6.0 && < 7.6,+ happstack-server >= 6.0 && < 7.8, happstack-hsp >= 7.3 && < 7.4, http-conduit >= 2.1.0 && < 2.4, http-types >= 0.6 && < 0.13, hsp >= 0.10 && < 0.11, hsx-jmacro >= 7.3 && < 7.4,- safecopy >= 0.8 && < 0.10,+ safecopy >= 0.8 && < 0.11, mime-mail >= 0.4 && < 0.6, mtl >= 2.0 && < 2.3,- lens >= 4.2 && < 4.18,+ lens >= 4.2 && < 5.2, pwstore-purehaskell == 2.1.*,- text >= 0.11 && < 1.3,- time >= 1.2 && < 1.10,+ stm >= 2.4 && < 2.6,+ text >= 0.11 && < 2.1,+ time >= 1.2 && < 1.14, userid >= 0.1 && < 0.2,- random >= 1.0 && < 1.2,+ random >= 1.0 && < 1.3, shakespeare >= 2.0 && < 2.1, unordered-containers == 0.2.*, web-routes >= 0.26 && < 0.28,
messages/openid/partials/en.msg view
@@ -1,4 +1,3 @@-UsingGoogleMsg: Google OpenId UsingYahooMsg: Yahoo OpenId SetRealmMsg: Update OpenId Realm OpenIdRealmMsg: OpenId Realm