happstack-authenticate 2.2.0 → 2.3.0
raw patch · 14 files changed
+462/−619 lines, 14 filesdep +email-validatePVP ok
version bump matches the API change (PVP)
Dependencies added: email-validate
API changes (from Hackage documentation)
+ Happstack.Authenticate.Core: AuthenticateConfig :: (UserId -> IO Bool) -> (Username -> Maybe CoreError) -> Bool -> AuthenticateConfig
+ Happstack.Authenticate.Core: InvalidEmail :: CoreError
+ Happstack.Authenticate.Core: TextError :: Text -> CoreError
+ Happstack.Authenticate.Core: UsernameNotAcceptable :: CoreError
+ Happstack.Authenticate.Core: [_isAuthAdmin] :: AuthenticateConfig -> UserId -> IO Bool
+ Happstack.Authenticate.Core: [_requireEmail] :: AuthenticateConfig -> Bool
+ Happstack.Authenticate.Core: [_usernameAcceptable] :: AuthenticateConfig -> Username -> Maybe CoreError
+ Happstack.Authenticate.Core: data AuthenticateConfig
+ Happstack.Authenticate.Core: instance Data.Aeson.Types.Class.FromJSON Happstack.Authenticate.Core.JSONResponse
+ Happstack.Authenticate.Core: instance Data.Aeson.Types.Class.FromJSON Happstack.Authenticate.Core.Status
+ Happstack.Authenticate.Core: instance Data.Aeson.Types.Class.ToJSON Happstack.Authenticate.Core.JSONResponse
+ Happstack.Authenticate.Core: instance Data.Aeson.Types.Class.ToJSON Happstack.Authenticate.Core.Status
+ Happstack.Authenticate.Core: instance Data.Data.Data Happstack.Authenticate.Core.JSONResponse
+ Happstack.Authenticate.Core: instance Data.Data.Data Happstack.Authenticate.Core.Status
+ Happstack.Authenticate.Core: instance Data.SafeCopy.SafeCopy.SafeCopy Happstack.Authenticate.Core.Status
+ Happstack.Authenticate.Core: instance GHC.Classes.Eq Happstack.Authenticate.Core.JSONResponse
+ Happstack.Authenticate.Core: instance GHC.Classes.Eq Happstack.Authenticate.Core.Status
+ Happstack.Authenticate.Core: instance GHC.Classes.Ord Happstack.Authenticate.Core.Status
+ Happstack.Authenticate.Core: instance GHC.Generics.Constructor Happstack.Authenticate.Core.C1_0AuthenticateConfig
+ Happstack.Authenticate.Core: instance GHC.Generics.Constructor Happstack.Authenticate.Core.C1_0JSONResponse
+ Happstack.Authenticate.Core: instance GHC.Generics.Constructor Happstack.Authenticate.Core.C1_0Status
+ Happstack.Authenticate.Core: instance GHC.Generics.Constructor Happstack.Authenticate.Core.C1_1Status
+ Happstack.Authenticate.Core: instance GHC.Generics.Constructor Happstack.Authenticate.Core.C1_7CoreError
+ Happstack.Authenticate.Core: instance GHC.Generics.Constructor Happstack.Authenticate.Core.C1_8CoreError
+ Happstack.Authenticate.Core: instance GHC.Generics.Constructor Happstack.Authenticate.Core.C1_9CoreError
+ Happstack.Authenticate.Core: instance GHC.Generics.Datatype Happstack.Authenticate.Core.D1AuthenticateConfig
+ Happstack.Authenticate.Core: instance GHC.Generics.Datatype Happstack.Authenticate.Core.D1JSONResponse
+ Happstack.Authenticate.Core: instance GHC.Generics.Datatype Happstack.Authenticate.Core.D1Status
+ Happstack.Authenticate.Core: instance GHC.Generics.Generic Happstack.Authenticate.Core.AuthenticateConfig
+ Happstack.Authenticate.Core: instance GHC.Generics.Generic Happstack.Authenticate.Core.JSONResponse
+ Happstack.Authenticate.Core: instance GHC.Generics.Generic Happstack.Authenticate.Core.Status
+ Happstack.Authenticate.Core: instance GHC.Generics.Selector Happstack.Authenticate.Core.S1_0_0AuthenticateConfig
+ Happstack.Authenticate.Core: instance GHC.Generics.Selector Happstack.Authenticate.Core.S1_0_0JSONResponse
+ Happstack.Authenticate.Core: instance GHC.Generics.Selector Happstack.Authenticate.Core.S1_0_1AuthenticateConfig
+ Happstack.Authenticate.Core: instance GHC.Generics.Selector Happstack.Authenticate.Core.S1_0_1JSONResponse
+ Happstack.Authenticate.Core: instance GHC.Generics.Selector Happstack.Authenticate.Core.S1_0_2AuthenticateConfig
+ Happstack.Authenticate.Core: instance GHC.Read.Read Happstack.Authenticate.Core.JSONResponse
+ Happstack.Authenticate.Core: instance GHC.Read.Read Happstack.Authenticate.Core.Status
+ Happstack.Authenticate.Core: instance GHC.Show.Show Happstack.Authenticate.Core.JSONResponse
+ Happstack.Authenticate.Core: instance GHC.Show.Show Happstack.Authenticate.Core.Status
+ Happstack.Authenticate.Core: isAuthAdmin :: Lens' AuthenticateConfig (UserId -> IO Bool)
+ Happstack.Authenticate.Core: requireEmail :: Lens' AuthenticateConfig Bool
+ Happstack.Authenticate.Core: usernameAcceptable :: Lens' AuthenticateConfig (Username -> Maybe CoreError)
+ Happstack.Authenticate.Core: usernamePolicy :: Username -> Maybe CoreError
+ Happstack.Authenticate.Password.Core: PasswordConfig :: Text -> Text -> (Text -> Maybe Text) -> PasswordConfig
+ Happstack.Authenticate.Password.Core: UnacceptablePassword :: Text -> PasswordError
+ Happstack.Authenticate.Password.Core: [_domain] :: PasswordConfig -> Text
+ Happstack.Authenticate.Password.Core: [_passwordAcceptable] :: PasswordConfig -> Text -> Maybe Text
+ Happstack.Authenticate.Password.Core: [_resetLink] :: PasswordConfig -> Text
+ Happstack.Authenticate.Password.Core: [passwordErrorMessageMsg] :: PasswordError -> Text
+ Happstack.Authenticate.Password.Core: data PasswordConfig
+ Happstack.Authenticate.Password.Core: domain :: Lens' PasswordConfig Text
+ Happstack.Authenticate.Password.Core: instance GHC.Generics.Constructor Happstack.Authenticate.Password.Core.C1_0PasswordConfig
+ Happstack.Authenticate.Password.Core: instance GHC.Generics.Constructor Happstack.Authenticate.Password.Core.C1_10PasswordError
+ Happstack.Authenticate.Password.Core: instance GHC.Generics.Datatype Happstack.Authenticate.Password.Core.D1PasswordConfig
+ Happstack.Authenticate.Password.Core: instance GHC.Generics.Generic Happstack.Authenticate.Password.Core.PasswordConfig
+ Happstack.Authenticate.Password.Core: instance GHC.Generics.Selector Happstack.Authenticate.Password.Core.S1_0_0PasswordConfig
+ Happstack.Authenticate.Password.Core: instance GHC.Generics.Selector Happstack.Authenticate.Password.Core.S1_0_1PasswordConfig
+ Happstack.Authenticate.Password.Core: instance GHC.Generics.Selector Happstack.Authenticate.Password.Core.S1_0_2PasswordConfig
+ Happstack.Authenticate.Password.Core: instance GHC.Generics.Selector Happstack.Authenticate.Password.Core.S1_10_0PasswordError
+ Happstack.Authenticate.Password.Core: passwordAcceptable :: Lens' PasswordConfig (Text -> Maybe Text)
+ Happstack.Authenticate.Password.Core: resetLink :: Lens' PasswordConfig Text
- Happstack.Authenticate.Core: addTokenCookie :: (Happstack m) => AcidState AuthenticateState -> (UserId -> IO Bool) -> User -> m TokenText
+ Happstack.Authenticate.Core: addTokenCookie :: (Happstack m) => AcidState AuthenticateState -> AuthenticateConfig -> User -> m TokenText
- Happstack.Authenticate.Core: decodeAndVerifyToken :: (MonadIO m) => AcidState AuthenticateState -> TokenText -> m (Maybe (Token, JWT VerifiedJWT))
+ Happstack.Authenticate.Core: decodeAndVerifyToken :: (MonadIO m) => AcidState AuthenticateState -> UTCTime -> TokenText -> m (Maybe (Token, JWT VerifiedJWT))
- Happstack.Authenticate.Core: issueToken :: (MonadIO m) => AcidState AuthenticateState -> (UserId -> IO Bool) -> User -> m TokenText
+ Happstack.Authenticate.Core: issueToken :: (MonadIO m) => AcidState AuthenticateState -> AuthenticateConfig -> User -> m TokenText
- Happstack.Authenticate.OpenId.Core: token :: (Alternative m, Happstack m) => AcidState AuthenticateState -> (UserId -> IO Bool) -> AcidState OpenIdState -> m Response
+ Happstack.Authenticate.OpenId.Core: token :: (Alternative m, Happstack m) => AcidState AuthenticateState -> AuthenticateConfig -> AcidState OpenIdState -> m Response
- Happstack.Authenticate.OpenId.Route: initOpenId :: FilePath -> AcidState AuthenticateState -> (UserId -> IO Bool) -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)
+ Happstack.Authenticate.OpenId.Route: initOpenId :: FilePath -> AcidState AuthenticateState -> AuthenticateConfig -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)
- Happstack.Authenticate.OpenId.Route: routeOpenId :: (Happstack m) => AcidState AuthenticateState -> (UserId -> IO Bool) -> AcidState OpenIdState -> [Text] -> RouteT AuthenticateURL (ReaderT [Lang] m) Response
+ Happstack.Authenticate.OpenId.Route: routeOpenId :: (Happstack m) => AcidState AuthenticateState -> AuthenticateConfig -> AcidState OpenIdState -> [Text] -> RouteT AuthenticateURL (ReaderT [Lang] m) Response
- Happstack.Authenticate.Password.Core: account :: (Happstack m) => AcidState AuthenticateState -> AcidState PasswordState -> Maybe (UserId, AccountURL) -> m (Either PasswordError UserId)
+ Happstack.Authenticate.Password.Core: account :: (Happstack m) => AcidState AuthenticateState -> AcidState PasswordState -> AuthenticateConfig -> PasswordConfig -> Maybe (UserId, AccountURL) -> m (Either PasswordError UserId)
- Happstack.Authenticate.Password.Core: passwordRequestReset :: (Happstack m) => Text -> Text -> AcidState AuthenticateState -> AcidState PasswordState -> m (Either PasswordError Text)
+ Happstack.Authenticate.Password.Core: passwordRequestReset :: (Happstack m) => PasswordConfig -> AcidState AuthenticateState -> AcidState PasswordState -> m (Either PasswordError Text)
- Happstack.Authenticate.Password.Core: passwordReset :: (Happstack m) => AcidState AuthenticateState -> AcidState PasswordState -> m (Either PasswordError ())
+ Happstack.Authenticate.Password.Core: passwordReset :: (Happstack m) => AcidState AuthenticateState -> AcidState PasswordState -> PasswordConfig -> m (Either PasswordError Text)
- Happstack.Authenticate.Password.Core: token :: (Happstack m) => AcidState AuthenticateState -> (UserId -> IO Bool) -> AcidState PasswordState -> m Response
+ Happstack.Authenticate.Password.Core: token :: (Happstack m) => AcidState AuthenticateState -> AuthenticateConfig -> AcidState PasswordState -> m Response
- Happstack.Authenticate.Password.Route: initPassword :: Text -> Text -> FilePath -> AcidState AuthenticateState -> (UserId -> IO Bool) -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)
+ Happstack.Authenticate.Password.Route: initPassword :: PasswordConfig -> FilePath -> AcidState AuthenticateState -> AuthenticateConfig -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)
- Happstack.Authenticate.Password.Route: routePassword :: (Happstack m) => Text -> Text -> AcidState AuthenticateState -> (UserId -> IO Bool) -> AcidState PasswordState -> [Text] -> RouteT AuthenticateURL (ReaderT [Lang] m) Response
+ Happstack.Authenticate.Password.Route: routePassword :: (Happstack m) => PasswordConfig -> AcidState AuthenticateState -> AuthenticateConfig -> AcidState PasswordState -> [Text] -> RouteT AuthenticateURL (ReaderT [Lang] m) Response
- Happstack.Authenticate.Route: initAuthentication :: Maybe FilePath -> (UserId -> IO Bool) -> [FilePath -> AcidState AuthenticateState -> (UserId -> IO Bool) -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)] -> IO (IO (), AuthenticateURL -> RouteT AuthenticateURL (ServerPartT IO) Response, AcidState AuthenticateState)
+ Happstack.Authenticate.Route: 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)
Files
- Happstack/Authenticate/Controller.hs +37/−0
- Happstack/Authenticate/Core.hs +139/−63
- Happstack/Authenticate/OpenId/Controllers.hs +46/−310
- Happstack/Authenticate/OpenId/Core.hs +4/−4
- Happstack/Authenticate/OpenId/Partials.hs +1/−127
- Happstack/Authenticate/OpenId/Route.hs +7/−7
- Happstack/Authenticate/Password/Controllers.hs +121/−45
- Happstack/Authenticate/Password/Core.hs +71/−29
- Happstack/Authenticate/Password/Partials.hs +11/−11
- Happstack/Authenticate/Password/Route.hs +14/−16
- Happstack/Authenticate/Route.hs +5/−5
- happstack-authenticate.cabal +2/−1
- messages/core/en.msg +3/−0
- messages/password/error/en.msg +1/−1
Happstack/Authenticate/Controller.hs view
@@ -52,6 +52,31 @@ }]); + happstackAuthentication.factory('authInterceptor', ['$rootScope', '$q', '$window', 'userService', function ($rootScope, $q, $window, userService) {+ return {+ 'request': function (config) {+ config.headers = config.headers || {};+ u = userService.getUser();+ if (u && u.token) {+ config.headers.Authorization = 'Bearer ' + u.token;+ }+ return config;+ },+ 'responseError': function (rejection) {+ if (rejection.status === 401) {+ // handle the case where the user is not authenticated+ userService.clearUser();+ document.cookie = 'atc=; path=/; expires=Thu, 01-Jan-70 00:00:01 GMT;';+ }+ return $q.reject(rejection);+ }+ };+ }]);++ happstackAuthentication.config(['$httpProvider', function ($httpProvider) {+ $httpProvider.interceptors.push('authInterceptor');+ }]);+ // add userService happstackAuthentication.factory('userService', ['$rootScope', function ($rootScope) { @@ -87,6 +112,18 @@ }, getUser: function() {+ function getCookie(cname) {+ var name = cname + "=";+ var ca = document.cookie.split(';');+ for(var i=0; i<ca.length; i++) {+ var c = ca[i];+ while (c.charAt(0)==' ') c = c.substring(1);+ if (c.indexOf(name) == 0) return c.substring(name.length,c.length);+ }+ return "";+ };+ if (getCookie('atc').length == 0 && this.userCache.isAuthenticated == true)+ this.clearUser(); return(this.userCache); },
Happstack/Authenticate/Core.hs view
@@ -33,7 +33,11 @@ -} module Happstack.Authenticate.Core- ( HappstackAuthenticateI18N(..)+ ( AuthenticateConfig(..)+ , isAuthAdmin+ , usernameAcceptable+ , requireEmail+ , HappstackAuthenticateI18N(..) , UserId(..) , unUserId , rUserId@@ -45,6 +49,7 @@ , Username(..) , unUsername , rUsername+ , usernamePolicy , Email(..) , unEmail , User(..)@@ -140,28 +145,82 @@ import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import Data.Time (UTCTime, addUTCTime, diffUTCTime, getCurrentTime)+import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds, posixSecondsToUTCTime) import Data.UserId (UserId(..), rUserId, succUserId, unUserId) import GHC.Generics (Generic) import Happstack.Server (Cookie(secure), CookieLife(Session, MaxAge), Happstack, ServerPartT, Request(rqSecure), Response, addCookie, askRq, expireCookie, getHeaderM, lookCookie, lookCookieValue, mkCookie, notFound, toResponseBS)+-- import Happstack.Server.Internal.Clock (getApproximateUTCTime) import Language.Javascript.JMacro-import Prelude hiding ((.), id)+import Prelude hiding ((.), id, exp) import System.IO (IOMode(ReadMode), withFile) import System.Random (randomRIO) import Text.Boomerang.TH (makeBoomerangs) import Text.Shakespeare.I18N (RenderMessage(renderMessage), mkMessageFor)-import Web.JWT (Algorithm(HS256), JWT, VerifiedJWT, JWTClaimsSet(..), encodeSigned, claims, decode, decodeAndVerifySignature, secret, verify)+import Web.JWT (Algorithm(HS256), JWT, VerifiedJWT, JWTClaimsSet(..), encodeSigned, claims, decode, decodeAndVerifySignature, secondsSinceEpoch, intDate, secret, verify) import Web.Routes (RouteT, PathInfo(..), nestURL) import Web.Routes.Boomerang import Web.Routes.Happstack () import Web.Routes.TH (derivePathInfo) -data HappstackAuthenticateI18N = HappstackAuthenticateI18N- -- | when creating JSON field names, drop the first character. Since -- we are using lens, the leading character should always be _. jsonOptions :: Options jsonOptions = defaultOptions { fieldLabelModifier = drop 1 } +data HappstackAuthenticateI18N = HappstackAuthenticateI18N++------------------------------------------------------------------------------+-- CoreError+------------------------------------------------------------------------------++-- | the `CoreError` type is used to represent errors in a language+-- agnostic manner. The errors are translated into human readable form+-- via the I18N translations.+data CoreError+ = HandlerNotFound -- AuthenticationMethod+ | URLDecodeFailed+ | UsernameAlreadyExists+ | AuthorizationRequired+ | Forbidden+ | JSONDecodeFailed+ | InvalidUserId+ | UsernameNotAcceptable+ | InvalidEmail+ | TextError Text+ deriving (Eq, Ord, Read, Show, Data, Typeable, Generic)+instance ToJSON CoreError where toJSON = genericToJSON jsonOptions+instance FromJSON CoreError where parseJSON = genericParseJSON jsonOptions++instance ToJExpr CoreError where+ toJExpr = toJExpr . toJSON++deriveSafeCopy 0 'base ''CoreError++mkMessageFor "HappstackAuthenticateI18N" "CoreError" "messages/core" ("en")++data Status+ = Ok+ | NotOk+ deriving (Eq, Ord, Read, Show, Data, Typeable, Generic)+deriveSafeCopy 1 'base ''Status+-- makeLenses ''Status+makeBoomerangs ''Status++instance ToJSON Status where toJSON = genericToJSON jsonOptions+instance FromJSON Status where parseJSON = genericParseJSON jsonOptions++data JSONResponse = JSONResponse+ { _jrStatus :: Status+ , _jrData :: A.Value+ }+ deriving (Eq, Read, Show, Data, Typeable, Generic)+-- deriveSafeCopy 1 'base ''JSONResponse+makeLenses ''JSONResponse+makeBoomerangs ''JSONResponse++instance ToJSON JSONResponse where toJSON = genericToJSON jsonOptions+instance FromJSON JSONResponse where parseJSON = genericParseJSON jsonOptions+ -- | convert a value to a JSON encoded 'Response' toJSONResponse :: (RenderMessage HappstackAuthenticateI18N e, ToJSON a) => Either e a -> Response toJSONResponse (Left e) = toJSONError e@@ -169,11 +228,14 @@ -- | convert a value to a JSON encoded 'Response' toJSONSuccess :: (ToJSON a) => a -> Response-toJSONSuccess a = toResponseBS "application/json" (A.encode a)+toJSONSuccess a = toResponseBS "application/json" (A.encode (JSONResponse Ok (A.toJSON a))) -- | convert an error to a JSON encoded 'Response'+--+-- FIXME: I18N toJSONError :: forall e. (RenderMessage HappstackAuthenticateI18N e) => e -> Response-toJSONError e = toResponseBS "application/json" (A.encode (A.object ["error" A..= renderMessage HappstackAuthenticateI18N ["en"] e]))+toJSONError e = toResponseBS "application/json" (A.encode (JSONResponse NotOk (A.toJSON (renderMessage HappstackAuthenticateI18N ["en"] e))))+-- (A.encode (A.object ["error" A..= renderMessage HappstackAuthenticateI18N ["en"] e])) ------------------------------------------------------------------------------ @@ -261,6 +323,31 @@ (ixFun $ maybeToList . view email) ------------------------------------------------------------------------------+-- 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+ }+ deriving (Typeable, Generic)+makeLenses ''AuthenticateConfig++-- | a very basic policy for 'userAcceptable'+--+-- Enforces:+--+-- 'Username' can not be empty+usernamePolicy :: Username+ -> Maybe CoreError+usernamePolicy username =+ if Text.null $ username ^. unUsername+ then Just UsernameNotAcceptable+ else Nothing++------------------------------------------------------------------------------ -- SharedSecret ------------------------------------------------------------------------------ @@ -304,32 +391,6 @@ initialSharedSecrets = Map.empty --------------------------------------------------------------------------------- CoreError----------------------------------------------------------------------------------- | the `CoreError` type is used to represent errors in a language--- agnostic manner. The errors are translated into human readable form--- via the I18N translations.-data CoreError- = HandlerNotFound -- AuthenticationMethod- | URLDecodeFailed- | UsernameAlreadyExists- | AuthorizationRequired- | Forbidden- | JSONDecodeFailed- | InvalidUserId- deriving (Eq, Ord, Read, Show, Data, Typeable, Generic)-instance ToJSON CoreError where toJSON = genericToJSON jsonOptions-instance FromJSON CoreError where parseJSON = genericParseJSON jsonOptions--instance ToJExpr CoreError where- toJExpr = toJExpr . toJSON--deriveSafeCopy 0 'base ''CoreError--mkMessageFor "HappstackAuthenticateI18N" "CoreError" "messages/core" ("en")-------------------------------------------------------------------------------- -- NewAccountMode ------------------------------------------------------------------------------ @@ -425,17 +486,16 @@ createUser :: User -> Update AuthenticateState (Either CoreError User) createUser u =- do as@AuthenticateState{..} <- get- if IxSet.null $ (as ^. users) @= (u ^. username)- then do- let user' = set userId _nextUserId u- as' = as { _users = IxSet.insert user' _users- , _nextUserId = succ _nextUserId- }- put as'- return (Right user')- else- return (Left UsernameAlreadyExists)+ do as@AuthenticateState{..} <- get+ if IxSet.null $ (as ^. users) @= (u ^. username)+ then do let user' = set userId _nextUserId u+ as' = as { _users = IxSet.insert user' _users+ , _nextUserId = succ _nextUserId+ }+ put as'+ return (Right user')+ else+ return (Left UsernameAlreadyExists) -- | Create a new 'User'. This will allocate a new 'UserId'. The -- returned 'User' value will have the updated 'UserId'.@@ -452,7 +512,6 @@ put as' return user - -- | Update an existing 'User'. Must already have a valid 'UserId'. updateUser :: User -> Update AuthenticateState ()@@ -486,6 +545,8 @@ return $ getOne $ us @= userId -- | look up a 'User' by their 'Email'+--+-- NOTE: if the email is associated with more than one account this will return 'Nothing' getUserByEmail :: Email -> Query AuthenticateState (Maybe User) getUserByEmail email =@@ -558,25 +619,29 @@ -- `OpenId` realm, change the registeration mode, etc. issueToken :: (MonadIO m) => AcidState AuthenticateState- -> (UserId -> IO Bool) -- ^ isAuthAdmin function+ -> AuthenticateConfig -> User -- ^ the user -> m TokenText-issueToken authenticateState isAuthAdmin user =+issueToken authenticateState authenticateConfig user = do ssecret <- getOrGenSharedSecret authenticateState (user ^. userId)- admin <- liftIO $ isAuthAdmin (user ^. userId)- let claims = def { unregisteredClaims =+ admin <- liftIO $ (authenticateConfig ^. isAuthAdmin) (user ^. userId)+ now <- liftIO getCurrentTime+ let claims = def { exp = intDate $ utcTimeToPOSIXSeconds (addUTCTime (60*60*24*30) now)+ , unregisteredClaims = Map.fromList [ ("user" , toJSON user) , ("authAdmin", toJSON admin)- ] }+ ]+ } return $ encodeSigned HS256 (secret $ _unSharedSecret ssecret) claims -- | decode and verify the `TokenText`. If successful, return the -- `Token` otherwise `Nothing`. decodeAndVerifyToken :: (MonadIO m) => AcidState AuthenticateState+ -> UTCTime -> TokenText -> m (Maybe (Token, JWT VerifiedJWT))-decodeAndVerifyToken authenticateState token =+decodeAndVerifyToken authenticateState now token = do -- decode unverified token let mUnverified = decode token case mUnverified of@@ -598,13 +663,19 @@ -- finally we can verify all the claims case verify (secret (_unSharedSecret ssecret)) unverified of Nothing -> return Nothing- (Just verified) ->- case Map.lookup "authAdmin" (unregisteredClaims (claims verified)) of- Nothing -> return (Just (Token u False, verified))- (Just a) ->- case fromJSON a of- (Error _) -> return (Just (Token u False, verified))- (Success b) -> return (Just (Token u b, verified))+ (Just verified) -> -- check expiration+ case exp (claims verified) of+ -- exp field missing, expire now+ Nothing -> return Nothing+ (Just exp') ->+ if (utcTimeToPOSIXSeconds now) > (secondsSinceEpoch exp')+ then return Nothing+ else case Map.lookup "authAdmin" (unregisteredClaims (claims verified)) of+ Nothing -> return (Just (Token u False, verified))+ (Just a) ->+ case fromJSON a of+ (Error _) -> return (Just (Token u False, verified))+ (Success b) -> return (Just (Token u b, verified)) ------------------------------------------------------------------------------ -- Token in a Cookie@@ -619,13 +690,14 @@ -- see also: `issueToken` addTokenCookie :: (Happstack m) => AcidState AuthenticateState- -> (UserId -> IO Bool)+ -> AuthenticateConfig -> User -> m TokenText-addTokenCookie authenticateState isAuthAdmin user =- do token <- issueToken authenticateState isAuthAdmin user+addTokenCookie authenticateState authenticateConfig user =+ do token <- issueToken authenticateState authenticateConfig user s <- rqSecure <$> askRq -- FIXME: this isn't that accurate in the face of proxies addCookie (MaxAge (60*60*24*30)) ((mkCookie authCookieName (Text.unpack token)) { secure = s })+-- addCookie (MaxAge 60) ((mkCookie authCookieName (Text.unpack token)) { secure = s }) return token -- | delete the `Token` `Cookie`@@ -643,7 +715,9 @@ do mToken <- optional $ lookCookieValue authCookieName case mToken of Nothing -> return Nothing- (Just token) -> decodeAndVerifyToken authenticateState (Text.pack token)+ (Just token) ->+ do now <- liftIO getCurrentTime+ decodeAndVerifyToken authenticateState now (Text.pack token) ------------------------------------------------------------------------------@@ -660,7 +734,8 @@ Nothing -> return Nothing (Just auth') -> do let auth = B.drop 7 auth'- decodeAndVerifyToken authenticateState (Text.decodeUtf8 auth)+ now <- liftIO getCurrentTime+ decodeAndVerifyToken authenticateState now (Text.decodeUtf8 auth) ------------------------------------------------------------------------------ -- Token in a Header or Cookie@@ -752,3 +827,4 @@ -> RouteT AuthenticateURL m a nestAuthenticationMethod authenticationMethod = nestURL $ \methodURL -> AuthenticationMethods $ Just (authenticationMethod, toPathSegments methodURL)+
Happstack/Authenticate/OpenId/Controllers.hs view
@@ -39,322 +39,58 @@ -> JStat openIdCtrlJs mRealm showURL = [jmacro|- var openId = angular.module('openId', ['happstackAuthentication']);- var openIdWindow;- tokenCB = function (token) { alert('tokenCB: ' + token); };-- openId.controller('OpenIdCtrl', ['$scope','$http','$window', '$location', 'userService', function ($scope, $http, $window, $location, userService) {- $scope.openIdRealm = { srOpenIdRealm: `(fromMaybe "" mRealm)` };-// $scope.isAuthenticated = userService.getUser().isAuthenticated;--// $scope.$watch(function () { return userService.getUser().isAuthenticated; }, function(newVal, oldVal) { $scope.isAuthenticated = newVal; });-- $scope.openIdWindow = function (providerUrl) {-// openIdWindow = window.open(`(showURL ReturnTo [])`, "_blank", "toolbar=0");- tokenCB = function(token) { var u = userService.updateFromToken(token); $scope.isAuthenticated = u.isAuthenticated; $scope.$apply(); };- openIdWindow = window.open(providerUrl, "_blank", "toolbar=0");- };-- $scope.setOpenIdRealm = function (setRealmUrl) {- $http.post(setRealmUrl, $scope.openIdRealm).- success(function(datum, status, headers,config) {- $scope.set_openid_realm_msg = 'Realm Updated.'; // FIXME -- I18N- }).- error(function (datum, status, headers, config) {- $scope.set_openid_realm_msg = datum.error;- })};- }]);-- openId.directive('openidGoogle', ['$rootScope', function ($rootScope) {- return {- restrict: 'E',- replace: true,- templateUrl: `(showURL (Partial UsingGoogle) [])`- };- }]);-- openId.directive('openidYahoo', ['$rootScope', function ($rootScope) {- return {- restrict: 'E',- replace: true,- templateUrl: `(showURL (Partial UsingYahoo) [])`- };- }]);-- openId.directive('openidRealm', ['$rootScope', function ($rootScope) {- return {- restrict: 'E',- templateUrl: `(showURL (Partial RealmForm) [])`- };- }]);-- |]--{--openIdCtrlJs showURL = [jmacro|- {- //this is used to parse the profile- function url_base64_decode(str) {- var output = str.replace('-', '+').replace('_', '/');- switch (output.length % 4) {- case 0:- break;- case 2:- output += '==';- break;- case 3:- output += '=';- break;- default:- throw 'Illegal base64url string!';- }- return window.atob(output); //polifyll https://github.com/davidchambers/Base64.js- }-- var happstackAuthentication = angular.module('happstackAuthentication', []);- happstackAuthentication.factory('userService', ['$rootScope', function ($rootScope) {- var defaultUser = { isAuthenticated: false,- username: '<unset>',- token: null- };-- var service = {- userCache: defaultUser,- setUser: function(u) {-// alert('setUser:' + JSON.stringify(u));- userCache = u;- localStorage.setItem('user', JSON.stringify(u));- },-- getUser: function() {- var item = localStorage.getItem('user');- if (item) {-// alert('getUser: ' + item);- var user = JSON.parse(item);- return(user);- }- },-- clearUser: function () {- userCache = defaultUser;- this.setUser(defaultUser);- }- };-- if (!localStorage.getItem('user')) {- service.clearUser();- }-- return service;- }]);-- var openId = angular.module('openId', ['happstackAuthentication']);-- openId.controller('UsernamePasswordCtrl', ['$scope','$http','$window', '$location', 'userService', function ($scope, $http, $window, $location, userService) {- $scope.isAuthenticated = userService.getUser().isAuthenticated;-- $scope.login = function () {- $http.- post(`(showURL Token [])`, $scope.user).- success(function (datum, status, headers, config) {- var encodedClaims = datum.token.split('.')[1];- var claims = JSON.parse(url_base64_decode(encodedClaims));- $scope.username_password_error = '';-- var u = userService.getUser();- u.isAuthenticated = true;- $scope.isAuthenticated = true;- u.username = $scope.user.user; // FIXME: this should come from the token- u.token = datum.token;- userService.setUser(u);- }).- error(function (datum, status, headers, config) {- // Erase the token if the user fails to log in- userService.clearUser();- $scope.isAuthenticated = false;-- // Handle login errors here- $scope.username_password_error = datum.error;- });- };-- $scope.logout = function () {- userService.clearUser();- $scope.isAuthenticated = false;- };-- $scope.signupPassword = function () {- $scope.signup.naUser.userId = 0;- $http.- post(`(showURL (Account Nothing) [])`, $scope.signup).- success(function (datum, status, headers, config) {- $scope.signup_error = 'Account Created'; // FIXME -- I18N- $scope.signup = {};- }).- error(function (datum, status, headers, config) {- $scope.signup_error = datum.error;- });- };-- $scope.changePassword = function (url) {- var u = userService.getUser();- if (u.isAuthenticated) {- $http.- post(url, $scope.password).- success(function (datum, status, headers, config) {- $scope.change_password_error = 'Password Changed.'; // FIXME -- I18N- $scope.password = {};- }).- error(function (datum, status, headers, config) {- $scope.change_password_error = datum.error;- });- } else {- $scope.change_password_error = 'Not Authenticated.'; // FIXME -- I18N- }- };-- $scope.requestResetPassword = function () {- $http.post(`(showURL PasswordRequestReset [])`, $scope.requestReset).- success(function (datum, status, headers, config) {- $scope.request_reset_password_msg = datum;- }).- error(function (datum, status, headers, config) {- $scope.request_reset_password_msg = datum.error;- });- };-- $scope.resetPassword = function () {- var resetToken = $location.search().reset_token;- if (resetToken) {- $scope.reset.rpResetToken = resetToken;- $http.post(`(showURL PasswordReset [])`, $scope.reset).- success(function (datum, status, headers, config) {- $scope.reset_password_msg = datum;- }).- error(function (datum, status, headers, config) {- $scope.reset_password_msg = datum.error;- });- } else {- $scope.reset_password_msg = "reset token not found."; // FIXME -- I18N- }- };-- }]);-- openId.factory('authInterceptor', ['$rootScope', '$q', '$window', 'userService', function ($rootScope, $q, $window, userService) {- return {- request: function (config) {- config.headers = config.headers || {};- u = userService.getUser();- if (u && u.token) {- config.headers.Authorization = 'Bearer ' + u.token;- }- return config;- },- responseError: function (rejection) {- if (rejection.status === 401) {- // handle the case where the user is not authenticated- }- return $q.reject(rejection);- }- };- }]);-- openId.config(['$httpProvider', function ($httpProvider) {- $httpProvider.interceptors.push('authInterceptor');- }]);-- // upAuthenticated directive- openId.directive('upAuthenticated', ['$rootScope', 'userService', function ($rootScope, userService) {- return {- restrict: 'A',- link: function (scope, element, attrs) {- var prevDisp = element.css('display');- $rootScope.$watch(function () { return userService.getUser().isAuthenticated; },- function(auth) {- if (auth != (attrs.upAuthenticated == 'true')) {- element.css('display', 'none');- } else {- element.css('display', prevDisp);- }- });- }- };- }]);-- // upLoginInline directive- openId.directive('upLoginInline', ['$rootScope', 'userService', function ($rootScope, userService) {- return {- restrict: 'E',-// replace: true,- templateUrl: `(showURL (Partial LoginInline) [])`- };- }]);-- // upChangePassword directive- openId.directive('upChangePassword', ['$rootScope', '$http', '$compile', 'userService', function ($rootScope, $http, $compile, userService) {-- function link(scope, element, attrs) {- $rootScope.$watch(function() { return userService.getUser().isAuthenticated; },- function(auth) {- if (auth == true) {- $http.get(`(showURL (Partial ChangePassword) [])`).- success(function(datum, status, headers, config) {- element.empty();- var newElem = angular.element(datum);- element.append(newElem);- $compile(newElem)(scope);- });- } else {- element.empty();- }- });-- }+ var openId = angular.module('openId', ['happstackAuthentication']);+ var openIdWindow;+ tokenCB = function (token) { alert('tokenCB: ' + token); }; - return {- restrict: 'E',- link: link-// templateUrl: `(showURL (Partial ChangePassword) [])`-// template: "<div ng-include=\"'" + `(showURL (Partial ChangePassword) [])` + "'\"></div>"-// template: "<div ng-include=\"'/authenticate/authentication-methods/password/partial/change-password'\"></div>"- };- /*- return {- restrict: 'E',- templateUrl: `(showURL (Partial ChangePassword) [])`- };-*/- }]);+ openId.controller('OpenIdCtrl', ['$scope','$http','$window', '$location', 'userService', function ($scope, $http, $window, $location, userService)+ { $scope.openIdRealm = { srOpenIdRealm: `(fromMaybe "" mRealm)` }; + $scope.openIdWindow = function (providerUrl) {+ tokenCB = function(token) { var u = userService.updateFromToken(token); $scope.isAuthenticated = u.isAuthenticated; $scope.$apply(); };+ openIdWindow = window.open(providerUrl, "_blank", "toolbar=0");+ }; - // upRequestResetPassword directive- openId.directive('upRequestResetPassword', [function () {- return {- restrict: 'E',-// replace: true,- templateUrl: `(showURL (Partial RequestResetPasswordForm) [])`- };- }]);+ $scope.setOpenIdRealm = function (setRealmUrl) {+ function callback(datum, status, headers, config) {+ if (datum == null) {+ $scope.username_password_error = 'error communicating with the server.';+ } else {+ if (datum.jrStatus == "Ok") {+ $scope.set_openid_realm_msg = 'Realm Updated.'; // FIXME -- I18N+// $scope.openIdRealm = '';+ } else {+ $scope.set_open_id_realm_msg = datum.jrData;+ }+ }+ }; - // upResetPassword directive- openId.directive('upResetPassword', [function () {- return {- restrict: 'E',-// replace: true,- templateUrl: `(showURL (Partial ResetPasswordForm) [])`- };- }]);+ $http.post(setRealmUrl, $scope.openIdRealm).+ success(callback).+ error(callback);+ };+ }]); - openId.directive('upSignupPassword', [function () {- return {- restrict: 'E',-// replace: true,- templateUrl: `(showURL (Partial SignupPassword) [])`- };+ openId.directive('openidGoogle', ['$rootScope', function ($rootScope) {+ return {+ restrict: 'E',+ replace: true,+ templateUrl: `(showURL (Partial UsingGoogle) [])`+ }; }]); + openId.directive('openidYahoo', ['$rootScope', function ($rootScope) {+ return {+ restrict: 'E',+ replace: true,+ templateUrl: `(showURL (Partial UsingYahoo) [])`+ };+ }]); - }+ openId.directive('openidRealm', ['$rootScope', function ($rootScope) {+ return {+ restrict: 'E',+ templateUrl: `(showURL (Partial RealmForm) [])`+ };+ }]); |]--}
Happstack/Authenticate/OpenId/Core.hs view
@@ -25,7 +25,7 @@ import qualified Data.Map as Map import Data.UserId (UserId) import GHC.Generics (Generic)-import Happstack.Authenticate.Core (AuthenticateState, CoreError(..), CreateAnonymousUser(..), GetUserByUserId(..), HappstackAuthenticateI18N(..), addTokenCookie, getToken, jsonOptions, toJSONError, toJSONSuccess, toJSONResponse, tokenIsAuthAdmin, userId)+import Happstack.Authenticate.Core (AuthenticateConfig(..), AuthenticateState, CoreError(..), CreateAnonymousUser(..), GetUserByUserId(..), HappstackAuthenticateI18N(..), addTokenCookie, getToken, jsonOptions, toJSONError, toJSONSuccess, toJSONResponse, tokenIsAuthAdmin, userId) import Happstack.Authenticate.OpenId.URL import Happstack.Server (RqBody(..), Happstack, Method(..), Response, askRq, unauthorized, badRequest, internalServerError, forbidden, lookPairsBS, method, resp, takeRequestBody, toResponse, toResponseBS, ok) import Language.Javascript.JMacro@@ -172,10 +172,10 @@ token :: (Alternative m, Happstack m) => AcidState AuthenticateState- -> (UserId -> IO Bool)+ -> AuthenticateConfig -> AcidState OpenIdState -> m Response-token authenticateState isAuthAdmin openIdState =+token authenticateState authenticateConfig openIdState = do identifier <- getIdentifier mUserId <- query' openIdState (IdentifierToUserId identifier) mUser <- case mUserId of@@ -192,7 +192,7 @@ return (Just u) case mUser of Nothing -> internalServerError $ toJSONError $ CoreError InvalidUserId- (Just user) -> do token <- addTokenCookie authenticateState isAuthAdmin user+ (Just user) -> do token <- addTokenCookie authenticateState authenticateConfig user let tokenBS = TL.encodeUtf8 $ TL.fromStrict token -- ok $ toResponse token ok $ toResponseBS "text/html" $ "<html><head><script type='text/javascript'>window.opener.tokenCB('" <> tokenBS <> "'); window.close();</script></head><body></body></html>"
Happstack/Authenticate/OpenId/Partials.hs view
@@ -90,6 +90,7 @@ [hsx| <div ng-show="claims.authAdmin"> <form ng-submit=setOpenIdRealmFn role="form">+ <div class="form-group">{{set_openid_realm_msg}}</div> <div class="form-group"> <label for="openid-realm"><% OpenIdRealmMsg %></label> <input class="form-control" ng-model="openIdRealm.srOpenIdRealm" type="text" id="openid-realm" name="openIdRealm" />@@ -98,132 +99,5 @@ <input class="form-control" type="submit" value=SetRealmMsg /> </div> </form>- <div>{{set_openid_realm_msg}}</div> </div> |]--{--signupPasswordForm :: (Functor m, Monad m) =>- Partial m XML-signupPasswordForm =- [hsx|- <form ng-submit="signupPassword()" role="form">- <div class="form-group">- <label class="sr-only" for="su-username"><% UsernameMsg %></label>- <input class="form-control" ng-model="signup.naUser.username" type="text" id="username" name="su-username" placeholder=UsernameMsg />- </div>- <div class="form-group">- <label class="sr-only" for="su-email"><% EmailMsg %></label>- <input class="form-control" ng-model="signup.naUser.email" type="email" id="su-email" name="email" placeholder=EmailMsg />- </div>- <div class="form-group">- <label class="sr-only" for="su-password"><% PasswordMsg %></label>- <input class="form-control" ng-model="signup.naPassword" type="password" id="su-password" name="su-pass" placeholder=PasswordMsg />- </div>- <div class="form-group">- <label class="sr-only" for="su-password-confirm"><% PasswordConfirmationMsg %></label>- <input class="form-control" ng-model="signup.naPasswordConfirm" type="password" id="su-password-confirm" name="su-pass-confirm" placeholder=PasswordConfirmationMsg />- </div>- <div class="form-group">- <input class="form-control" type="submit" value=SignUpMsg />- </div>- <div>{{signup_error}}</div>- </form>- |]--usernamePasswordForm :: (Functor m, Monad m) =>- Partial m XML-usernamePasswordForm = [hsx|- <div>- <div ng-show="!isAuthenticated">- <form ng-submit="login()" role="form" class="navbar-form navbar-right">- <div class="form-group">- <label class="sr-only" for="username"><% UsernameMsg %> </label>- <input class="form-control" ng-model="user.user" type="text" id="username" name="user" placeholder=UsernameMsg />- </div><% " " :: Text %>- <div class="form-group">- <label class="sr-only" for="password"><% PasswordMsg %></label>- <input class="form-control" ng-model="user.password" type="password" id="password" name="pass" placeholder=PasswordMsg />- </div><% " " :: Text %>- <div class="form-group">- <input class="form-control" type="submit" value=SignInMsg />- </div>- </form>- <div>{{username_password_error}}</div>- </div>- <div ng-show="isAuthenticated">- <a class="navbar-right" ng-click="logout()" href=""><% LogoutMsg %></a>- </div>- </div>- |]--changePasswordForm :: (Functor m, MonadIO m) =>- UserId- -> Partial m XML-changePasswordForm userId =- do url <- lift $ nestPasswordURL $ showURL (Account (Just (userId, Password)))- let changePasswordFn = "changePassword('" <> url <> "')"- [hsx|- <form ng-submit=changePasswordFn role="form">- <div class="form-group">- <label class="sr-only" for="password"><% OldPasswordMsg %></label>- <input class="form-control" ng-model="password.cpOldPassword" type="password" id="old-password" name="old-pass" placeholder=OldPasswordMsg />- </div>- <div class="form-group">- <label class="sr-only" for="password"><% NewPasswordMsg %></label>- <input class="form-control" ng-model="password.cpNewPassword" type="password" id="new-password" name="new-pass" placeholder=NewPasswordMsg />- </div>- <div class="form-group">- <label class="sr-only" for="password"><% NewPasswordConfirmationMsg %></label>- <input class="form-control" ng-model="password.cpNewPasswordConfirm" type="password" id="new-password-confirm" name="new-pass-confirm" placeholder=NewPasswordConfirmationMsg />- </div>- <div class="form-group">- <input class="form-control" type="submit" value=ChangePasswordMsg />- </div>- <div>{{change_password_error}}</div>- </form>-- |]--requestResetPasswordForm :: (Functor m, MonadIO m) =>- Partial m XML-requestResetPasswordForm =- do -- url <- lift $ nestPasswordURL $ showURL PasswordReset- -- let changePasswordFn = "resetPassword('" <> url <> "')"- [hsx|- <div>- <form ng-submit="requestResetPassword()" role="form">- <div class="form-group">- <label class="sr-only" for="reset-username"><% UsernameMsg %></label>- <input class="form-control" ng-model="requestReset.rrpUsername" type="text" id="reset-username" name="username" placeholder=UsernameMsg />- </div>- <div class="form-group">- <input class="form-control" type="submit" value=RequestPasswordResetMsg />- </div>- </form>- <div>{{request_reset_password_msg}}</div>- </div>- |]--resetPasswordForm :: (Functor m, MonadIO m) =>- Partial m XML-resetPasswordForm =- [hsx|- <div>- <form ng-submit="resetPassword()" role="form">- <div class="form-group">- <label class="sr-only" for="reset-password"><% PasswordMsg %></label>- <input class="form-control" ng-model="reset.rpPassword" type="password" id="reset-password" name="reset-password" placeholder=PasswordMsg />- </div>- <div class="form-group">- <label class="sr-only" for="reset-password-confirm"><% PasswordConfirmationMsg %></label>- <input class="form-control" ng-model="reset.rpPasswordConfirm" type="password" id="reset-password-confirm" name="reset-password-confirm" placeholder=PasswordConfirmationMsg />- </div>- <div class="form-group">- <input class="form-control" type="submit" value=ChangePasswordMsg />- </div>- </form>- <div>{{reset_password_msg}}</div>- </div>- |]--}
Happstack/Authenticate/OpenId/Route.hs view
@@ -9,7 +9,7 @@ import Data.Acid.Local (createCheckpointAndClose, openLocalStateFrom) import Data.Text (Text) import Data.UserId (UserId)-import Happstack.Authenticate.Core (AuthenticationHandler, AuthenticationMethod, AuthenticateState, AuthenticateURL, CoreError(..), toJSONError, toJSONResponse)+import Happstack.Authenticate.Core (AuthenticationHandler, AuthenticationMethod, AuthenticateConfig, AuthenticateState, AuthenticateURL, CoreError(..), toJSONError, toJSONResponse) import Happstack.Authenticate.OpenId.Core (GetOpenIdRealm(..), OpenIdError(..), OpenIdState, initialOpenIdState, realm, token) import Happstack.Authenticate.OpenId.Controllers (openIdCtrl) import Happstack.Authenticate.OpenId.URL (OpenIdURL(..), openIdAuthenticationMethod, nestOpenIdURL)@@ -31,11 +31,11 @@ routeOpenId :: (Happstack m) => AcidState AuthenticateState- -> (UserId -> IO Bool)+ -> AuthenticateConfig -> AcidState OpenIdState -> [Text] -> RouteT AuthenticateURL (ReaderT [Lang] m) Response-routeOpenId authenticateState isAuthAdmin openIdState pathSegments =+routeOpenId authenticateState authenticateConfig openIdState pathSegments = case parseSegments fromPathSegments pathSegments of (Left _) -> notFound $ toJSONError URLDecodeFailed (Right url) ->@@ -48,7 +48,7 @@ realm <- query' openIdState GetOpenIdRealm forwardURL <- liftIO $ withManager $ getForwardUrl providerURL returnURL realm [] -- [("Email", "http://schema.openid.net/contact/email")] seeOther forwardURL (toResponse ())- ReturnTo -> token authenticateState isAuthAdmin openIdState+ ReturnTo -> token authenticateState authenticateConfig openIdState Realm -> realm authenticateState openIdState ------------------------------------------------------------------------------@@ -57,9 +57,9 @@ initOpenId :: FilePath -> AcidState AuthenticateState- -> (UserId -> IO Bool)+ -> AuthenticateConfig -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)-initOpenId basePath authenticateState isAuthAdmin =+initOpenId basePath authenticateState authenticateConfig = do openIdState <- openLocalStateFrom (combine basePath "openId") initialOpenIdState let shutdown = \normal -> if normal@@ -69,6 +69,6 @@ do langsOveride <- queryString $ lookTexts' "_LANG" langs <- bestLanguage <$> acceptLanguage mapRouteT (flip runReaderT (langsOveride ++ langs)) $- routeOpenId authenticateState isAuthAdmin openIdState pathSegments+ routeOpenId authenticateState authenticateConfig openIdState pathSegments return (shutdown, (openIdAuthenticationMethod, authenticationHandler), openIdCtrl authenticateState openIdState)
Happstack/Authenticate/Password/Controllers.hs view
@@ -21,86 +21,160 @@ var usernamePassword = angular.module('usernamePassword', ['happstackAuthentication']); usernamePassword.controller('UsernamePasswordCtrl', ['$scope','$http','$window', '$location', 'userService', function ($scope, $http, $window, $location, userService) {-/* $scope.isAuthenticated = userService.getUser().isAuthenticated; - $scope.$watch(function () { return userService.getUser().isAuthenticated; }, function(newVal, oldVal) { $scope.isAuthenticated = newVal; });-*/+ // login()+ emptyUser = function() {+ return { user: '',+ password: ''+ };+ };++ $scope.user = emptyUser(); $scope.login = function () {+ function callback(datum, status, headers, config) {+ if (datum == null) {+ $scope.username_password_error = 'error communicating with the server.';+ } else {+ if (datum.jrStatus == "Ok") {+ $scope.username_password_error = '';+ userService.updateFromToken(datum.jrData.token);+ } else {+ userService.clearUser();+ $scope.username_password_error = datum.jrData;+ }+ }+ }; $http. post(`(showURL Token [])`, $scope.user).- success(function (datum, status, headers, config) {- $scope.username_password_error = '';-// $scope.isAuthenticated = true;- userService.updateFromToken(datum.token);- }).- error(function (datum, status, headers, config) {- // Erase the token if the user fails to log in- userService.clearUser();-// $scope.isAuthenticated = false;+ success(callback).+ error(callback);+ }; - // Handle login errors here- $scope.username_password_error = datum.error;- });+ // signupPassword()+ emptySignup = function () {+ return { naUser: { username: '',+ email: ''+ },+ naPassword: '',+ naPasswordConfirm: ''+ }; };+ $scope.signup = emptySignup(); $scope.signupPassword = function () { $scope.signup.naUser.userId = 0;++ function callback(datum, status, headers, config) {+ if (datum == null) {+ $scope.username_password_error = 'error communicating with server.';+ } else {+ if (datum.jrStatus == "Ok") {+ $scope.signup_error = 'Account Created'; // FIXME -- I18N+ $scope.signup = emptySignup();+ } else {+ $scope.signup_error = datum.jrData;+ }+ }+ };+ $http. post(`(showURL (Account Nothing) [])`, $scope.signup).- success(function (datum, status, headers, config) {- $scope.signup_error = 'Account Created'; // FIXME -- I18N- $scope.signup = {};- }).- error(function (datum, status, headers, config) {- $scope.signup_error = datum.error;- });+ success(callback).+ error(callback); }; + // changePassword()+ emptyPassword = function () {+ return { cpOldPassword: '',+ cpNewPassword: '',+ cpNewPasswordConfirm: ''+ };+ };++ $scope.password = emptyPassword(); $scope.changePassword = function (url) { var u = userService.getUser();++ function callback(datum, status, headers, config) {+ if (datum == null) {+ $scope.username_password_error = 'error communicating with server.';+ } else {+ if (datum.jrStatus == "Ok") {+ $scope.change_password_error = 'Password Changed.'; // FIXME -- I18N+ $scope.password = emptyPassword();+ } else {+ $scope.change_password_error = datum.jrData;+ }+ }+ };+ if (u.isAuthenticated) { $http. post(url, $scope.password).- success(function (datum, status, headers, config) {- $scope.change_password_error = 'Password Changed.'; // FIXME -- I18N- $scope.password = {};- }).- error(function (datum, status, headers, config) {- $scope.change_password_error = datum.error;- });+ success(callback).+ error(callback); } else {- $scope.change_password_error = 'Not Authenticated.'; // FIXME -- I18N+ $scope.change_password_error = 'Not Authenticated.'; // FIXME -- I18N } }; + // requestResetPassword()+ requestResetEmpty = function () {+ return { rrpUsername: '' };+ };+ $scope.requestReset = requestResetEmpty(); $scope.requestResetPassword = function () {+ function callback(datum, status, headers, config) {+ if (datum == null) {+ $scope.request_reset_password_msg = 'error communicating with the server.';+ } else {+ if (datum.jrStatus == "Ok") {+ $scope.request_reset_password_msg = datum.jrData;+ $scope.requestReset = requestResetEmpty();+ } else {+ $scope.request_reset_password_msg = datum.jrData;+ }+ }+ }+ $http.post(`(showURL PasswordRequestReset [])`, $scope.requestReset).- success(function (datum, status, headers, config) {- $scope.request_reset_password_msg = datum;- }).- error(function (datum, status, headers, config) {- $scope.request_reset_password_msg = datum.error;- });+ success(callback).+ error(callback); }; + // resetPassword()+ resetEmpty = function () {+ return { rpPassword: '',+ rpPasswordConfirm: ''+ };+ };+ $scope.reset = resetEmpty(); $scope.resetPassword = function () {+ function callback(datum, status, headers, config) {+ if (datum == null) {+ $scope.reset_password_msg = 'error communicating with the server.';+ } else {+ if (datum.jrStatus == "Ok") {+ $scope.reset_password_msg = datum.jrData;+ $scope.reset = resetEmpty();+ } else {+ $scope.reset_password_msg = datum.jrData;+ }+ }+ }+ var resetToken = $location.search().reset_token; if (resetToken) { $scope.reset.rpResetToken = resetToken; $http.post(`(showURL PasswordReset [])`, $scope.reset).- success(function (datum, status, headers, config) {- $scope.reset_password_msg = datum;- }).- error(function (datum, status, headers, config) {- $scope.reset_password_msg = datum.error;- });+ success(callback).+ error(callback); } else { $scope.reset_password_msg = "reset token not found."; // FIXME -- I18N } };- }]);-+ /* usernamePassword.factory('authInterceptor', ['$rootScope', '$q', '$window', 'userService', function ($rootScope, $q, $window, userService) { return { request: function (config) {@@ -114,6 +188,8 @@ responseError: function (rejection) { if (rejection.status === 401) { // handle the case where the user is not authenticated+ userService.clearUser();+ document.cookie = 'atc=; path=/; expires=Thu, 01-Jan-70 00:00:01 GMT;'; } return $q.reject(rejection); }@@ -123,7 +199,7 @@ usernamePassword.config(['$httpProvider', function ($httpProvider) { $httpProvider.interceptors.push('authInterceptor'); }]);-+ */ // upAuthenticated directive usernamePassword.directive('upAuthenticated', ['$rootScope', 'userService', function ($rootScope, userService) { return {
Happstack/Authenticate/Password/Core.hs view
@@ -30,7 +30,7 @@ import Data.Time.Clock.POSIX (getPOSIXTime) import Data.UserId (UserId) import GHC.Generics (Generic)-import Happstack.Authenticate.Core (AuthenticationHandler, AuthenticationMethod(..), AuthenticateState(..), AuthenticateURL, CoreError(..), CreateUser(..), Email(..), GetUserByUsername(..), HappstackAuthenticateI18N(..), SharedSecret(..), User(..), Username(..), GetSharedSecret(..), addTokenCookie, email, getToken, getOrGenSharedSecret, issueToken, jsonOptions, userId, username, toJSONResponse, toJSONError, tokenUser)+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.Password.URL (AccountURL(..)) import Happstack.Server import HSP.JMacro@@ -38,6 +38,7 @@ import Network.HTTP.Types (toQuery, renderQuery) import Network.Mail.Mime (Address(..), Mail, simpleMail', renderMail', renderSendMail, sendmail) import System.FilePath (combine)+import qualified Text.Email.Validate as Email import Text.Shakespeare.I18N (RenderMessage(..), Lang, mkMessageFor) import qualified Web.JWT as JWT import Web.JWT (Algorithm(HS256), JWT, VerifiedJWT, JWTClaimsSet(..), encodeSigned, claims, decode, decodeAndVerifySignature, intDate, secret, secondsSinceEpoch, verify)@@ -45,6 +46,18 @@ import Web.Routes.TH ------------------------------------------------------------------------------+-- PasswordConfig+------------------------------------------------------------------------------++data PasswordConfig = PasswordConfig+ { _resetLink :: Text+ , _domain :: Text+ , _passwordAcceptable :: Text -> Maybe Text+ }+ deriving (Typeable, Generic)+makeLenses ''PasswordConfig++------------------------------------------------------------------------------ -- PasswordError ------------------------------------------------------------------------------ @@ -58,6 +71,7 @@ | MissingResetToken | InvalidResetToken | PasswordMismatch+ | UnacceptablePassword { passwordErrorMessageMsg :: Text } | CoreError { passwordErrorMessageE :: CoreError } deriving (Eq, Ord, Read, Show, Data, Typeable, Generic) instance ToJSON PasswordError where toJSON = genericToJSON jsonOptions@@ -179,10 +193,10 @@ token :: (Happstack m) => AcidState AuthenticateState- -> (UserId -> IO Bool)+ -> AuthenticateConfig -> AcidState PasswordState -> m Response-token authenticateState isAuthAdmin passwordState =+token authenticateState authenticateConfig passwordState = do method POST (Just (Body body)) <- takeRequestBody =<< askRq case Aeson.decode body of@@ -195,8 +209,8 @@ do valid <- query' passwordState (VerifyPasswordForUserId (u ^. userId) password) if not valid then unauthorized $ toJSONError InvalidUsernamePassword- else do token <- addTokenCookie authenticateState isAuthAdmin u- resp 201 $ toResponseBS "application/json" $ encode $ Object $ HashMap.fromList [("token", toJSON token)]+ else do token <- addTokenCookie authenticateState authenticateConfig u+ resp 201 $ toJSONSuccess (Object $ HashMap.fromList [("token", toJSON token)]) -- toResponseBS "application/json" $ encode $ Object $ HashMap.fromList [("token", toJSON token)] ------------------------------------------------------------------------------ -- account@@ -228,25 +242,47 @@ account :: (Happstack m) => AcidState AuthenticateState -> AcidState PasswordState+ -> AuthenticateConfig+ -> PasswordConfig -> Maybe (UserId, AccountURL) -> m (Either PasswordError UserId) -- handle new account creation via POST to /account -- FIXME: check that password and password confirmation match-account authenticateState passwordState Nothing =+account authenticateState passwordState authenticateConfig passwordConfig Nothing = do method POST (Just (Body body)) <- takeRequestBody =<< askRq case Aeson.decode body of Nothing -> badRequest (Left $ CoreError JSONDecodeFailed) (Just newAccount) ->- do eUser <- update' authenticateState (CreateUser $ _naUser newAccount)- case eUser of- (Left e) -> return $ Left (CoreError e)- (Right user) -> do- hashed <- mkHashedPass (_naPassword newAccount)- update' passwordState (SetPassword (user ^. userId) hashed)- ok $ (Right (user ^. userId))+ case (authenticateConfig ^. usernameAcceptable) (newAccount ^. naUser ^. username) of+ (Just e) -> return $ Left (CoreError e)+ Nothing ->+ case validEmail (authenticateConfig ^. requireEmail) (newAccount ^. naUser ^. email) of+ (Just e) -> return $ Left e+ Nothing ->+ if (newAccount ^. naPassword /= newAccount ^. naPasswordConfirm)+ then ok $ Left PasswordMismatch+ else case (passwordConfig ^. passwordAcceptable) (newAccount ^. naPassword) of+ (Just passwdError) -> ok $ Left (UnacceptablePassword passwdError)+ Nothing -> do+ eUser <- update' authenticateState (CreateUser $ _naUser newAccount)+ case eUser of+ (Left e) -> return $ Left (CoreError e)+ (Right user) -> do+ hashed <- mkHashedPass (_naPassword newAccount)+ update' passwordState (SetPassword (user ^. userId) hashed)+ ok $ (Right (user ^. userId))+ where+ validEmail :: Bool -> Maybe Email -> Maybe PasswordError+ validEmail required mEmail =+ case (required, mEmail) of+ (True, Nothing) -> Just $ CoreError InvalidEmail+ (False, Just (Email "")) -> Nothing+ (False, Nothing) -> Nothing+ (_, Just email) -> if Email.isValid (Text.encodeUtf8 (email ^. unEmail)) then Nothing else Just $ CoreError InvalidEmail+ -- handle updates to /account/<userId>/*-account authenticateState passwordState (Just (uid, url)) =+account authenticateState passwordState authenticateConfig passwordConfig (Just (uid, url)) = case url of Password -> do method POST@@ -268,9 +304,14 @@ do b <- verifyPassword authenticateState passwordState (token ^. tokenUser ^. username) (changePassword ^. cpOldPassword) if not b then forbidden (Left InvalidPassword)- else do pw <- mkHashedPass (changePassword ^. cpNewPassword)- update' passwordState (SetPassword uid pw)- ok $ (Right uid)+ else if (changePassword ^. cpNewPassword /= changePassword ^. cpNewPasswordConfirm)+ then ok $ (Left PasswordMismatch)+ else case (passwordConfig ^. passwordAcceptable) (changePassword ^. cpNewPassword) of+ (Just e) -> ok (Left $ UnacceptablePassword e)+ Nothing -> do+ pw <- mkHashedPass (changePassword ^. cpNewPassword)+ update' passwordState (SetPassword uid pw)+ ok $ (Right uid) ------------------------------------------------------------------------------ -- passwordReset@@ -287,12 +328,11 @@ -- | request reset password passwordRequestReset :: (Happstack m) =>- Text- -> Text+ PasswordConfig -> AcidState AuthenticateState -> AcidState PasswordState -> m (Either PasswordError Text)-passwordRequestReset resetLink domain authenticateState passwordState =+passwordRequestReset passwordConfig authenticateState passwordState = do method POST (Just (Body body)) <- takeRequestBody =<< askRq case Aeson.decode body of@@ -309,9 +349,9 @@ case eResetToken of (Left err) -> return (Left err) (Right resetToken) ->- do let resetLink' = resetLink <> (Text.decodeUtf8 $ renderQuery True $ toQuery [("reset_token"::Text, resetToken)])--- liftIO $ Text.putStrLn resetLink' -- FIXME: don't print to stdout- sendResetEmail toEm (Email ("no-rneplay@" <> domain)) resetLink'+ 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 -- | issueResetToken@@ -357,8 +397,9 @@ passwordReset :: (Happstack m) => AcidState AuthenticateState -> AcidState PasswordState- -> m (Either PasswordError ())-passwordReset authenticateState passwordState =+ -> PasswordConfig+ -> m (Either PasswordError Text)+passwordReset authenticateState passwordState passwordConfig = do method POST (Just (Body body)) <- takeRequestBody =<< askRq case Aeson.decode body of@@ -370,10 +411,11 @@ (Just (user, _)) -> if password /= passwordConfirm then return (Left PasswordMismatch)- else do pw <- mkHashedPass password- update' passwordState (SetPassword (user ^. userId) pw)- ok $ Right ()-+ else case (passwordConfig ^. passwordAcceptable) password of+ (Just e) -> ok $ Left $ UnacceptablePassword e+ Nothing -> do pw <- mkHashedPass password+ update' passwordState (SetPassword (user ^. userId) pw)+ ok $ Right "Password Reset." -- I18N {- do mTokenTxt <- optional $ queryString $ lookText' "reset_btoken" case mTokenTxt of
Happstack/Authenticate/Password/Partials.hs view
@@ -71,7 +71,7 @@ ChangePassword -> do mUser <- getToken authenticateState case mUser of- Nothing -> [hsx| <p><% show NotAuthenticated %></p> |]+ Nothing -> unauthorized =<< [hsx| <p><% show NotAuthenticated %></p> |] -- FIXME: I18N (Just (token, _)) -> changePasswordForm (token ^. tokenUser ^. userId) RequestResetPasswordForm -> requestResetPasswordForm ResetPasswordForm -> resetPasswordForm@@ -81,26 +81,26 @@ signupPasswordForm = [hsx| <form ng-submit="signupPassword()" role="form">+ <div>{{signup_error}}</div> <div class="form-group"> <label class="sr-only" for="su-username"><% UsernameMsg %></label>- <input class="form-control" ng-model="signup.naUser.username" type="text" id="username" name="su-username" placeholder=UsernameMsg />+ <input class="form-control" ng-model="signup.naUser.username" type="text" id="username" name="su-username" value="" placeholder=UsernameMsg /> </div> <div class="form-group"> <label class="sr-only" for="su-email"><% EmailMsg %></label>- <input class="form-control" ng-model="signup.naUser.email" type="email" id="su-email" name="email" placeholder=EmailMsg />+ <input class="form-control" ng-model="signup.naUser.email" type="email" id="su-email" name="email" value="" placeholder=EmailMsg /> </div> <div class="form-group"> <label class="sr-only" for="su-password"><% PasswordMsg %></label>- <input class="form-control" ng-model="signup.naPassword" type="password" id="su-password" name="su-pass" placeholder=PasswordMsg />+ <input class="form-control" ng-model="signup.naPassword" type="password" id="su-password" name="su-pass" value="" placeholder=PasswordMsg /> </div> <div class="form-group"> <label class="sr-only" for="su-password-confirm"><% PasswordConfirmationMsg %></label>- <input class="form-control" ng-model="signup.naPasswordConfirm" type="password" id="su-password-confirm" name="su-pass-confirm" placeholder=PasswordConfirmationMsg />+ <input class="form-control" ng-model="signup.naPasswordConfirm" type="password" id="su-password-confirm" name="su-pass-confirm" value="" placeholder=PasswordConfirmationMsg /> </div> <div class="form-group"> <input class="form-control" type="submit" value=SignUpMsg /> </div>- <div>{{signup_error}}</div> </form> |] @@ -111,6 +111,7 @@ <span> <span ng-show="!isAuthenticated"> <form ng-submit="login()" role="form" (if inline then ["class" := "navbar-form navbar-left"] :: [Attr Text Text] else [])>+ <div class="form-group">{{username_password_error}}</div> <div class="form-group"> <label class="sr-only" for="username"><% UsernameMsg %> </label> <input class="form-control" ng-model="user.user" type="text" id="username" name="user" placeholder=UsernameMsg />@@ -123,7 +124,6 @@ <input class="form-control" type="submit" value=SignInMsg /> </div> </form>- <div>{{username_password_error}}</div> </span> </span> |]@@ -132,7 +132,7 @@ logoutForm = [hsx| <span ng-show="isAuthenticated"> <div class="form-group">- <a class="navbar-right" ng-click="logout()" href=""><% LogoutMsg %></a>+ <a ng-click="logout()" href="#"><% LogoutMsg %></a> </div> </span> |]@@ -145,6 +145,7 @@ let changePasswordFn = "changePassword('" <> url <> "')" [hsx| <form ng-submit=changePasswordFn role="form">+ <div class="form-group">{{change_password_error}}</div> <div class="form-group"> <label class="sr-only" for="password"><% OldPasswordMsg %></label> <input class="form-control" ng-model="password.cpOldPassword" type="password" id="old-password" name="old-pass" placeholder=OldPasswordMsg />@@ -160,7 +161,6 @@ <div class="form-group"> <input class="form-control" type="submit" value=ChangePasswordMsg /> </div>- <div>{{change_password_error}}</div> </form> |]@@ -173,6 +173,7 @@ [hsx| <div> <form ng-submit="requestResetPassword()" role="form">+ <div class="form-group">{{request_reset_password_msg}}</div> <div class="form-group"> <label class="sr-only" for="reset-username"><% UsernameMsg %></label> <input class="form-control" ng-model="requestReset.rrpUsername" type="text" id="reset-username" name="username" placeholder=UsernameMsg />@@ -181,7 +182,6 @@ <input class="form-control" type="submit" value=RequestPasswordResetMsg /> </div> </form>- <div>{{request_reset_password_msg}}</div> </div> |] @@ -191,6 +191,7 @@ [hsx| <div> <form ng-submit="resetPassword()" role="form">+ <div class="form-group">{{reset_password_msg}}</div> <div class="form-group"> <label class="sr-only" for="reset-password"><% PasswordMsg %></label> <input class="form-control" ng-model="reset.rpPassword" type="password" id="reset-password" name="reset-password" placeholder=PasswordMsg />@@ -203,6 +204,5 @@ <input class="form-control" type="submit" value=ChangePasswordMsg /> </div> </form>- <div>{{reset_password_msg}}</div> </div> |]
Happstack/Authenticate/Password/Route.hs view
@@ -6,8 +6,8 @@ import Data.Acid.Local (createCheckpointAndClose, openLocalStateFrom) import Data.Text (Text) import Data.UserId (UserId)-import Happstack.Authenticate.Core (AuthenticationHandler, AuthenticationMethod, AuthenticateState, AuthenticateURL, CoreError(..), toJSONError, toJSONResponse)-import Happstack.Authenticate.Password.Core (PasswordError(..), PasswordState, account, initialPasswordState, passwordReset, passwordRequestReset, token)+import Happstack.Authenticate.Core (AuthenticationHandler, AuthenticationMethod, AuthenticateConfig(..), AuthenticateState, AuthenticateURL, CoreError(..), toJSONError, toJSONResponse)+import Happstack.Authenticate.Password.Core (PasswordConfig(..), PasswordError(..), PasswordState, account, initialPasswordState, passwordReset, passwordRequestReset, token) import Happstack.Authenticate.Password.Controllers (usernamePasswordCtrl) import Happstack.Authenticate.Password.URL (PasswordURL(..), passwordAuthenticationMethod) import Happstack.Authenticate.Password.Partials (routePartial)@@ -25,37 +25,35 @@ ------------------------------------------------------------------------------ routePassword :: (Happstack m) =>- Text- -> Text+ PasswordConfig -> AcidState AuthenticateState- -> (UserId -> IO Bool)+ -> AuthenticateConfig -> AcidState PasswordState -> [Text] -> RouteT AuthenticateURL (ReaderT [Lang] m) Response-routePassword resetLink domain authenticateState isAuthAdmin passwordState pathSegments =+routePassword passwordConfig authenticateState authenticateConfig passwordState pathSegments = case parseSegments fromPathSegments pathSegments of (Left _) -> notFound $ toJSONError URLDecodeFailed (Right url) -> case url of- Token -> token authenticateState isAuthAdmin passwordState- Account mUrl -> toJSONResponse <$> account authenticateState passwordState mUrl+ Token -> token authenticateState authenticateConfig passwordState+ Account mUrl -> toJSONResponse <$> account authenticateState passwordState authenticateConfig passwordConfig mUrl (Partial u) -> do xml <- unXMLGenT (routePartial authenticateState u)- ok $ toResponse (html4StrictFrag, xml)- PasswordRequestReset -> toJSONResponse <$> passwordRequestReset resetLink domain authenticateState passwordState- PasswordReset -> toJSONResponse <$> passwordReset authenticateState passwordState+ return $ toResponse (html4StrictFrag, xml)+ PasswordRequestReset -> toJSONResponse <$> passwordRequestReset passwordConfig authenticateState passwordState+ PasswordReset -> toJSONResponse <$> passwordReset authenticateState passwordState passwordConfig UsernamePasswordCtrl -> toResponse <$> usernamePasswordCtrl ------------------------------------------------------------------------------ -- initPassword ------------------------------------------------------------------------------ -initPassword :: Text- -> Text+initPassword :: PasswordConfig -> FilePath -> AcidState AuthenticateState- -> (UserId -> IO Bool)+ -> AuthenticateConfig -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)-initPassword resetLink domain basePath authenticateState isAuthAdmin =+initPassword passwordConfig basePath authenticateState authenticateConfig = do passwordState <- openLocalStateFrom (combine basePath "password") initialPasswordState let shutdown = \normal -> if normal@@ -65,5 +63,5 @@ do langsOveride <- queryString $ lookTexts' "_LANG" langs <- bestLanguage <$> acceptLanguage mapRouteT (flip runReaderT (langsOveride ++ langs)) $- routePassword resetLink domain authenticateState isAuthAdmin passwordState pathSegments+ routePassword passwordConfig authenticateState authenticateConfig passwordState pathSegments return (shutdown, (passwordAuthenticationMethod, authenticationHandler), usernamePasswordCtrl)
Happstack/Authenticate/Route.hs view
@@ -13,7 +13,7 @@ import Data.UserId (UserId) import HSP.JMacro (IntegerSupply(..)) import Happstack.Authenticate.Controller (authenticateCtrl)-import Happstack.Authenticate.Core (AuthenticateState, AuthenticateURL(..), AuthenticationHandler, AuthenticationHandlers, AuthenticationMethod, CoreError(HandlerNotFound), initialAuthenticateState, toJSONError)+import Happstack.Authenticate.Core (AuthenticateConfig, AuthenticateState, AuthenticateURL(..), AuthenticationHandler, AuthenticationHandlers, AuthenticationMethod, CoreError(HandlerNotFound), initialAuthenticateState, toJSONError) import Happstack.Server (notFound, ok, Response, ServerPartT, ToMessage(toResponse)) import Happstack.Server.JMacro () import Language.Javascript.JMacro (JStat)@@ -46,14 +46,14 @@ initAuthentication :: Maybe FilePath- -> (UserId -> IO Bool)- -> [FilePath -> AcidState AuthenticateState -> (UserId -> IO Bool) -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)]+ -> 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)-initAuthentication mBasePath isAuthAdmin initMethods =+initAuthentication mBasePath authenticateConfig initMethods = do let authenticatePath = combine (fromMaybe "state" mBasePath) "authenticate" authenticateState <- openLocalStateFrom (combine authenticatePath "core") initialAuthenticateState -- FIXME: need to deal with one of the initMethods throwing an exception- (cleanupPartial, handlers, javascript) <- unzip3 <$> mapM (\initMethod -> initMethod authenticatePath authenticateState isAuthAdmin) initMethods+ (cleanupPartial, handlers, javascript) <- unzip3 <$> mapM (\initMethod -> initMethod authenticatePath authenticateState authenticateConfig) initMethods let cleanup = sequence_ $ createCheckpointAndClose authenticateState : (map (\c -> c True) cleanupPartial) h = route javascript (Map.fromList handlers) return (cleanup, h, authenticateState)
happstack-authenticate.cabal view
@@ -1,5 +1,5 @@ Name: happstack-authenticate-Version: 2.2.0+Version: 2.3.0 Synopsis: Happstack Authentication Library Description: A themeable authentication library with support for username+password and OpenId. Homepage: http://www.happstack.com/@@ -49,6 +49,7 @@ bytestring >= 0.9 && < 0.11, containers >= 0.4 && < 0.6, data-default >= 0.5 && < 0.6,+ email-validate >= 2.1 && < 2.2, filepath >= 1.3 && < 1.5, hsx2hs >= 0.13 && < 0.14, jmacro >= 0.6.11 && < 0.7,
messages/core/en.msg view
@@ -5,5 +5,8 @@ Forbidden: Forbidden. JSONDecodeFailed: Failed to decode JSON data. InvalidUserId: Invalid UserId+UsernameNotAcceptable: Username not acceptable.+InvalidEmail: Invalid email address.+
messages/password/error/en.msg view
@@ -7,5 +7,5 @@ MissingResetToken: Missing reset token InvalidResetToken: Invalid reset token PasswordMismatch: Passwords do not match+UnacceptablePassword msg@Text: Unacceptable Password. #{msg} CoreError e@CoreError: #{renderMessage HappstackAuthenticateI18N ["en"] e}-