Skip to content

Commit

Permalink
Merge pull request #60 from InjectiveLabs/feat/geolocation-and-vpn-ch…
Browse files Browse the repository at this point in the history
…ecks

feat: geolocation and vpn checks
  • Loading branch information
Thomas authored Aug 6, 2024
2 parents 77a74a4 + 267f569 commit 5acd067
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 4 deletions.
4 changes: 0 additions & 4 deletions layer/data/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ import {

export const stableCoinSymbols = ['USDT', 'USDC', 'USDCet']

// address: IS_TESTNET
// ? '0x5512c04B6FF813f3571bDF64A1d74c98B5257332'
// : '0xe28b3b32b6c345a34ff64674606124dd5aceca30',

export const injectivePeggyAddress = {
[Network.Mainnet]: '0xF955C57f9EA9Dc8781965FEaE0b6A2acE2BAD6f3',
[Network.MainnetK8s]: '0xF955C57f9EA9Dc8781965FEaE0b6A2acE2BAD6f3',
Expand Down
230 changes: 230 additions & 0 deletions layer/store/geo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import { defineStore } from 'pinia'
import { HttpRequestException } from '@injectivelabs/exceptions'
import { HttpClient, SECONDS_IN_A_DAY } from '@injectivelabs/utils'
import {
GOOGLE_MAPS_KEY,
VPN_CHECKS_ENABLED,
PROXY_DETECTION_API_KEY
} from './../utils/constant'

type GeoStoreState = {
geoContinent: string
geoCountry: string
ipAddress: string
browserCountry: string
vpnDetected: boolean
vpnCheckedTimestamp: number
}

const initialStateFactory = (): GeoStoreState => ({
vpnDetected: false,
geoContinent: '',
geoCountry: '',
ipAddress: '',
browserCountry: '',
vpnCheckedTimestamp: 0
})

export const useSharedGeoStore = defineStore('sharedGeo', {
state: (): GeoStoreState => initialStateFactory(),
getters: {
country: (state) => state.browserCountry || state.geoCountry
},
actions: {
async fetchGeoLocation() {
const sharedGeoStore = useSharedGeoStore()

const httpClient = new HttpClient('https://geoip.injective.dev/')

try {
const { data } = (await httpClient.get('info')) as {
data: {
continent: string
country: string
}
}

sharedGeoStore.$patch({
geoContinent: data.continent,
geoCountry: data.country
})
} catch (error: any) {
// silently throw
}
},

async fetchIpAddress() {
const sharedGeoStore = useSharedGeoStore()

try {
const httpClient = new HttpClient('https://www.myexternalip.com/json')

const { data } = (await httpClient.get('')) as any

sharedGeoStore.$patch({
ipAddress: data.ip
})
} catch (e: unknown) {
throw new HttpRequestException(new Error((e as any).message), {
contextModule: 'region'
})
}
},

async fetchVPNStatus() {
const sharedGeoStore = useSharedGeoStore()

if (!sharedGeoStore.ipAddress) {
await sharedGeoStore.fetchIpAddress()
}

const httpClient = new HttpClient('https://vpnapi.io/', { timeout: 1000 })

try {
const response = (await httpClient.get(
`api/${sharedGeoStore.ipAddress}`,
{
key: PROXY_DETECTION_API_KEY
}
)) as {
data: {
security: {
vpn: boolean
proxy: boolean
tor: boolean
relay: boolean
}
location: {
country_code: string
}
}
}

if (!response.data) {
sharedGeoStore.$patch({
vpnDetected: false
})

return
}

const { security } = response.data

const vpnDetected =
security.proxy || security.vpn || security.tor || security.relay

sharedGeoStore.$patch({
vpnDetected
})
} catch (e: unknown) {
sharedGeoStore.$patch({
vpnDetected: false
})
}
},

async fetchUserCountryFromBrowser() {
const sharedGeoStore = useSharedGeoStore()

const position = (await new Promise((resolve, reject) =>
navigator.geolocation.getCurrentPosition(resolve, reject)
).catch(() => {
return {
longitude: '',
latitude: ''
}
})) as {
coords: {
longitude: string
latitude: string
}
}

if (position.coords.longitude === '' || position.coords.latitude === '') {
return
}

const googleMapsHttpClient = new HttpClient(
'https://maps.googleapis.com/maps/api/geocode/'
)
const GOOGLE_MAPS_SUFFIX = `json?latlng=${position.coords.latitude},${position.coords.longitude}&sensor=false&key=${GOOGLE_MAPS_KEY}`

try {
const response = (await googleMapsHttpClient.get(
GOOGLE_MAPS_SUFFIX
)) as {
data: {
results: {
address_components: { types: string[]; short_name: string }[]
}[]
}
}

const [results] = response.data.results

const country = results.address_components.find((component) =>
component.types.includes('country')
)

sharedGeoStore.$patch({
browserCountry: country?.short_name || ''
})
} catch (e: unknown) {
// silently throw
}
},

async showVpnToast(docLink?: string) {
const sharedNotificationStore = useSharedNotificationStore()

sharedNotificationStore.info({
title: 'VPN or proxy detected',
description:
'Please make sure that you have allowed location access in your browser and system settings.',
timeout: 10 * 1000,
...(docLink
? {
actions: [
{
key: docLink,
label: 'Learn More',
callback: () => window.open(docLink, '_blank')
}
]
}
: {})
})
},

async fetchVpnLocation(docLink?: string) {
const sharedGeoStore = useSharedGeoStore()

if (!VPN_CHECKS_ENABLED) {
return
}

const todayInSeconds = Math.floor(Date.now() / 1000)

await sharedGeoStore.fetchVPNStatus()

if (!sharedGeoStore.vpnDetected) {
return
}

const shouldCheckVpnOrProxyUsage = SECONDS_IN_A_DAY.times(7)
.plus(sharedGeoStore.vpnCheckedTimestamp)
.lte(todayInSeconds)

if (!shouldCheckVpnOrProxyUsage) {
return
}

sharedGeoStore.showVpnToast(docLink)
await sharedGeoStore.fetchUserCountryFromBrowser()

sharedGeoStore.$patch({
vpnCheckedTimestamp: todayInSeconds
})
}
}
})
16 changes: 16 additions & 0 deletions layer/utils/constant/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,26 @@ export const AMPLITUDE_KEY_PROD = import.meta.env
.VITE_AMPLITUDE_KEY_PROD as string
export const GOOGLE_ANALYTICS_KEY = import.meta.env
.VITE_GOOGLE_ANALYTICS_KEY as string
export const PROXY_DETECTION_API_KEY =
import.meta.env.VITE_PROXY_DETECTION_API_KEY || ''
export const HOTJAR_KEY = import.meta.env.VITE_HOTJAR_KEY_DEV as string
export const MIXPANEL_KEY = import.meta.env.VITE_MIXPANEL_KEY || ''
export const GOOGLE_MAPS_KEY = import.meta.env.VITE_GOOGLE_MAPS_KEY || ''

export const VPN_CHECKS_ENABLED: boolean =
import.meta.env.VITE_VPN_CHECKS_ENABLED === 'true'
export const GEO_IP_RESTRICTIONS_ENABLED: boolean =
import.meta.env.VITE_GEO_IP_RESTRICTIONS_ENABLED === 'true'

export const CW20_ADAPTER_CONTRACT = getCw20AdapterContractForNetwork(NETWORK)
export const APP_NAME = env.VITE_NAME
export const APP_BASE_URL = env.VITE_BASE_URL
export const WALLET_CONNECT_PROJECT_ID = env.VITE_WALLET_CONNECT_PROJECT_ID

if (VPN_CHECKS_ENABLED && !GOOGLE_MAPS_KEY) {
throw new Error('GOOGLE_MAPS_KEY is required when VPN_CHECKS_ENABLED')
}

if (VPN_CHECKS_ENABLED && !PROXY_DETECTION_API_KEY) {
throw new Error('PROXY_DETECTION_API_KEY is required when VPN_CHECKS_ENABLED')
}

0 comments on commit 5acd067

Please sign in to comment.