diff --git a/.gitignore b/.gitignore index e766dea6..5c8a8b91 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*~ *.o *.o_p *.hi @@ -6,7 +7,12 @@ dist client_session_key.aes cabal-dev/ yesod/foobar/ -.virthualenv +.hsenv/ +.cabal-sandbox/ +cabal.sandbox.config /vendor/ -/.shelly/ -/tarballs/ +.shelly/ +tarballs/ +*.swp +dist +client_session_key.aes diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 91468aac..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "scripts"] - path = scripts - url = git://github.com/yesodweb/scripts.git diff --git a/.travis.yml b/.travis.yml index bac77556..a48e4200 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,12 @@ language: haskell install: - cabal update - - cabal install mega-sdist hspec cabal-meta cabal-src - - git clone https://github.com/snoyberg/tagstream-conduit.git - - cd tagstream-conduit - - cabal-src-install --src-only - - cd .. - - cabal-meta install --force-reinstalls --enable-tests + - cabal install --force-reinstalls hspec cabal-meta cabal-src alex + - cabal-meta install --force-reinstalls script: - echo Done + - cabal-meta install --enable-tests + - mega-sdist --test + - cabal install hspec cabal-meta cabal-src + - cabal-meta install --force-reinstalls diff --git a/README b/README new file mode 100644 index 00000000..c2ba6ace --- /dev/null +++ b/README @@ -0,0 +1,15 @@ +Authentication methods for Haskell web applications. + +Note for Rpxnow: +By default on some (all?) installs wget does not come with root certificates +for SSL. If this is the case then Web.Authenticate.Rpxnow.authenticate will +fail as wget cannot establish a secure connection to rpxnow's servers. + +A simple *nix solution, if potentially insecure (man in the middle attacks as +you are downloading the certs) is to grab a copy of the certs extracted from +those that come with firefox, hosted by CURL at +http://curl.haxx.se/ca/cacert.pem , put them somewhere (for ex, +~/.wget/cacert.pem) and then edit your ~/.wgetrc to include: +ca_certificate=~/.wget/cacert.pem + +This should fix the problem. diff --git a/README.md b/README.md index 5098481a..7536fb5a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ An advanced web framework using the Haskell programming language. Featuring: * techniques for constant-space memory consumption * asynchronous IO * this is built in to the Haskell programming language (like Erlang) - * handles a greater concurrent load than any other web application server # Learn more: http://yesodweb.com/ @@ -27,18 +26,19 @@ Your application is a cabal package and you use `cabal` to install its dependenc Install conflicts are unfortunately common in Haskell development. If you are not using any sandbox tools, you may discover that some of the other haskell installs on your system are broken. -You can prevent this by using sandbox tools: `cabal-dev` or `hsenv`. +You can prevent this by using cabal sandbox. -Isolating an entire project with a virtual machine is also a great idea, you just need some tools to help that process. -[Vagrant](http://vagrantup.com) is a great tool for that and there is a [Haskell Platform installer](https://bitbucket.org/puffnfresh/vagrant-haskell-heroku) for it. +Isolating an entire project is also a great idea, you just need some tools to help that process. +On Linux you can use Docker. +On any OS you can use a virtual machine. [Vagrant](http://vagrantup.com) is a great tool for that and there is a [Haskell Platform installer](https://bitbucket.org/puffnfresh/vagrant-haskell-heroku) for it. -## Using cabal-dev +## Using cabal sandbox -cabal-dev creates a sandboxed environment for an individual cabal package. -Instead of using the `cabal` command, use the `cabal-dev` command which will use the sandbox. +To sandbox a project, type: -Use `yesod devel --dev` when developing your application. + cabal sandbox init +This ensures that future installs will be local to the sandboxed directory. ## Installing the latest development version from github for use with your application @@ -55,32 +55,18 @@ In your application folder, create a `sources.txt` file with the following conte https://github.com/yesodweb/wai `./` means build your app. The yesod repos will be cloned and placed in a `vendor` repo. -Now run: `cabal-meta install`. If you use `cabal-dev`, run `cabal-meta --dev install` +Now run: `cabal-meta install`. This should work almost all of the time. You can read more on [cabal-meta](https://github.com/yesodweb/cabal-meta) If you aren't building from an application, remove the `./` and create a new directory for your sources.txt first. -## hsenv (Linux only) +## hsenv (Linux and Mac OS X) -[hsenv](http://hackage.haskell.org/package/hsenv) prevents your custom build of Yesod from interfering with your currently installed cabal packages: +[hsenv](https://github.com/tmhedberg/hsenv) also provides a sandbox, but works at the shell level. +Generally we recommend using cabal sandbox, but hsenv has tools for allowing you to use different versions of GHC, which may be useful for you. -* hsenv creates an isolated environment like cabal-dev -* hsenv works at the shell level, so every shell must activate the hsenv -* cabal-dev by default isolates a single cabal package, but hsenv isolates multiple packages together. -* cabal-dev can isolate multiple packages together by using the -s sandbox argument - - -## cabal-src - -The cabal-src tool helps resolve dependency conflicts when installing local packages. -This capability is already built in if you are using cabal-dev or cabal-meta. Otherwise install cabal-src with: - - cabal install cabal-src - -Whenever you would use `cabal install` to install a local package, use `cabal-src-install` instead. -Our installer script now uses cabal-src-install when it is available. ## Cloning the repos @@ -100,7 +86,7 @@ done ## Building your changes to Yesod -Yesod is composed of 4 "mega-repos", each with multiple cabal packages. `./script/install` will run tests against each package and install each package. +The traditional Yesod stack requires 4 "mega-repos", each with multiple cabal packages. `./script/install` will run tests against each package and install each package. ### install package in all repos diff --git a/package-list.sh b/package-list.sh deleted file mode 100644 index 744f06ec..00000000 --- a/package-list.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -pkgs=( ./yesod-routes - ./yesod-core - ./yesod-json - ./crypto-conduit - ./authenticate/authenticate - ./yesod-static - ./yesod-persistent - ./yesod-newsfeed - ./yesod-form - ./yesod-auth - ./yesod-sitemap - ./yesod-default - ./yesod ) diff --git a/scripts b/scripts deleted file mode 160000 index 9902ff80..00000000 --- a/scripts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9902ff808afbcb417c6ad125941343878e3afe11 diff --git a/sources.txt b/sources.txt index 415fc37e..b7eec6ed 100644 --- a/sources.txt +++ b/sources.txt @@ -9,3 +9,5 @@ ./yesod-test ./yesod-bin ./yesod +./yesod-eventsource +./yesod-websockets diff --git a/yesod-auth-oauth/Yesod/Auth/OAuth.hs b/yesod-auth-oauth/Yesod/Auth/OAuth.hs index fa7f1c76..4839a356 100644 --- a/yesod-auth-oauth/Yesod/Auth/OAuth.hs +++ b/yesod-auth-oauth/Yesod/Auth/OAuth.hs @@ -43,6 +43,7 @@ authOAuth oauth mkCreds = AuthPlugin name dispatch login url = PluginR name [] lookupTokenSecret = bsToText . fromMaybe "" . lookup "oauth_token_secret" . unCredential oauthSessionName = "__oauth_token_secret" + dispatch "GET" ["forward"] = do render <- lift getUrlRender tm <- getRouteToParent @@ -72,8 +73,9 @@ authOAuth oauth mkCreds = AuthPlugin name dispatch login master <- getYesod accTok <- getAccessToken oauth reqTok (authHttpManager master) creds <- liftIO $ mkCreds accTok - setCreds True creds + setCredsRedirect creds dispatch _ _ = notFound + login tm = do render <- getUrlRender let oaUrl = render $ tm $ oauthUrl name diff --git a/yesod-auth-oauth/yesod-auth-oauth.cabal b/yesod-auth-oauth/yesod-auth-oauth.cabal index 625e27d9..dff15167 100644 --- a/yesod-auth-oauth/yesod-auth-oauth.cabal +++ b/yesod-auth-oauth/yesod-auth-oauth.cabal @@ -1,9 +1,9 @@ name: yesod-auth-oauth -version: 1.2.0 +version: 1.3.0 license: BSD3 license-file: LICENSE author: Hiromi Ishii -maintainer: Hiromi Ishii +maintainer: Michael Litchard synopsis: OAuth Authentication for Yesod. category: Web, Yesod stability: Stable @@ -20,13 +20,13 @@ library cpp-options: -DGHC7 else build-depends: base >= 4 && < 4.3 - build-depends: authenticate-oauth >= 1.4 && < 1.5 + build-depends: authenticate-oauth >= 1.5 && < 1.6 , bytestring >= 0.9.1.4 , yesod-core >= 1.2 && < 1.3 - , yesod-auth >= 1.2 && < 1.3 - , text >= 0.7 && < 0.12 + , yesod-auth >= 1.3 && < 1.4 + , text >= 0.7 , yesod-form >= 1.3 && < 1.4 - , transformers >= 0.2.2 && < 0.4 + , transformers >= 0.2.2 && < 0.5 , lifted-base >= 0.2 && < 0.3 exposed-modules: Yesod.Auth.OAuth ghc-options: -Wall diff --git a/yesod-auth/Yesod/Auth.hs b/yesod-auth/Yesod/Auth.hs index a54f5dfd..d5f5d166 100644 --- a/yesod-auth/Yesod/Auth.hs +++ b/yesod-auth/Yesod/Auth.hs @@ -22,6 +22,7 @@ module Yesod.Auth -- * Plugin interface , Creds (..) , setCreds + , setCredsRedirect , clearCreds , loginErrorMessage , loginErrorMessageI @@ -34,6 +35,11 @@ module Yesod.Auth , AuthException (..) -- * Helper , AuthHandler + -- * Internal + , credsKey + , provideJsonMessage + , messageJson401 + , asHtml ) where import Control.Monad (when) @@ -62,6 +68,7 @@ import Control.Exception (Exception) import Network.HTTP.Types (unauthorized401) import Control.Monad.Trans.Resource (MonadResourceBase) import qualified Control.Monad.Trans.Writer as Writer +import Control.Monad (void) type AuthRoute = Route Auth @@ -72,7 +79,7 @@ type Piece = Text data AuthPlugin master = AuthPlugin { apName :: Text - , apDispatch :: Method -> [Piece] -> AuthHandler master () + , apDispatch :: Method -> [Piece] -> AuthHandler master TypedContent , apLogin :: (Route Auth -> Route master) -> WidgetT master IO () } @@ -89,6 +96,10 @@ data Creds master = Creds class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage) => YesodAuth master where type AuthId master + -- | specify the layout. Uses defaultLayout by default + authLayout :: WidgetT master IO () -> HandlerT master IO Html + authLayout = defaultLayout + -- | Default destination on successful login, if no other -- destination exists. loginDest :: master -> Route master @@ -104,10 +115,10 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage authPlugins :: master -> [AuthPlugin master] -- | What to show on the login page. - loginHandler :: AuthHandler master RepHtml + loginHandler :: AuthHandler master Html loginHandler = do tp <- getRouteToParent - lift $ defaultLayout $ do + lift $ authLayout $ do setTitleI Msg.LoginTitle master <- getYesod mapM_ (flip apLogin tp) (authPlugins master) @@ -163,6 +174,16 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage => HandlerT master IO (Maybe (AuthId master)) maybeAuthId = defaultMaybeAuthId + -- | Called on login error for HTTP requests. By default, calls + -- @setMessage@ and redirects to @dest@. + onErrorHtml :: (MonadResourceBase m) => Route master -> Text -> HandlerT master m Html + onErrorHtml dest msg = do + setMessage $ toHtml msg + fmap asHtml $ redirect dest + +-- | Internal session key used to hold the authentication information. +-- +-- Since 1.2.3 credsKey :: Text credsKey = "_ID" @@ -212,7 +233,7 @@ cachedAuth aid = runMaybeT $ do loginErrorMessageI :: (MonadResourceBase m, YesodAuth master) => Route child -> AuthMessage - -> HandlerT child (HandlerT master m) a + -> HandlerT child (HandlerT master m) TypedContent loginErrorMessageI dest msg = do toParent <- getRouteToParent lift $ loginErrorMessageMasterI (toParent dest) msg @@ -221,61 +242,74 @@ loginErrorMessageI dest msg = do loginErrorMessageMasterI :: (YesodAuth master, MonadResourceBase m, RenderMessage master AuthMessage) => Route master -> AuthMessage - -> HandlerT master m a + -> HandlerT master m TypedContent loginErrorMessageMasterI dest msg = do mr <- getMessageRender loginErrorMessage dest (mr msg) -- | For HTML, set the message and redirect to the route. -- For JSON, send the message and a 401 status -loginErrorMessage :: MonadResourceBase m - => Route site +loginErrorMessage :: (YesodAuth master, MonadResourceBase m) + => Route master -> Text - -> HandlerT site m a -loginErrorMessage dest msg = - sendResponseStatus unauthorized401 =<< ( - selectRep $ do - provideRep $ do - setMessage $ toHtml msg - fmap asHtml $ redirect dest - provideJsonMessage msg - ) - where - asHtml :: Html -> Html - asHtml = id + -> HandlerT master m TypedContent +loginErrorMessage dest msg = messageJson401 msg (onErrorHtml dest msg) + +messageJson401 :: MonadResourceBase m => Text -> HandlerT master m Html -> HandlerT master m TypedContent +messageJson401 msg html = selectRep $ do + provideRep html + provideRep $ do + let obj = object ["message" .= msg] + void $ sendResponseStatus unauthorized401 obj + return obj provideJsonMessage :: Monad m => Text -> Writer.Writer (Endo [ProvidedRep m]) () provideJsonMessage msg = provideRep $ return $ object ["message" .= msg] +setCredsRedirect :: YesodAuth master + => Creds master -- ^ new credentials + -> HandlerT master IO TypedContent +setCredsRedirect creds = do + y <- getYesod + maid <- getAuthId creds + case maid of + Nothing -> + case authRoute y of + Nothing -> do + messageJson401 "Invalid Login" $ authLayout $ + toWidget [shamlet|
_{msg}|] + where + msg = Msg.ConfirmationEmailSent identifier + + -- | Additional normalization of email addresses, besides standard canonicalization. + -- + -- Default: Lower case the email address. + -- + -- Since 1.2.3 + normalizeEmailAddress :: site -> Text -> Text + normalizeEmailAddress _ = TS.toLower + + -- | Handler called to render the registration page. The + -- default works fine, but you may want to override it in + -- order to have a different DOM. + -- + -- Default: 'defaultRegisterHandler'. + -- + -- Since: 1.2.6. + registerHandler :: AuthHandler site Html + registerHandler = defaultRegisterHandler + + -- | Handler called to render the \"forgot password\" page. + -- The default works fine, but you may want to override it in + -- order to have a different DOM. + -- + -- Default: 'defaultForgotPasswordHandler'. + -- + -- Since: 1.2.6. + forgotPasswordHandler :: AuthHandler site Html + forgotPasswordHandler = defaultForgotPasswordHandler + + -- | Handler called to render the \"set password\" page. The + -- default works fine, but you may want to override it in + -- order to have a different DOM. + -- + -- Default: 'defaultSetPasswordHandler'. + -- + -- Since: 1.2.6. + setPasswordHandler :: + Bool + -- ^ Whether the old password is needed. If @True@, a + -- field for the old password should be presented. + -- Otherwise, just two fields for the new password are + -- needed. + -> AuthHandler site TypedContent + setPasswordHandler = defaultSetPasswordHandler + + authEmail :: YesodAuthEmail m => AuthPlugin m authEmail = AuthPlugin "email" dispatch $ \tm -> @@ -181,8 +250,11 @@ $newline never