Skip to content

Commit 4aa73ed

Browse files
committed
Merge branch 'master' of github.com:fiqus/coobs into production
2 parents 182a3b6 + bc974cc commit 4aa73ed

11 files changed

Lines changed: 288 additions & 37 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ frontend/dist/
2121
/coobs/bin/
2222
/coobs/include/
2323
/coobs/lib/
24-
/coobs/settings/dev.py
24+
/coobs/settings/dev.py
25+
staticfiles/

Dockerfile.backend

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
FROM python:3.9-slim
2+
3+
# Instalar dependencias del sistema
4+
RUN apt-get update && apt-get install -y \
5+
postgresql-client \
6+
build-essential \
7+
libpq-dev \
8+
&& rm -rf /var/lib/apt/lists/*
9+
10+
# Establecer directorio de trabajo
11+
WORKDIR /app
12+
13+
# Copiar requirements y instalar dependencias Python
14+
COPY requirements.txt .
15+
RUN pip install --no-cache-dir -r requirements.txt
16+
17+
# Copiar código de la aplicación
18+
COPY . .
19+
20+
# Crear usuario no-root
21+
RUN useradd --create-home --shell /bin/bash app && chown -R app:app /app
22+
USER app
23+
24+
# Exponer puerto
25+
EXPOSE 8000
26+
27+
# Comando por defecto
28+
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

Dockerfile.frontend

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
FROM node:16-alpine
2+
3+
# Instalar dependencias del sistema para node-sass
4+
RUN apk add --no-cache python3 make g++
5+
6+
# Establecer directorio de trabajo
7+
WORKDIR /app
8+
9+
# Copiar package.json y package-lock.json
10+
COPY frontend/package*.json ./
11+
12+
# Instalar dependencias principales
13+
RUN npm install
14+
15+
# Instalar dependencias del landing globalmente
16+
RUN npm install bootstrap magnific-popup jquery jquery.easing
17+
18+
# Copiar código fuente
19+
COPY frontend/ .
20+
21+
# Crear usuario no-root y cambiar permisos
22+
RUN addgroup -g 1001 -S nodejs && \
23+
adduser -S nextjs -u 1001 && \
24+
chown -R nextjs:nodejs /app
25+
26+
# Exponer puerto
27+
EXPOSE 8080
28+
29+
# Comando por defecto
30+
CMD ["npm", "run", "start"]

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
# Cooperative social balance app
1+
# Cooperative social balance app
2+
3+
## Running all the project with Docker Compose
4+
You need to install Docker and Docker Compose in your machine
5+
6+
```bash
7+
docker compose -f docker-compose-dev.yml up
8+
```
29

310
## Running Backend
411

api/recaptcha_utils.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import requests
2+
import json
3+
from django.conf import settings
4+
from django.utils.translation import gettext as _
5+
6+
7+
def verify_recaptcha_enterprise(token, action='submit'):
8+
if not all([settings.RECAPTCHA_PROJECT_ID, settings.RECAPTCHA_API_KEY, settings.RECAPTCHA_SITE_KEY]):
9+
return {
10+
'success': False,
11+
'error': 'reCAPTCHA configuration incomplete'
12+
}
13+
14+
url = settings.RECAPTCHA_VERIFY_URL.format(project_id=settings.RECAPTCHA_PROJECT_ID, api_key=settings.RECAPTCHA_API_KEY)
15+
16+
assessment_data = {
17+
"event": {
18+
"token": token,
19+
"siteKey": settings.RECAPTCHA_SITE_KEY,
20+
"expectedAction": action
21+
}
22+
}
23+
24+
headers = {
25+
'Content-Type': 'application/json',
26+
}
27+
28+
try:
29+
response = requests.post(url, json=assessment_data, headers=headers)
30+
response.raise_for_status()
31+
32+
result = response.json()
33+
34+
if 'tokenProperties' in result and 'valid' in result['tokenProperties']:
35+
is_valid = result['tokenProperties']['valid']
36+
score = result.get('riskAnalysis', {}).get('score', 0.0)
37+
action_matched = result.get('tokenProperties', {}).get('action') == action
38+
39+
return {
40+
'success': is_valid and action_matched,
41+
'score': score,
42+
'action_matched': action_matched,
43+
'raw_response': result
44+
}
45+
else:
46+
return {
47+
'success': False,
48+
'error': 'Invalid response format from reCAPTCHA Enterprise'
49+
}
50+
51+
except requests.exceptions.RequestException as e:
52+
return {
53+
'success': False,
54+
'error': f'Request failed: {str(e)}'
55+
}
56+
except json.JSONDecodeError as e:
57+
return {
58+
'success': False,
59+
'error': f'Invalid JSON response: {str(e)}'
60+
}
61+
except Exception as e:
62+
return {
63+
'success': False,
64+
'error': f'Unexpected error: {str(e)}'
65+
}
66+
67+
68+
def is_recaptcha_score_valid(score, threshold=None):
69+
if threshold is None:
70+
threshold = getattr(settings, 'RECAPTCHA_SCORE_THRESHOLD', 0.5)
71+
72+
return score >= threshold

api/views/views.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from django.urls import reverse
3333
from django.dispatch import receiver
3434
from markdownify import markdownify as md
35+
from api.recaptcha_utils import verify_recaptcha_enterprise, is_recaptcha_score_valid
3536

3637
public_url = f"{settings.WEB_PROTOCOL}://{settings.WEB_URL}"
3738

@@ -250,17 +251,21 @@ def assign_coop_to_partner():
250251
partner.save()
251252
assign_principles_to_coop()
252253

253-
recaptchaResult = requests.post(
254-
settings.RECAPTCHA_VERIFY_URL,
255-
data={
256-
'secret': settings.RECAPTCHA_SECRET_KEY,
257-
'response': data['reCaptchaToken']
258-
}
259-
)
260-
261-
if not recaptchaResult.json()['success']:
262-
return Response(data={'detail': _("There has been an error validating recaptcha. Please, contact the site administrator.")},
263-
status=status.HTTP_400_BAD_REQUEST)
254+
recaptcha_result = verify_recaptcha_enterprise(data['reCaptchaToken'], 'signup')
255+
256+
if not recaptcha_result['success']:
257+
error_msg = recaptcha_result.get('error', 'reCAPTCHA verification failed')
258+
return Response(
259+
data={'detail': _("There has been an error validating recaptcha. Please, contact the site administrator.")},
260+
status=status.HTTP_400_BAD_REQUEST
261+
)
262+
263+
score = recaptcha_result.get('score', 0.0)
264+
if not is_recaptcha_score_valid(score):
265+
return Response(
266+
data={'detail': _("reCAPTCHA verification failed. Please try again.")},
267+
status=status.HTTP_400_BAD_REQUEST
268+
)
264269

265270
coop = {'business_name': data['businessName']}
266271
coop_serializer = CooperativeSerializer(data=coop)

coobs/settings/common.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@
9292

9393
STATIC_URL = '/static/'
9494

95-
STATICFILES_DIRS = []
95+
if os.environ.get('DEVELOPMENT_MODE', 'False') == 'True':
96+
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
9697

9798
REST_FRAMEWORK = {
9899
'DEFAULT_RENDERER_CLASSES': (
@@ -122,8 +123,12 @@
122123
'REFRESH_TOKEN_LIFETIME': timedelta(days=1)
123124
}
124125

125-
RECAPTCHA_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify'
126-
RECAPTCHA_SECRET_KEY = ''
126+
# reCAPTCHA Enterprise Configuration
127+
RECAPTCHA_VERIFY_URL = 'https://recaptchaenterprise.googleapis.com/v1/projects/{project_id}/assessments?key={api_key}'
128+
RECAPTCHA_PROJECT_ID = 'coobs-476415' # Google Cloud Project ID
129+
RECAPTCHA_API_KEY = '' # Google Cloud API Key
130+
RECAPTCHA_SITE_KEY = '6Lc_wfgrAAAAADuYYdB51FTwU6OETYbQUEP84q9u' # reCAPTCHA Enterprise Site Key
131+
RECAPTCHA_SCORE_THRESHOLD = 0.5 # Minimum score to consider the request as legitimate
127132

128133
EMAIL_USE_TLS = True
129134
EMAIL_BACKEND = ''

docker-compose-dev.yml

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
version: "3.8"
2+
3+
services:
4+
postgres:
5+
container_name: coobs_postgres
6+
image: postgres:12.1
7+
hostname: postgres
8+
environment:
9+
POSTGRES_DB: coobs
10+
POSTGRES_USER: coobs
11+
POSTGRES_PASSWORD: coobspass
12+
ports:
13+
- "5433:5432"
14+
volumes:
15+
- 'pg_data:/var/lib/postgresql/data'
16+
networks:
17+
- coobs_network
18+
healthcheck:
19+
test: ["CMD-SHELL", "pg_isready -U coobs -d coobs"]
20+
interval: 10s
21+
timeout: 5s
22+
retries: 5
23+
24+
backend:
25+
container_name: coobs_backend
26+
build:
27+
context: .
28+
dockerfile: Dockerfile.backend
29+
environment:
30+
- POSTGRES_DB=coobs
31+
- POSTGRES_USER=coobs
32+
- POSTGRES_PASSWORD=coobspass
33+
- POSTGRES_HOST=postgres
34+
- POSTGRES_PORT=5432
35+
- SECRET_KEY=your-secret-key-change-this-in-production
36+
- DEBUG=True
37+
- ALLOWED_HOSTS=*
38+
- EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
39+
- EMAIL_USE_TLS=True
40+
- WEB_PROTOCOL=http
41+
- WEB_URL=localhost:8080
42+
- DEVELOPMENT_MODE=True
43+
ports:
44+
- "8000:8000"
45+
volumes:
46+
- .:/app
47+
networks:
48+
- coobs_network
49+
depends_on:
50+
postgres:
51+
condition: service_healthy
52+
command: >
53+
sh -c "python manage.py migrate &&
54+
python manage.py collectstatic --noinput &&
55+
python manage.py runserver 0.0.0.0:8000"
56+
57+
frontend:
58+
container_name: coobs_frontend
59+
build:
60+
context: .
61+
dockerfile: Dockerfile.frontend
62+
ports:
63+
- "8080:8080"
64+
volumes:
65+
- ./frontend:/app
66+
- /app/node_modules
67+
networks:
68+
- coobs_network
69+
environment:
70+
- NODE_ENV=development
71+
- CHOKIDAR_USEPOLLING=true # Para hot reload en Docker
72+
command: npm run start
73+
74+
pgadmin:
75+
container_name: coobs_pgadmin
76+
image: dpage/pgadmin4:6.21
77+
environment:
78+
- PGADMIN_DEFAULT_EMAIL=admin@fiqus.coop
79+
- PGADMIN_DEFAULT_PASSWORD=admin
80+
ports:
81+
- "5050:80"
82+
volumes:
83+
- 'pgadmin_data:/var/lib/pgadmin'
84+
networks:
85+
- coobs_network
86+
depends_on:
87+
- postgres
88+
89+
networks:
90+
coobs_network:
91+
driver: bridge
92+
93+
volumes:
94+
pg_data:
95+
pgadmin_data:

frontend/landing/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<html lang="en">
33

44
<head>
5+
<script src="https://www.google.com/recaptcha/enterprise.js?render=6Lc_wfgrAAAAADuYYdB51FTwU6OETYbQUEP84q9u"></script>
56
<script async src="https://www.googletagmanager.com/gtag/js?id=G-6DM4VRWC2T"></script>
67
<script>
78
window.dataLayer = window.dataLayer || [];
@@ -201,6 +202,5 @@ <h2 id="signup" class="text-center custom-violet mb-4"></h2>
201202
</div>
202203
</div>
203204
</footer>
204-
<script src="https://www.google.com/recaptcha/api.js?render=6LepzsgUAAAAAJTtaoDibT1Duf2CFQrI0RFl3srT"></script>
205205
</body>
206206
</html>

frontend/landing/js/contact_me.js

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -87,24 +87,22 @@ $(function() {
8787
return cookieValue;
8888
}
8989

90-
grecaptcha.ready(() => {
91-
grecaptcha.execute('6LepzsgUAAAAAJTtaoDibT1Duf2CFQrI0RFl3srT', {action: 'signup'})
92-
.then((reCaptchaToken) => {
93-
data.reCaptchaToken = reCaptchaToken;
94-
$.ajax({
95-
url: `${scheme}://${hostname}/api/cooperatives/`,
96-
type: "POST",
97-
headers: {
98-
"X-CSRFToken": getCookie("csrftoken"),
99-
"Accept-Language": languageStr || 'en'
100-
},
101-
data,
102-
cache: false,
103-
success: (msgs) => {onSucess(msgs)},
104-
error: (err) => {onError(err)},
105-
complete: onComplete
106-
});
107-
})
90+
grecaptcha.enterprise.ready(async () => {
91+
const reCaptchaToken = await grecaptcha.enterprise.execute('6Lc_wfgrAAAAADuYYdB51FTwU6OETYbQUEP84q9u', {action: 'signup'})
92+
data.reCaptchaToken = reCaptchaToken;
93+
$.ajax({
94+
url: `${scheme}://${hostname}/api/cooperatives/`,
95+
type: "POST",
96+
headers: {
97+
"X-CSRFToken": getCookie("csrftoken"),
98+
"Accept-Language": languageStr || 'en'
99+
},
100+
data,
101+
cache: false,
102+
success: (msgs) => {onSucess(msgs)},
103+
error: (err) => {onError(err)},
104+
complete: onComplete
105+
});
108106
});
109107
},
110108
filter: function() {

0 commit comments

Comments
 (0)