Skip to content

Commit

Permalink
fix: ip check fallback following axios regression (#2054)
Browse files Browse the repository at this point in the history
* chore: fix ip check fallback following axios regression

* chore: remove duplicated otel wrapper
  • Loading branch information
nicolasburtey committed Dec 20, 2022
1 parent 99662d8 commit 7df3880
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 83 deletions.
4 changes: 2 additions & 2 deletions galoy.yaml
Expand Up @@ -143,9 +143,9 @@ spamLimits:
memoSharingSatsThreshold: 1000
memoSharingCentsThreshold: 50
ipRecording:
enabled: true
enabled: false
proxyChecking:
enabled: true
enabled: false
fees:
withdraw:
method: flat
Expand Down
167 changes: 103 additions & 64 deletions src/app/accounts/update-account-ip.ts
@@ -1,9 +1,13 @@
import { getIpConfig } from "@config"
import { RepositoryError } from "@domain/errors"
import { IpFetcherServiceError } from "@domain/ipfetcher"
import { ErrorLevel } from "@domain/shared"
import { IpFetcher } from "@services/ipfetcher"
import { AccountsIpRepository } from "@services/mongoose/accounts-ips"
import { asyncRunInSpan, SemanticAttributes } from "@services/tracing"
import {
addAttributesToCurrentSpan,
recordExceptionInCurrentSpan,
} from "@services/tracing"

const accountsIp = AccountsIpRepository()

Expand All @@ -15,67 +19,102 @@ export const updateAccountIPsInfo = async ({
accountId: AccountId
ip?: IpAddress
logger: Logger
}): Promise<void | RepositoryError> =>
asyncRunInSpan(
"app.users.updateAccountIPsInfo",
{
attributes: {
[SemanticAttributes.CODE_FUNCTION]: "updateAccountIPsInfo",
[SemanticAttributes.CODE_NAMESPACE]: "app.users",
},
},
async () => {
const ipConfig = getIpConfig()

const lastConnection = new Date()

const userIP = await accountsIp.findById(accountId)
if (userIP instanceof RepositoryError) return userIP

if (!ip || !ipConfig.ipRecordingEnabled) {
const result = await accountsIp.update(userIP)

if (result instanceof Error) {
logger.error(
{ result, accountId, ip },
"impossible to update user last connection",
)

return result
}

return
}

const lastIP = userIP.lastIPs.find((ipObject) => ipObject.ip === ip)

if (lastIP) {
lastIP.lastConnection = lastConnection
} else {
let ipInfo: IPType = {
}): Promise<void | RepositoryError> => {
const ipConfig = getIpConfig()

const lastConnection = new Date()

const accountIP = await accountsIp.findById(accountId)
if (accountIP instanceof RepositoryError) return accountIP

if (!ip || !ipConfig.ipRecordingEnabled) {
const result = await accountsIp.update(accountIP)

if (result instanceof Error) {
logger.error(
{ result, accountId, ip },
"impossible to update account last connection",
)

return result
}

return
}

let ipInfo: IPType

const ipFromDb = accountIP.lastIPs.find((ipObject) => ipObject.ip === ip)

if (ipFromDb) {
ipInfo = ipFromDb
ipInfo.lastConnection = lastConnection
} else {
ipInfo = {
ip,
firstConnection: lastConnection,
lastConnection: lastConnection,
}
}

if (
ipConfig.proxyCheckingEnabled &&
(!ipInfo.isoCode || !ipInfo.proxy || !ipInfo.asn)
) {
console.log({ ipInfo }, "ipInfo")

const ipFetcher = IpFetcher()
const ipFetcherInfo = await ipFetcher.fetchIPInfo(ip)

if (ipFetcherInfo instanceof IpFetcherServiceError) {
recordExceptionInCurrentSpan({
error: ipFetcherInfo.message,
level: ErrorLevel.Warn,
attributes: {
ip,
accountId,
},
})

logger.error({ accountId, ip }, "impossible to get ip detail")
return ipFetcherInfo
}

// deep copy
const ipFetcherInfoForOtel = JSON.parse(JSON.stringify(ipFetcherInfo))

for (const key in ipFetcherInfoForOtel) {
ipFetcherInfoForOtel["proxycheck." + key] = ipFetcherInfoForOtel[key]
delete ipFetcherInfoForOtel[key]
}

addAttributesToCurrentSpan(ipFetcherInfoForOtel)

if (!ipFetcherInfo.isoCode || !ipFetcherInfo.proxy || !ipFetcherInfo.asn) {
const error = `missing mandatory fields. isoCode: ${ipFetcherInfo.isoCode}, proxy: ${ipFetcherInfo.proxy}, asn: ${ipFetcherInfo.asn}`
recordExceptionInCurrentSpan({
error,
level: ErrorLevel.Warn,
attributes: {
ip,
firstConnection: lastConnection,
lastConnection: lastConnection,
}

if (ipConfig.proxyCheckingEnabled) {
const ipFetcher = IpFetcher()
const ipFetcherInfo = await ipFetcher.fetchIPInfo(ip)

if (ipFetcherInfo instanceof IpFetcherServiceError) {
logger.error({ accountId, ip }, "impossible to get ip detail")
return ipFetcherInfo
}

ipInfo = { ...ipInfo, ...ipFetcherInfo }
}
userIP.lastIPs.push(ipInfo)
}
const result = await accountsIp.update(userIP)

if (result instanceof Error) {
logger.error({ result, accountId, ip }, "impossible to update ip")
return result
}
},
)
accountId,
},
})
} else {
// using Object.assign instead of ... because of conflict with mongoose hidden properties
ipInfo = Object.assign(ipInfo, ipFetcherInfo)

// removing current ip from lastIPs - if it exists
accountIP.lastIPs = accountIP.lastIPs.filter((ipDb) => ipDb.ip !== ip)

// adding it back with the correct info
accountIP.lastIPs.push(ipInfo)
}
}
const result = await accountsIp.update(accountIP)

if (result instanceof Error) {
logger.error({ result, accountId, ip }, "impossible to update ip")
return result
}
}
7 changes: 6 additions & 1 deletion src/app/auth/request-phone-code.ts
@@ -1,5 +1,6 @@
import { getTestAccounts } from "@config"
import { getTestAccounts, getTwilioConfig } from "@config"
import { TestAccountsChecker } from "@domain/accounts/test-accounts-checker"
import { NotImplementedError } from "@domain/errors"
import { RateLimitConfig } from "@domain/rate-limit"
import { RateLimiterExceededError } from "@domain/rate-limit/errors"
import { consumeLimiter } from "@services/rate-limit"
Expand Down Expand Up @@ -69,6 +70,10 @@ export const requestPhoneCode = async ({
return true
}

if (getTwilioConfig().accountSid === "AC_twilio_id") {
return new NotImplementedError("use test account for local dev and tests")
}

return TwilioClient().initiateVerify(phone)
}

Expand Down
4 changes: 2 additions & 2 deletions src/config/schema.ts
Expand Up @@ -452,9 +452,9 @@ export const configSchema = {
required: ["enabled"],
additionalProperties: false,
default: {
enabled: true,
enabled: false,
proxyChecking: {
enabled: true,
enabled: false,
},
},
},
Expand Down
5 changes: 2 additions & 3 deletions src/config/yaml.ts
Expand Up @@ -264,9 +264,8 @@ export const getBuildVersions = (): {
export const PROXY_CHECK_APIKEY = yamlConfig?.PROXY_CHECK_APIKEY

export const getIpConfig = (config = yamlConfig): IpConfig => ({
ipRecordingEnabled:
process.env.NODE_ENV === "test" ? false : config.ipRecording?.enabled,
proxyCheckingEnabled: config.ipRecording?.proxyChecking?.enabled,
ipRecordingEnabled: config.ipRecording.enabled,
proxyCheckingEnabled: config.ipRecording.proxyChecking.enabled,
})

export const getApolloConfig = (config = yamlConfig): ApolloConfig => config.apollo
Expand Down
27 changes: 16 additions & 11 deletions src/services/ipfetcher/index.ts
@@ -1,24 +1,29 @@
import axios from "axios"
import { UnknownIpFetcherServiceError } from "@domain/ipfetcher"
import { PROXY_CHECK_APIKEY } from "@config"
import { addAttributesToCurrentSpan } from "@services/tracing"

export const IpFetcher = (): IIpFetcherService => {
const fetchIPInfo = async (ip: string): Promise<IPInfo | IpFetcherServiceError> => {
try {
const { data } = await axios.get(
`https://proxycheck.io/v2/${ip}?key=${PROXY_CHECK_APIKEY}&vpn=1&asn=1`,
)
const proxy = !!(data[ip] && data[ip].proxy && data[ip].proxy === "yes")
const isoCode = data[ip] && data[ip].isocode
const params: { [id: string]: string } = {
vpn: "1",
asn: "1",
time: "1",
risk: "1",
}

if (PROXY_CHECK_APIKEY) {
params["key"] = PROXY_CHECK_APIKEY
}

addAttributesToCurrentSpan({
...data[ip],
isoCode,
proxy,
status: data.status,
const { data } = await axios.request({
url: `https://proxycheck.io/v2/${ip}`,
params,
})

const proxy = !!(data[ip] && data[ip].proxy && data[ip].proxy === "yes")
const isoCode = data[ip] && data[ip].isocode

return { ...data[ip], isoCode, proxy, status: data.status }
} catch (err) {
return new UnknownIpFetcherServiceError(err)
Expand Down

0 comments on commit 7df3880

Please sign in to comment.