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
) where
import Control.Lens ( makePrisms )
import qualified Data.Aeson as A
import Data.Aeson ( Value, (.=), (.:), (.:?), object, encode )
import Data.Aeson.Types ( Parser )
import qualified Data.Text.Encoding as T
import 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 ''ZAddr
data ZAddrType
= Sprout
| Sapling
data ZcashdConfig = ZcashdConfig
{ zcashdHost :: Text
, zcashdPort :: Int
}
data ZAddrError
= ServiceError Status
| ParseError String
| ZAddrInvalid
| SproutAddress
| DataMissing
validateZAddrRequest :: Text -> Value
validateZAddrRequest 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 where
parseJSON = parseValidateZAddrResponse
parseAddrType :: Text -> Maybe ZAddrType
parseAddrType = \case
"sprout" -> Just Sprout
"sapling" -> Just Sapling
_ -> Nothing
parseValidateZAddrResponse :: Value -> Parser ValidateZAddrResponse
parseValidateZAddrResponse = \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 = do
let req = defaultRequest { host = T.encodeUtf8 $ zcashdHost cfg
, port = zcashdPort cfg
, method = "POST"
, requestBody = RequestBodyLBS $ encode (validateZAddrRequest addr)
}
response <- httpLbs req mgr
let status = responseStatus response
pure $ case statusCode status of
200 ->
case A.eitherDecode (responseBody response) of
Left err -> Left (ParseError err)
Right resp ->
if isValid resp
then
case addrType resp of
Just Sprout -> Left SproutAddress
Just Sapling -> Right (ZAddr addr)
_ -> Left DataMissing
else
Left ZAddrInvalid
_ ->
Left (ServiceError status)
module Aftok.Users
( RegisterOps(..)
, RegisterError(..)
)
where
import Aftok.Types (Email(..))
import Aftok.Currency.Zcash (ZAddr, ZAddrError)
data RegisterError
= ZAddrParseError ZAddrError
data RegisterOps m = RegisterOps
{ parseZAddr :: Text -> m (Either RegisterError ZAddr)
, sendConfirmationEmail :: Email -> m ()
}
import Aftok.Currency.ZCash ( ZAddr(..) )
import Aftok.Database
import Aftok.Project
import 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) of
RecoverByEmail e -> do
liftIO $ sendConfirmationEmail ops e
pure $ RecoverByEmail e
RecoverByZAddr z -> do
zaddrValid <- liftIO $ parseZAddr ops z
case zaddrValid of
Left _ -> snapError 400 "The Z-Address provided for account recovery was invalid."
Right r -> pure $ RecoverByZAddr r
now <- liftIO C.getCurrentTime