From 97008caf31f55815aa5cbd28f9f588d41f55a05c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoni=20Nu=C3=B1ez=20Romeu?= Date: Wed, 26 Aug 2026 17:14:05 +0200 Subject: [PATCH] =?UTF-8?q?A=C3=B1adidos=20settings=20de=20cookies=20y=20p?= =?UTF-8?q?rivacidad?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/docker.yaml | 21 +++++ apps/backend/create-admin.js | 6 +- apps/backend/package.json | 1 + apps/frontend/nginx.conf | 5 +- apps/frontend/src/App.jsx | 6 ++ apps/frontend/src/components/BottomNav.css | 3 - apps/frontend/src/components/BottomNav.jsx | 3 - .../src/components/PrivacySidebar.css | 78 +++++++++++++++++++ .../src/components/PrivacySidebar.jsx | 36 +++++++++ apps/parapharmacy-api/src/routes/products.js | 14 +++- .../src/infrastructure/config/settings.py | 6 +- docker-compose.yml | 6 ++ package-lock.json | 12 +++ 13 files changed, 185 insertions(+), 12 deletions(-) create mode 100644 apps/frontend/src/components/PrivacySidebar.css create mode 100644 apps/frontend/src/components/PrivacySidebar.jsx diff --git a/.gitea/workflows/docker.yaml b/.gitea/workflows/docker.yaml index 6a72615..788d60f 100644 --- a/.gitea/workflows/docker.yaml +++ b/.gitea/workflows/docker.yaml @@ -5,8 +5,29 @@ on: push: branches: - main + schedule: + - cron: '0 6 * * 1' jobs: + audit-dependencies: + name: Dependency Audit + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + - name: Install dependencies + run: npm ci --ignore-scripts + - name: Audit for known vulnerabilities + run: npm audit --omit=dev --audit-level=high + - name: Audit (all, informational) + if: always() + run: npm audit --audit-level=moderate || true + detect-changes: name: Detect Changes runs-on: ubuntu-latest diff --git a/apps/backend/create-admin.js b/apps/backend/create-admin.js index 16860e4..d6f264b 100644 --- a/apps/backend/create-admin.js +++ b/apps/backend/create-admin.js @@ -12,7 +12,11 @@ const PG_URL = process.env.PG_URL; async function createAdmin() { const username = process.env.ADMIN_USERNAME || 'admin'; - const password = process.env.ADMIN_PASSWORD || 'admin123'; + const password = process.env.ADMIN_PASSWORD; + if (!password) { + console.error('Error: ADMIN_PASSWORD environment variable is required'); + process.exit(1); + } const passwordHash = await bcrypt.hash(password, 10); if (PG_URL) { diff --git a/apps/backend/package.json b/apps/backend/package.json index 12fb476..6ae1587 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -39,6 +39,7 @@ "express": "^4.18.2", "express-rate-limit": "^8.5.2", "express-session": "^1.17.3", + "helmet": "^8.1.0", "multer": "^2.2.0", "nodemailer": "^6.10.1", "pg": "^8.13.0", diff --git a/apps/frontend/nginx.conf b/apps/frontend/nginx.conf index bfc0155..cbfb814 100644 --- a/apps/frontend/nginx.conf +++ b/apps/frontend/nginx.conf @@ -1,3 +1,5 @@ +resolver 127.0.0.11 valid=10s ipv6=off; + server { listen 80; server_tokens off; @@ -10,7 +12,8 @@ server { add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; location /api/ { - proxy_pass http://backend:3001; + set $backend http://backend:3001; + proxy_pass $backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/apps/frontend/src/App.jsx b/apps/frontend/src/App.jsx index f3e7b29..99a58d3 100644 --- a/apps/frontend/src/App.jsx +++ b/apps/frontend/src/App.jsx @@ -12,6 +12,7 @@ import ForgotPasswordModal from './components/ForgotPasswordModal'; import ResetPasswordView from './views/ResetPasswordView'; import SavedNotifications from './components/SavedNotifications'; import BottomNav from './components/BottomNav'; +import PrivacySidebar from './components/PrivacySidebar'; import CookieBanner from './components/CookieBanner'; import HealthConsentModal from './components/HealthConsentModal'; import PrivacyView from './views/PrivacyView'; @@ -323,6 +324,11 @@ function App() { badgeCount={badgeCount} /> + + {showLogin && ( ); })} - ); } diff --git a/apps/frontend/src/components/PrivacySidebar.css b/apps/frontend/src/components/PrivacySidebar.css new file mode 100644 index 0000000..8ee46ac --- /dev/null +++ b/apps/frontend/src/components/PrivacySidebar.css @@ -0,0 +1,78 @@ +.privacy-sidebar { + position: fixed; + left: 0; + top: 50%; + transform: translateY(-50%); + z-index: 40; + display: flex; + flex-direction: column; + align-items: center; +} + +.privacy-sidebar-btn { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.25rem; + width: 2.5rem; + padding: 0.75rem 0.25rem; + background: var(--surface-container-low); + border: none; + border-radius: 0 var(--radius-md) var(--radius-md) 0; + color: var(--on-surface-variant); + cursor: pointer; + transition: background 0.15s, color 0.15s; + box-shadow: 2px 0 8px rgba(0, 0, 0, 0.08); + min-height: unset; +} + +.privacy-sidebar-btn:hover { + background: var(--surface-container); + color: var(--primary); +} + +.privacy-sidebar-btn:focus { + outline: none; + box-shadow: 0 0 0 3px var(--primary-ring), 2px 0 8px rgba(0, 0, 0, 0.08); +} + +.privacy-sidebar-text { + font-size: 0.55rem; + font-weight: 500; + writing-mode: vertical-rl; + text-orientation: mixed; + white-space: nowrap; + letter-spacing: 0.02em; +} + +/* Desktop - wider sidebar */ +@media (min-width: 1025px) { + .privacy-sidebar-btn { + width: 2.75rem; + padding: 1rem 0.35rem; + } + + .privacy-sidebar-text { + font-size: 0.6rem; + } +} + +/* Mobile - adjust for safe area */ +@media (max-width: 768px) { + .privacy-sidebar { + top: auto; + bottom: 6rem; + transform: none; + } + + .privacy-sidebar-btn { + width: 2.25rem; + padding: 0.5rem 0.2rem; + border-radius: 0 var(--radius-sm) var(--radius-sm) 0; + } + + .privacy-sidebar-text { + font-size: 0.5rem; + } +} diff --git a/apps/frontend/src/components/PrivacySidebar.jsx b/apps/frontend/src/components/PrivacySidebar.jsx new file mode 100644 index 0000000..ea06e2e --- /dev/null +++ b/apps/frontend/src/components/PrivacySidebar.jsx @@ -0,0 +1,36 @@ +import { useTranslation } from '../i18n'; +import './PrivacySidebar.css'; + +function PrivacySidebar({ onNavigate, isVisible }) { + const { t } = useTranslation(); + + if (!isVisible) return null; + + return ( + + ); +} + +export default PrivacySidebar; diff --git a/apps/parapharmacy-api/src/routes/products.js b/apps/parapharmacy-api/src/routes/products.js index cd5394a..4a3415c 100644 --- a/apps/parapharmacy-api/src/routes/products.js +++ b/apps/parapharmacy-api/src/routes/products.js @@ -343,9 +343,21 @@ router.post('/bulk', requireServiceKey('INGEST_API_KEY'), async (req, res) => { */ router.put('/:id', requireServiceKey('ADMIN_API_KEY'), async (req, res) => { try { + // Allowlist: only these fields can be modified via PUT + const ALLOWED_FIELDS = [ + 'name', 'brand', 'category', 'subcategory', 'description', + 'image_url', 'source_url', 'price', 'original_price', 'source', + 'source_product_id', 'available', 'rating', 'review_count', + ]; + const update = {}; + for (const field of ALLOWED_FIELDS) { + if (field in req.body) update[field] = req.body[field]; + } + update.updated_at = new Date(); + const product = await Product.findByIdAndUpdate( req.params.id, - { ...req.body, updated_at: new Date() }, + update, { new: true } ); diff --git a/apps/pip-platform/src/infrastructure/config/settings.py b/apps/pip-platform/src/infrastructure/config/settings.py index 76328ae..b232eb0 100644 --- a/apps/pip-platform/src/infrastructure/config/settings.py +++ b/apps/pip-platform/src/infrastructure/config/settings.py @@ -15,7 +15,7 @@ class Settings(BaseSettings): DEBUG: bool = False NODE_ENV: str = "development" - DATABASE_URL: str = "postgresql+asyncpg://pip:pip@localhost:5432/pip" + DATABASE_URL: str DATABASE_POOL_SIZE: int = 20 DATABASE_MAX_OVERFLOW: int = 10 DATABASE_POOL_RECYCLE: int = 3600 @@ -23,9 +23,9 @@ class Settings(BaseSettings): REDIS_URL: str = "redis://localhost:6379/0" REDIS_CACHE_TTL: int = 300 - RABBITMQ_URL: str = "amqp://pip:pip@localhost:5672/pip" + RABBITMQ_URL: str - JWT_SECRET_KEY: str = "change-me-in-production" + JWT_SECRET_KEY: str JWT_ALGORITHM: str = "HS256" JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = 7 diff --git a/docker-compose.yml b/docker-compose.yml index f912bd3..cce32b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -158,6 +158,12 @@ services: mailpit: image: axllent/mailpit:latest restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "-qO", "/dev/null", "http://localhost:8025/"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 5s ports: - "8025:8025" # Web UI - "1025:1025" # SMTP diff --git a/package-lock.json b/package-lock.json index d11186d..357c275 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,6 +53,7 @@ "express": "^4.18.2", "express-rate-limit": "^8.5.2", "express-session": "^1.17.3", + "helmet": "^8.1.0", "multer": "^2.2.0", "nodemailer": "^6.10.1", "pg": "^8.13.0", @@ -17756,6 +17757,17 @@ "node": ">= 0.4" } }, + "node_modules/helmet": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.3.0.tgz", + "integrity": "sha512-Qgpiaws3Sm30Av8Eah6sjMCZZwjlBu+E68rhpCWBshY1lb09HtLwj5GviX0OyQIn+ulUS0iX0AxN5n3tLZzz1w==", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/EvanHahn" + } + }, "node_modules/hermes-compiler": { "version": "250829098.0.14", "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.14.tgz",