5IDB3IWSB6LFW4U772Y7BH5Y3FQOQ7IFWLVXDZE5XS6SKJITFV4QC U256ZALIPTVWLNACYPIMWLNEYDQWP7CHF4Y4CGMILQTONJHMGQVQC B6HWAPDPXIWH7CHK5VLMWLL6EQN6NOFZEFYO47BPUY2ZO4SL7VDAC 64C6AWH66FDKU6UE6Z6JPX2J2GBM2JOPTH2GL6LHKAIUBGNGDZ5AC 4U7F3CPIDTK6JSEDMNMHVKSR7HOQDLZQD2PPVMDLHO5SFSIMUXZAC UUR6SMCAJMA7O3ZFUCQMPZFDDIPUVQ5IHUAC5F252YVD6H3JIKPQC QMRKFEPGFBCEWAIXPEIG5ILKAJ2JH5L3TOITHR4HNJXK5FN3KXBQC HMDM3B557TO5RYP2IGFFC2C2VN6HYZTDQ47CJY2O37BW55DSMFZAC O2BZOX7MS4JCDS3C6EJQXAWUEQV6HVDCIF2FIN2BCJNRLIU6ZVKAC IZEVQF627FA7VV25KJAWYWGGC35LZUUBBQRPN5ZAINTQLPEDRTEAC NEDDHXUK3GNFMOFO3KLU7NRIHCTYNWBT3D6HTKZAOXFDE6HMDZ6AC EFSXYZPOGA5M4DN65IEIDO7JK6U34DMQELAPHOIL2UAT6HRC66BAC KEP5WUFJXTMKRRNZLYTGYYWA4VLFCMHTKTJYF5EA5IWBYFMU6WYQC V2VDN77HCSRYYWXDJJ2XOVHV4P6PVWNJZLXZ7JUYPQEZQIH5BZ3QC IPG33FAWXGEQ2PO6OXRT2PWWXHRNMPVUKKADL6UKKN5GD2CNZ25AC 6L5BK5EHPAOQX3JCKUJ273UDNAC23LPQL4HIJGM4AV3P3QK5OKIQC 2XQD6KKKD6QVHFHAEMVE3XXY7X2T7BLTLL7XIILZAXNJJH2YONUQC BROSTG5KP3NUNLSYPVQID254TE47E5RKQAKLPIY7PGWETE6JNMTAC HALRDT2F22DAQ54M334YSKD2XJCAXDXEZPDKGMOWKH7XELBAS6MAC BXGLKYRXO2O4NRM3BLNWQ7AWVPQXAMFS57MFYHJNOZZEZZW5BH6AC MB5SHULBN3WP7TGUWZDP6BRGP423FTYKF67T5IF5YHHLNXKQ5REAC ADMKQQGCGVSHHIMVQ4XFRDCG544SBJCYALSKZV45CQQBZ4ACUH2AC W35DDBFYF6Z4ZPCFEO5RPAONZLYCSNTXUSTS6FIUUVZHCI6Q7GHAC PT4276XCOP5NJ3GRFJLIBZKVNVAOATAY5PLWV7FWK6RZW5FTEP5AC UWMGUJOW5X5HQTS76T2FD7MNAJF7SESPQVU5FDIZO52V75TT2X6AC RFYEVKZQLOOQP536GRZOROSQW2O7TEHJ2HZDRVVUSBKLY5FBEO3QC {-# LANGUAGE TemplateHaskell #-}module Aftok.Currency.Zcash( ZAddr(..), _ZAddr, ZAddrError(..), ZcashdConfig(..), rpcValidateZAddr) whereimport Control.Lens ( makePrisms )import qualified Data.Aeson as Aimport Data.Aeson ( Value, (.=), (.:), (.:?), object, encode )import Data.Aeson.Types ( Parser )import qualified Data.Text.Encoding as Timport Network.HTTP.Client ( Manager, RequestBody(..), defaultRequest, responseBody, responseStatus, httpLbs, host, port, method, requestBody)import Network.HTTP.Types ( Status, statusCode )newtype ZAddr = ZAddr { zaddrText :: Text }deriving (Eq, Ord, Show)makePrisms ''ZAddrdata ZAddrType= Sprout| Saplingdata ZcashdConfig = ZcashdConfig{ zcashdHost :: Text, zcashdPort :: Int}data ZAddrError= ServiceError Status| ParseError String| ZAddrInvalid| SproutAddress| DataMissingvalidateZAddrRequest :: Text -> ValuevalidateZAddrRequest addr = object[ "jsonrpc" .= ("1.0" :: Text), "id" .= ("aftok-z_validateaddress" :: Text), "method" .= ("z_validateaddress" :: Text), "params" .= [addr]]data ValidateZAddrResponse = ValidateZAddrResponse{ isValid :: Bool, _address :: Maybe Text, addrType :: Maybe ZAddrType}instance A.FromJSON ValidateZAddrResponse whereparseJSON = parseValidateZAddrResponseparseAddrType :: Text -> Maybe ZAddrTypeparseAddrType = \case"sprout" -> Just Sprout"sapling" -> Just Sapling_ -> NothingparseValidateZAddrResponse :: Value -> Parser ValidateZAddrResponseparseValidateZAddrResponse = \case(A.Object v) ->ValidateZAddrResponse <$> v .: "isvalid"<*> v .:? "address"<*> ((traverse (maybe (fail "Not a recognized zaddr type") pure) . fmap parseAddrType) =<< v .:? "type")_ ->fail "ZAddr validation response body was not a valid JSON object"rpcValidateZAddr :: Manager -> ZcashdConfig -> Text -> IO (Either ZAddrError ZAddr)rpcValidateZAddr mgr cfg addr = dolet req = defaultRequest { host = T.encodeUtf8 $ zcashdHost cfg, port = zcashdPort cfg, method = "POST", requestBody = RequestBodyLBS $ encode (validateZAddrRequest addr)}response <- httpLbs req mgrlet status = responseStatus responsepure $ case statusCode status of200 ->case A.eitherDecode (responseBody response) ofLeft err -> Left (ParseError err)Right resp ->if isValid respthencase addrType resp ofJust Sprout -> Left SproutAddressJust Sapling -> Right (ZAddr addr)_ -> Left DataMissingelseLeft ZAddrInvalid_ ->Left (ServiceError status)
module Aftok.Users( RegisterOps(..), RegisterError(..))whereimport Aftok.Types (Email(..))import Aftok.Currency.Zcash (ZAddr, ZAddrError)data RegisterError= ZAddrParseError ZAddrErrordata RegisterOps m = RegisterOps{ parseZAddr :: Text -> m (Either RegisterError ZAddr), sendConfirmationEmail :: Email -> m ()}
import Aftok.Currency.ZCash ( ZAddr(..) )import Aftok.Databaseimport Aftok.Projectimport Aftok.Types
import Aftok.Database ( createUser, acceptInvitation )import Aftok.Project ( InvitationCode, parseInvCode )import Aftok.Users ( RegisterOps(..) )import Aftok.Types ( UserId, User(..), AccountRecovery(..), Email(..), UserName(..), _UserName)
<$> (fromString <$> v .: "password")<*> (v .: "captchaToken")<*> (parseInvitationCodes =<< v .: "invitation_codes")
<$> (fromString <$> v .: "password")<*> (v .: "captchaToken")<*> (parseInvitationCodes =<< v .: "invitation_codes")
void . either (const . throwDenied $ AU.AuthError "Captcha check failed, please try again.") pure $ captchaResult
let captchaFailed = throwDenied $ AU.AuthError "Captcha check failed, please try again."void . either (const captchaFailed) pure $ captchaResult
now <- liftIO C.getCurrentTime
acctRecovery <- case (userData ^. regUser . userAccountRecovery) ofRecoverByEmail e -> doliftIO $ sendConfirmationEmail ops epure $ RecoverByEmail eRecoverByZAddr z -> dozaddrValid <- liftIO $ parseZAddr ops zcase zaddrValid ofLeft _ -> snapError 400 "The Z-Address provided for account recovery was invalid."Right r -> pure $ RecoverByZAddr rnow <- liftIO C.getCurrentTime