Skip to content

Commit

Permalink
Merge pull request #50 from gnosischain/feat/improvements
Browse files Browse the repository at this point in the history
Feat/improvements
  • Loading branch information
giacomognosis authored Dec 22, 2024
2 parents b94a287 + 4744472 commit af604f0
Show file tree
Hide file tree
Showing 25 changed files with 351 additions and 138 deletions.
46 changes: 23 additions & 23 deletions .github/workflows/publish-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,28 +77,28 @@ jobs:
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

eks-deployment-restart:
# Run job on branch dev only
if: github.ref == 'refs/heads/dev'
runs-on: ubuntu-latest
needs: build-and-push-image
permissions:
id-token: write # Required for the OIDC, see https://github.com/aws-actions/configure-aws-credentials?tab=readme-ov-file#OIDC
contents: read
steps:
- name: configure aws credentials
uses: aws-actions/[email protected]
with:
audience: sts.amazonaws.com
role-to-assume: ${{ secrets.DEV_AWS_EKS_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
aws-region: ${{ secrets.DEV_AWS_REGION }}
# eks-deployment-restart:
# # Run job on branch dev only
# if: github.ref == 'refs/heads/dev'
# runs-on: ubuntu-latest
# needs: build-and-push-image
# permissions:
# id-token: write # Required for the OIDC, see https://github.com/aws-actions/configure-aws-credentials?tab=readme-ov-file#OIDC
# contents: read
# steps:
# - name: configure aws credentials
# uses: aws-actions/[email protected]
# with:
# audience: sts.amazonaws.com
# role-to-assume: ${{ secrets.DEV_AWS_EKS_ROLE }}
# role-session-name: GitHub_to_AWS_via_FederatedOIDC
# aws-region: ${{ secrets.DEV_AWS_REGION }}

- name: Configure kubectl for EKS
run: aws eks update-kubeconfig --name ${{ secrets.DEV_AWS_EKS_CLUSTER }} --region ${{ secrets.DEV_AWS_REGION }}
# - name: Configure kubectl for EKS
# run: aws eks update-kubeconfig --name ${{ secrets.DEV_AWS_EKS_CLUSTER }} --region ${{ secrets.DEV_AWS_REGION }}

- name: Restart Bridge Explorer Deployment
if: github.ref == 'refs/heads/dev'
run: |
kubectl config use-context arn:aws:eks:${{ secrets.DEV_AWS_REGION }}:${{ secrets.DEV_AWS_ACCOUNT_ID }}:cluster/${{ secrets.DEV_AWS_EKS_CLUSTER }}
kubectl rollout restart deploy/${{ secrets.DEV_AWS_EKS_DEPLOYMENT_API }} -n ${{ secrets.DEV_AWS_EKS_NAMESPACE }}
# - name: Restart Deployment
# if: github.ref == 'refs/heads/dev'
# run: |
# kubectl config use-context arn:aws:eks:${{ secrets.DEV_AWS_REGION }}:${{ secrets.DEV_AWS_ACCOUNT_ID }}:cluster/${{ secrets.DEV_AWS_EKS_CLUSTER }}
# kubectl rollout restart deploy/${{ secrets.DEV_AWS_EKS_DEPLOYMENT_API }} -n ${{ secrets.DEV_AWS_EKS_NAMESPACE }}
52 changes: 26 additions & 26 deletions .github/workflows/publish-ui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
"REACT_APP_HCAPTCHA_SITE_KEY=${{ secrets.DEV_REACT_APP_HCAPTCHA_SITE_KEY }}"
"REACT_APP_CAPTCHA_SITE_KEY=${{ secrets.DEV_REACT_APP_CAPTCHA_SITE_KEY }}"
"REACT_APP_FAUCET_API_URL=${{ secrets.DEV_REACT_APP_FAUCET_API_URL}}"
- name: Gnosis Chain - Main branch / tags - Build and push Docker image
Expand All @@ -67,7 +67,7 @@ jobs:
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-gc
labels: ${{ steps.meta.outputs.labels }}
build-args: |
"REACT_APP_HCAPTCHA_SITE_KEY=${{ secrets.PROD_GC_REACT_APP_HCAPTCHA_SITE_KEY }}"
"REACT_APP_CAPTCHA_SITE_KEY=${{ secrets.PROD_GC_REACT_APP_CAPTCHA_SITE_KEY }}"
"REACT_APP_FAUCET_API_URL=${{ secrets.PROD_GC_REACT_APP_FAUCET_API_URL}}"
- name: Chiado Chain - Main branch / tags - Build and push Docker image
Expand All @@ -79,31 +79,31 @@ jobs:
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-chiado
labels: ${{ steps.meta.outputs.labels }}
build-args: |
"REACT_APP_HCAPTCHA_SITE_KEY=${{ secrets.PROD_CHIADO_REACT_APP_HCAPTCHA_SITE_KEY }}"
"REACT_APP_CAPTCHA_SITE_KEY=${{ secrets.PROD_CHIADO_REACT_APP_CAPTCHA_SITE_KEY }}"
"REACT_APP_FAUCET_API_URL=${{ secrets.PROD_CHIADO_REACT_APP_FAUCET_API_URL}}"
eks-deployment-restart:
# Run job on branch dev only
if: github.ref == 'refs/heads/dev'
runs-on: ubuntu-latest
needs: build-and-push-image
permissions:
id-token: write # Required for the OIDC, see https://github.com/aws-actions/configure-aws-credentials?tab=readme-ov-file#OIDC
contents: read
steps:
- name: configure aws credentials
uses: aws-actions/[email protected]
with:
audience: sts.amazonaws.com
role-to-assume: ${{ secrets.DEV_AWS_EKS_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
aws-region: ${{ secrets.DEV_AWS_REGION }}
# eks-deployment-restart:
# # Run job on branch dev only
# if: github.ref == 'refs/heads/dev'
# runs-on: ubuntu-latest
# needs: build-and-push-image
# permissions:
# id-token: write # Required for the OIDC, see https://github.com/aws-actions/configure-aws-credentials?tab=readme-ov-file#OIDC
# contents: read
# steps:
# - name: configure aws credentials
# uses: aws-actions/[email protected]
# with:
# audience: sts.amazonaws.com
# role-to-assume: ${{ secrets.DEV_AWS_EKS_ROLE }}
# role-session-name: GitHub_to_AWS_via_FederatedOIDC
# aws-region: ${{ secrets.DEV_AWS_REGION }}

- name: Configure kubectl for EKS
run: aws eks update-kubeconfig --name ${{ secrets.DEV_AWS_EKS_CLUSTER }} --region ${{ secrets.DEV_AWS_REGION }}
# - name: Configure kubectl for EKS
# run: aws eks update-kubeconfig --name ${{ secrets.DEV_AWS_EKS_CLUSTER }} --region ${{ secrets.DEV_AWS_REGION }}

- name: Restart Bridge Explorer Deployment
if: github.ref == 'refs/heads/dev'
run: |
kubectl config use-context arn:aws:eks:${{ secrets.DEV_AWS_REGION }}:${{ secrets.DEV_AWS_ACCOUNT_ID }}:cluster/${{ secrets.DEV_AWS_EKS_CLUSTER }}
kubectl rollout restart deploy/${{ secrets.DEV_AWS_EKS_DEPLOYMENT_UI }} -n ${{ secrets.DEV_AWS_EKS_NAMESPACE }}
# - name: Restart Deployment
# if: github.ref == 'refs/heads/dev'
# run: |
# kubectl config use-context arn:aws:eks:${{ secrets.DEV_AWS_REGION }}:${{ secrets.DEV_AWS_ACCOUNT_ID }}:cluster/${{ secrets.DEV_AWS_EKS_CLUSTER }}
# kubectl rollout restart deploy/${{ secrets.DEV_AWS_EKS_DEPLOYMENT_UI }} -n ${{ secrets.DEV_AWS_EKS_NAMESPACE }}
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ venv/
ENV/
env.bak/
venv.bak/
*/.env

# Spyder project settings
.spyderproject
Expand Down Expand Up @@ -157,4 +158,12 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
#.idea/


*/*.whl
*/*.tar.gz

*/bin

*/pyvenv.cfg
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ cd api
python3 -m venv .venv
. .venv/bin/activate
pip3 install -r requirements-dev.txt
pip install setuptools
```

### Run application
Expand Down Expand Up @@ -66,6 +68,19 @@ flask -A api create_enabled_token xDAI 10200 0x000000000000000000000000000000000

Once enabled, the token will appear in the list of enabled tokens on the endpoint `api/v1/info`.

#### Create access keys

To create access keys on the API just run the command `create_access_keys`.
Accepted parameters: token name, chain ID, token address, maximum amount per day per user, whether native or erc20

Samples below:

```
cd /api
flask -A api create_access_keys
```


#### Change maximum daily amounts per user

If you want to change the amount you are giving out for a specific token, make sure you have sqlite
Expand Down Expand Up @@ -96,4 +111,26 @@ yarn
```
cd app
yarn start
```
```


### Docker Compose Up and create Access keys

If you do not reset the volume you will be able to reuse the sqlite database with latest data (access keys and enabled tokens)

```
docker-compose up --build -d
docker ps
docker exec -it <container_name_or_id> /bin/bash
docker exec -it <container_name_or_id> flask -A api create_access_keys
docker exec -it <container_name_or_id> flask -A api create_enabled_token xDAI 100 0x0000000000000000000000000000000000000000 0.01 native
docker logs -f <container_name_or_id>
```

6 changes: 4 additions & 2 deletions api/.env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
FAUCET_AMOUNT=0.1
FAUCET_AMOUNT=0.001
FAUCET_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000
FAUCET_RPC_URL=https://rpc.chiadochain.net
FAUCET_CHAIN_ID=10200
FAUCET_DATABASE_URI=sqlite://
CAPTCHA_VERIFY_ENDPOINT=https://api.hcaptcha.com/siteverify
CAPTCHA_SECRET_KEY=0x0000000000000000000000000000000000000000
CAPTCHA_SITE_KEY=xxxxx-xxxxx-xxxxx-xxxxx
CAPTCHA_SITE_KEY=xxxxx-xxxxx-xxxxx-xxxxx
CSRF_PRIVATE_KEY="!!CREATE_YOUR_RSA_PRIVATE_KEY!!"
CSRF_SECRET_SALT="test-salt"
9 changes: 9 additions & 0 deletions api/api/routes.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import logging

from flask import Blueprint, current_app, jsonify, request
from web3 import Web3

Expand All @@ -6,6 +8,7 @@
claim_token)
from .services.database import AccessKey, Token, Transaction


apiv1 = Blueprint("version1", "version1")


Expand Down Expand Up @@ -69,6 +72,7 @@ def _ask(request_data, request_headers, validate_captcha=True, validate_csrf=Tru
try:
# convert recipient address to checksum address
recipient = Web3.to_checksum_address(validator.recipient)
logging.info(f'will try to send {amount_wei} to {recipient}')

w3 = Web3Singleton(current_app.config['FAUCET_RPC_URL'],
current_app.config['FAUCET_PRIVATE_KEY'])
Expand All @@ -78,11 +82,13 @@ def _ask(request_data, request_headers, validate_captcha=True, validate_csrf=Tru
current_app.config['FAUCET_ADDRESS'],
recipient,
amount_wei)
logging.info(f'native token txn: {tx_hash}')
else:
tx_hash = claim_token(w3, current_app.config['FAUCET_ADDRESS'],
recipient,
amount_wei,
validator.token.address)
logging.info(f'token with address {validator.token.address} txn: {tx_hash}')

# save transaction data on DB
transaction = Transaction()
Expand Down Expand Up @@ -111,6 +117,9 @@ def ask():

@apiv1.route("/cli/ask", methods=["POST"])
def cli_ask():
if not current_app.config['FAUCET_ENABLE_CLI_API']:
return jsonify(errors=['Endpoint disabled']), 403

access_key_id = request.headers.get('X-faucet-access-key-id', None)
secret_access_key = request.headers.get('X-faucet-secret-access-key', None)

Expand Down
1 change: 1 addition & 0 deletions api/api/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
from .token import Token
from .transaction import Web3Singleton, claim_native, claim_token
from .validator import AskEndpointValidator
from .captcha import CaptchaSingleton
72 changes: 59 additions & 13 deletions api/api/services/captcha.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,62 @@
logging.basicConfig(level=logging.INFO)


def captcha_verify(client_response, catpcha_api_url, secret_key, remote_ip, site_key):
request = requests.post(catpcha_api_url, data={
'response': client_response,
'secret': secret_key,
'remoteip': remote_ip,
'sitekey': site_key
})

logging.info('Captcha verify response: %s' % request.json())

if request.status_code != 200:
return False
return request.json()['success'] == True
class Captcha:
def __init__(self, provider):
self.provider = provider

def verify(self, client_response, catpcha_api_url, secret_key, remote_ip, site_key=None):
logging.info('Captcha: Remote IP %s' % remote_ip)

if self.provider == 'HCAPTCHA':
request = requests.post(catpcha_api_url, data={
'response': client_response,
'secret': secret_key,
'remoteip': remote_ip,
'sitekey': site_key
})

logging.info('Captcha: verify response %s' % request.json())

if request.status_code != 200:
return False
return request.json()['success'] is True
elif self.provider == 'CLOUDFLARE':
request = requests.post(catpcha_api_url, data={
'response': client_response,
'secret': secret_key,
'remoteip': remote_ip
})

logging.info('Captcha: verify response %s' % request.json())

if request.status_code != 200:
return False
return request.json()['success'] is True
else:
raise NotImplementedError


class CaptchaSingleton:
_instance = None

def __new__(cls, provider):
if not hasattr(cls, 'instance'):
cls.instance = Captcha(provider)
return cls.instance


# def captcha_verify(client_response, catpcha_api_url, secret_key, remote_ip, site_key):
# logging.info('Captcha: Remote IP %s' % remote_ip)
# request = requests.post(catpcha_api_url, data={
# 'response': client_response,
# 'secret': secret_key,
# 'remoteip': remote_ip,
# 'sitekey': site_key
# })

# logging.info('Captcha: verify response %s' % request.json())

# if request.status_code != 200:
# return False
# return request.json()['success'] == True
13 changes: 7 additions & 6 deletions api/api/services/csrf.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA

# Waiting period: the minimum time interval between UI asks for the CSFR token
# and the time it asks for funds.
CSRF_TIMESTAMP_MIN_SECONDS = 15
# Waiting period: the minimum time interval between the UI asks
# for the CSFR token to /api/v1/info and the time the UI can ask for funds.
# This check aims to block any bots that could be triggering actions through the UI.
CSRF_TIMESTAMP_MIN_SECONDS = 5


class CSRFTokenItem:
Expand Down Expand Up @@ -45,9 +46,9 @@ def validate_token(self, request_id, token, timestamp):
decrypted_text = cipher_rsa.decrypt(bytes.fromhex(token)).decode()
expected_text = '%s%s%f' % (request_id, self._salt, timestamp)
if decrypted_text == expected_text:
# Check that timestamp is OK, the diff between now() and creation time in seconds
# must be greater than min. waiting period.
# Waiting period: the minimum time interval between UI asks for the CSFR token and the time it asks for funds.
# Check that the timestamp is OK, the diff between now() and creation time in seconds
# must be greater than the minimum waiting period.
# Waiting period: the minimum time interval between UI asks for the CSFR token and the time the UI can ask for funds.
seconds_diff = (datetime.now()-datetime.fromtimestamp(timestamp)).total_seconds()
if seconds_diff > CSRF_TIMESTAMP_MIN_SECONDS:
return True
Expand Down
Loading

0 comments on commit af604f0

Please sign in to comment.