{ "meta": { "generated_at": "2025-01-27T00:00:00Z", "scope": "backend veza-backend-api <-> frontend apps/web integration", "repo_commit": "e4212ee59420e1c2d53e84af5cba1d9351d123d5", "auditor": "Cursor AI", "severity_model": { "P0": "Critical - breaks core functionality or security, must fix immediately", "P1": "High - frequent functional breakage or major UX regression", "P2": "Medium - correctness/consistency issues that will create bugs soon", "P3": "Low - maintainability, dead code, tech debt" } }, "summary": { "health_score_0_to_10": 4, "issue_counts": { "P0": 5, "P1": 10, "P2": 8, "P3": 7 }, "top5_issue_ids": [ "INT-000001", "INT-000002", "INT-000003", "INT-000004", "INT-000005" ] }, "issues": [ { "id": "INT-000001", "severity": "P0", "priority_rank": 1, "title": "CORS Configuration Will Break Production - Empty Origins Reject All Requests", "component": { "frontend": "", "backend": "veza-backend-api/internal/api/router.go, internal/config/config.go, internal/middleware/cors.go", "infra": "deployment configs" }, "category": ["cors", "env", "production"], "status": "open", "impact": { "user_visible": true, "breaks_core_flow": true, "security_risk": "medium", "notes": "All API requests will fail in production if CORS_ALLOWED_ORIGINS is not set" }, "evidence": { "files": [ { "path": "veza-backend-api/internal/api/router.go", "lines": "L119-L128", "excerpt": "if r.config != nil {\n router.Use(middleware.CORS(r.config.CORSOrigins))\n if len(r.config.CORSOrigins) == 0 {\n r.logger.Warn(\"CORS origins not configured - strict mode enabled: ALL CORS requests will be rejected.\")\n }\n}" }, { "path": "veza-backend-api/internal/config/config.go", "lines": "L638-L664", "excerpt": "case EnvProduction:\n return []string{} // Empty list = reject all" }, { "path": "veza-backend-api/internal/middleware/cors.go", "lines": "L34-L37", "excerpt": "if len(allowed) == 0 {\n return false // Reject all origins" } ], "runtime": [] }, "repro": { "steps": [ "Set APP_ENV=production", "Leave CORS_ALLOWED_ORIGINS unset", "Start backend server", "Make CORS request from frontend", "Observe: Request rejected with CORS error" ], "expected": "CORS requests should work with configured origins", "actual": "All CORS requests rejected because origins list is empty" }, "root_cause": "Production environment defaults to strict mode (empty origins list) but frontend expects CORS to work. No validation ensures CORS_ALLOWED_ORIGINS is set in production.", "fix_plan": { "minimal_steps": [ "Add startup validation: fail fast if APP_ENV=production and CORS_ALLOWED_ORIGINS empty", "Document CORS_ALLOWED_ORIGINS as REQUIRED in production deployment guide", "Update docker-compose.production.yml with example CORS_ALLOWED_ORIGINS", "Add integration test with production-like CORS config" ], "tests_to_add": [ "E2E test: Verify CORS works with configured origins", "Unit test: Verify startup fails if prod + empty CORS" ], "regression_risks": ["None - this is a new validation"] }, "dependencies": [], "owner_suggestion": "backend" }, { "id": "INT-000002", "severity": "P0", "priority_rank": 2, "title": "Multiple Auth Storage Mechanisms Cause Token Sync Failures", "component": { "frontend": "apps/web/src/services/tokenStorage.ts, apps/web/src/services/api/client.ts, apps/web/src/stores/auth.ts, apps/web/src/utils/token-manager.ts", "backend": "", "infra": "" }, "category": ["auth", "state-management"], "status": "open", "impact": { "user_visible": true, "breaks_core_flow": true, "security_risk": "low", "notes": "Tokens stored in localStorage, Zustand persist, and TokenStorage can desync, causing auth failures" }, "evidence": { "files": [ { "path": "apps/web/src/services/tokenStorage.ts", "lines": "L35-L36", "excerpt": "localStorage.setItem(ACCESS_TOKEN_KEY, accessToken);\nlocalStorage.setItem(REFRESH_TOKEN_KEY, refreshToken);" }, { "path": "apps/web/src/services/api/client.ts", "lines": "L48-L64", "excerpt": "// Fallback: Si pas de token dans TokenStorage, chercher dans auth-storage Zustand\nif (!token && typeof window !== 'undefined') {\n const authStorage = localStorage.getItem('auth-storage');\n // Parse Zustand storage..." }, { "path": "apps/web/src/utils/token-manager.ts", "lines": "L25", "excerpt": "localStorage.setItem(this.ACCESS_TOKEN_KEY, accessToken);" } ], "runtime": [] }, "repro": { "steps": [ "Login via Zustand store", "Token stored in auth-storage (Zustand)", "API call uses TokenStorage.getAccessToken()", "TokenStorage returns null", "Fallback to Zustand storage works but fragile" ], "expected": "Single source of truth for token storage", "actual": "Tokens stored in 3+ different places, can desync" }, "root_cause": "No single source of truth for token storage. Multiple classes (TokenStorage, token-manager, Zustand) all manage tokens independently.", "fix_plan": { "minimal_steps": [ "Standardize on TokenStorage class as single source", "Remove token storage from Zustand (keep only user and isAuthenticated flags)", "Remove token-manager.ts or migrate all usage to TokenStorage", "Update apiClient interceptor to only use TokenStorage", "Add tests for token persistence across page reloads" ], "tests_to_add": [ "Test: Token persists after page reload", "Test: Token sync across multiple tabs", "Test: Token cleared on logout" ], "regression_risks": ["Existing code using Zustand token storage may break"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000003", "severity": "P0", "priority_rank": 3, "title": "Type Mismatch: User.id (string vs number) Causes Runtime Errors", "component": { "frontend": "apps/web/src/types/api.ts, apps/web/src/features/auth/types/index.ts, apps/web/src/services/api/auth.ts", "backend": "veza-backend-api/internal/dto/user_response.go", "infra": "" }, "category": ["schema-drift", "type-safety"], "status": "open", "impact": { "user_visible": true, "breaks_core_flow": true, "security_risk": "none", "notes": "TypeScript type errors and runtime coercion bugs when accessing user.id" }, "evidence": { "files": [ { "path": "apps/web/src/types/api.ts", "lines": "L3", "excerpt": "id: string; // ✅ Correct" }, { "path": "apps/web/src/features/auth/types/index.ts", "lines": "L8", "excerpt": "id: number; // ❌ Wrong type" }, { "path": "veza-backend-api/internal/dto/user_response.go", "lines": "L30", "excerpt": "ID: uuid.UUID // Serializes as string" } ], "runtime": [] }, "repro": { "steps": [ "Import User type from features/auth/types", "Access user.id in TypeScript", "TypeScript expects number but receives string", "Runtime: String operations on number or vice versa" ], "expected": "User.id is string (UUID) everywhere", "actual": "Mixed types: string in some places, number in others" }, "root_cause": "Inconsistent type definitions across frontend modules. Backend uses UUID (string) but some frontend types use number.", "fix_plan": { "minimal_steps": [ "Audit all User type definitions in frontend", "Update apps/web/src/features/auth/types/index.ts to use id: string", "Update all Zod schemas to validate UUID format", "Run TypeScript strict mode checks", "Fix all type errors" ], "tests_to_add": [ "TypeScript compilation test", "Runtime validation: Verify user.id is string" ], "regression_risks": ["Code expecting number may break"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000004", "severity": "P0", "priority_rank": 4, "title": "Deprecated ApiService Expects Wrong Response Format", "component": { "frontend": "apps/web/src/services/api.ts", "backend": "veza-backend-api/internal/handlers/auth.go", "infra": "" }, "category": ["api-contract", "error-handling"], "status": "open", "impact": { "user_visible": true, "breaks_core_flow": true, "security_risk": "none", "notes": "Code using deprecated ApiService will break when backend response format changes" }, "evidence": { "files": [ { "path": "apps/web/src/services/api.ts", "lines": "L238-L239", "excerpt": "const { user, token } = response.data.data; // Expects flat structure" }, { "path": "veza-backend-api/internal/handlers/auth.go", "lines": "L96-L106", "excerpt": "RespondSuccess(c, http.StatusOK, dto.LoginResponse{\n User: {...},\n Token: {...}\n}) // Returns { success: true, data: { user: {...}, token: {...} } }" }, { "path": "apps/web/src/services/api.ts", "lines": "L74-L80", "excerpt": "@deprecated Cette classe est dépréciée. Utilisez apiClient" } ], "runtime": [] }, "repro": { "steps": [ "Use ApiService.login()", "Backend returns { success: true, data: { user: {...}, token: {...} } }", "ApiService expects { user, token } directly in data", "Code works but fragile - depends on exact structure" ], "expected": "All code uses apiClient with consistent response unwrapping", "actual": "Deprecated ApiService still used, expects different format" }, "root_cause": "Legacy ApiService class expects flat structure in data, but backend wraps in nested structure. Class marked deprecated but not fully migrated.", "fix_plan": { "minimal_steps": [ "Complete migration from ApiService to apiClient (see MIGRATION_GUIDE.md)", "Search for all ApiService usage", "Replace with apiClient calls", "Remove ApiService class entirely", "Update all imports" ], "tests_to_add": [ "Verify all API calls use apiClient", "No references to ApiService in codebase" ], "regression_risks": ["Code still using ApiService will break"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000005", "severity": "P0", "priority_rank": 5, "title": "Missing CSRF Protection for State-Changing Operations", "component": { "frontend": "apps/web/src/services/csrf.ts, apps/web/src/services/secure-auth.ts", "backend": "veza-backend-api (no CSRF middleware found)", "infra": "" }, "category": ["security", "csrf"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "high", "notes": "Vulnerable to CSRF attacks on POST/PUT/DELETE requests" }, "evidence": { "files": [ { "path": "apps/web/src/services/csrf.ts", "lines": "L14-L17", "excerpt": "public async refreshCsrfToken(): Promise {\n // Placeholder: fetch from backend if needed\n // this.csrfToken = ...\n}" }, { "path": "apps/web/src/services/secure-auth.ts", "lines": "L119", "excerpt": "await csrfService.refreshCsrfToken(); // Does nothing" } ], "runtime": [] }, "repro": { "steps": [ "Create malicious website", "Include form that POSTs to https://veza.com/api/v1/tracks (user logged in)", "User visits malicious site", "Form submits automatically", "Request succeeds (no CSRF protection)" ], "expected": "CSRF token required for POST/PUT/DELETE", "actual": "No CSRF protection implemented" }, "root_cause": "CSRF protection not implemented. Frontend has placeholder service, backend has no CSRF middleware. Chat server has CSRF but REST API does not.", "fix_plan": { "minimal_steps": [ "Implement CSRF token generation endpoint: GET /api/v1/csrf-token", "Backend: Add CSRF middleware for POST/PUT/DELETE (exclude GET, OPTIONS)", "Frontend: Fetch CSRF token on app init", "Update apiClient interceptor to add X-CSRF-Token header", "Implement csrfService.refreshCsrfToken() to fetch from backend" ], "tests_to_add": [ "CSRF attack simulation test", "Verify tokens validated on backend", "Test token rotation" ], "regression_risks": ["Existing API calls may fail if CSRF required"] }, "dependencies": [], "owner_suggestion": "backend" }, { "id": "INT-000006", "severity": "P1", "priority_rank": 6, "title": "18 Frontend API Calls Target Missing Backend Endpoints", "component": { "frontend": "apps/web/src/features/*/services/*.ts, apps/web/src/services/2fa-service.ts", "backend": "veza-backend-api/internal/api/router.go", "infra": "" }, "category": ["api-contract"], "status": "open", "impact": { "user_visible": true, "breaks_core_flow": true, "security_risk": "none", "notes": "Frontend calls endpoints that don't exist, causing 404 errors" }, "evidence": { "files": [ { "path": "apps/web/src/services/2fa-service.ts", "lines": "L17, L22, L27, L31, L35", "excerpt": "GET /api/v1/2fa/status, POST /api/v1/2fa/setup, POST /api/v1/2fa/enable, POST /api/v1/2fa/disable, POST /api/v1/2fa/verify" }, { "path": "apps/web/src/features/playlists/services/playlistService.ts", "lines": "L125, L136, L175, L186, L218", "excerpt": "Missing: /playlists/:id/collaborators, /playlists/search, /playlists/:id/share, /playlists/recommendations" }, { "path": "veza-backend-api/internal/api/router.go", "lines": "L494-L528", "excerpt": "Playlist routes only: GET, POST, PUT, DELETE /playlists, POST/DELETE tracks" } ], "runtime": [] }, "repro": { "steps": [ "Call playlistService.addCollaborator()", "Request: POST /api/v1/playlists/:id/collaborators", "Backend returns 404", "Feature broken" ], "expected": "All frontend API calls have corresponding backend endpoints", "actual": "18 endpoints called but not implemented" }, "root_cause": "Frontend features implemented but backend endpoints not created. Incomplete feature implementation.", "fix_plan": { "minimal_steps": [ "Audit all frontend API calls (grep apiClient.get/post/put/delete)", "Compare with backend routes (router.go)", "For each missing endpoint: implement OR remove frontend call", "Update API documentation", "Add integration tests" ], "tests_to_add": [ "Integration test: Verify all frontend calls have backend endpoints", "E2E test: Verify feature works end-to-end" ], "regression_risks": ["Removing frontend calls may break UI features"] }, "dependencies": [], "owner_suggestion": "backend" }, { "id": "INT-000007", "severity": "P1", "priority_rank": 7, "title": "Environment Variable Naming Inconsistency (VITE_API_BASE_URL vs VITE_API_URL)", "component": { "frontend": "apps/web/src/config/env.ts, apps/web/scripts/*.sh, apps/web/Dockerfile", "backend": "", "infra": "deployment configs" }, "category": ["env", "build"], "status": "open", "impact": { "user_visible": true, "breaks_core_flow": true, "security_risk": "none", "notes": "Some scripts use VITE_API_BASE_URL, others use VITE_API_URL, causing build failures" }, "evidence": { "files": [ { "path": "apps/web/src/config/env.ts", "lines": "L6", "excerpt": "VITE_API_URL: z.string().url().default('http://127.0.0.1:8080/api/v1')" }, { "path": "apps/web/scripts/check_backend.sh", "lines": "L16", "excerpt": "API_URL=\"${VITE_API_BASE_URL:-http://localhost:8080/api/v1}\"" }, { "path": "apps/web/Dockerfile", "lines": "L17", "excerpt": "ARG VITE_API_BASE_URL" } ], "runtime": [] }, "repro": { "steps": [ "Set VITE_API_BASE_URL in environment", "Frontend code reads VITE_API_URL", "VITE_API_URL is undefined", "Uses default or throws error" ], "expected": "Consistent env var name: VITE_API_URL", "actual": "Mixed usage: VITE_API_BASE_URL and VITE_API_URL" }, "root_cause": "Migration from VITE_API_BASE_URL to VITE_API_URL incomplete. Some scripts and configs still use old name.", "fix_plan": { "minimal_steps": [ "Search for all VITE_API_BASE_URL usage", "Replace with VITE_API_URL", "Update scripts/check_backend.sh", "Update Dockerfile", "Update deployment docs" ], "tests_to_add": [ "Verify env var name consistent everywhere", "Build test with VITE_API_URL" ], "regression_risks": ["Scripts using old name will break"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000008", "severity": "P1", "priority_rank": 8, "title": "Path Mismatch: /users/:userId/profile vs /users/:id", "component": { "frontend": "apps/web/src/features/profile/services/profileService.ts", "backend": "veza-backend-api/internal/api/router.go", "infra": "" }, "category": ["api-contract"], "status": "open", "impact": { "user_visible": true, "breaks_core_flow": true, "security_risk": "none", "notes": "Frontend calls /users/:userId/profile but backend expects /users/:id" }, "evidence": { "files": [ { "path": "apps/web/src/features/profile/services/profileService.ts", "lines": "L17, L43", "excerpt": "GET /api/v1/users/${userId}/profile, PUT /api/v1/users/${userId}/profile" }, { "path": "veza-backend-api/internal/api/router.go", "lines": "L344, L358", "excerpt": "GET /api/v1/users/:id, PUT /api/v1/users/:id" } ], "runtime": [] }, "repro": { "steps": [ "Call profileService.getProfile(userId)", "Request: GET /api/v1/users/:userId/profile", "Backend returns 404", "Feature broken" ], "expected": "Frontend paths match backend routes", "actual": "Frontend uses /profile suffix, backend doesn't" }, "root_cause": "Frontend uses /profile suffix but backend routes don't include it. Inconsistent API design.", "fix_plan": { "minimal_steps": [ "Option A: Update frontend to use /users/:id (recommended)", "Option B: Add /profile routes to backend", "Update profileService to use correct paths", "Test profile features" ], "tests_to_add": [ "Integration test: Verify profile endpoints work", "E2E test: Verify profile page loads" ], "regression_risks": ["Changing paths may break existing code"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000009", "severity": "P1", "priority_rank": 9, "title": "Error Code Type Mismatch: number vs string in ApiError", "component": { "frontend": "apps/web/src/types/api.ts, apps/web/src/schemas/validation.ts", "backend": "veza-backend-api/internal/errors/codes.go", "infra": "" }, "category": ["schema-drift", "error-handling"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "Type mismatches in error handling, potential runtime errors" }, "evidence": { "files": [ { "path": "apps/web/src/types/api.ts", "lines": "L245", "excerpt": "code: number; // ✅ Correct" }, { "path": "apps/web/src/schemas/validation.ts", "lines": "L338", "excerpt": "code: z.string(), // ❌ Wrong type" }, { "path": "veza-backend-api/internal/errors/codes.go", "lines": "L1-L32", "excerpt": "ErrorCode is int type" } ], "runtime": [] }, "repro": { "steps": [ "Backend returns error with code: 1000 (number)", "Frontend Zod schema expects code: string", "Validation fails or coerces incorrectly" ], "expected": "Error code is number everywhere", "actual": "Mixed types: number in types, string in Zod schemas" }, "root_cause": "Inconsistent error code types. Backend uses int, frontend types use number, but Zod schemas use string.", "fix_plan": { "minimal_steps": [ "Update Zod schemas to use z.number() for error code", "Verify all error parsing uses number type", "Update apiErrorHandler to handle number codes", "Test error handling" ], "tests_to_add": [ "Test: Verify error codes parsed as numbers", "Test: Error display works correctly" ], "regression_risks": ["Code expecting string may break"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000010", "severity": "P1", "priority_rank": 10, "title": "Remember Me Field Handling Inconsistency", "component": { "frontend": "apps/web/src/services/api/auth.ts, apps/web/src/features/auth/types/index.ts", "backend": "veza-backend-api/internal/dto/login_request.go", "infra": "" }, "category": ["api-contract"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "Field name inconsistency: remember_me vs rememberMe, may cause issues" }, "evidence": { "files": [ { "path": "apps/web/src/services/api/auth.ts", "lines": "L150", "excerpt": "remember_me: data.remember_me || false" }, { "path": "apps/web/src/features/auth/types/index.ts", "lines": "L11", "excerpt": "remember_me?: boolean;" }, { "path": "veza-backend-api/internal/dto/login_request.go", "lines": "L6", "excerpt": "RememberMe bool `json:\"remember_me\"`" } ], "runtime": [] }, "repro": { "steps": [ "Login form uses rememberMe (camelCase)", "API call converts to remember_me (snake_case)", "Backend expects remember_me", "Works but inconsistent naming" ], "expected": "Consistent field naming", "actual": "Mixed: rememberMe in forms, remember_me in API" }, "root_cause": "Frontend uses camelCase in forms but backend expects snake_case. Conversion happens but inconsistent.", "fix_plan": { "minimal_steps": [ "Standardize on snake_case for API (matches backend)", "Update form types to use remember_me", "Update all references", "Test login with remember me" ], "tests_to_add": [ "Test: Login with remember_me=true", "Test: Token expiration matches remember_me setting" ], "regression_risks": ["Forms using rememberMe may break"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000011", "severity": "P1", "priority_rank": 11, "title": "Token Refresh Response Format Ambiguity", "component": { "frontend": "apps/web/src/services/tokenRefresh.ts", "backend": "veza-backend-api/internal/handlers/auth.go", "infra": "" }, "category": ["api-contract", "error-handling"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": true, "security_risk": "none", "notes": "Multiple format checks in tokenRefresh suggest uncertainty about response structure" }, "evidence": { "files": [ { "path": "apps/web/src/services/tokenRefresh.ts", "lines": "L70-L84", "excerpt": "// Format with wrapper { success: true, data: {...} }\nif (response.data?.data?.access_token) { ... }\n// Format direct { access_token, refresh_token, expires_in }\nelse if (response.data?.access_token) { ... }\n// Format with token nested { token: { access_token, refresh_token } }\nelse if (response.data?.token?.access_token) { ... }" }, { "path": "veza-backend-api/internal/handlers/auth.go", "lines": "L197-L201", "excerpt": "RespondSuccess(c, http.StatusOK, dto.TokenResponse{...}) // Returns { success: true, data: {...} }" } ], "runtime": [] }, "repro": { "steps": [ "Token refresh called", "Backend returns { success: true, data: { access_token, refresh_token, expires_in } }", "Frontend checks 3 different formats", "Works but fragile" ], "expected": "Single, documented response format", "actual": "Multiple format checks suggest uncertainty" }, "root_cause": "Frontend handles multiple response formats to be defensive, but this indicates contract uncertainty.", "fix_plan": { "minimal_steps": [ "Document exact response format in FRONTEND_INTEGRATION.md", "Simplify tokenRefresh to only handle correct format", "Remove fallback format checks", "Add test for refresh response parsing" ], "tests_to_add": [ "Test: Verify refresh response parsed correctly", "Test: Error handling for invalid format" ], "regression_risks": ["If format changes, code will break"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000012", "severity": "P1", "priority_rank": 12, "title": "No Retry Logic for Transient Errors (503, 502)", "component": { "frontend": "apps/web/src/services/api/client.ts", "backend": "", "infra": "" }, "category": ["error-handling", "reliability"], "status": "open", "impact": { "user_visible": true, "breaks_core_flow": false, "security_risk": "none", "notes": "503/502 errors are handled but not retried, causing user-visible failures" }, "evidence": { "files": [ { "path": "apps/web/src/services/api/client.ts", "lines": "L201-L213", "excerpt": "if (status === 503) { return Promise.reject(apiError); }\nif (status === 502) { return Promise.reject(apiError); } // No retry" } ], "runtime": [] }, "repro": { "steps": [ "Backend returns 503 (Service Unavailable)", "Frontend rejects immediately", "User sees error", "No automatic retry" ], "expected": "Transient errors (503, 502) should be retried with exponential backoff", "actual": "Errors rejected immediately, no retry" }, "root_cause": "No retry logic for transient errors. 429 (rate limit) has retry but 503/502 do not.", "fix_plan": { "minimal_steps": [ "Add retry logic for 503/502 errors", "Implement exponential backoff (max 3 retries)", "Add retry-after header support", "Update error messages to indicate retry" ], "tests_to_add": [ "Test: Verify 503 retries with backoff", "Test: Verify 502 retries with backoff", "Test: Max retries respected" ], "regression_risks": ["May cause excessive retries if not configured correctly"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000013", "severity": "P1", "priority_rank": 13, "title": "Missing Error Correlation IDs in Frontend Error Boundaries", "component": { "frontend": "apps/web/src (error boundaries)", "backend": "veza-backend-api/internal/response/response.go", "infra": "" }, "category": ["observability", "error-handling"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "Backend returns request_id but frontend doesn't log it, making debugging difficult" }, "evidence": { "files": [ { "path": "veza-backend-api/internal/response/response.go", "lines": "L106", "excerpt": "RequestID: c.GetString(\"request_id\")" } ], "runtime": [] }, "repro": { "steps": [ "API error occurs", "Backend returns { error: { request_id: \"req-123\", ... } }", "Frontend logs error but doesn't include request_id", "Debugging difficult" ], "expected": "Frontend error logs include request_id for correlation", "actual": "Request ID not logged in frontend" }, "root_cause": "Backend provides request_id for error correlation but frontend doesn't extract or log it.", "fix_plan": { "minimal_steps": [ "Update apiErrorHandler to extract request_id", "Include request_id in error logs", "Update error boundaries to log request_id", "Add request_id to user-facing error messages (optional)" ], "tests_to_add": [ "Test: Verify request_id logged in errors", "Test: Error correlation works" ], "regression_risks": ["None"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000014", "severity": "P1", "priority_rank": 14, "title": "CORS Allow-Credentials Always True (Security Risk if Origins Misconfigured)", "component": { "frontend": "", "backend": "veza-backend-api/internal/middleware/cors.go", "infra": "" }, "category": ["cors", "security"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "medium", "notes": "If CORS origins misconfigured, credentials flag enables cookie theft" }, "evidence": { "files": [ { "path": "veza-backend-api/internal/middleware/cors.go", "lines": "L21", "excerpt": "c.Header(\"Access-Control-Allow-Credentials\", \"true\") // Always true" } ], "runtime": [] }, "repro": { "steps": [ "CORS origins misconfigured (e.g., contains wildcard or wrong domain)", "Malicious site makes credentialed request", "Credentials flag allows cookies/tokens to be sent", "Security risk" ], "expected": "Credentials only allowed if origins correctly whitelisted", "actual": "Credentials always true, relies on origins being correct" }, "root_cause": "Credentials flag hardcoded to true. Relies on origins being correctly configured. If origins misconfigured, security risk.", "fix_plan": { "minimal_steps": [ "Add validation: credentials only if origins non-empty and no wildcard", "Log warning if credentials=true but origins contain wildcard", "Document security implications", "Add test for credentials + wildcard combination" ], "tests_to_add": [ "Test: Verify credentials rejected with wildcard", "Test: Verify credentials allowed with specific origins" ], "regression_risks": ["If origins misconfigured, requests may fail"] }, "dependencies": ["INT-000001"], "owner_suggestion": "backend" }, { "id": "INT-000015", "severity": "P1", "priority_rank": 15, "title": "GetMe Endpoint Returns Incomplete User Data", "component": { "frontend": "apps/web/src/features/auth/api/authApi.ts", "backend": "veza-backend-api/internal/handlers/auth.go", "infra": "" }, "category": ["api-contract"], "status": "open", "impact": { "user_visible": true, "breaks_core_flow": false, "security_risk": "none", "notes": "GET /auth/me returns only id, email, role but frontend expects full user object" }, "evidence": { "files": [ { "path": "veza-backend-api/internal/handlers/auth.go", "lines": "L369-L373", "excerpt": "RespondSuccess(c, http.StatusOK, gin.H{\n \"id\": userID,\n \"email\": c.GetString(\"email\"),\n \"role\": c.GetString(\"role\")\n}) // Missing username, avatar, etc." }, { "path": "apps/web/src/features/auth/api/authApi.ts", "lines": "L34-L36", "excerpt": "getMe: async (): Promise => { ... } // Expects full User object" } ], "runtime": [] }, "repro": { "steps": [ "Call authApi.getMe()", "Backend returns { id, email, role }", "Frontend expects full User object with username, avatar, etc.", "Type mismatch or missing fields" ], "expected": "GET /auth/me returns full user object", "actual": "Returns only id, email, role" }, "root_cause": "GetMe handler returns minimal user data from context, not full user object from database.", "fix_plan": { "minimal_steps": [ "Update GetMe handler to fetch full user from database", "Return complete UserResponse object", "Update frontend types if needed", "Test getMe returns all expected fields" ], "tests_to_add": [ "Test: Verify getMe returns full user object", "Test: Verify all User fields present" ], "regression_risks": ["Frontend may break if response structure changes"] }, "dependencies": [], "owner_suggestion": "backend" }, { "id": "INT-000016", "severity": "P2", "priority_rank": 16, "title": "Field Name Mismatch: cover_art_path vs cover_art_url", "component": { "frontend": "apps/web/src/types/api.ts", "backend": "veza-backend-api (track models)", "infra": "" }, "category": ["schema-drift"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "Field name inconsistency may cause display issues" }, "evidence": { "files": [ { "path": "apps/web/src/types/api.ts", "lines": "L66", "excerpt": "cover_art_path?: string; // Backend uses cover_art_path" } ], "runtime": [] }, "repro": { "steps": [ "Backend returns cover_art_path", "Frontend expects cover_art_path", "Works but naming inconsistent with other _url fields" ], "expected": "Consistent field naming", "actual": "Mixed: _path vs _url" }, "root_cause": "Inconsistent naming convention. Some fields use _url, others use _path.", "fix_plan": { "minimal_steps": [ "Document field naming convention", "Standardize on _url for URLs, _path for file paths", "Update types and backend if needed", "Update documentation" ], "tests_to_add": [ "Test: Verify cover art displays correctly", "Test: Verify field names consistent" ], "regression_risks": ["Changing field names may break existing code"] }, "dependencies": [], "owner_suggestion": "backend" }, { "id": "INT-000017", "severity": "P2", "priority_rank": 17, "title": "Pagination Format Inconsistencies", "component": { "frontend": "apps/web/src/types/api.ts", "backend": "veza-backend-api (pagination responses)", "infra": "" }, "category": ["schema-drift"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "Different pagination formats may cause parsing issues" }, "evidence": { "files": [ { "path": "apps/web/src/types/api.ts", "lines": "L222-L231", "excerpt": "PaginationData: { page, limit, total, total_pages, has_next, has_prev, next_cursor?, prev_cursor? }" } ], "runtime": [] }, "repro": { "steps": [ "Backend returns pagination in different format", "Frontend expects specific format", "Parsing may fail or be inconsistent" ], "expected": "Consistent pagination format across all endpoints", "actual": "May vary by endpoint" }, "root_cause": "No standardized pagination format. Different endpoints may return different structures.", "fix_plan": { "minimal_steps": [ "Document standard pagination format", "Audit all paginated endpoints", "Standardize pagination responses", "Update frontend types" ], "tests_to_add": [ "Test: Verify pagination format consistent", "Test: Verify pagination parsing works" ], "regression_risks": ["Changing pagination format may break existing code"] }, "dependencies": [], "owner_suggestion": "backend" }, { "id": "INT-000018", "severity": "P2", "priority_rank": 18, "title": "Missing Validation Schema Alignment Between Frontend and Backend", "component": { "frontend": "apps/web/src/schemas/validation.ts", "backend": "veza-backend-api/internal/validators", "infra": "" }, "category": ["schema-drift", "validation"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "Frontend and backend validation rules may differ, causing inconsistent errors" }, "evidence": { "files": [ { "path": "apps/web/src/schemas/validation.ts", "lines": "L1-L359", "excerpt": "Zod schemas for validation" } ], "runtime": [] }, "repro": { "steps": [ "Frontend validates email format", "Backend validates differently", "Frontend passes, backend rejects (or vice versa)", "Inconsistent user experience" ], "expected": "Frontend and backend validation rules match", "actual": "Validation rules may differ" }, "root_cause": "No synchronization between frontend Zod schemas and backend validators. Rules may drift.", "fix_plan": { "minimal_steps": [ "Audit validation rules in both frontend and backend", "Document validation rules in shared doc", "Align frontend Zod schemas with backend validators", "Add tests to verify alignment" ], "tests_to_add": [ "Test: Verify validation rules match", "Test: Verify error messages consistent" ], "regression_risks": ["Changing validation may break existing forms"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000019", "severity": "P2", "priority_rank": 19, "title": "No Request ID Propagation to Frontend Logs", "component": { "frontend": "apps/web/src/services/api/client.ts", "backend": "veza-backend-api/internal/middleware/request_id.go", "infra": "" }, "category": ["observability"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "Backend generates request_id but frontend doesn't log it, making debugging difficult" }, "evidence": { "files": [ { "path": "veza-backend-api/internal/middleware/request_id.go", "lines": "L1-L20", "excerpt": "RequestID middleware generates X-Request-ID header" } ], "runtime": [] }, "repro": { "steps": [ "API request made", "Backend generates request_id", "Frontend doesn't log request_id", "Debugging difficult" ], "expected": "Frontend logs include request_id for correlation", "actual": "Request ID not logged in frontend" }, "root_cause": "Backend generates request_id but frontend doesn't extract or log it in request/response interceptors.", "fix_plan": { "minimal_steps": [ "Update apiClient interceptor to extract X-Request-ID from response", "Include request_id in request logs", "Include request_id in error logs", "Add request_id to error objects" ], "tests_to_add": [ "Test: Verify request_id logged", "Test: Verify request_id in error objects" ], "regression_risks": ["None"] }, "dependencies": ["INT-000013"], "owner_suggestion": "frontend" }, { "id": "INT-000020", "severity": "P2", "priority_rank": 20, "title": "Inconsistent Error Detail Formats", "component": { "frontend": "apps/web/src/utils/apiErrorHandler.ts", "backend": "veza-backend-api/internal/errors/errors.go", "infra": "" }, "category": ["error-handling"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "Different error detail formats may cause parsing issues" }, "evidence": { "files": [ { "path": "apps/web/src/utils/apiErrorHandler.ts", "lines": "L145-L154", "excerpt": "normalizeApiError handles multiple error formats" } ], "runtime": [] }, "repro": { "steps": [ "Backend returns error with details array", "Frontend expects specific format", "Parsing may fail or be inconsistent" ], "expected": "Consistent error detail format", "actual": "Multiple formats handled, suggests inconsistency" }, "root_cause": "Backend may return errors in different formats, frontend handles multiple formats defensively.", "fix_plan": { "minimal_steps": [ "Standardize error detail format in backend", "Simplify frontend error parsing", "Remove fallback format handling", "Update error documentation" ], "tests_to_add": [ "Test: Verify error parsing works", "Test: Verify error details displayed correctly" ], "regression_risks": ["If format changes, parsing may break"] }, "dependencies": [], "owner_suggestion": "backend" }, { "id": "INT-000021", "severity": "P2", "priority_rank": 21, "title": "Missing Health Check Integration Tests", "component": { "frontend": "apps/web/e2e", "backend": "veza-backend-api/internal/handlers/health.go", "infra": "" }, "category": ["testing", "observability"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "No E2E tests verify health check endpoints work" }, "evidence": { "files": [ { "path": "apps/web/e2e/qa-audit.spec.ts", "lines": "L80", "excerpt": "const response = await page.request.get(`${API_URL}/health`); // Basic check but no assertion" } ], "runtime": [] }, "repro": { "steps": [ "Health check endpoint called", "No test verifies response format", "If format changes, no test catches it" ], "expected": "E2E tests verify health check endpoints", "actual": "Basic check exists but no assertions" }, "root_cause": "Health check endpoints exist but not properly tested in E2E suite.", "fix_plan": { "minimal_steps": [ "Add E2E test for GET /api/v1/health", "Verify response format", "Verify health status", "Add tests for /healthz and /readyz" ], "tests_to_add": [ "E2E test: Verify /health returns correct format", "E2E test: Verify /healthz returns 200", "E2E test: Verify /readyz returns 200" ], "regression_risks": ["None"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000022", "severity": "P2", "priority_rank": 22, "title": "No API Versioning Strategy", "component": { "frontend": "apps/web/src/services/api/client.ts", "backend": "veza-backend-api/internal/api/router.go", "infra": "" }, "category": ["api-contract"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "All routes under /api/v1, no versioning strategy for future changes" }, "evidence": { "files": [ { "path": "veza-backend-api/internal/api/router.go", "lines": "L152", "excerpt": "v1 := router.Group(\"/api/v1\") // Hardcoded v1" } ], "runtime": [] }, "repro": { "steps": [ "API needs breaking change", "No versioning strategy", "Must break existing clients or maintain backward compatibility forever" ], "expected": "Versioning strategy for API changes", "actual": "All routes under /api/v1, no migration path" }, "root_cause": "No API versioning strategy. All routes under /api/v1 with no plan for v2.", "fix_plan": { "minimal_steps": [ "Document API versioning strategy", "Plan for v2 when needed", "Add version negotiation if needed", "Update documentation" ], "tests_to_add": [ "Test: Verify versioning works", "Test: Verify backward compatibility" ], "regression_risks": ["None - planning only"] }, "dependencies": [], "owner_suggestion": "backend" }, { "id": "INT-000023", "severity": "P2", "priority_rank": 23, "title": "WebSocket Token in Query Params (Security Risk)", "component": { "frontend": "apps/web/src/services/api.ts", "backend": "", "infra": "" }, "category": ["security", "websocket"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "medium", "notes": "WebSocket tokens in URL query params exposed in logs, browser history" }, "evidence": { "files": [ { "path": "apps/web/src/services/api.ts", "lines": "L565", "excerpt": "return `${WS_BASE_URL}?token=${token}`; // Token in query param" } ], "runtime": [] }, "repro": { "steps": [ "WebSocket connection established", "Token in URL: ws://example.com/ws?token=xxx", "Token exposed in browser history, server logs", "Security risk" ], "expected": "Token in header or subprotocol, not query param", "actual": "Token in query param" }, "root_cause": "WebSocket authentication uses query param instead of header or subprotocol.", "fix_plan": { "minimal_steps": [ "Move token to WebSocket subprotocol or header", "Update WebSocket client to use header", "Update chat server to read from header", "Remove token from query params" ], "tests_to_add": [ "Test: Verify WebSocket auth works with header", "Test: Verify token not in URL" ], "regression_risks": ["WebSocket connections may break if not updated correctly"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000024", "severity": "P3", "priority_rank": 24, "title": "Deprecated Routes Still Accessible", "component": { "frontend": "", "backend": "veza-backend-api/internal/api/router.go", "infra": "" }, "category": ["api-contract", "maintainability"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "Deprecated routes still work, causing confusion and maintenance burden" }, "evidence": { "files": [ { "path": "veza-backend-api/internal/api/router.go", "lines": "L596-L603", "excerpt": "router.GET(\"/health\", deprecationMW, healthCheckHandler) // Deprecated but still works" } ], "runtime": [] }, "repro": { "steps": [ "Call deprecated /health endpoint", "Returns deprecation warning but still works", "No removal plan" ], "expected": "Deprecated routes removed or have removal timeline", "actual": "Deprecated routes still accessible indefinitely" }, "root_cause": "Deprecated routes marked with warning but not removed. No removal timeline.", "fix_plan": { "minimal_steps": [ "Document removal timeline for deprecated routes", "Add removal date to deprecation warnings", "Plan migration path", "Remove deprecated routes after migration period" ], "tests_to_add": [ "Test: Verify deprecated routes return warnings", "Test: Verify new routes work" ], "regression_risks": ["Removing routes may break existing clients"] }, "dependencies": [], "owner_suggestion": "backend" }, { "id": "INT-000025", "severity": "P3", "priority_rank": 25, "title": "Inconsistent Logging Formats", "component": { "frontend": "apps/web/src (logging)", "backend": "veza-backend-api (structured logging)", "infra": "" }, "category": ["observability"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "Frontend uses console.log, backend uses structured logging, making correlation difficult" }, "evidence": { "files": [ { "path": "apps/web/src/services/api/client.ts", "lines": "L62", "excerpt": "console.error('[API] Failed to parse auth storage:', e); // Unstructured" } ], "runtime": [] }, "repro": { "steps": [ "Frontend logs error with console.error", "Backend logs with structured format", "Log aggregation difficult", "Correlation challenging" ], "expected": "Structured logging in frontend for correlation", "actual": "Mixed: console.log in frontend, structured in backend" }, "root_cause": "Frontend uses console.log while backend uses structured logging. No correlation.", "fix_plan": { "minimal_steps": [ "Introduce structured logging library for frontend", "Replace console.log with structured logger", "Include request_id, user_id in logs", "Update log aggregation config" ], "tests_to_add": [ "Test: Verify logs structured", "Test: Verify correlation works" ], "regression_risks": ["Changing logging may break log aggregation"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000026", "severity": "P3", "priority_rank": 26, "title": "Missing Observability Hooks in Frontend", "component": { "frontend": "apps/web/src (error boundaries, API calls)", "backend": "", "infra": "" }, "category": ["observability"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "No error tracking (Sentry), no performance monitoring, making production debugging difficult" }, "evidence": { "files": [], "runtime": [] }, "repro": { "steps": [ "Error occurs in production", "No error tracking", "Debugging difficult", "No visibility into issues" ], "expected": "Error tracking and performance monitoring in frontend", "actual": "No observability tools integrated" }, "root_cause": "No error tracking or performance monitoring integrated in frontend.", "fix_plan": { "minimal_steps": [ "Integrate Sentry or similar error tracking", "Add performance monitoring", "Add error boundaries with tracking", "Configure error reporting" ], "tests_to_add": [ "Test: Verify errors tracked", "Test: Verify performance monitored" ], "regression_risks": ["None"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000027", "severity": "P3", "priority_rank": 27, "title": "No Rate Limit Headers in Responses", "component": { "frontend": "apps/web/src/services/api/client.ts", "backend": "veza-backend-api (rate limiting)", "infra": "" }, "category": ["observability", "api-contract"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "Rate limit info not exposed, frontend can't show remaining requests" }, "evidence": { "files": [ { "path": "apps/web/src/services/api/client.ts", "lines": "L181-L199", "excerpt": "Handles 429 but no rate limit headers checked" } ], "runtime": [] }, "repro": { "steps": [ "Rate limit hit", "Backend returns 429", "Frontend doesn't know remaining requests", "Can't show user-friendly message" ], "expected": "Rate limit headers (X-RateLimit-*) in responses", "actual": "No rate limit headers" }, "root_cause": "Backend rate limiting exists but doesn't expose rate limit info in headers.", "fix_plan": { "minimal_steps": [ "Add X-RateLimit-* headers to rate limit middleware", "Update frontend to read and display rate limit info", "Update error messages to show remaining requests", "Document rate limit headers" ], "tests_to_add": [ "Test: Verify rate limit headers present", "Test: Verify frontend displays rate limit info" ], "regression_risks": ["None"] }, "dependencies": [], "owner_suggestion": "backend" }, { "id": "INT-000028", "severity": "P3", "priority_rank": 28, "title": "Missing API Documentation Updates", "component": { "frontend": "apps/web/FRONTEND_INTEGRATION.md", "backend": "veza-backend-api/docs", "infra": "" }, "category": ["documentation"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "API documentation may be out of sync with implementation" }, "evidence": { "files": [ { "path": "apps/web/FRONTEND_INTEGRATION.md", "lines": "L1-L307", "excerpt": "API documentation" } ], "runtime": [] }, "repro": { "steps": [ "API endpoint changes", "Documentation not updated", "Developers use wrong format", "Errors occur" ], "expected": "API documentation always up to date", "actual": "Documentation may be outdated" }, "root_cause": "No automated process to keep API documentation in sync with implementation.", "fix_plan": { "minimal_steps": [ "Use OpenAPI/Swagger for API documentation", "Generate docs from code", "Update FRONTEND_INTEGRATION.md from OpenAPI spec", "Add CI check for doc sync" ], "tests_to_add": [ "Test: Verify docs match implementation", "Test: Verify CI catches doc drift" ], "regression_risks": ["None"] }, "dependencies": [], "owner_suggestion": "backend" }, { "id": "INT-000029", "severity": "P3", "priority_rank": 29, "title": "No Vite Proxy Configuration for Development", "component": { "frontend": "apps/web/vite.config.ts", "backend": "", "infra": "" }, "category": ["env", "build"], "status": "open", "impact": { "user_visible": false, "breaks_core_flow": false, "security_risk": "none", "notes": "Direct CORS requests in dev, no proxy. Works but not ideal." }, "evidence": { "files": [ { "path": "apps/web/vite.config.ts", "lines": "L58-L92", "excerpt": "server config but no proxy setup" } ], "runtime": [] }, "repro": { "steps": [ "Frontend on localhost:3000", "Backend on localhost:8080", "CORS required", "No proxy, direct requests" ], "expected": "Vite proxy for /api to backend (optional but cleaner)", "actual": "No proxy, direct CORS requests" }, "root_cause": "No Vite proxy configuration. Relies on CORS for dev. Works but proxy would be cleaner.", "fix_plan": { "minimal_steps": [ "Add Vite proxy config for /api to http://localhost:8080", "Update VITE_API_URL to use relative path /api/v1", "Test proxy works", "Update dev docs" ], "tests_to_add": [ "Test: Verify proxy works", "Test: Verify API calls work through proxy" ], "regression_risks": ["Changing to proxy may break existing dev setup"] }, "dependencies": [], "owner_suggestion": "frontend" }, { "id": "INT-000030", "severity": "P3", "priority_rank": 30, "title": "Missing HLS Endpoints (Frontend Calls But Backend Doesn't Implement)", "component": { "frontend": "apps/web/src/features/streaming/services/hlsService.ts", "backend": "veza-backend-api/internal/api/router.go", "infra": "" }, "category": ["api-contract"], "status": "open", "impact": { "user_visible": true, "breaks_core_flow": false, "security_risk": "none", "notes": "HLS streaming features may not work" }, "evidence": { "files": [ { "path": "apps/web/src/features/streaming/services/hlsService.ts", "lines": "L85, L97", "excerpt": "GET /api/v1/tracks/:id/hls/info, GET /api/v1/tracks/:id/hls/status" } ], "runtime": [] }, "repro": { "steps": [ "Call hlsService.getHLSInfo()", "Request: GET /api/v1/tracks/:id/hls/info", "Backend returns 404", "HLS features broken" ], "expected": "HLS endpoints implemented or frontend calls removed", "actual": "Frontend calls endpoints that don't exist" }, "root_cause": "HLS streaming features in frontend but backend endpoints not implemented.", "fix_plan": { "minimal_steps": [ "Implement HLS endpoints in backend OR", "Remove HLS service calls from frontend", "Update documentation", "Test streaming works" ], "tests_to_add": [ "Test: Verify HLS endpoints work", "Test: Verify streaming works end-to-end" ], "regression_risks": ["Removing calls may break streaming features"] }, "dependencies": [], "owner_suggestion": "backend" } ] }