O2BZOX7MS4JCDS3C6EJQXAWUEQV6HVDCIF2FIN2BCJNRLIU6ZVKAC UWMGUJOW5X5HQTS76T2FD7MNAJF7SESPQVU5FDIZO52V75TT2X6AC SAESJLLYCQJUIHKFYFV53AWHFOSGI5SKLVS7DPTQO6BKGITPYPUQC WZFQDWW4XK6M4A4PQ7WQJUTZUPRGQR7V7ZVZY5ZTL5AMGIFMHB2QC 64C6AWH66FDKU6UE6Z6JPX2J2GBM2JOPTH2GL6LHKAIUBGNGDZ5AC TCOAKCGGHOIRJCTZYEZQ3K6KCGL2LGAYGYFRGSPCHBTJJY2V6AXAC NJNMO72S7VIUV22JXB4IFPZMHWTJAOTP6EC6Z4QSKYIROSXT52MQC WRPIYG3EUHZR6N6T74ZXZDXATRMIRLXAQ24UNUNSVTVYGMT2VDSQC RSF6UAJKG7CEKILSVXI6C4YZXY7PIYZM2EMA2IXKQ7SADKNVSH7QC EA5BFM5GMM7KNMDLTVOSUKVKMSIDD72TAFVHDVGEOUY5VELECU3QC JXG3FCXYBDKMUD77DOM7RCIJYKB7BILC43OHHDZBE7YQRGAMUCCAC QMEYU4MWLTSWPWEEOFRLK2IKE64BY3V5X73323WPLCGPP3TPDYGAC PT4276XCOP5NJ3GRFJLIBZKVNVAOATAY5PLWV7FWK6RZW5FTEP5AC 3LMXT7Z6SIGLQ2OMH7OKPJPWNPN2CSGD3BKUD2NMJVCX2CSAMFYQC TUA4HMUDRRXLVOH4WPID2ZJGEIJTSCMM5OBP3E26ECYHSHG3IBDQC TKGBRIQT7XCPJ3LA5JAEMMGMPFWQWINMSDRW76V2IMZZGT5AWTYAC 5R2Z7FSXJD7Z53QSU2NSTEBONTYK43FIJOSOMUST5XMYIWRXY2HQC RB2ETNIFLQUA6OA66DAEOXZ25ENMQGNKX5CZRSKEYHTD6BQ6NTFQC 4R7XIYK3BP664CO3YJ2VM64ES2JYN27UTQG5KS34OTEPAIODSZLQC 6L5BK5EHPAOQX3JCKUJ273UDNAC23LPQL4HIJGM4AV3P3QK5OKIQC V2VDN77HCSRYYWXDJJ2XOVHV4P6PVWNJZLXZ7JUYPQEZQIH5BZ3QC EFSXYZPOGA5M4DN65IEIDO7JK6U34DMQELAPHOIL2UAT6HRC66BAC 2XQD6KKKD6QVHFHAEMVE3XXY7X2T7BLTLL7XIILZAXNJJH2YONUQC PBD7LZYQHXAA3KLH2ZUX5GW4UFML6BQ32KXZF4KZ6OYFASUYFJ5QC BROSTG5KP3NUNLSYPVQID254TE47E5RKQAKLPIY7PGWETE6JNMTAC I2KHGVD44KT4MQJXGCTVSQKMBO6TVCY72F26TLXGWRL6PHGF6RNQC MB5SHULBN3WP7TGUWZDP6BRGP423FTYKF67T5IF5YHHLNXKQ5REAC NEDDHXUK3GNFMOFO3KLU7NRIHCTYNWBT3D6HTKZAOXFDE6HMDZ6AC QMRKFEPGFBCEWAIXPEIG5ILKAJ2JH5L3TOITHR4HNJXK5FN3KXBQC BXGLKYRXO2O4NRM3BLNWQ7AWVPQXAMFS57MFYHJNOZZEZZW5BH6AC ADMKQQGCGVSHHIMVQ4XFRDCG544SBJCYALSKZV45CQQBZ4ACUH2AC module Aftok.Api.Account whereimport Preludeimport Control.Monad.Trans.Class (lift)import Data.Argonaut.Encode (encodeJson)import Data.Either (Either(..))import Data.Maybe (Maybe(..))import Effect.Aff (Aff)import Effect.Class.Console (log)import Affjax (post, get, printError)import Affjax.StatusCode (StatusCode(..))import Affjax.RequestBody as RBimport Affjax.ResponseFormat as RFimport Halogen as Himport Halogen.HTML.Core (ClassName(..))import Halogen.HTML as HHimport Halogen.HTML.CSS as CSSimport Halogen.HTML.Events as Eimport Web.Event.Event as WEimport Halogen.HTML.Properties as Pimport CSS (backgroundImage, url)import Landkit.Card as Cardimport Aftok.Types (System)type LoginRequest = { username :: String, password :: String }data LoginResponse= LoginOK| LoginForbidden| LoginError { status :: Maybe StatusCode, message :: String }-- | Post credentials to the login service and interpret the responselogin :: String -> String -> Aff LoginResponselogin user pass = dolog "Sending login request to /api/login ..."result <- post RF.ignore "/api/login" (Just <<< RB.Json <<< encodeJson $ { username: user, password : pass })case result ofLeft err -> log ("Login failed: " <> printError err)Right r -> log ("Login status: " <> show r.status)pure $ case result ofLeft err -> LoginError { status: Nothing, message: printError err }Right r -> case r.status ofStatusCode 403 -> LoginForbiddenStatusCode 200 -> LoginOKother -> LoginError { status: Just other, message: r.statusText }checkLogin :: Aff LoginResponsecheckLogin = doresult <- get RF.ignore "/api/login/check"case result ofLeft err -> dopure $ LoginError { status: Nothing, message: printError err }Right r -> dopure $ case r.status ofStatusCode 200 -> LoginOKStatusCode _ -> LoginForbiddenlogout :: Aff Unitlogout = void $ get RF.ignore "/api/logout"data RecoverBy= RecoverByEmail String| RecoverByZAddr Stringtype SignupRequest ={ username :: String, password :: String, recoverBy :: RecoverBy, captchaToken :: String}signupRequest :: String -> String -> RecoverBy -> String -> SignupRequestsignupRequest username password recoverBy captchaToken ={ username, password, recoverBy, captchaToken }data SignupResponse= SignupOK| CaptchaInvalid| ZAddrInvalid| UsernameTakendata UsernameCheckResponse= UsernameCheckOK| UsernameCheckTakendata ZAddrCheckResponse= ZAddrCheckOK| ZAddrCheckInvalidcheckUsername :: String -> Aff UsernameCheckResponsecheckUsername uname = dopure UsernameCheckOKcheckZAddr :: String -> Aff ZAddrCheckResponsecheckZAddr zaddr = dopure ZAddrCheckOKsignup :: SignupRequest -> Aff SignupResponsesignup req = dopure SignupOK
"use strict";exports.getRecaptchaResponseInternal = useElemId => elemId => () => {if (useElemId) {return grecaptcha.getResponse(elemId);} else {return grecaptcha.getResponse();}}
module Aftok.Api.Recaptcha( getRecaptchaResponse) whereimport Prelude (bind, (==), ($), pure)import Data.Maybe (Maybe(..))import Effect (Effect)getRecaptchaResponse :: Maybe String -> Effect (Maybe String)getRecaptchaResponse elemId = doresp <- case elemId ofJust eid -> getRecaptchaResponseInternal true eidNothing -> getRecaptchaResponseInternal false ""pure $ if resp == "" then Nothing else Just respforeign import getRecaptchaResponseInternal :: Boolean -> String -> Effect String
import Effect.Class.Console (log)import Affjax (post, get, printError)import Affjax.StatusCode (StatusCode(..))import Affjax.RequestBody as RBimport Affjax.ResponseFormat as RF
Just OK ->HH.div[ P.classes (ClassName <$> ["alert alert-warning"]) ][ HH.text "Login ok, but you should have been redirected. Why are you still here?" ]Just Forbidden ->HH.div[ P.classes (ClassName <$> ["alert alert-danger"]) ][ HH.text "Login failed. Check your username and password." ]Just (Error e) ->HH.div[ P.classes (ClassName <$> ["alert alert-danger"]) ][ HH.text ("Login failed: " <> e.message) ]
Just err ->let message = case err ofForbidden -> "Login failed. Check your username and password."ServerError -> "Login failed due to an internal error. Please contact support."in HH.div[ P.classes (ClassName <$> ["alert alert-danger"]) ][ HH.text message ]
OK -> H.raise (LoginComplete { username: user })_ -> pure unit-- | Post credentials to the login service and interpret the responselogin :: String -> String -> Aff LoginResponselogin user pass = dolog "Sending login request to /api/login ..."result <- post RF.ignore "/api/login" (Just <<< RB.Json <<< encodeJson $ { username: user, password : pass })case result ofLeft err -> log ("Login failed: " <> printError err)Right r -> log ("Login status: " <> show r.status)pure $ case result ofLeft err -> Error { status: Nothing, message: printError err }Right r -> case r.status ofStatusCode 403 -> ForbiddenStatusCode 200 -> OKother -> Error { status: Just other, message: r.statusText }checkLogin :: Aff LoginResponsecheckLogin = dolog "Sending login check to /api/login/check ..."result <- get RF.ignore "/api/login/check"case result ofLeft err -> dolog ("Login failed: " <> printError err)pure $ Error { status: Nothing, message: printError err }Right r -> dolog ("Login status: " <> show r.status)pure $ case r.status ofStatusCode 200 -> OKStatusCode _ -> Forbidden
LoginOK -> H.raise (LoginComplete { username: user })LoginForbidden -> H.modify_ (_ { loginError = Just Forbidden })LoginError _ -> H.modify_ (_ { loginError = Just ServerError })
data SignupResponse= OK| Error { status :: Maybe StatusCode, message :: String }
data SignupError= UsernameRequired| UsernameTaken| PasswordRequired| ConfirmRequired| PasswordMismatch| EmailRequired| ZAddrRequired| ZAddrInvalid| CaptchaError| APIError { status :: Maybe StatusCode, message :: String }
{ signup :: String -> String -> m SignupResponse
{ checkUsername :: String -> m Acc.UsernameCheckResponse, checkZAddr :: String -> m Acc.ZAddrCheckResponse, signup :: SignupRequest -> m SignupResponse, getRecaptchaResponse :: Maybe String -> m (Maybe String)
SetUsername user -> H.modify_ (_ { username = Just user })SetPassword pass -> H.modify_ (_ { password = Just pass })
SetUsername user -> doures <- lift $ caps.checkUsername userH.modify_ (_ { username = Just user })case ures ofAcc.UsernameCheckOK -> pure unitAcc.UsernameCheckTaken -> H.modify_ (_ { signupErrors = [UsernameTaken] })SetPassword pass -> doH.modify_ (_ { password = Just pass })confirm <- H.gets (_.passwordConfirm)when (any (notEq pass) confirm) (H.modify_ (_ { signupErrors = [PasswordMismatch] }))ConfirmPassword confirm -> doH.modify_ (_ { passwordConfirm = Just confirm })password <- H.gets (_.password)when (any (notEq confirm) password) (H.modify_ (_ { signupErrors = [PasswordMismatch] }))
ConfirmPassword pass -> H.modify_ (_ { passwordConfirm = Just pass })_ -> pure unit
SetRecoveryEmail email -> H.modify_ (_ { recoveryEmail = Just email })SetRecoveryZAddr addr -> dozres <- lift $ caps.checkZAddr addrH.modify_ (_ { recoveryZAddr = Just addr })case zres ofAcc.ZAddrCheckOK -> pure unitAcc.ZAddrCheckInvalid -> H.modify_ (_ { signupErrors = [ZAddrInvalid] })
Signin ev -> dolift $ system.preventDefault (ME.toEvent ev)H.raise SigninNavSignup ev -> dolift $ system.preventDefault evrecType <- H.gets (_.recoveryType)usernameV <- V <<< note [UsernameRequired] <$> H.gets (_.username)pwdFormV <- V <<< note [PasswordRequired] <$> H.gets (_.password)pwdConfV <- V <<< note [ConfirmRequired ] <$> H.gets (_.passwordConfirm)recoveryType <- H.gets (_.recoveryType)recoveryV <- case recoveryType ofRecoveryEmail ->V <<< note [EmailRequired] <<< map Acc.RecoverByEmail <$> H.gets (_.recoveryEmail)RecoveryZAddr ->V <<< note [ZAddrRequired] <<< map Acc.RecoverByZAddr <$> H.gets (_.recoveryZAddr)recapV <- lift $ V <<< note [CaptchaError] <$> caps.getRecaptchaResponse Nothinglet reqV :: V (Array SignupError) Acc.SignupRequestreqV = signupRequest <$> usernameV<*> ((eq <$> pwdFormV <*> pwdConfV) `andThen`(if _ then pwdFormV else invalid [PasswordMismatch]))<*> recoveryV<*> recapVcase toEither reqV ofLeft errors ->H.modify_ (_ { signupErrors = errors })Right req -> doresponse <- lift (caps.signup req)case response ofAcc.SignupOK -> H.raise (SignupComplete $ req.username)Acc.CaptchaInvalid -> H.modify_ (_ { signupErrors = [CaptchaError] })Acc.ZAddrInvalid -> H.modify_ (_ { signupErrors = [ZAddrInvalid] })Acc.UsernameTaken -> H.modify_ (_ { signupErrors = [UsernameTaken] })
data CaptchaError= MissingInputSecret| InvalidInputSecret| MissingInputResponse| InvalidInputResponse| BadRequest| TimeoutOrDuplicate| CaptchaError Textderiving (Eq, Show)data CaptchaConfig = CaptchaConfig{ secretKey :: Text }data CaptchaResponse = CaptchaResponse{ success :: Bool, errorCodes :: [CaptchaError]}instance A.FromJSON CaptchaResponse whereparseJSON (A.Object v) =CaptchaResponse <$> v .: "success"<*> (fmap toError . join . toList <$> v .:? "error-codes")wheretoError = \case"missing-input-secret" -> MissingInputSecret"invalid-input-secret" -> InvalidInputSecret"missing-input-response" -> MissingInputResponse"invalid-input-response" -> InvalidInputResponse"bad-request" -> BadRequest"timeout-or-duplicate" -> TimeoutOrDuplicateother -> CaptchaError $ "Unexpected error code: " <> otherparseJSON _ =fail "Captcha response body was not a valid JSON object."
checkCaptcha :: CaptchaConfig -> Text -> IO CaptchaCheckResultcheckCaptcha cfg token = dorequest <- parseRequest "https://www.google.com/recaptcha/api/siteverify"reqWithBody <- formDataBody [partBS "secret" (T.encodeUtf8 $ secretKey cfg), partBS "response" (T.encodeUtf8 token)] requestmanager <- newTlsManagerresponse <- httpLbs reqWithBody managerpure $ case statusCode (responseStatus response) of200 ->case A.eitherDecode (responseBody response) ofLeft err -> Left [CaptchaError $ "Failed to decode JSON response: " <> T.pack err]Right cr -> if success cr then Right () else Left (errorCodes cr)errCode ->Left $ [CaptchaError $ "Unexpected status code: " <> T.pack (show errCode)]