on master help sign in
Merge pull request #125 from ilyakooo0/develop

Develop

commitba18aa18593b5e958941e495bb4fe17a15a8e05c authoriko <ilyakooo0@gmail.com> dateSat 11 May 2019, 20:32 (7 years ago) parentd183c40f9c223e
8 files changed+157−33
denis/email.html+2−3 denis/package.yaml+2 denis/src/Data/Query.hs+7−9 denis/src/Server/App.hs+1−1 denis/src/Server/Auth.hs+68−11 denis/src/Server/Auth/HTML.hs+46 denis/src/Server/Auth/Mail.hs+29−7 denis/src/Server/Server.hs+2−2
denis/email.html+2−3
@@ -1,8 +1,7 @@
11<html>
22
33<body style="font-family: sans-serif;">
4− <h1>
5− <div align="center" style="margin-top: 55px;">
4+ <div style="text-align: center; margin-top: 55px;">
65 <a href="#" style="font-size: 34px; font-weight: bold; display: inline-block; padding: 7px 13px 7px 13px; margin-bottom: 89px; text-decoration: underline; font-family: sans-serif; color: #555;">
76 Log in
87 </a>
@@ -15,7 +14,7 @@
1514 21<div style="display: inline-block; width: 13px;"></div>55<div style="display: inline-block; width: 13px;"></div>43
1615 </div>
1716 </div>
18− <div align="center" style="margin-top: 233px;">
17+ <div style="text-align: center; margin-top: 233px;">
1918 <a href="#" style="text-decoration: underline; font-family: sans-serif; color: #555;font-size: 13px; margin-bottom: 21px;">Revoke access</a>
2019 </div>
2120</body>
denis/package.yaml+2
@@ -64,6 +64,8 @@ dependencies:
6464- periodic
6565- ua-parser
6666- cryptohash-sha512
67+- blaze-html
68+- servant-blaze
6769
6870
6971library:
denis/src/Data/Query.hs+7−9
@@ -85,7 +85,7 @@ import Data.Text (Text)
8585import qualified Data.Text as T
8686import Data.Channel.NamedChannel
8787import Data.Channel.AnonymousChannel
88−import Data.Maybe (fromMaybe, isJust)
88+import Data.Maybe (fromMaybe)
8989import Squeal.PostgreSQL.Render
9090import Data.GroupChat
9191import Data.Message
@@ -631,11 +631,11 @@ getTokenActivationQ = selectStar (from (table #tokens) &
631631 where_ (#tokenUserId .== param @1 .&&
632632 #tokenActivationCode .== param @2))
633633
634−deactivateTokenQ :: Manipulation Schema (TuplePG (UserId, ByteString)) (RowPG (Only UserId))
634+deactivateTokenQ :: Manipulation Schema (TuplePG (UserId, ByteString)) (RowPG (Only Text))
635635deactivateTokenQ = deleteFrom #tokens
636636 (#tokenUserId .== param @1 .&&
637637 #tokenDeactivationCode .== param @2)
638− (Returning (#tokenUserId `as` #fromOnly))
638+ (Returning (#tokenUserAgent `as` #fromOnly))
639639
640640getVerifiedTokenQ :: Query Schema (TuplePG (UserId, ByteString)) (RowPG Token)
641641getVerifiedTokenQ = selectStar (from (table #tokens) &
@@ -946,7 +946,7 @@ tryVerifyToken uId tokenData code = commitedTransactionallyUpdate $ do
946946 _ <- manipulateParams updateTokenQ newToken
947947 return Nothing
948948
949−tryActivateToken :: UserId -> ByteString -> StaticPQ (Maybe ByteString)
949+tryActivateToken :: UserId -> ByteString -> StaticPQ (Maybe Token)
950950tryActivateToken uId activationData = do
951951 let hData = hash activationData
952952 token' <- runQueryParams getTokenActivationQ (uId, hData) >>= firstRow
@@ -954,13 +954,11 @@ tryActivateToken uId activationData = do
954954 Nothing -> return Nothing
955955 Just token -> do
956956 if (tokenActivationCode token == Just hData)
957− then commitedTransactionallyUpdate (validateToken token) >> return (Just $ tokenValue token)
957+ then commitedTransactionallyUpdate (validateToken token) >> (return $ Just token)
958958 else return Nothing
959959
960−deactivateToken :: UserId -> ByteString -> StaticPQ Bool
961−deactivateToken uId dData = do
962− res <- manipulateParams deactivateTokenQ (uId, hash dData) >>= firstRow
963− return $ isJust (res :: Maybe (Only UserId))
960+deactivateToken :: UserId -> ByteString -> StaticPQ (Maybe Text)
961+deactivateToken uId dData = fmap fromOnly <$> (manipulateParams deactivateTokenQ (uId, hash dData) >>= firstRow)
964962
965963getVerifiedToken :: UserId -> ByteString -> StaticPQ (Maybe Token)
966964getVerifiedToken uId tokenData = runQueryParams getVerifiedTokenQ (uId, hash tokenData) >>= firstRow
denis/src/Server/App.hs+1−1
@@ -33,7 +33,7 @@ type DBConnection = Pool (K Connection Schema)
3333data Config = Config {
3434 getPool :: !DBConnection,
3535 cryptoState :: !CryptoRNGState,
36− -- selfRootUrl :: !String,
36+ selfRootUrl :: !String,
3737 mailConfig :: !MailConfig
3838}
3939
denis/src/Server/Auth.hs+68−11
@@ -43,16 +43,17 @@ import qualified GHC.Generics as GHC
4343import Data.Aeson
4444import qualified Data.Aeson as A
4545import Data.Binary.Builder (toLazyByteString)
46−import Servant.Docs (ToSample, samples, toSamples, singleSample)
46+import Servant.Docs
4747import qualified Data.Text as T
4848import qualified Data.Text.Encoding as T
4949import Text.Regex
50−import qualified Data.ByteString.Base64 as BS
50+import qualified Data.ByteString.Base64.URL as BS
5151import Data.Maybe
5252import Server.Auth.Token
5353import Server.Auth.Mail
5454import Web.UAParser
5555import qualified Data.Text.Lazy as TL
56+import Server.Auth.HTML
5657
5758-- MARK: Documentation
5859
@@ -74,6 +75,23 @@ instance ToSample TokenActivationData where
7475instance ToSample TokenActivationResult where
7576 toSamples _ = samples [TokenActivationResultYou, TokenActivationResultNotYou]
7677
78+instance ToParam (QueryParam' mods "data" String) where
79+ toParam _ =
80+ DocQueryParam "data"
81+ ["AAABBBC="]
82+ "The data recieved from the email."
83+ Normal
84+
85+instance ToParam (QueryParam' mods "id" UserId) where
86+ toParam _ =
87+ DocQueryParam "id"
88+ ["8"]
89+ "The id of the user."
90+ Normal
91+
92+instance ToSample Markup where
93+ toSamples _ = noSamples
94+
7795type LoginDescription = Description "Try to log in with the given email\n\nResponse fields:\n\n- `registered` -- User is registered and the cookie has been set. You now need to get a verification code from the user and pass it to `/verify`.\n- `canRegister` -- the email is valid, but the user needs to register. Prompt him for information and pass it to `/register`.\n- `invalid` -- the email is invalid."
7896
7997type LogoutDescription = Description "Log out with the given cookie"
@@ -84,6 +102,10 @@ type RegisterDescription = Description "Register the user for a new account.\n\n
84102
85103type CheckCookieDescription = Description "Checks if a parseable cookie is present in the request.\n\nDoes not check if the cookie itself contains valid credentials.\n\nReturns 204 NoContent if cookie is present.\n\nReturns 401 Unauthorized if the cookie is not present."
86104
105+type ActivateDescription = Description "Activates a token using data from email"
106+
107+type DeactivateDescription = Description "Deactivates a token using data from email"
108+
87109-- MARK: Implementation
88110
89111type AuthenticationHandler =
@@ -102,13 +124,21 @@ type AuthenticationHandler =
102124 PostNoContent '[JSON, PlainText, FormUrlEncoded] (Headers '[Header "Set-Cookie" String] NoContent) :<|>
103125 "checkCookie" :> CheckCookieDescription :>
104126 Header "cookie" String :> GetNoContent '[JSON, PlainText, FormUrlEncoded] NoContent :<|>
105− "activate"
127+ "activate" :> ActivateDescription
106128 :> Header "cookie" String
107129 :> ReqBody '[JSON] TokenActivationData
108130 :> Post '[JSON] TokenActivationResult :<|>
109− "deactivate"
131+ "deactivate" :> DeactivateDescription
110132 :> ReqBody '[JSON] TokenActivationData
111− :> PostNoContent '[JSON] NoContent
133+ :> PostNoContent '[JSON] NoContent :<|>
134+ "activateUser"
135+ :> QueryParam "id" UserId
136+ :> QueryParam "data" String
137+ :> Get '[HTML] Markup :<|>
138+ "deactivateUser"
139+ :> QueryParam "id" UserId
140+ :> QueryParam "data" String
141+ :> Get '[HTML] Markup
112142
113143data UserResponseStatus = UserIsRegistered | UserCanRegister | InvalidUser
114144 deriving (Show)
@@ -160,8 +190,35 @@ instance FromJSON BS.ByteString where
160190newtype AuthenticationCredits = AuthenticationCredits {authenticationEmail :: UserEmail}
161191 deriving (GHC.Generic, ToJSON, FromJSON)
162192
193+activateUser :: Maybe UserId -> Maybe String -> App Markup
194+activateUser Nothing _ = return $ page "The link is invalid."
195+activateUser _ Nothing = return $ page "The link is invalid."
196+activateUser (Just uId) (Just aData') = catchErrorPage $ do
197+ case BS.decode . C.pack $ aData' of
198+ Left _ -> return $ page $ "The link is invalid."
199+ Right aData -> do
200+ tokenData' <- runQerror $ tryActivateToken uId aData
201+ case tokenData' of
202+ Nothing -> return $ page "This activation link has been invalidated."
203+ Just Token{..} -> return $ genActivationPage "in" tokenUserAgent
204+
205+deactivateUser :: Maybe UserId -> Maybe String -> App Markup
206+deactivateUser Nothing _ = return $ page "The link is invalid."
207+deactivateUser _ Nothing = return $ page "The link is invalid."
208+deactivateUser (Just uId) (Just dData') = catchErrorPage $ do
209+ case BS.decode . C.pack $ dData' of
210+ Left _ -> return $ page $ "The link is invalid."
211+ Right dData -> do
212+ ua' <- runQerror $ deactivateToken uId dData
213+ case ua' of
214+ Nothing -> return $ page "This activation link has been invalidated."
215+ Just ua -> return $ genActivationPage "out" ua
216+
217+catchErrorPage :: App Markup -> App Markup
218+catchErrorPage = flip catchError $ \ (_ :: ServantErr) -> return errorPage
219+
163220authenticationAPI :: ServerT AuthenticationHandler App
164−authenticationAPI = logIn :<|> logOut :<|> verifyToken :<|> register :<|> checkCookie :<|> validateTokenApi :<|> deactivateTokenApi
221+authenticationAPI = logIn :<|> logOut :<|> verifyToken :<|> register :<|> checkCookie :<|> validateTokenApi :<|> deactivateTokenApi :<|> activateUser :<|> deactivateUser
165222
166223logIn :: AuthenticationCredits -> Maybe String -> App (Headers '[Header "Set-Cookie" String] UserLoginResponse)
167224logIn (AuthenticationCredits email') ua' = do
@@ -177,7 +234,7 @@ logIn (AuthenticationCredits email') ua' = do
177234 GeneratedToken token value code activation deactivation <- generateToken userId ua
178235 runQerror . createToken $ token
179236 let cookie = generateCookie token value
180− sendTokenVerificationEmail code (TL.fromStrict ua) email
237+ sendTokenVerificationEmail code userId (BS.encode activation) (BS.encode deactivation) (TL.fromStrict ua) email
181238 return . addHeader cookie $ UserLoginResponse UserIsRegistered
182239
183240generateCookie :: Token -> BS.ByteString -> String
@@ -228,18 +285,18 @@ validateTokenApi cookie' (TokenActivationData uId aData) = do
228285 tokenData' <- runQerror $ tryActivateToken uId aData
229286 case tokenData' of
230287 Nothing -> throwError err401
231− Just tokenData -> do
288+ Just Token{..} -> do
232289 case cookie' >>= getCookie of
233290 Nothing -> return TokenActivationResultNotYou
234291 Just (AuthenticationCookieData uId' cData) ->
235− if hash cData == tokenData && uId' == uId
292+ if hash cData == tokenValue && uId' == tokenUserId
236293 then return TokenActivationResultYou
237294 else return TokenActivationResultNotYou
238295
239296deactivateTokenApi :: TokenActivationData -> App NoContent
240297deactivateTokenApi (TokenActivationData uId dData) = do
241298 res <- runQerror $ deactivateToken uId dData
242− if res
299+ if isJust res
243300 then return NoContent
244301 else throwError err401
245302
@@ -261,7 +318,7 @@ register creation' ua' = do
261318 let gToken = genToken uId ua
262319 createToken . generatedToken $ gToken
263320 return gToken
264− sendTokenVerificationEmail code (TL.fromStrict ua) (userCreationUserEmail creation)
321+ sendTokenVerificationEmail code (tokenUserId token) (BS.encode activation) (BS.encode deactivation) (TL.fromStrict ua) (userCreationUserEmail creation)
265322 let cookie = generateCookie token value
266323 return $ addHeader cookie NoContent
267324
denis/src/Server/Auth/HTML.hsnew file+46
@@ -0,0 +1,46 @@
1+{-# LANGUAGE DataKinds,
2+ DeriveGeneric,
3+ FlexibleContexts,
4+ FlexibleInstances,
5+ MultiParamTypeClasses,
6+ OverloadedStrings,
7+ ScopedTypeVariables,
8+ TypeFamilies,
9+ TypeOperators,
10+ UndecidableInstances,
11+ DeriveAnyClass,
12+ RecordWildCards #-}
13+
14+module Server.Auth.HTML (
15+ HTML,
16+ genActivationPage,
17+ Markup,
18+ errorPage,
19+ page
20+ ) where
21+
22+import Servant.HTML.Blaze
23+import Text.Blaze.Html5 as H
24+import Text.Blaze.XHtml5.Attributes as A
25+import Data.Text (Text)
26+
27+genActivationPage :: Text -> Text -> Markup
28+genActivationPage verb ua = genPage $ do
29+ "You have successfully "
30+ u $ do
31+ "logged "
32+ toHtml verb
33+ " on "
34+ u $ toHtml ua
35+
36+
37+genPage :: Markup -> Markup
38+genPage pageContent = html $ body ! A.style "font-family: sans-serif; background-color: #eee" $ do
39+ H.div ! A.style "text-align: center; margin-top: 20vh; margin-left: 10%; margin-right: 10%" $ do
40+ H.div ! A.style "font-size: 40px; font-weight: bold;" $ pageContent
41+
42+errorPage :: Markup
43+errorPage = genPage $ "Sorry, something went wrong."
44+
45+page :: Text -> Markup
46+page t = genPage $ toHtml t
denis/src/Server/Auth/Mail.hs+29−7
@@ -23,22 +23,44 @@ import Control.Monad.IO.Class
2323import Network.Mail.Mime (Mail, Part)
2424import Data.Text (Text)
2525import qualified Data.Text.Lazy as TL
26+import Text.Blaze.Html5 as H
27+import Text.Blaze.XHtml5.Attributes as A
28+import Text.Blaze.Html.Renderer.Text
29+import Data.ByteString (ByteString)
30+import Data.User
31+import Data.ByteString.Char8 (unpack)
2632
27−sendTokenVerificationEmail :: TokenVerificationCode -> TL.Text -> Text -> App ()
28−sendTokenVerificationEmail code ua email = do
29− mailCfg <- mailConfig <$> ask
33+sendTokenVerificationEmail :: TokenVerificationCode -> UserId -> ByteString -> ByteString -> TL.Text -> Text -> App ()
34+sendTokenVerificationEmail userCode uId activation deactivation ua email = do
35+ cfg <- ask
36+ let mailCfg = mailConfig cfg
37+ let rootUrl = selfRootUrl cfg
3038 let mail = simpleMail
3139 (Address (Just "HSE Social Network") "noreply@hse.ru")
3240 [Address Nothing email] [] []
3341 "HSE Social Network Verification Code"
34− [genMail code ua]
42+ [genMail rootUrl userCode uId (unpack activation) (unpack deactivation) ua]
3543 sendMailWithConfig mailCfg mail
3644
3745sendMailWithConfig :: (MonadIO m) => MailConfig -> Mail -> m ()
3846sendMailWithConfig (MailConfig host port user pass) =
3947 liftIO . sendMailWithLogin' host (fromIntegral port) user pass
4048
41−genMail :: TokenVerificationCode -> TL.Text -> Part
42−genMail code ua = htmlPart $ "<html><body style=\"font-family: sans-serif;\"> <h1> <div align=\"center\" style=\"margin-top: 55px;\"> <a href=\"#\" style=\"font-size: 34px; font-weight: bold; display: inline-block; padding: 7px 13px 7px 13px; margin-bottom: 89px; text-decoration: underline; font-family: sans-serif; color: #555;\"> Log in </a> <br /> <div style=\"font-size: 34px; font-weight: bold;\"> <span style=\"color: #bbb; font-weight: bold;\">from: </span>" <> ua <> "</div> <br/> <div style=\"font-size: 55px; font-weight: bold; border-radius: 10px; background-color: #eee; display: inline-block; padding: 7px 13px 7px 13px; box-shadow: 0px 5px 21px #bbb; background-image: linear-gradient(#f9f9f9, #eee);\">" <> TL.pack [a, b] <> "<div style=\"display: inline-block; width: 13px;\"></div>" <> TL.pack [c, d] <> "<div style=\"display: inline-block; width: 13px;\"></div>" <> TL.pack [e, f] <> "</div></div><div align=\"center\" style=\"margin-top: 233px;\"> <a href=\"#\" style=\"text-decoration: underline; font-family: sans-serif; color: #555;font-size: 13px; margin-bottom: 21px;\">Revoke access</a> </div></body></html>"
49+genMail :: String -> TokenVerificationCode -> UserId -> String -> String -> TL.Text -> Part
50+genMail rootUrl userCode uId activation deactivation ua = htmlPart . renderHtml $ html $ body ! A.style "font-family: sans-serif;" $ do
51+ H.div ! A.style "text-align: center; margin-top: 55px;" $ do
52+ H.a ! href (stringValue $ rootUrl <> "/authentication/activateUser?id=" <> show uId <> "&data=" <> activation) ! A.style "font-size: 34px; font-weight: bold; display: inline-block; padding: 7px 13px 7px 13px; margin-bottom: 89px; text-decoration: underline; font-family: sans-serif; color: #555;" $ "Log in"
53+ br
54+ H.div ! A.style "font-size: 34px; font-weight: bold;" $ do
55+ H.span ! A.style "color: #bbb; font-weight: bold;" $ "from: "
56+ toHtml ua
57+ br
58+ H.div ! A.style "font-size: 55px; font-weight: bold; border-radius: 10px; background-color: #eee; display: inline-block; padding: 7px 13px 7px 13px; box-shadow: 0px 5px 21px #bbb; background-image: linear-gradient(#f9f9f9, #eee);" $ do
59+ toHtml [a', b']
60+ H.div ! A.style "display: inline-block; width: 13px;" $ mempty
61+ toHtml [c', d']
62+ H.div ! A.style "display: inline-block; width: 13px;" $ mempty
63+ toHtml [e', f']
64+ H.div ! A.style "text-align: center; margin-top: 233px;" $ H.a ! href (stringValue $ rootUrl <> "/authentication/deactivateUser?id=" <> show uId <> "&data=" <> deactivation) ! A.style "text-decoration: underline; font-family: sans-serif; color: #555;font-size: 13px; margin-bottom: 21px;" $ "Revoke access"
4365 where
44− [a, b, c, d, e, f] = reverse . take 6 . (++ repeat '0') . reverse . show $ code
66+ [a', b', c', d', e', f'] = reverse . take 6 . (++ repeat '0') . reverse . show $ userCode
denis/src/Server/Server.hs+2−2
@@ -45,9 +45,9 @@ getConfig = do
4545 databaseURL <- getDBString
4646 conn <- createConnectionPool databaseURL 3 0.5 10
4747 crypto <- newCryptoRNGState
48− -- (Jusr selfRoot) <- lookupEnv "SELF_ROOT_URL"
48+ selfRoot <- fromMaybe "http://localhost:2000" <$> lookupEnv "SELF_ROOT_URL"
4949 mail <- getMailConfig
50− return $ Config conn crypto mail
50+ return $ Config conn crypto selfRoot mail
5151
5252runServer :: IO ()
5353runServer = do