From b52b55ddc09db87e65e8a2c216a2269f631f3a20 Mon Sep 17 00:00:00 2001 From: Juan P Lopez Date: Sun, 17 Dec 2023 14:51:07 -0500 Subject: [PATCH] fix: account update (#3720) --- .../app/accounts/delete-business-map-info.ts | 5 +- .../app/accounts/mark-account-for-deletion.ts | 8 ++- .../src/app/accounts/update-account-level.ts | 5 +- .../app/accounts/update-business-map-info.ts | 9 ++- core/api/src/app/admin/update-user-phone.ts | 11 ++-- .../src/servers/graphql-public-api-server.ts | 12 +++- core/api/test/bats/admin.bats | 59 +++++++------------ core/api/test/bats/helpers/_common.bash | 33 +++++++++++ 8 files changed, 94 insertions(+), 48 deletions(-) diff --git a/core/api/src/app/accounts/delete-business-map-info.ts b/core/api/src/app/accounts/delete-business-map-info.ts index d12ffe6231..e69894a622 100644 --- a/core/api/src/app/accounts/delete-business-map-info.ts +++ b/core/api/src/app/accounts/delete-business-map-info.ts @@ -1,4 +1,5 @@ -import { checkedToUsername } from "@/domain/accounts" +import { AccountValidator, checkedToUsername } from "@/domain/accounts" + import { AccountsRepository } from "@/services/mongoose/accounts" export const deleteBusinessMapInfo = async ({ @@ -13,6 +14,8 @@ export const deleteBusinessMapInfo = async ({ const account = await accountsRepo.findByUsername(usernameChecked) if (account instanceof Error) return account + const accountValidator = AccountValidator(account) + if (accountValidator instanceof Error) return accountValidator const newAccount = { ...account, title: null, coordinates: null } diff --git a/core/api/src/app/accounts/mark-account-for-deletion.ts b/core/api/src/app/accounts/mark-account-for-deletion.ts index 35479e6553..d72f1779e0 100644 --- a/core/api/src/app/accounts/mark-account-for-deletion.ts +++ b/core/api/src/app/accounts/mark-account-for-deletion.ts @@ -1,9 +1,11 @@ import { getBalanceForWallet, listWalletsByAccountId } from "@/app/wallets" -import { AccountStatus } from "@/domain/accounts" + +import { AccountStatus, AccountValidator } from "@/domain/accounts" import { AccountHasPositiveBalanceError } from "@/domain/authentication/errors" + import { IdentityRepository } from "@/services/kratos" -import { AccountsRepository, UsersRepository } from "@/services/mongoose" import { addEventToCurrentSpan } from "@/services/tracing" +import { AccountsRepository, UsersRepository } from "@/services/mongoose" export const markAccountForDeletion = async ({ accountId, @@ -17,6 +19,8 @@ export const markAccountForDeletion = async ({ const accountsRepo = AccountsRepository() const account = await accountsRepo.findById(accountId) if (account instanceof Error) return account + const accountValidator = AccountValidator(account) + if (accountValidator instanceof Error) return accountValidator const wallets = await listWalletsByAccountId(account.id) if (wallets instanceof Error) return wallets diff --git a/core/api/src/app/accounts/update-account-level.ts b/core/api/src/app/accounts/update-account-level.ts index 33ffd1ae18..8e27a048c7 100644 --- a/core/api/src/app/accounts/update-account-level.ts +++ b/core/api/src/app/accounts/update-account-level.ts @@ -1,4 +1,5 @@ -import { checkedToAccountId } from "@/domain/accounts" +import { AccountValidator, checkedToAccountId } from "@/domain/accounts" + import { AccountsRepository } from "@/services/mongoose" export const updateAccountLevel = async ({ @@ -15,6 +16,8 @@ export const updateAccountLevel = async ({ const account = await accountsRepo.findById(accountId) if (account instanceof Error) return account + const accountValidator = AccountValidator(account) + if (accountValidator instanceof Error) return accountValidator account.level = level return accountsRepo.update(account) diff --git a/core/api/src/app/accounts/update-business-map-info.ts b/core/api/src/app/accounts/update-business-map-info.ts index 66fa775a8c..db0d100dda 100644 --- a/core/api/src/app/accounts/update-business-map-info.ts +++ b/core/api/src/app/accounts/update-business-map-info.ts @@ -1,4 +1,9 @@ -import { checkedCoordinates, checkedMapTitle, checkedToUsername } from "@/domain/accounts" +import { + AccountValidator, + checkedCoordinates, + checkedMapTitle, + checkedToUsername, +} from "@/domain/accounts" import { AccountsRepository } from "@/services/mongoose/accounts" export const updateBusinessMapInfo = async ({ @@ -17,6 +22,8 @@ export const updateBusinessMapInfo = async ({ const account = await accountsRepo.findByUsername(usernameChecked) if (account instanceof Error) return account + const accountValidator = AccountValidator(account) + if (accountValidator instanceof Error) return accountValidator const coordinates = checkedCoordinates({ latitude, longitude }) if (coordinates instanceof Error) return coordinates diff --git a/core/api/src/app/admin/update-user-phone.ts b/core/api/src/app/admin/update-user-phone.ts index 7bbad4b767..88b54d358a 100644 --- a/core/api/src/app/admin/update-user-phone.ts +++ b/core/api/src/app/admin/update-user-phone.ts @@ -1,9 +1,11 @@ import { markAccountForDeletion } from "@/app/accounts" -import { checkedToAccountId } from "@/domain/accounts" -import { AuthWithPhonePasswordlessService } from "@/services/kratos" -import { AccountsRepository } from "@/services/mongoose/accounts" + +import { AccountValidator, checkedToAccountId } from "@/domain/accounts" + import { UsersRepository } from "@/services/mongoose/users" import { addAttributesToCurrentSpan } from "@/services/tracing" +import { AccountsRepository } from "@/services/mongoose/accounts" +import { AuthWithPhonePasswordlessService } from "@/services/kratos" export const updateUserPhone = async ({ accountId: accountIdRaw, @@ -20,10 +22,11 @@ export const updateUserPhone = async ({ const accountsRepo = AccountsRepository() const account = await accountsRepo.findById(accountId) if (account instanceof Error) return account + const accountValidator = AccountValidator(account) + if (accountValidator instanceof Error) return accountValidator const kratosUserId = account.kratosUserId const usersRepo = UsersRepository() - const newUser = await usersRepo.findByPhone(phone) if (!(newUser instanceof Error)) { // if newUser exists, then we need to delete it (only if balance is 0 diff --git a/core/api/src/servers/graphql-public-api-server.ts b/core/api/src/servers/graphql-public-api-server.ts index 1589ed99e2..7329665444 100644 --- a/core/api/src/servers/graphql-public-api-server.ts +++ b/core/api/src/servers/graphql-public-api-server.ts @@ -28,6 +28,7 @@ import { } from "@/services/tracing" import { parseIps } from "@/domain/accounts-ips" +import { AccountStatus } from "@/domain/accounts" const isAuthenticated = rule({ cache: "contextual" })(( _parent, @@ -37,6 +38,15 @@ const isAuthenticated = rule({ cache: "contextual" })(( return "domainAccount" in ctx && !!ctx.domainAccount }) +const hasMutationPermissions = rule({ cache: "contextual" })(( + _parent, + _args, + ctx: GraphQLPublicContext | GraphQLPublicContextAuth, +) => { + const isAuthenticated = "domainAccount" in ctx && !!ctx.domainAccount + return isAuthenticated && ctx.domainAccount.status === AccountStatus.Active +}) + const setGqlContext = async ( req: Request, _res: Response, @@ -89,7 +99,7 @@ export async function startApolloServerForCoreSchema() { ...mutationFields.authed.atAccountLevel, ...mutationFields.authed.atWalletLevel, })) { - authedMutationFields[key] = isAuthenticated + authedMutationFields[key] = hasMutationPermissions } const permissions = shield( diff --git a/core/api/test/bats/admin.bats b/core/api/test/bats/admin.bats index d1f0d02cb5..71762e0b50 100644 --- a/core/api/test/bats/admin.bats +++ b/core/api/test/bats/admin.bats @@ -14,40 +14,6 @@ TESTER_TOKEN_NAME="tester" TESTER_PHONE="+19876543210" username="user1" -exec_admin_graphql() { - local token=$1 - local query_name=$2 - local variables=${3:-"{}"} - echo "GQL query - token: ${token} - query: ${query_name} - vars: ${variables}" - echo "{\"query\": \"$(gql_admin_query $query_name)\", \"variables\": $variables}" - - if [[ "${BATS_TEST_DIRNAME}" != "" ]]; then - run_cmd="run" - else - run_cmd="" - fi - - gql_route="admin/graphql" - - ${run_cmd} curl -s \ - -X POST \ - -H "Oauth2-Token: $token" \ - -H "Content-Type: application/json" \ - -d "{\"query\": \"$(gql_admin_query $query_name)\", \"variables\": $variables}" \ - "${GALOY_ENDPOINT}/${gql_route}" - - echo "GQL output: '$output'" -} - -gql_admin_query() { - cat "$(gql_admin_file $1)" | tr '\n' ' ' | sed 's/"/\\"/g' -} - -gql_admin_file() { - echo "${BATS_TEST_DIRNAME:-${CORE_ROOT}/test/bats}/admin-gql/$1.gql" -} - - @test "admin: perform admin queries/mutations" { client=$(curl -L -s -X POST $HYDRA_ADMIN_API/admin/clients \ -H 'Content-Type: application/json' \ @@ -63,7 +29,7 @@ gql_admin_file() { -H 'Content-Type: application/x-www-form-urlencoded' \ -u "$client_id:$client_secret" \ -d "grant_type=client_credentials" | jq -r '.access_token' - ) + ) echo "admin_token: $admin_token" @@ -122,7 +88,7 @@ gql_admin_file() { exec_admin_graphql "$admin_token" 'account-details-by-username' "$variables" refetched_id="$(graphql_output '.data.accountDetailsByUsername.id')" [[ "$refetched_id" == "$id" ]] || exit 1 - + variables=$( jq -n \ --arg level "TWO" \ @@ -150,6 +116,23 @@ gql_admin_file() { account_status="$(graphql_output '.data.accountUpdateStatus.accountDetails.status')" [[ "$account_status" == "LOCKED" ]] || exit 1 + # User cannot delete the account if it is locked + exec_graphql "$TESTER_TOKEN_NAME" 'account-delete' + delete_error_message="$(graphql_output '.errors[0].message')" + [[ "$delete_error_message" == "Not authorized" ]] || exit 1 + + # Admin cannot update the phone if it is locked + new_phone="$(random_phone)" + variables=$( + jq -n \ + --arg phone "$new_phone" \ + --arg accountId "$id" \ + '{input: {phone: $phone, accountId:$accountId}}' + ) + exec_admin_graphql $admin_token 'user-update-phone' "$variables" + update_error_message="$(graphql_output '.data.userUpdatePhone.errors[0].message')" + [[ "$update_error_message" == "Account is inactive." ]] || exit 1 + variables=$( jq -n \ --arg accountId "$id" \ @@ -162,7 +145,7 @@ gql_admin_file() { userId="$(graphql_output '.data.accountDetailsByAccountId.owner.id')" echo "userId: $userId" - + variables=$( jq -n \ --arg userId "$userId" \ @@ -175,6 +158,6 @@ gql_admin_file() { # TODO: add check by email - + # TODO: business update map info } diff --git a/core/api/test/bats/helpers/_common.bash b/core/api/test/bats/helpers/_common.bash index 2fcb6b952b..205c675220 100644 --- a/core/api/test/bats/helpers/_common.bash +++ b/core/api/test/bats/helpers/_common.bash @@ -207,3 +207,36 @@ is_contact() { ) [[ "$fetched_username" == "$contact_username" ]] || return 1 } + +exec_admin_graphql() { + local token=$1 + local query_name=$2 + local variables=${3:-"{}"} + echo "GQL query - token: ${token} - query: ${query_name} - vars: ${variables}" + echo "{\"query\": \"$(gql_admin_query $query_name)\", \"variables\": $variables}" + + if [[ "${BATS_TEST_DIRNAME}" != "" ]]; then + run_cmd="run" + else + run_cmd="" + fi + + gql_route="admin/graphql" + + ${run_cmd} curl -s \ + -X POST \ + -H "Oauth2-Token: $token" \ + -H "Content-Type: application/json" \ + -d "{\"query\": \"$(gql_admin_query $query_name)\", \"variables\": $variables}" \ + "${GALOY_ENDPOINT}/${gql_route}" + + echo "GQL output: '$output'" +} + +gql_admin_query() { + cat "$(gql_admin_file $1)" | tr '\n' ' ' | sed 's/"/\\"/g' +} + +gql_admin_file() { + echo "${BATS_TEST_DIRNAME:-${CORE_ROOT}/test/bats}/admin-gql/$1.gql" +}