Compare commits

..

22 commits

Author SHA1 Message Date
a8d4329e7d
Merge branch 'hotfix-a1' 2023-05-15 09:51:32 -05:00
6122a2d423
Adjust obfuscation of viewing key 2023-05-13 08:16:00 -05:00
e0f631fd03
Add obfuscated viewing key 2023-05-13 07:53:14 -05:00
24d8f25ed1
Add settings API endpoint 2023-05-12 15:17:13 -05:00
d4b56ca641
Update owner data endpoints 2023-05-12 13:57:56 -05:00
aef26675b4
Enhance owner endpoint 2023-05-12 08:32:55 -05:00
849f1d9120
Fix bug for parsing YWallet memos 2023-05-11 16:46:47 -05:00
8e05df0727
Improve debugging info 2023-05-11 15:31:27 -05:00
feea097405
Add debugging for memo parsing 2023-05-11 15:25:38 -05:00
e99db85feb
Add validation status to checkuser 2023-05-11 14:44:45 -05:00
04e0638752
Add getscanlang endpoint 2023-05-11 14:26:24 -05:00
f185c76fa0
Place price endpoint outside of fence 2023-05-11 13:15:17 -05:00
1c202cf817
Add getmainlang endpoint 2023-05-11 11:59:57 -05:00
f68675af03
Add checkuser endpoint 2023-05-11 11:36:28 -05:00
e80411d8bd
Fix new endpoint for login language 2023-05-10 15:24:16 -05:00
0afcaed076
Add new endpoint for login language 2023-05-10 15:09:19 -05:00
4e8ecb24e6
Correct owner id in API 2023-05-10 14:16:33 -05:00
e1919be03a
Correct xero endpoint 2023-05-10 10:52:25 -05:00
55d30b8b0f
Correct OwnerData 2023-05-10 10:42:40 -05:00
d67d1937f5
Rebuild owner endpoints 2023-05-09 11:03:26 -05:00
a201810134
Modify endpoint to create new owner 2023-05-08 16:01:46 -05:00
cbc4e02766
Implement API server access control 2023-05-08 11:21:09 -05:00
11 changed files with 549 additions and 111 deletions

View file

@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.5.0]
### Added
- `isUserValid` function
- New middleware to validated requests come from an existing user
- New endpoint for the language data of the login page
- New `OwnerData` to represent informational values
- New `OwnerSettings` to abstract configuration settings for owners
### Changed
- Modified API tests to use `session` parameter.
- Modified `api/owner` endpoint to use a specific data structure to create new owners
- Modified `api/owner` endpoint to use session as input
## [1.4.1] - 2023-05-02 ## [1.4.1] - 2023-05-02
### Fixed ### Fixed

View file

@ -1,5 +1,5 @@
name: zgo-backend name: zgo-backend
version: 1.4.1 version: 1.5.0
git: "https://git.vergara.tech/Vergara_Tech/zgo-backend" git: "https://git.vergara.tech/Vergara_Tech/zgo-backend"
license: BOSL license: BOSL
author: "Rene Vergara" author: "Rene Vergara"

View file

@ -275,6 +275,120 @@ instance Val Owner where
, "crmToken" =: cT , "crmToken" =: cT
] ]
-- | Type to represent informational data for Owners from UI
data OwnerData =
OwnerData
{ od_first :: T.Text
, od_last :: T.Text
, od_name :: T.Text
, od_street :: T.Text
, od_city :: T.Text
, od_state :: T.Text
, od_postal :: T.Text
, od_country :: T.Text
, od_email :: T.Text
, od_website :: T.Text
, od_phone :: T.Text
}
deriving (Eq, Show, Generic)
instance FromJSON OwnerData where
parseJSON =
withObject "OwnerData" $ \obj -> do
f <- obj .: "first"
l <- obj .: "last"
n <- obj .: "name"
s <- obj .: "street"
c <- obj .: "city"
st <- obj .: "state"
p <- obj .: "postal"
co <- obj .: "country"
e <- obj .: "email"
w <- obj .: "website"
ph <- obj .: "phone"
pure $ OwnerData f l n s c st p co e w ph
data OwnerSettings =
OwnerSettings
{ os_id :: Maybe ObjectId
, os_address :: T.Text
, os_name :: T.Text
, os_currency :: T.Text
, os_tax :: Bool
, os_taxValue :: Double
, os_vat :: Bool
, os_vatValue :: Double
, os_paid :: Bool
, os_zats :: Bool
, os_invoices :: Bool
, os_expiration :: UTCTime
, os_payconf :: Bool
, os_crmToken :: T.Text
, os_viewKey :: T.Text
}
deriving (Eq, Show, Generic)
instance FromJSON OwnerSettings where
parseJSON =
withObject "OwnerSettings" $ \obj -> do
i <- obj .:? "_id"
a <- obj .: "address"
n <- obj .: "name"
c <- obj .: "currency"
t <- obj .: "tax"
tV <- obj .: "taxValue"
v <- obj .: "vat"
vV <- obj .: "vatValue"
p <- obj .: "paid"
z <- obj .: "zats"
inv <- obj .: "invoices"
e <- obj .: "expiration"
pc <- obj .: "payconf"
cT <- obj .: "crmToken"
vK <- obj .: "viewkey"
pure $
OwnerSettings ((Just . read) =<< i) a n c t tV v vV p z inv e pc cT vK
instance ToJSON OwnerSettings where
toJSON (OwnerSettings i a n c t tV v vV p z inv e pc cT vK) =
object
[ "_id" .= maybe "" show i
, "address" .= a
, "name" .= n
, "currency" .= c
, "tax" .= t
, "taxValue" .= tV
, "vat" .= v
, "vatValue" .= vV
, "paid" .= p
, "zats" .= z
, "invoices" .= inv
, "expiration" .= e
, "payconf" .= pc
, "crmToken" .= cT
, "viewkey" .= (T.take 8 vK <> "...." <> T.takeEnd 8 vK)
]
-- Helper Functions
getOwnerSettings :: Owner -> OwnerSettings
getOwnerSettings o =
OwnerSettings
(o_id o)
(oaddress o)
(oname o)
(ocurrency o)
(otax o)
(otaxValue o)
(ovat o)
(ovatValue o)
(opaid o)
(ozats o)
(oinvoices o)
(oexpiration o)
(opayconf o)
(ocrmToken o)
(oviewkey o)
-- Database actions -- Database actions
-- | Function to upsert an Owner -- | Function to upsert an Owner
upsertOwner :: Owner -> Action IO () upsertOwner :: Owner -> Action IO ()
@ -306,6 +420,23 @@ removePro :: T.Text -> Action IO ()
removePro o = removePro o =
modify (select ["address" =: o] "owners") ["$set" =: ["invoices" =: False]] modify (select ["address" =: o] "owners") ["$set" =: ["invoices" =: False]]
updateOwnerSettings :: OwnerSettings -> Action IO ()
updateOwnerSettings os =
modify
(select ["_id" =: os_id os] "owners")
[ "$set" =:
[ "name" =: os_name os
, "currency" =: os_currency os
, "tax" =: os_tax os
, "taxValue" =: os_taxValue os
, "vat" =: os_vat os
, "vatValue" =: os_vatValue os
, "zats" =: os_zats os
, "payconf" =: os_payconf os
, "crmToken" =: os_crmToken os
]
]
-- | Type for a pro session -- | Type for a pro session
data ZGoProSession = data ZGoProSession =
ZGoProSession ZGoProSession

View file

@ -94,6 +94,16 @@ isUserNew p db tx =
isNothing <$> isNothing <$>
access p master db (findOne (select ["session" =: session tx] "users")) access p master db (findOne (select ["session" =: session tx] "users"))
-- | Function to verify if the given session has a valid user
isUserValid :: Pipe -> T.Text -> T.Text -> IO Bool
isUserValid p db s =
isJust <$>
access
p
master
db
(findOne (select ["session" =: s, "validated" =: True] "users"))
-- | Function to mark user as validated -- | Function to mark user as validated
validateUser :: T.Text -> Action IO () validateUser :: T.Text -> Action IO ()
validateUser session = validateUser session =
@ -106,11 +116,3 @@ generatePin = do
rngState <- newCryptoRNGState rngState <- newCryptoRNGState
runCryptoRNGT rngState $ runCryptoRNGT rngState $
randomString 7 ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] randomString 7 ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
-- | Helper function to pad a string to a given length
padLeft :: String -> Char -> Int -> String
padLeft s c m =
let isBaseLarger = length s > m
padder st ch m False = [ch | _ <- [1 .. (m - length st)]] ++ s
padder st _ _ True = st
in padder s c m isBaseLarger

View file

@ -30,8 +30,7 @@ data Xero =
deriving (Eq, Show) deriving (Eq, Show)
instance ToJSON Xero where instance ToJSON Xero where
toJSON (Xero i cI s) = toJSON (Xero i cI s) = object ["_id" .= show i, "clientId" .= cI]
object ["_id" .= show i, "clientId" .= cI, "clientSecret" .= s]
instance Val Xero where instance Val Xero where
val (Xero i cI s) = Doc ["_id" =: i, "clientId" =: cI, "clientSecret" =: s] val (Xero i cI s) = Doc ["_id" =: i, "clientId" =: cI, "clientSecret" =: s]

View file

@ -44,7 +44,7 @@ import Item
import LangComponent import LangComponent
import Network.HTTP.Simple import Network.HTTP.Simple
import Network.HTTP.Types.Status import Network.HTTP.Types.Status
import Network.Wai (Request, pathInfo) import Network.Wai (Application, Middleware, Request(..), pathInfo, responseLBS)
import Network.Wai.Middleware.Cors import Network.Wai.Middleware.Cors
import Network.Wai.Middleware.HttpAuth import Network.Wai.Middleware.HttpAuth
import Numeric import Numeric
@ -376,7 +376,7 @@ zToZGoTx' config pipe (ZcashTx t a aZ bh bt c conf m) = do
if m_payment zM' if m_payment zM'
then upsertPayment pipe (c_dbName config) tx then upsertPayment pipe (c_dbName config) tx
else access pipe master (c_dbName config) $ upsertZGoTx "txs" tx else access pipe master (c_dbName config) $ upsertZGoTx "txs" tx
Left e -> error "Failed to parse ZGo memo" Left e -> print $ "Failed to parse ZGo memo: " ++ show e
-- |Type to model a price in the ZGo database -- |Type to model a price in the ZGo database
data ZGoPrice = data ZGoPrice =
@ -545,6 +545,32 @@ needsAuth req =
"api":_ -> True "api":_ -> True
_ -> False _ -> False
zgoAuth :: Pipe -> T.Text -> Middleware
zgoAuth pipe dbName app req respond = do
let q = filter findSessionParam $ queryString req
isFenced <- needsAuth req
if isFenced
then do
if length q == 1
then do
isOk <- checkSession pipe dbName $ head q
if isOk
then app req respond
else respond $
responseLBS unauthorized401 [] "ZGo API access denied!"
else respond $ responseLBS unauthorized401 [] "ZGo API access denied!"
else app req respond
where
findSessionParam :: QueryItem -> Bool
findSessionParam (i, val) = i == "session"
checkSession ::
Pipe -> T.Text -> (BS.ByteString, Maybe BS.ByteString) -> IO Bool
checkSession p db (k, v) =
case v of
Just sessionId ->
isUserValid p db $ E.decodeUtf8With lenientDecode sessionId
Nothing -> return False
-- | Main API routes -- | Main API routes
routes :: Pipe -> Config -> ScottyM () routes :: Pipe -> Config -> ScottyM ()
routes pipe config = do routes pipe config = do
@ -566,6 +592,7 @@ routes pipe config = do
basicAuth basicAuth
(\u p -> return $ u == "user" && secureMemFromByteString p == passkey) (\u p -> return $ u == "user" && secureMemFromByteString p == passkey)
authSettings authSettings
middleware $ zgoAuth pipe $ c_dbName config
--Get list of countries for UI --Get list of countries for UI
get "/api/countries" $ do get "/api/countries" $ do
countries <- liftAndCatchIO $ run listCountries countries <- liftAndCatchIO $ run listCountries
@ -817,20 +844,29 @@ routes pipe config = do
status accepted202 status accepted202
Web.Scotty.json Web.Scotty.json
(object ["message" .= ("Incorrect plugin config" :: String)]) (object ["message" .= ("Incorrect plugin config" :: String)])
get "/checkuser" $ do
sess <- param "session"
user <- liftAndCatchIO $ run (findUser sess)
case parseUserBson =<< user of
Nothing -> status noContent204
Just u -> do
status ok200
Web.Scotty.json (object ["validated" .= uvalidated u])
--Get user associated with session --Get user associated with session
get "/api/user" $ do get "/api/user" $ do
sess <- param "session" sess <- param "session"
user <- liftAndCatchIO $ run (findUser sess) user <- liftAndCatchIO $ run (findUser sess)
case user of case user of
Nothing -> status noContent204 Nothing -> status noContent204
Just u -> Just u -> do
status ok200
Web.Scotty.json Web.Scotty.json
(object (object
[ "message" .= ("User found" :: String) [ "message" .= ("User found" :: String)
, "user" .= toJSON (parseUserBson u) , "user" .= toJSON (parseUserBson u)
]) ])
--Validate user, updating record --Validate user, updating record
post "/api/validateuser" $ do post "/validateuser" $ do
providedPin <- param "pin" providedPin <- param "pin"
sess <- param "session" sess <- param "session"
let pinHash = let pinHash =
@ -864,7 +900,7 @@ routes pipe config = do
status ok200 status ok200
else status noContent204 else status noContent204
--Get current blockheight from Zcash node --Get current blockheight from Zcash node
get "/api/blockheight" $ do get "/blockheight" $ do
blockInfo <- blockInfo <-
liftAndCatchIO $ makeZcashCall nodeUser nodePwd "getblock" ["-1"] liftAndCatchIO $ makeZcashCall nodeUser nodePwd "getblock" ["-1"]
let content = getResponseBody blockInfo :: RpcResponse Block let content = getResponseBody blockInfo :: RpcResponse Block
@ -875,23 +911,23 @@ routes pipe config = do
else do else do
status internalServerError500 status internalServerError500
--Get the ZGo node's shielded address --Get the ZGo node's shielded address
get "/api/getaddr" $ do Web.Scotty.json (object ["addr" .= nodeAddress]) get "/getaddr" $ do Web.Scotty.json (object ["addr" .= nodeAddress])
--Get owner by address --Get owner by address
get "/api/owner" $ do get "/api/owner" $ do
addr <- param "address" session <- param "session"
owner <- liftAndCatchIO $ run (findOwner addr) user <- liftAndCatchIO $ run (findUser session)
case owner of case parseUserBson =<< user of
Nothing -> status noContent204 Nothing -> status noContent204
Just o -> do Just u -> do
let pOwner = cast' (Doc o) owner <- liftAndCatchIO $ run (findOwner $ uaddress u)
case pOwner of case cast' . Doc =<< owner of
Nothing -> status internalServerError500 Nothing -> status noContent204
Just q -> do Just o -> do
status ok200 status ok200
Web.Scotty.json Web.Scotty.json
(object (object
[ "message" .= ("Owner found!" :: String) [ "message" .= ("Owner found!" :: String)
, "owner" .= toJSON (q :: Owner) , "owner" .= getOwnerSettings o
]) ])
get "/api/ownerid" $ do get "/api/ownerid" $ do
id <- param "id" id <- param "id"
@ -907,37 +943,78 @@ routes pipe config = do
Web.Scotty.json Web.Scotty.json
(object (object
[ "message" .= ("Owner found!" :: String) [ "message" .= ("Owner found!" :: String)
, "owner" .= toJSON (q :: Owner) , "owner" .=
object
[ "_id" .= (maybe "" show $ o_id q :: String)
, "address" .= oaddress q
, "name" .= oname q
, "currency" .= ocurrency q
, "tax" .= otax q
, "taxValue" .= otaxValue q
, "vat" .= ovat q
, "vatValue" .= ovatValue q
, "paid" .= opaid q
, "zats" .= ozats q
, "invoices" .= oinvoices q
, "expiration" .= oexpiration q
, "payconf" .= opayconf q
, "crmToken" .= ocrmToken q
]
]) ])
--Upsert owner to DB --Upsert owner to DB
post "/api/owner" $ do post "/api/owner" $ do
s <- param "session"
u <- liftAndCatchIO $ run (findUser s)
o <- jsonData o <- jsonData
let q = payload (o :: Payload Owner) now <- liftIO getCurrentTime
if not (opayconf q) let q = payload (o :: Payload OwnerData)
then do case parseUserBson =<< u of
_ <- liftAndCatchIO $ run (upsertOwner q) Nothing -> status internalServerError500
status created201 Just u' -> do
else do liftAndCatchIO $
known <- liftAndCatchIO $ listAddresses nodeUser nodePwd run $
if oaddress q `elem` map addy known upsertOwner $
Owner
Nothing
(uaddress u')
(od_name q)
"usd"
False
0
False
0
(od_first q)
(od_last q)
(od_email q)
(od_street q)
(od_city q)
(od_state q)
(od_postal q)
(od_phone q)
(od_website q)
(od_country q)
False
False
False
now
False
""
""
status accepted202
post "/api/ownersettings" $ do
s <- param "session"
u <- liftAndCatchIO $ run (findUser s)
o <- jsonData
now <- liftIO getCurrentTime
let q = payload (o :: Payload OwnerSettings)
case parseUserBson =<< u of
Nothing -> status internalServerError500
Just u' -> do
if os_address q == uaddress u'
then do then do
_ <- liftAndCatchIO $ run (upsertOwner q) liftAndCatchIO $ run $ updateOwnerSettings q
status created201 status accepted202
else do else status noContent204
vkInfo <-
liftAndCatchIO $
makeZcashCall
nodeUser
nodePwd
"z_importviewingkey"
[Data.Aeson.String (T.strip (oviewkey q)), "no"]
let content = getResponseBody vkInfo :: RpcResponse Object
if isNothing (err content)
then do
_ <- liftAndCatchIO $ run (upsertOwner q)
status created201
else do
status internalServerError500
--Get items associated with the given address --Get items associated with the given address
get "/api/items" $ do get "/api/items" $ do
addr <- param "address" addr <- param "address"
@ -966,7 +1043,7 @@ routes pipe config = do
status ok200 status ok200
else status noContent204 else status noContent204
--Get price for Zcash --Get price for Zcash
get "/api/price" $ do get "/price" $ do
curr <- param "currency" curr <- param "currency"
pr <- liftAndCatchIO $ run (findPrice curr) pr <- liftAndCatchIO $ run (findPrice curr)
case pr of case pr of
@ -1063,6 +1140,30 @@ routes pipe config = do
liftAndCatchIO $ run (deleteOrder oId) liftAndCatchIO $ run (deleteOrder oId)
status ok200 status ok200
-- Get language for component -- Get language for component
get "/getmainlang" $ do
lang <- param "lang"
txtPack' <- liftAndCatchIO $ run (findLangComponent lang "main")
case cast' . Doc =<< txtPack' of
Nothing -> status noContent204
Just textPack -> do
status ok200
Web.Scotty.json $ toJSON (textPack :: LangComponent)
get "/getscanlang" $ do
lang <- param "lang"
txtPack' <- liftAndCatchIO $ run (findLangComponent lang "scan")
case cast' . Doc =<< txtPack' of
Nothing -> status noContent204
Just textPack -> do
status ok200
Web.Scotty.json $ toJSON (textPack :: LangComponent)
get "/getloginlang" $ do
lang <- param "lang"
txtPack' <- liftAndCatchIO $ run (findLangComponent lang "login")
case cast' . Doc =<< txtPack' of
Nothing -> status noContent204
Just textPack -> do
status ok200
Web.Scotty.json $ toJSON (textPack :: LangComponent)
get "/api/getlang" $ do get "/api/getlang" $ do
component <- param "component" component <- param "component"
lang <- param "lang" lang <- param "lang"
@ -1073,12 +1174,12 @@ routes pipe config = do
Just tP -> do Just tP -> do
status ok200 status ok200
Web.Scotty.json $ toJSON (tP :: LangComponent) Web.Scotty.json $ toJSON (tP :: LangComponent)
post "/api/setlang" $ do {-post "/api/setlang" $ do-}
langComp <- jsonData {-langComp <- jsonData-}
_ <- {-_ <--}
liftAndCatchIO $ {-liftAndCatchIO $-}
mapM (run . loadLangComponent) (langComp :: [LangComponent]) {-mapM (run . loadLangComponent) (langComp :: [LangComponent])-}
status created201 {-status created201-}
-- | Make a Zcash RPC call -- | Make a Zcash RPC call
makeZcashCall :: makeZcashCall ::

View file

@ -119,6 +119,7 @@ type Parser = Parsec Void T.Text
pSession :: Parser MemoToken pSession :: Parser MemoToken
pSession = do pSession = do
optional spaceChar
string "ZGO" string "ZGO"
pay <- optional $ char 'p' pay <- optional $ char 'p'
string "::" string "::"
@ -142,9 +143,7 @@ pSaplingAddress = do
pMsg :: Parser MemoToken pMsg :: Parser MemoToken
pMsg = do pMsg = do
Msg . T.pack <$> Msg . T.pack <$>
some some (alphaNumChar <|> punctuationChar <|> charCategory OtherSymbol)
(alphaNumChar <|> punctuationChar <|> symbolChar <|>
charCategory OtherSymbol)
pMemo :: Parser MemoToken pMemo :: Parser MemoToken
pMemo = do pMemo = do

View file

@ -17,7 +17,7 @@
# #
# resolver: ./custom-snapshot.yaml # resolver: ./custom-snapshot.yaml
# resolver: https://example.com/snapshots/2018-01-01.yaml # resolver: https://example.com/snapshots/2018-01-01.yaml
resolver: lts-20.17 resolver: lts-20.19
#url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/4.yaml #url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/4.yaml
# User packages to be built. # User packages to be built.

View file

@ -31,7 +31,7 @@ packages:
hackage: crypto-rng-0.3.0.1@sha256:04f4ae75943ecad8b794950985054130f272d65a588b6b6528f56df0bfedc4dc,1565 hackage: crypto-rng-0.3.0.1@sha256:04f4ae75943ecad8b794950985054130f272d65a588b6b6528f56df0bfedc4dc,1565
snapshots: snapshots:
- completed: - completed:
sha256: 14ca51a9a597c32dd7804c10d079feea3d0ae40c5fbbb346cbd67b3ae49f6d01 sha256: 42f77c84b34f68c30c2cd0bf8c349f617a0f428264362426290847a6a2019b64
size: 649598 size: 649618
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/17.yaml url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/19.yaml
original: lts-20.17 original: lts-20.19

View file

@ -144,35 +144,99 @@ main = do
length pin `shouldBe` 7 length pin `shouldBe` 7
describe "API endpoints" $ do describe "API endpoints" $ do
beforeAll_ (startAPI loadedConfig) $ do beforeAll_ (startAPI loadedConfig) $ do
describe "Validate user session" $ do
it "validate with correct pin" $ do
req <-
testPost
"/validateuser"
[ ("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")
, ("pin", Just "1234567")
]
res <- httpLBS req
getResponseStatus res `shouldBe` accepted202
describe "Price endpoint" $ do describe "Price endpoint" $ do
it "returns a price for an existing currency" $ do it "returns a price for an existing currency" $ do
req <- testGet "/api/price" [("currency", Just "usd")] req <-
testGet
"/api/price"
[ ("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")
, ("currency", Just "usd")
]
res <- httpJSON req res <- httpJSON req
getResponseStatus (res :: Response A.Value) `shouldBe` ok200 getResponseStatus (res :: Response A.Value) `shouldBe` ok200
it "returns 204 when the currency is not supported" $ do it "returns 204 when the currency is not supported" $ do
req <- testGet "/api/price" [("currency", Just "jpy")] req <-
testGet
"/api/price"
[ ("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")
, ("currency", Just "jpy")
]
res <- httpLBS req res <- httpLBS req
getResponseStatus res `shouldBe` noContent204 getResponseStatus res `shouldBe` noContent204
it "returs 401 when the session is not valid" $ do
req <-
testGet
"/api/price"
[ ("session", Just "th7s1sa-fake-6u1d-7h47-1m4deuph3r3")
, ("currency", Just "usd")
]
res <- httpLBS req
getResponseStatus res `shouldBe` unauthorized401
describe "Countries endpoint" $ do describe "Countries endpoint" $ do
it "returns a list of countries" $ do it "returns a list of countries" $ do
req <- testGet "/api/countries" [] req <-
testGet
"/api/countries"
[("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")]
res <- httpJSON req res <- httpJSON req
getResponseStatus (res :: Response A.Value) `shouldBe` ok200 getResponseStatus (res :: Response A.Value) `shouldBe` ok200
it "returns 401 with invalid session" $ do
req <-
testGet
"/api/countries"
[("session", Just "fake-id-string-283that0")]
res <- httpLBS req
getResponseStatus res `shouldBe` unauthorized401
describe "blockheight endpoint" $ do describe "blockheight endpoint" $ do
it "returns a block number" $ do it "returns a block number" $ do
req <- testGet "/api/blockheight" [] req <-
testGet
"/api/blockheight"
[("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")]
res <- httpJSON req res <- httpJSON req
height (getResponseBody (res :: Response Block)) `shouldSatisfy` \x -> height (getResponseBody (res :: Response Block)) `shouldSatisfy` \x ->
x > 1600000 x > 1600000
describe "xero config endpoint" $ do describe "Xero endpoints" $ do
it "returns the config" $ do describe "xero" $ do
req <- testGet "/api/xero" [] it "returns the xero config" $ do
res <- httpJSON req req <-
getResponseStatus (res :: Response A.Value) `shouldBe` ok200 testGet
it "returns the account code" $ do "/api/xero"
req <- testGet "/api/xeroaccount" [("address", Just "Zaddy")] [("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")]
res <- httpJSON req res <- httpJSON req
getResponseStatus (res :: Response A.Value) `shouldBe` ok200 getResponseStatus (res :: Response A.Value) `shouldBe` ok200
it "returns 401 with invalid session" $ do
req <-
testGet "/api/xero" [("session", Just "fnelrkgnlyebrlvns82949")]
res <- httpLBS req
getResponseStatus res `shouldBe` unauthorized401
describe "xeroaccount" $ do
it "returns the account code" $ do
req <-
testGet
"/api/xeroaccount"
[ ("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")
, ("address", Just "Zaddy")
]
res <- httpJSON req
getResponseStatus (res :: Response A.Value) `shouldBe` ok200
it "returns 401 with invalid session" $ do
req <-
testGet
"/api/xeroaccount"
[("session", Just "fnelrkgnlyebrlvns82949")]
res <- httpLBS req
getResponseStatus res `shouldBe` unauthorized401
describe "User endpoint" $ do describe "User endpoint" $ do
it "returns a user for a session" $ do it "returns a user for a session" $ do
req <- req <-
@ -181,28 +245,24 @@ main = do
[("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")] [("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")]
res <- httpJSON req res <- httpJSON req
getResponseStatus (res :: Response A.Value) `shouldBe` ok200 getResponseStatus (res :: Response A.Value) `shouldBe` ok200
it "returns 204 when no user" $ do it "returns 401 when user doesn't exist" $ do
req <- req <-
testGet testGet
"/api/user" "/api/user"
[("session", Just "suchafak-euui-dican-eve-nbelieveitca")] [("session", Just "suchafak-euui-dican-eve-nbelieveitca")]
res <- httpLBS req res <- httpLBS req
getResponseStatus res `shouldBe` noContent204 getResponseStatus res `shouldBe` unauthorized401
it "validate with correct pin" $ do
req <-
testPost
"/api/validateuser"
[ ("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")
, ("pin", Just "1234567")
]
res <- httpLBS req
getResponseStatus res `shouldBe` accepted202
it "deletes user by id" $ do it "deletes user by id" $ do
req <- testDelete "/api/user/" "6272a90f2b05a74cf1000001" req <-
testDelete
"/api/user/"
"6272a90f2b05a74cf1000003"
[("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")]
res <- httpLBS req res <- httpLBS req
getResponseStatus res `shouldBe` ok200 getResponseStatus res `shouldBe` ok200
describe "Owner endpoint" $ do describe "Owner endpoint" $
prop "add owner" testOwnerAdd --prop "add owner" testOwnerAdd
do
it "return owner by address" $ do it "return owner by address" $ do
req <- req <-
testGet testGet
@ -210,15 +270,31 @@ main = do
[ ( "address" [ ( "address"
, Just , Just
"zs1w6nkameazc5gujm69350syl5w8tgvyaphums3pw8eytzy5ym08x7dvskmykkatmwrucmgv3er8e") "zs1w6nkameazc5gujm69350syl5w8tgvyaphums3pw8eytzy5ym08x7dvskmykkatmwrucmgv3er8e")
, ("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")
] ]
res <- httpJSON req res <- httpJSON req
getResponseStatus (res :: Response A.Value) `shouldBe` ok200 getResponseStatus (res :: Response A.Value) `shouldBe` ok200
it "owner by address returns 401 with bad session" $ do
req <-
testGet
"/api/owner"
[ ( "address"
, Just
"zs1w6nkameazc5gujm69350syl5w8tgvyaphums3pw8eytzy5ym08x7dvskmykkatmwrucmgv3er8e")
, ("session", Just "3fake94j-rbal-jeber-nvlke-4bal8dcdcd")
]
res <- httpLBS req
getResponseStatus res `shouldBe` unauthorized401
it "return owner by id" $ do it "return owner by id" $ do
req <- req <-
testGet "/api/ownerid" [("id", Just "627ad3492b05a76be3000001")] testGet
"/api/ownerid"
[ ("id", Just "627ad3492b05a76be3000001")
, ("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")
]
res <- httpLBS req res <- httpLBS req
getResponseStatus res `shouldBe` ok200 getResponseStatus res `shouldBe` ok200
describe "Order endpoint" $ do describe "Order endpoints" $ do
prop "upsert order" testOrderAdd prop "upsert order" testOrderAdd
it "get order by session" $ do it "get order by session" $ do
req <- req <-
@ -227,30 +303,85 @@ main = do
[("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")] [("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")]
res <- httpJSON req res <- httpJSON req
getResponseStatus (res :: Response A.Value) `shouldBe` ok200 getResponseStatus (res :: Response A.Value) `shouldBe` ok200
it "get order by session fails when invalid" $ do
req <-
testGet
"/api/order"
[("session", Just "35bfb9c2-9ad2-4fe5-fake-99d63b8dcdcd")]
res <- httpLBS req
getResponseStatus res `shouldBe` unauthorized401
it "get order by id" $ do it "get order by id" $ do
req <- testGet "/api/order/627ab3ea2b05a76be3000000" [] req <-
testGet
"/api/order/627ab3ea2b05a76be3000000"
[("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")]
res <- httpJSON req res <- httpJSON req
getResponseStatus (res :: Response A.Value) `shouldBe` ok200 getResponseStatus (res :: Response A.Value) `shouldBe` ok200
it "get order with wrong id" $ do it "get order with wrong id" $ do
req <- testGet "/api/order/6273hrb" [] req <-
testGet
"/api/order/6273hrb"
[("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")]
res <- httpLBS req res <- httpLBS req
getResponseStatus res `shouldBe` noContent204 getResponseStatus res `shouldBe` noContent204
it "get order by id fails with bad session" $ do
req <-
testGet
"/api/order/627ab3ea2b05a76be3000000"
[("session", Just "35bfb9c2-9ad2-4fe5-fake-99d63b8dcdcd")]
res <- httpLBS req
getResponseStatus res `shouldBe` unauthorized401
it "get all orders for owner" $ do it "get all orders for owner" $ do
req <- testGet "/api/allorders" [("address", Just "Zaddy")] req <-
testGet
"/api/allorders"
[ ("address", Just "Zaddy")
, ("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")
]
res <- httpJSON req res <- httpJSON req
getResponseStatus (res :: Response A.Value) `shouldBe` ok200 getResponseStatus (res :: Response A.Value) `shouldBe` ok200
it "get all orders for owner fails with bad session" $ do
req <-
testGet
"/api/allorders"
[ ("address", Just "Zaddy")
, ("session", Just "35bfb9c2-9ad2-4fe5-fake-99d63b8dcdcd")
]
res <- httpLBS req
getResponseStatus res `shouldBe` unauthorized401
it "delete order by id" $ do it "delete order by id" $ do
req <- testDelete "/api/order/" "627ab3ea2b05a76be3000000" req <-
testDelete
"/api/order/"
"627ab3ea2b05a76be3000000"
[("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")]
res <- httpLBS req res <- httpLBS req
getResponseStatus res `shouldBe` ok200 getResponseStatus res `shouldBe` ok200
it "delete order by id fails with bad session" $ do
req <-
testDelete
"/api/order/"
"627ab3ea2b05a76be3000000"
[("session", Just "35bfb9c2-9ad2-4fe5-fake-99d63b8dcdcd")]
res <- httpLBS req
getResponseStatus res `shouldBe` unauthorized401
describe "Item endpoint" $ do describe "Item endpoint" $ do
prop "add item" testItemAdd prop "add item" testItemAdd
it "get items" $ do it "get items" $ do
req <- testGet "/api/items" [("address", Just "Zaddy")] req <-
testGet
"/api/items"
[ ("address", Just "Zaddy")
, ("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")
]
res <- httpJSON req res <- httpJSON req
getResponseStatus (res :: Response A.Value) `shouldBe` ok200 getResponseStatus (res :: Response A.Value) `shouldBe` ok200
it "delete item" $ do it "delete item" $ do
req <- testDelete "/api/item/" "627d7ba92b05a76be3000003" req <-
testDelete
"/api/item/"
"627d7ba92b05a76be3000003"
[("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")]
res <- httpLBS req res <- httpLBS req
getResponseStatus res `shouldBe` ok200 getResponseStatus res `shouldBe` ok200
describe "WooCommerce endpoints" $ do describe "WooCommerce endpoints" $ do
@ -258,7 +389,9 @@ main = do
req <- req <-
testPost testPost
"/api/wootoken" "/api/wootoken"
[("ownerid", Just "627ad3492b05a76be3000001")] [ ("ownerid", Just "627ad3492b05a76be3000001")
, ("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")
]
res <- httpLBS req res <- httpLBS req
getResponseStatus res `shouldBe` accepted202 getResponseStatus res `shouldBe` accepted202
it "authenticate with incorrect owner" $ do it "authenticate with incorrect owner" $ do
@ -329,21 +462,40 @@ main = do
req <- req <-
testGet testGet
"/api/getlang" "/api/getlang"
[("lang", Just "en-US"), ("component", Just "login")] [ ("lang", Just "en-US")
, ("component", Just "login")
, ("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")
]
res <- httpJSON req res <- httpJSON req
getResponseStatus (res :: Response A.Value) `shouldBe` ok200 getResponseStatus (res :: Response A.Value) `shouldBe` ok200
it "existing component with bad session" $ do
req <-
testGet
"/api/getlang"
[ ("lang", Just "en-US")
, ("component", Just "login")
, ("session", Just "35bfb9c2-fake-4fe5-adda-99d63b8dcdcd")
]
res <- httpLBS req
getResponseStatus res `shouldBe` unauthorized401
it "wrong component" $ do it "wrong component" $ do
req <- req <-
testGet testGet
"/api/getlang" "/api/getlang"
[("lang", Just "en-US"), ("component", Just "test")] [ ("lang", Just "en-US")
, ("component", Just "test")
, ("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")
]
res <- httpLBS req res <- httpLBS req
getResponseStatus res `shouldBe` noContent204 getResponseStatus res `shouldBe` noContent204
it "wrong language" $ do it "wrong language" $ do
req <- req <-
testGet testGet
"/api/getlang" "/api/getlang"
[("lang", Just "fr-FR"), ("component", Just "login")] [ ("lang", Just "fr-FR")
, ("component", Just "login")
, ("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")
]
res <- httpLBS req res <- httpLBS req
getResponseStatus res `shouldBe` noContent204 getResponseStatus res `shouldBe` noContent204
around handleDb $ around handleDb $
@ -632,11 +784,16 @@ testPostJson endpoint body = do
setRequestMethod "POST" $ setRequestPath endpoint defaultRequest setRequestMethod "POST" $ setRequestPath endpoint defaultRequest
return testRequest return testRequest
testDelete :: B.ByteString -> B.ByteString -> IO Request testDelete ::
testDelete endpoint par = do B.ByteString
-> B.ByteString
-> [(B.ByteString, Maybe B.ByteString)]
-> IO Request
testDelete endpoint par body = do
let user = "user" let user = "user"
let pwd = "superSecret" let pwd = "superSecret"
let testRequest = let testRequest =
setRequestQueryString body $
setRequestPort 3000 $ setRequestPort 3000 $
setRequestBasicAuth user pwd $ setRequestBasicAuth user pwd $
setRequestMethod "DELETE" $ setRequestMethod "DELETE" $
@ -658,14 +815,22 @@ testOrderAdd o =
monadicIO $ do monadicIO $ do
req <- req <-
run $ testPostJson "/api/order" (A.object ["payload" A..= A.toJSON o]) run $ testPostJson "/api/order" (A.object ["payload" A..= A.toJSON o])
res <- httpLBS req res <-
httpLBS $
setRequestQueryString
[("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")]
req
assert $ getResponseStatus res == created201 assert $ getResponseStatus res == created201
testItemAdd :: Item -> Property testItemAdd :: Item -> Property
testItemAdd i = do testItemAdd i = do
monadicIO $ do monadicIO $ do
req <- run $ testPostJson "/api/item" (A.object ["payload" A..= A.toJSON i]) req <- run $ testPostJson "/api/item" (A.object ["payload" A..= A.toJSON i])
res <- httpLBS req res <-
httpLBS $
setRequestQueryString
[("session", Just "35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd")]
req
assert $ getResponseStatus res == created201 assert $ getResponseStatus res == created201
-- | Open the MongoDB connection -- | Open the MongoDB connection
@ -692,6 +857,9 @@ startAPI config = do
_ <- forkIO (scotty 3000 appRoutes) _ <- forkIO (scotty 3000 appRoutes)
_ <- _ <-
access pipe master "test" (Database.MongoDB.delete (select [] "wootokens")) access pipe master "test" (Database.MongoDB.delete (select [] "wootokens"))
_ <- access pipe master "test" (Database.MongoDB.delete (select [] "users"))
_ <- access pipe master "test" (Database.MongoDB.delete (select [] "items"))
_ <- access pipe master "test" (Database.MongoDB.delete (select [] "orders"))
let myUser = let myUser =
User User
(Just (read "6272a90f2b05a74cf1000001" :: ObjectId)) (Just (read "6272a90f2b05a74cf1000001" :: ObjectId))
@ -714,6 +882,28 @@ startAPI config = do
, "pin" =: upin myUser , "pin" =: upin myUser
, "validated" =: uvalidated myUser , "validated" =: uvalidated myUser
]) ])
let myUser1 =
User
(Just (read "6272a90f2b05a74cf1000003" :: ObjectId))
"zs1w6nkameazc5gujm69350syl5w8tgvyaphums3pw8eytzy5ym08x7dvskmykkatmwrucmgv3er8e"
"35bfb9c2-9ad2-4fe5-adda-99d63b8dcdcd"
1613487
"8ea140fbb30615d6cae383c4f62f3ad9afb10b804f63138d5b53990bd56e0162"
True
_ <-
access
pipe
master
"test"
(insert_
"users"
[ "address" =: uaddress myUser1
, "_id" =: u_id myUser1
, "session" =: usession myUser1
, "blocktime" =: ublocktime myUser1
, "pin" =: upin myUser1
, "validated" =: uvalidated myUser1
])
let myOwner = let myOwner =
Owner Owner
(Just (read "627ad3492b05a76be3000001")) (Just (read "627ad3492b05a76be3000001"))

View file

@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
name: zgo-backend name: zgo-backend
version: 1.4.1 version: 1.5.0
synopsis: Haskell Back-end for the ZGo point-of-sale application synopsis: Haskell Back-end for the ZGo point-of-sale application
description: Please see the README at <https://git.vergara.tech/Vergara_Tech//zgo-backend#readme> description: Please see the README at <https://git.vergara.tech/Vergara_Tech//zgo-backend#readme>
category: Web category: Web