1
+ {-# LANGUAGE DeriveDataTypeable #-}
2
+ {-# LANGUAGE FlexibleContexts #-}
3
+ {-# LANGUAGE GADTs #-}
4
+ {-# LANGUAGE MultiParamTypeClasses #-}
5
+ {-# LANGUAGE OverloadedStrings #-}
6
+ {-# LANGUAGE QuasiQuotes #-}
7
+ {-# LANGUAGE TemplateHaskell #-}
8
+ {-# LANGUAGE TypeFamilies #-}
9
+ import Control.Monad (join )
10
+ import Control.Monad.Logger (runNoLoggingT )
11
+ import Data.Maybe (isJust )
12
+ import qualified Data.Text.Lazy.Encoding
13
+ import Data.Typeable (Typeable )
14
+ import Database.Persist.Sqlite
15
+ import Database.Persist.TH
16
+ import Network.Mail.Mime
17
+ import Network.Mail.Mime.SES
18
+ import Text.Blaze.Html.Renderer.Utf8 (renderHtml )
19
+ import Text.Hamlet (shamlet )
20
+ import Text.Shakespeare.Text (stext )
21
+ import Yesod
22
+ import Yesod.Auth
23
+ import Yesod.Auth.Email
24
+ import SESCreds (access , secret )
25
+ import Data.Text (Text , pack , unpack )
26
+ import Data.Text.Encoding (encodeUtf8 )
27
+ import qualified Data.ByteString.Lazy.UTF8 as LU
28
+ import Network.HTTP.Conduit (newManager , conduitManagerSettings )
29
+ import Network.HTTP.Conduit (Manager )
30
+ import Text.Blaze.Html.Renderer.Utf8 (renderHtml )
31
+ share [mkPersist sqlSettings { mpsGeneric = False }, mkMigrate " migrateAll" ] [persistLowerCase |
32
+ User
33
+ email Text
34
+ password Text Maybe -- Password may not be set yet
35
+ verkey Text Maybe -- Used for resetting passwords
36
+ verified Bool
37
+ UniqueUser email
38
+ deriving Typeable
39
+ |]
40
+
41
+ data App = App
42
+ { httpManager :: Manager ,
43
+ connPool :: Connection
44
+ }
45
+
46
+ mkYesod " App" [parseRoutes |
47
+ / HomeR GET
48
+ /auth AuthR Auth getAuth
49
+ |]
50
+
51
+ instance Yesod App where
52
+ -- Emails will include links, so be sure to include an approot so that
53
+ -- the links are valid!
54
+ approot = ApprootStatic " http://localhost:3000"
55
+
56
+ instance RenderMessage App FormMessage where
57
+ renderMessage _ _ = defaultFormMessage
58
+
59
+ -- Set up Persistent
60
+ instance YesodPersist App where
61
+ type YesodPersistBackend App = SqlPersistT
62
+ runDB f = do
63
+ h <- getYesod
64
+ runSqlConn f $ connPool h
65
+
66
+ instance YesodAuth App where
67
+ type AuthId App = UserId
68
+
69
+ loginDest _ = HomeR
70
+ logoutDest _ = HomeR
71
+ authPlugins _ = [authEmail]
72
+
73
+ -- Need to find the UserId for the given email address.
74
+ getAuthId creds = runDB $ do
75
+ x <- insertBy $ User (credsIdent creds) Nothing Nothing False
76
+ return $ Just $
77
+ case x of
78
+ Left (Entity userid _) -> userid -- newly added user
79
+ Right userid -> userid -- existing user
80
+
81
+ authHttpManager = error " Email doesn't need an HTTP manager"
82
+
83
+ -- Here's all of the email-specific code
84
+ instance YesodAuthEmail App where
85
+ type AuthEmailId App = UserId
86
+
87
+ afterPasswordRoute _ = HomeR
88
+
89
+ addUnverified email verkey =
90
+ runDB $ insert $ User email Nothing (Just verkey) False
91
+
92
+ sendVerifyEmail email _ verurl = sendEmail email verurl
93
+ where
94
+ textPart = Part
95
+ { partType = " text/plain; charset=utf-8"
96
+ , partEncoding = None
97
+ , partFilename = Nothing
98
+ , partContent = Data.Text.Lazy.Encoding. encodeUtf8
99
+ [stext |
100
+ Please confirm your email address by clicking on the link below.
101
+
102
+ #{verurl}
103
+
104
+ Thank you
105
+ |]
106
+ , partHeaders = []
107
+ }
108
+ htmlPart = Part
109
+ { partType = " text/html; charset=utf-8"
110
+ , partEncoding = None
111
+ , partFilename = Nothing
112
+ , partContent = renderHtml
113
+ [shamlet |
114
+ <p>Please confirm your email address by clicking on the link below.
115
+ <p>
116
+ <a href=#{verurl}>#{verurl}
117
+ <p>Thank you
118
+ |]
119
+ , partHeaders = []
120
+ }
121
+ getVerifyKey = runDB . fmap (join . fmap userVerkey) . get
122
+ setVerifyKey uid key = runDB $ update uid [UserVerkey =. Just key]
123
+ verifyAccount uid = runDB $ do
124
+ mu <- get uid
125
+ case mu of
126
+ Nothing -> return Nothing
127
+ Just u -> do
128
+ update uid [UserVerified =. True ]
129
+ return $ Just uid
130
+ getPassword = runDB . fmap (join . fmap userPassword) . get
131
+ setPassword uid pass = runDB $ update uid [UserPassword =. Just pass]
132
+ getEmailCreds email = runDB $ do
133
+ mu <- getBy $ UniqueUser email
134
+ case mu of
135
+ Nothing -> return Nothing
136
+ Just (Entity uid u) -> return $ Just EmailCreds
137
+ { emailCredsId = uid
138
+ , emailCredsAuthId = Just uid
139
+ , emailCredsStatus = isJust $ userPassword u
140
+ , emailCredsVerkey = userVerkey u
141
+ , emailCredsEmail = email
142
+ }
143
+ getEmail = runDB . fmap (fmap userEmail) . get
144
+
145
+ ------------
146
+
147
+
148
+
149
+ sendEmail email url = do
150
+
151
+ let ses = SES
152
+
153
+ , sesTo = [encodeUtf8 email]
154
+ , sesAccessKey = encodeUtf8 $ pack access
155
+ , sesSecretKey = encodeUtf8 $ pack secret
156
+ }
157
+ h <- getYesod
158
+ lift $ renderSendMailSES ( httpManager h) ses Mail
159
+ { mailHeaders =
160
+ [ (" Subject" , " Verify your email address" )
161
+ ]
162
+ , mailFrom
= Address Nothing " [email protected] "
163
+ , mailTo = [Address Nothing email]
164
+ , mailCc = []
165
+ , mailBcc = []
166
+ , mailParts = return
167
+ [ Part " text/plain" None Nothing [] $ LU. fromString $ unlines
168
+ [ " Please go to the URL below to verify your email address."
169
+ , " "
170
+ , unpack url
171
+ ]
172
+ , Part " text/html" None Nothing [] $ renderHtml [shamlet |\
173
+ <img src="" alt="Haskellers">
174
+ <p>Please go to the URL below to verify your email address.
175
+ <p>
176
+ <a href="#{url}">#{url}
177
+ |]
178
+ ]
179
+ }
180
+
181
+
182
+
183
+ ----------------------
184
+
185
+
186
+ getHomeR :: Handler Html
187
+ getHomeR = do
188
+ maid <- maybeAuthId
189
+ defaultLayout
190
+ [whamlet |
191
+ <p>Your current auth ID: #{show maid}
192
+ $maybe _ <- maid
193
+ <p>
194
+ <a href=@{AuthR LogoutR}>Logout
195
+ $nothing
196
+ <p>
197
+ <a href=@{AuthR LoginR}>Go to the login page
198
+ |]
199
+
200
+ main :: IO ()
201
+ main = do
202
+ manager <- newManager conduitManagerSettings
203
+ withSqliteConn " email.db3" $ \ conn -> do
204
+ runNoLoggingT $ runSqlConn (runMigration migrateAll) conn
205
+ warp 3000 $ App manager conn
0 commit comments