diff --git a/backend/__pycache__/jules_engine.cpython-312.pyc b/backend/__pycache__/jules_engine.cpython-312.pyc deleted file mode 100644 index e245657..0000000 Binary files a/backend/__pycache__/jules_engine.cpython-312.pyc and /dev/null differ diff --git a/backend/__pycache__/main.cpython-312.pyc b/backend/__pycache__/main.cpython-312.pyc deleted file mode 100644 index 326c467..0000000 Binary files a/backend/__pycache__/main.cpython-312.pyc and /dev/null differ diff --git a/backend/__pycache__/models.cpython-312.pyc b/backend/__pycache__/models.cpython-312.pyc deleted file mode 100644 index a1357dc..0000000 Binary files a/backend/__pycache__/models.cpython-312.pyc and /dev/null differ diff --git a/backend/__pycache__/test_jules.cpython-312-pytest-9.0.2.pyc b/backend/__pycache__/test_jules.cpython-312-pytest-9.0.2.pyc deleted file mode 100644 index a7209d8..0000000 Binary files a/backend/__pycache__/test_jules.cpython-312-pytest-9.0.2.pyc and /dev/null differ diff --git a/backend/main.py b/backend/main.py index fb88c85..3d7dc9e 100644 --- a/backend/main.py +++ b/backend/main.py @@ -7,7 +7,7 @@ from fastapi.responses import JSONResponse from fastapi.middleware.cors import CORSMiddleware from dotenv import load_dotenv -from models import UserScan, SHOPIFY_INVENTORY +from models import UserScan, StaffLogin, SHOPIFY_INVENTORY from jules_engine import get_jules_advice # Load .env file @@ -28,6 +28,7 @@ # 🛡️ Configuración Maestra (abvetos.com) - Secrets moved to environment variables SECRET_KEY = os.getenv("LVT_SECRET_KEY", "DEVELOPMENT_SECRET_DO_NOT_USE_IN_PROD") +STAFF_PASSWORD = os.getenv("STAFF_PASSWORD", "DEVELOPMENT_STAFF_PASS_DO_NOT_USE_IN_PROD") PATENT = "PCT/EP2025/067317" def verify_auth(user_id: str, token: str) -> bool: @@ -49,6 +50,17 @@ def calculate_fit(user_waist: float, item_id: str): is_perfect = 0.95 <= fit_index <= 1.05 return is_perfect, round(fit_index, 3), item +@app.post("/api/verify-staff") +async def verify_staff(login: StaffLogin): + """ + 🛡️ Secure staff verification using hmac.compare_digest + to prevent timing attacks. + """ + if hmac.compare_digest(login.password.encode(), STAFF_PASSWORD.encode()): + return {"status": "SUCCESS", "message": "Acceso concedido"} + else: + raise HTTPException(status_code=401, detail="Acceso denegado") + @app.post("/api/recommend") async def recommend_garment(scan: UserScan, garment_id: str = "BALMAIN_SS26_SLIM"): # 1. Seguridad y Handshake diff --git a/backend/models.py b/backend/models.py index 652f4cd..8a33bba 100644 --- a/backend/models.py +++ b/backend/models.py @@ -7,6 +7,9 @@ class UserScan(BaseModel): waist: float event_type: str # e.g., 'Gala', 'Business', 'Cocktail' +class StaffLogin(BaseModel): + password: str + class Garment(BaseModel): id: str name: str diff --git a/backend/tests/__pycache__/test_main.cpython-312-pytest-9.0.2.pyc b/backend/tests/__pycache__/test_main.cpython-312-pytest-9.0.2.pyc deleted file mode 100644 index 8ee3541..0000000 Binary files a/backend/tests/__pycache__/test_main.cpython-312-pytest-9.0.2.pyc and /dev/null differ diff --git a/backend/tests/test_staff.py b/backend/tests/test_staff.py new file mode 100644 index 0000000..c2dcbb5 --- /dev/null +++ b/backend/tests/test_staff.py @@ -0,0 +1,34 @@ +import pytest +from fastapi.testclient import TestClient +from backend.main import app +import os + +client = TestClient(app) + +def test_verify_staff_success(monkeypatch): + # Set a known staff password for testing + monkeypatch.setenv("STAFF_PASSWORD", "TEST_STAFF_PASS") + # Reload the app's STAFF_PASSWORD from env + import backend.main + monkeypatch.setattr(backend.main, "STAFF_PASSWORD", "TEST_STAFF_PASS") + + response = client.post("/api/verify-staff", json={"password": "TEST_STAFF_PASS"}) + assert response.status_code == 200 + assert response.json()["status"] == "SUCCESS" + +def test_verify_staff_failure(monkeypatch): + monkeypatch.setenv("STAFF_PASSWORD", "TEST_STAFF_PASS") + import backend.main + monkeypatch.setattr(backend.main, "STAFF_PASSWORD", "TEST_STAFF_PASS") + + response = client.post("/api/verify-staff", json={"password": "WRONG_PASSWORD"}) + assert response.status_code == 401 + assert response.json()["detail"] == "Acceso denegado" + +def test_verify_staff_empty_password(monkeypatch): + monkeypatch.setenv("STAFF_PASSWORD", "TEST_STAFF_PASS") + import backend.main + monkeypatch.setattr(backend.main, "STAFF_PASSWORD", "TEST_STAFF_PASS") + + response = client.post("/api/verify-staff", json={"password": ""}) + assert response.status_code == 401 diff --git a/js/main.js b/js/main.js index dcf1947..5e37651 100644 --- a/js/main.js +++ b/js/main.js @@ -232,14 +232,29 @@ class TryOnYouBunker { modal.style.display = 'none'; } - verifyPrivatePass() { + async verifyPrivatePass() { const input = document.getElementById('private-pass-input'); - if (input.value === "SAC_MUSEUM_2026") { - this.showNotification('ACCESO CONCEDIDO', 'success'); - setTimeout(() => { window.location.href = "/staff-dashboard"; }, 1500); - } else { - this.showNotification('ACCESO DENEGADO', 'error'); - input.value = ""; + const password = input.value; + + try { + const response = await fetch('/api/verify-staff', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ password }) + }); + + if (response.ok) { + this.showNotification('ACCESO CONCEDIDO', 'success'); + setTimeout(() => { window.location.href = "/staff-dashboard"; }, 1500); + } else { + this.showNotification('ACCESO DENEGADO', 'error'); + input.value = ""; + } + } catch (error) { + console.error('Staff verification failed:', error); + this.showNotification('BUNKER OFFLINE', 'error'); } } } diff --git a/verification/collection.png b/verification/collection.png deleted file mode 100644 index 8e62084..0000000 Binary files a/verification/collection.png and /dev/null differ diff --git a/verification/consultation.png b/verification/consultation.png deleted file mode 100644 index 024bf61..0000000 Binary files a/verification/consultation.png and /dev/null differ diff --git a/verification/full_page.png b/verification/full_page.png deleted file mode 100644 index 9e3e628..0000000 Binary files a/verification/full_page.png and /dev/null differ diff --git a/verification/jules_form_verification.png b/verification/jules_form_verification.png deleted file mode 100644 index 6d707db..0000000 Binary files a/verification/jules_form_verification.png and /dev/null differ diff --git a/verification/verify_jules_form.py b/verification/verify_jules_form.py deleted file mode 100644 index f1addfb..0000000 --- a/verification/verify_jules_form.py +++ /dev/null @@ -1,29 +0,0 @@ -from playwright.sync_api import Page, expect, sync_playwright - -def verify_jules_form(page: Page): - # Navigate to the app - page.goto("http://localhost:5173/") - - # Scroll to consultation section - page.locator("#consultation").scroll_into_view_if_needed() - - # Check for the correct labels - expect(page.get_by_label("Body Shape")).to_be_visible() - expect(page.get_by_label("Fit Preference")).to_be_visible() - - # Ensure "Height (cm)" and "Weight (kg)" are NOT visible - expect(page.get_by_text("Height (cm)")).not_to_be_visible() - expect(page.get_by_text("Weight (kg)")).not_to_be_visible() - - # Take screenshot of the form - page.screenshot(path="verification/jules_form_verification.png") - print("Screenshot taken.") - -if __name__ == "__main__": - with sync_playwright() as p: - browser = p.chromium.launch(headless=True) - page = browser.new_page() - try: - verify_jules_form(page) - finally: - browser.close() diff --git a/verification/verify_tryonyou.py b/verification/verify_tryonyou.py deleted file mode 100644 index 9678a4b..0000000 --- a/verification/verify_tryonyou.py +++ /dev/null @@ -1,42 +0,0 @@ -from playwright.sync_api import Page, expect, sync_playwright - -def verify_tryonyou(page: Page): - # Go to localhost - page.goto("http://localhost:8080") - - # Check for Jules Consultation Section - expect(page.locator("#consultation")).to_be_visible() - expect(page.get_by_role("heading", name="AI Stylist Consultation (Jules)")).to_be_visible() - - # Check for Form inputs - expect(page.locator("#height")).to_be_visible() - expect(page.locator("#weight")).to_be_visible() - expect(page.locator("#event_type")).to_be_visible() - - # Check for Lafayette Collection - expect(page.get_by_text("Lafayette x TryOnYou Exclusive")).to_be_visible() - - # Check for the specific new items - expect(page.get_by_text("Cubist Art Jacket")).to_be_visible() - expect(page.get_by_text("Peacock Couture Blazer")).to_be_visible() - - # Take screenshot of the consultation section - page.locator("#consultation").screenshot(path="verification/consultation.png") - - # Take screenshot of the collection section - page.locator(".exclusive-collection").screenshot(path="verification/collection.png") - - # Take full page screenshot - page.screenshot(path="verification/full_page.png", full_page=True) - -if __name__ == "__main__": - with sync_playwright() as p: - browser = p.chromium.launch(headless=True) - page = browser.new_page() - try: - verify_tryonyou(page) - print("Verification successful!") - except Exception as e: - print(f"Verification failed: {e}") - finally: - browser.close() diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..7af6018 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + }, + }, + }, +});