Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added captcha requirement #605

Merged
merged 20 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ RUN pip install -r requirements.txt
COPY . .
RUN mkdir db
RUN mkdir -p static
RUN mkdir media
RUN mkdir media -p
RUN chmod +x start_dev.sh

EXPOSE 5678
Expand Down
4 changes: 3 additions & 1 deletion brightIDfaucet/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ def str2bool(v):
MEMCACHED_PASSWORD = os.environ.get("MEMCACHEDCLOUD_PASSWORD")
DEPLOYMENT_ENV = os.environ.get("DEPLOYMENT_ENV")

CLOUDFLARE_TURNSTILE_SECRET_KEY = os.environ.get("CLOUDFLARE_TURNSTILE_SECRET_KEY")

assert DEPLOYMENT_ENV in ["dev", "main"]


Expand Down Expand Up @@ -259,4 +261,4 @@ def before_send(event, hint):
"djangorestframework_camel_case.parser.CamelCaseJSONParser",
),
}
CELERY_BROKER_URL = REDIS_URL
CELERY_BROKER_URL = REDIS_URL
1 change: 1 addition & 0 deletions core/constraints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
IsFollowingTwitterBatch,
IsFollowinTwitterUser,
)
from core.constraints.captcha import HasVerifiedCloudflareCaptcha


def get_constraint(constraint_label: str) -> ConstraintVerification:
Expand Down
3 changes: 2 additions & 1 deletion core/constraints/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ class ConstraintVerification(ABC):
app_name = ConstraintApp.GENERAL.value
__response_text = ""

def __init__(self, user_profile) -> None:
def __init__(self, user_profile, context=None) -> None:
self.user_profile = user_profile
self._param_values = {}
self.context = context

def get_info(self, *args, **kwargs):
pass
Expand Down
32 changes: 32 additions & 0 deletions core/constraints/captcha.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from core.constraints.abstract import ConstraintApp, ConstraintVerification
from core.thirdpartyapp.cloudflare import CloudflareUtil


import logging

from core.utils import RequestContextExtractor


logger = logging.getLogger(__name__)


class HasVerifiedCloudflareCaptcha(ConstraintVerification):
_param_keys = []
app_name = ConstraintApp.GENERAL.value

def is_observed(self, *args, **kwargs) -> bool:

if self.context is None or self.context.get("requset") is None:
return False

cloudflare = CloudflareUtil()

request_context: RequestContextExtractor = RequestContextExtractor(
self.context["requset"]
)

turnstile_token = request_context.data.get("cf-turnstile-response")

return turnstile_token is not None and cloudflare.is_verified(
turnstile_token, request_context.ip
)
2 changes: 2 additions & 0 deletions core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils.translation import gettext_lazy as _
from core.constraints.captcha import HasVerifiedCloudflareCaptcha
from encrypted_model_fields.fields import EncryptedCharField
from rest_framework.exceptions import ValidationError
from solders.keypair import Keypair
Expand Down Expand Up @@ -155,6 +156,7 @@ class Type(models.TextChoices):
GLMStakingVerification,
IsFollowingTwitterBatch,
IsFollowingFarcasterBatch,
HasVerifiedCloudflareCaptcha,
]

name = models.CharField(
Expand Down
24 changes: 24 additions & 0 deletions core/thirdpartyapp/cloudflare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import logging
from django.conf import settings
import requests


logger = logging.getLogger(__name__)


class CloudflareUtil:
secret_key = settings.CLOUDFLARE_TURNSTILE_SECRET_KEY
api_url = "https://challenges.cloudflare.com/turnstile/v0"

def is_verified(self, token: str, ip: str) -> bool:
try:
res = requests.post(
f"{self.api_url}/siteverify",
data={"secret": self.secret_key, "response": token, "remoteip": ip},
)

return res.ok and res.json()["success"]
except Exception as e:
logger.info(f"Error occurred during cloudflare verification {str(e)}")

return False
21 changes: 21 additions & 0 deletions core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import uuid
from contextlib import contextmanager

from django.http import HttpRequest
import pytz
import web3.exceptions
from django.core.cache import cache
Expand Down Expand Up @@ -377,3 +378,23 @@ def save(self, file: UploadedFile):
str(self.upload_to or "") + file_name + file_extension, file
)
return MEDIA_ROOT + "/" + path




class RequestContextExtractor:
def __init__(self, request) -> None:
self.headers = request.headers
self.ip = RequestContextExtractor.get_client_ip(request.META.get('HTTP_X_FORWARDED_FOR') or request.META['REMOTE_ADDR'])
self.data = {**request.query_params, **request.data}

@staticmethod
def get_client_ip(x_forwarded_for):
if x_forwarded_for:
ip_list = [ip.strip() for ip in x_forwarded_for.split(',')]
for ip in ip_list:
if ip and not ip.startswith(('10.', '172.16.', '192.168.')):
return ip
return None


3 changes: 1 addition & 2 deletions faucet/migrations/0019_brightuser__last_verified_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import datetime
from django.db import migrations, models
from django.utils.timezone import utc


class Migration(migrations.Migration):
Expand All @@ -15,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='brightuser',
name='_last_verified_datetime',
field=models.DateTimeField(default=datetime.datetime(1970, 1, 1, 0, 0, tzinfo=utc)),
field=models.DateTimeField(default=datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)),
),
]
89 changes: 89 additions & 0 deletions prizetap/migrations/0076_alter_constraint_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Generated by Django 4.0.4 on 2024-08-25 09:12

from django.db import migrations, models


def create_prizetap_constraint(apps, schema_editor):
Constraint = apps.get_model("prizetap", "Constraint")

Constraint.objects.create(
name="core.HasVerifiedCloudflareCaptcha",
description="HasVerifiedCloudflareCaptcha",
title="Passed Cloudflare Captcha",
type="VER",
)



class Migration(migrations.Migration):

dependencies = [
("prizetap", "0075_alter_constraint_name"),
]
alimaktabi marked this conversation as resolved.
Show resolved Hide resolved

operations = [
migrations.AlterField(
model_name="constraint",
name="name",
field=models.CharField(
choices=[
("core.BrightIDMeetVerification", "BrightIDMeetVerification"),
("core.BrightIDAuraVerification", "BrightIDAuraVerification"),
("core.HasNFTVerification", "HasNFTVerification"),
("core.HasTokenVerification", "HasTokenVerification"),
(
"core.HasTokenTransferVerification",
"HasTokenTransferVerification",
),
("core.AllowListVerification", "AllowListVerification"),
("core.HasENSVerification", "HasENSVerification"),
("core.HasLensProfile", "HasLensProfile"),
("core.IsFollowingLensUser", "IsFollowingLensUser"),
("core.BeFollowedByLensUser", "BeFollowedByLensUser"),
("core.DidMirrorOnLensPublication", "DidMirrorOnLensPublication"),
("core.DidCollectLensPublication", "DidCollectLensPublication"),
("core.HasMinimumLensPost", "HasMinimumLensPost"),
("core.HasMinimumLensFollower", "HasMinimumLensFollower"),
("core.BeFollowedByFarcasterUser", "BeFollowedByFarcasterUser"),
("core.HasMinimumFarcasterFollower", "HasMinimumFarcasterFollower"),
("core.DidLikedFarcasterCast", "DidLikedFarcasterCast"),
("core.DidRecastFarcasterCast", "DidRecastFarcasterCast"),
("core.IsFollowingFarcasterUser", "IsFollowingFarcasterUser"),
("core.HasFarcasterProfile", "HasFarcasterProfile"),
("core.BeAttestedBy", "BeAttestedBy"),
("core.Attest", "Attest"),
("core.HasDonatedOnGitcoin", "HasDonatedOnGitcoin"),
("core.HasMinimumHumanityScore", "HasMinimumHumanityScore"),
("core.HasGitcoinPassportProfile", "HasGitcoinPassportProfile"),
("core.IsFollowingFarcasterChannel", "IsFollowingFarcasterChannel"),
("core.BridgeEthToArb", "BridgeEthToArb"),
("core.IsFollowinTwitterUser", "IsFollowinTwitterUser"),
("core.BeFollowedByTwitterUser", "BeFollowedByTwitterUser"),
("core.DidRetweetTweet", "DidRetweetTweet"),
("core.DidQuoteTweet", "DidQuoteTweet"),
("core.HasMuonNode", "HasMuonNode"),
("core.DelegateArb", "DelegateArb"),
("core.DelegateOP", "DelegateOP"),
("core.DidDelegateArbToAddress", "DidDelegateArbToAddress"),
("core.DidDelegateOPToAddress", "DidDelegateOPToAddress"),
("core.GLMStakingVerification", "GLMStakingVerification"),
("core.IsFollowingTwitterBatch", "IsFollowingTwitterBatch"),
("core.IsFollowingFarcasterBatch", "IsFollowingFarcasterBatch"),
(
"core.HasVerifiedCloudflareCaptcha",
"HasVerifiedCloudflareCaptcha",
),
("prizetap.HaveUnitapPass", "HaveUnitapPass"),
("prizetap.NotHaveUnitapPass", "NotHaveUnitapPass"),
("faucet.OptimismDonationConstraint", "OptimismDonationConstraint"),
(
"faucet.OptimismClaimingGasConstraint",
"OptimismClaimingGasConstraint",
),
],
max_length=255,
unique=True,
),
),
migrations.RunPython(create_prizetap_constraint),
]
3 changes: 2 additions & 1 deletion prizetap/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def __init__(self, *args, **kwargs):
self.user_profile: UserProfile = kwargs["user_profile"]
self.raffle: Raffle = kwargs["raffle"]
self.raffle_data: dict = kwargs.get("raffle_data", dict())
self.request = kwargs.get("requset")

def can_enroll_in_raffle(self):
if not self.raffle.is_claimable:
Expand All @@ -29,7 +30,7 @@ def check_user_constraints(self, raise_exception=True):
result = dict()
for c in self.raffle.constraints.all():
constraint: ConstraintVerification = get_constraint(c.name)(
self.user_profile
self.user_profile, context={"request": self.request}
)
constraint.response = c.response
try:
Expand Down
10 changes: 8 additions & 2 deletions prizetap/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ def post(self, request, pk):
)

validator = RaffleEnrollmentValidator(
user_profile=user_profile, raffle=raffle, raffle_data=raffle_data
user_profile=user_profile,
raffle=raffle,
raffle_data=raffle_data,
request=request,
)

validator.is_valid(self.request.data)
Expand Down Expand Up @@ -192,7 +195,10 @@ def get(self, request, raffle_pk):
reversed_constraints = raffle.reversed_constraints_list
response_constraints = []
validator = RaffleEnrollmentValidator(
user_profile=user_profile, raffle=raffle, raffle_data=raffle_data
user_profile=user_profile,
raffle=raffle,
raffle_data=raffle_data,
request=request,
)

validated_constraints = validator.check_user_constraints(raise_exception=False)
Expand Down
2 changes: 1 addition & 1 deletion start_dev.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash
python manage.py collectstatic --noinput
python manage.py migrate
python manage.py runserver 0.0.0.0:5678 &
python manage.py runserver 0.0.0.0:5678
celery -A brightIDfaucet worker -B
31 changes: 31 additions & 0 deletions tokenTap/migrations/0062_alter_constraint_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 4.0.4 on 2024-08-25 09:12

from django.db import migrations, models



def create_tokentap_constraint(apps, schema_editor):
Constraint = apps.get_model("tokenTap", "Constraint")

Constraint.objects.create(
name="core.HasVerifiedCloudflareCaptcha",
description="HasVerifiedCloudflareCaptcha",
title="Passed Cloudflare Captcha",
type="VER",
)


class Migration(migrations.Migration):

dependencies = [
('tokenTap', '0061_alter_constraint_name'),
]

operations = [
migrations.AlterField(
model_name='constraint',
name='name',
field=models.CharField(choices=[('core.BrightIDMeetVerification', 'BrightIDMeetVerification'), ('core.BrightIDAuraVerification', 'BrightIDAuraVerification'), ('core.HasNFTVerification', 'HasNFTVerification'), ('core.HasTokenVerification', 'HasTokenVerification'), ('core.HasTokenTransferVerification', 'HasTokenTransferVerification'), ('core.AllowListVerification', 'AllowListVerification'), ('core.HasENSVerification', 'HasENSVerification'), ('core.HasLensProfile', 'HasLensProfile'), ('core.IsFollowingLensUser', 'IsFollowingLensUser'), ('core.BeFollowedByLensUser', 'BeFollowedByLensUser'), ('core.DidMirrorOnLensPublication', 'DidMirrorOnLensPublication'), ('core.DidCollectLensPublication', 'DidCollectLensPublication'), ('core.HasMinimumLensPost', 'HasMinimumLensPost'), ('core.HasMinimumLensFollower', 'HasMinimumLensFollower'), ('core.BeFollowedByFarcasterUser', 'BeFollowedByFarcasterUser'), ('core.HasMinimumFarcasterFollower', 'HasMinimumFarcasterFollower'), ('core.DidLikedFarcasterCast', 'DidLikedFarcasterCast'), ('core.DidRecastFarcasterCast', 'DidRecastFarcasterCast'), ('core.IsFollowingFarcasterUser', 'IsFollowingFarcasterUser'), ('core.HasFarcasterProfile', 'HasFarcasterProfile'), ('core.BeAttestedBy', 'BeAttestedBy'), ('core.Attest', 'Attest'), ('core.HasDonatedOnGitcoin', 'HasDonatedOnGitcoin'), ('core.HasMinimumHumanityScore', 'HasMinimumHumanityScore'), ('core.HasGitcoinPassportProfile', 'HasGitcoinPassportProfile'), ('core.IsFollowingFarcasterChannel', 'IsFollowingFarcasterChannel'), ('core.BridgeEthToArb', 'BridgeEthToArb'), ('core.IsFollowinTwitterUser', 'IsFollowinTwitterUser'), ('core.BeFollowedByTwitterUser', 'BeFollowedByTwitterUser'), ('core.DidRetweetTweet', 'DidRetweetTweet'), ('core.DidQuoteTweet', 'DidQuoteTweet'), ('core.HasMuonNode', 'HasMuonNode'), ('core.DelegateArb', 'DelegateArb'), ('core.DelegateOP', 'DelegateOP'), ('core.DidDelegateArbToAddress', 'DidDelegateArbToAddress'), ('core.DidDelegateOPToAddress', 'DidDelegateOPToAddress'), ('core.GLMStakingVerification', 'GLMStakingVerification'), ('core.IsFollowingTwitterBatch', 'IsFollowingTwitterBatch'), ('core.IsFollowingFarcasterBatch', 'IsFollowingFarcasterBatch'), ('core.HasVerifiedCloudflareCaptcha', 'HasVerifiedCloudflareCaptcha'), ('tokenTap.OncePerMonthVerification', 'OncePerMonthVerification'), ('tokenTap.OnceInALifeTimeVerification', 'OnceInALifeTimeVerification'), ('faucet.OptimismHasClaimedGasConstraint', 'OptimismHasClaimedGasConstraint')], max_length=255, unique=True),
),
migrations.RunPython(create_tokentap_constraint),
]
11 changes: 9 additions & 2 deletions tokenTap/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,17 @@ def is_valid(self, data):

class TokenDistributionValidator:
def __init__(
self, td: TokenDistribution, user_profile: UserProfile, td_data: dict
self,
td: TokenDistribution,
user_profile: UserProfile,
td_data: dict,
*args,
**kwargs,
) -> None:
self.td = td
self.td_data = td_data
self.user_profile = user_profile
self.request = kwargs.get("request")

def check_user_permissions(self, raise_exception=True):
try:
Expand All @@ -54,7 +60,8 @@ def check_user_permissions(self, raise_exception=True):
result = dict()
for c in self.td.constraints.all():
constraint: ConstraintVerification = get_constraint(c.name)(
self.user_profile
self.user_profile,
context={"request": self.request}
)
constraint.response = c.response
try:
Expand Down
9 changes: 7 additions & 2 deletions tokenTap/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ def post(self, request, *args, **kwargs):
pass

validator = TokenDistributionValidator(
token_distribution, user_profile, td_data
token_distribution,
user_profile,
td_data,
request=request,
)
validator.is_valid()

Expand Down Expand Up @@ -184,7 +187,9 @@ def get(self, request, td_id):
reversed_constraints = td.reversed_constraints_list
response_constraints = []

validator = TokenDistributionValidator(td, user_profile, td_data)
validator = TokenDistributionValidator(
td, user_profile, td_data, request=request
)
validated_constraints = validator.check_user_permissions(raise_exception=False)
for c_pk, data in validated_constraints.items():
response_constraints.append(
Expand Down
Loading