Conversation
Implement unified login process across legacy and modern apps using session-based form authentication (form_login only). React app now only available when authenticated. Backend Changes: - Create AuthenticationSuccessHandler for role-based post-login redirects * Regular users → /game/index * Admin users → /admin dashboard - Remove json_login entirely (API login no longer supported) - Create SpaController to serve React app for authenticated users - Add optional /admin/login convenience redirect - Enhance login.html.twig with professional Bootstrap styling - Remove API login/logout endpoints (API/LoginController.php, API/LogoutController.php) - Keep /api/me for session verification (checkRememberMeAsync) Frontend Changes: - Remove LoginView.tsx (login now handled by server form) - Simplify Router to authenticated-only routes - Update auth API: remove loginAsync, simplify logoutAsync - Simplify UserContext (remove login/pending logic) - Frontend now assumes session-based authentication Docker Build: - Add Node 20 build stage to compile React with Vite - Copy built frontend assets (dist/) to prod image public/dist - Single production image ships both backend + frontend Configuration: - Add SPA_BACKEND_API_URL and SPA_OTEL_COLLECTOR_ADDRESS env variables - Clarify access control rules in security.yaml - Ensure route priority: admin > login > game > spa catch-all Architecture: - Unauthenticated users see TWIG login form at /login - After login, redirected to /game or /admin based on role - Authenticated users access React SPA via SpaController - Session persisted via form_login (shared session store via Redis) - Logout clears session and redirects to login form This implements a session-based SPA pattern inspired by split-fairly, where the frontend is only served when authenticated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The YAML route configuration does not support 'priority' key. Attribute-based routes (defined in controllers) automatically have precedence over YAML routes, so the priority is implicit. This fixes the routing error that prevented the app from loading. Application now correctly: - Serves login page at /login to unauthenticated users - Redirects / to /game/index (caught by firewall first if not authenticated) - Serves React app via SpaController when authenticated Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eact access
Backend changes:
- Added #[IsGranted('ROLE_USER')] to SpaController::index()
- Firewall now intercepts unauthenticated requests to SPA routes
- Unauthenticated users are redirected to /login (HTTP 302)
Frontend changes:
- Updated App.tsx to create AppContent component
- Added useEffect that detects when me is undefined (not authenticated)
- Redirects to /login when user is not authenticated
- Provides defense-in-depth protection in case firewall is bypassed
- Uses useNavigate hook for client-side redirect
Testing verified:
- Unauthenticated requests to /dashboard → redirected to /login ✓
- Unauthenticated requests to /login → accessible (HTTP 200) ✓
- Frontend build includes updated authentication handling ✓
This ensures the React SPA is only served to authenticated users,
maintaining the session-based authentication design.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Issue: When not authenticated on port 8090 (Vite dev frontend), the app
would show 'Redirecting to login...' but nothing would happen.
Root Cause: React Router's useNavigate('/login') creates a relative URL
redirect to http://localhost:8090/login (current domain), but the login
form is only served by the backend at http://localhost:8080/login.
Solution:
- Use window.location.href instead of React Router navigation
- Extract backend base URL from BACKEND_API_URL config
- Calculate full redirect URL: http://localhost:8080/login
- Works in both development (port 8090) and production (port 8080)
Changes:
- Removed useNavigate hook (not needed for cross-domain redirects)
- Added BACKEND_API_URL import
- Changed to window.location.href for full page reload and redirect
- Simplified dependency array
Testing:
- Frontend build verified
- Redirect URL calculation verified (outputs correct login URL)
- Vite dev server responding with new build
This fixes the development workflow where users on port 8090 are now
properly redirected to the backend login form on port 8080.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t_path) Uses Symfony's official target path mechanism for post-login redirects: Backend changes: - Added target_path_parameter: _target_path to form_login in security.yaml - Added default_target_path fallback to /game/index - Updated AuthenticationSuccessHandler to check for target path first - Uses TargetPathTrait::getTargetPath() to retrieve session value - Falls back to role-based redirects if no target path Frontend changes: - Updated App.tsx to pass _target_path parameter when redirecting to login - Encodes current URL as the target path - Login flow: 8090 → /login?_target_path=http://localhost:8090 → 8090 Template changes: - Added _target_path hidden field to login form - Field is only rendered if _target_path query parameter is present - Value is passed along with form submission Flow: 1. User on http://localhost:8090 (React app) is not authenticated 2. App redirects to: /login?_target_path=http%3A%2F%2Flocalhost%3A8090%2F 3. Form stores _target_path as hidden field 4. User logs in 5. Symfony firewall reads _target_path from request 6. AuthenticationSuccessHandler retrieves it from session 7. User is redirected back to original URL (http://localhost:8090) Fallback behavior: - If no _target_path is provided (legacy login page access) - Admin users → /admin - Regular users → /game/index This implements the official Symfony pattern for post-login redirects, ensuring users are taken back to where they came from. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e URL Issue: Symfony's URL validator rejects absolute URLs like http://localhost:8090 as 'external' for security reasons. Solution: Use relative backend path /spa instead - Symfony accepts relative URLs - Works in development: frontend (8090) → backend (8080) → /spa - Works in production: same origin, both on same port - SpaController serves the React app at the /spa catch-all route Flow: 1. User on React app (8090) detects not authenticated 2. Redirects to: /login?_target_path=%2Fspa 3. Logs in successfully 4. AuthenticationSuccessHandler retrieves /spa from session 5. Redirects to http://localhost:8080/spa 6. SpaController serves React app (same as production behavior) This approach is cleaner because: - React app is served by backend SpaController (not by dev server) - Same code path in development and production - Symfony's built-in validation accepts relative URLs - No need for custom URL validation or open redirect risks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Issue: AuthenticationSuccessHandler was trying to read _target_path from session using TargetPathTrait, but Symfony's firewall form_login doesn't automatically save it there when using a custom success handler. Solution: Read _target_path directly from the POST request (where the form sends it) instead of looking in the session. This matches how Symfony's DefaultAuthenticationSuccessHandler works: 1. Check POST/query parameter first (_target_path from form) 2. Validate that it's a valid path (starts with /) or URL 3. If valid, redirect to it 4. Otherwise fall back to role-based redirects Changes: - Removed TargetPathTrait (no longer needed) - Added getTargetPathFromRequest() to check POST and query params - Added isValidPath() and isValidUrl() validators - Enhanced logging to show POST/query data for debugging - Form still sends _target_path as hidden field Flow: 1. Frontend redirects to: /login?_target_path=%2Fspa 2. Form displays with hidden field: <input name="_target_path" value="/spa"> 3. User submits form 4. Handler reads _target_path from POST data 5. Validates it's a valid path (starts with /) 6. Redirects to /spa ✓ This fixes the post-login redirect to return users to the React app instead of falling back to /game/index. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove temporary redirect to localhost:8090 from SpaController - Fix logout to redirect to backend /logout (not relative path) - Add logout redirect target in security.yaml to go to login - Cleanup unused environment parameter from SpaController The SpaController should simply serve the React app template for authenticated users. In development, the Vite dev server on 8090 fetches from this backend endpoint (/spa) for the app HTML. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add clear comments to frontend/index.html (dev entry point) - Add clear comments to backend/templates/spa/index.html.twig (prod entry point) - Update README.md architecture diagram to reflect current SPA setup - Update Services & Access Points table (remove legacy UI references) - Add SPA serving section to mkdocs/architecture (development vs production) - Simplify README by removing Observability layer diagram (focus on core) Documentation now clearly explains: - frontend/index.html: Used only by Vite dev server (8090) with HMR - backend/templates/spa/index.html.twig: Used only by SpaController (8080) - Each has distinct purpose and lifecycle Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Updated System Architecture diagram to show all three frontends - React UI (Modern SPA) on port 8090 (Vite dev) / 8080 (SpaController) - Legacy UI (TWIG/Game) on port 8080 - Login Form (shared TWIG) on port 8080 - Added backend details: Game Routes alongside SpaController - Updated Services & Access Points table - Added separate entries for Modern App (dev vs production) - Added Legacy App entry - Added descriptive notes Makes clear that: - Both modern (React) and legacy (TWIG) apps coexist - Both share the same backend and authentication - Legacy app routes remain functional and accessible Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The frontend is now built into the backend Docker image and served via SpaController. A separate Node.js frontend service is no longer needed. Changes: - Delete helm/app/templates/deploy-frontend.yaml - Delete helm/app/templates/svc-frontend.yaml - Remove frontend section from helm/app/values.yaml - Update NOTES.txt to reference backend port instead Impact: - Removes requirement for non-existent fullstack-symfony-react-web image - Eliminates separate frontend pod (50% pod reduction per deployment) - Simplifies Kubernetes networking (single backend service) - All features now accessible from single IP:port The backend image includes: - Compiled React app in public/dist/ - SpaController to serve React at /spa - All original backend functionality unchanged Helm chart now validates: helm lint helm/app/ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- SpaController now has explicit dev/prod logic: * Dev: Redirects to Vite dev server (http://localhost:5173) for HMR * Prod: Reads pre-built Vite index.html and injects env vars - Removed spa/index.html.twig template (no longer needed) - Vite dev server runs in docker-compose frontend service - Port 8090 externally, 5173 internally - No build step needed, HMR works immediately - Production flow still builds frontend, copies to backend/public/dist/ This eliminates the index.html confusion - there's only one source of truth: - Dev: Vite's dev server generates index.html with HMR client - Prod: Vite's build generates index.html with assets Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ture - Removed reference to deleted spa/index.html.twig template - Added clarification on production entry point behavior - SpaController now handles serving Vite output (not Twig template) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- SpaController now rewrites asset paths from /assets/ to /dist/assets/ (Vite generates /assets/ references but files are in /dist/assets/) - Nginx /dist/ location block bypasses PHP fallback (Serves static files directly with 404 if not found) - Asset MIME types now correct: application/javascript and text/css Now when SpaController serves index.html, asset requests return proper files instead of Symfony 404 errors with application/json MIME type. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Added /spa route (entry point) to SpaController - Added basename='/spa' to BrowserRouter in App.tsx React Router now correctly handles: - /spa → serves index.html, routes /spa/* to React Router - /spa/games → Browser at /spa/games, React Router sees /games - /spa/anything → Browser at /spa/anything, React Router sees /anything This allows all URLs under /spa/* to be handled by React Router on the client. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Changed default_target_path from /game/index to /spa - Added /spa to access_control rules - Users now land on modern React app after login instead of legacy app When users access /spa and get redirected to login, they'll now return to /spa after successful authentication, not /game/index. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Re-enable Vite dev server redirect in SpaController (HMR active in dev) - Make React Router basename dynamic (basename depends on URL path) - Simplify SpaController to single route with optional parameter - Fix route regex to allow React routes like /spa/games - CustomAuthenticationEntryPoint preserves target path on login redirect - LogoutEventListener preserves referrer to return users to correct app - Session cookies now work correctly across dev server redirects Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Update docker-compose to build dev stage directly (includes Composer) - Makes 'make qa' and other Composer commands work in docker-compose - Fix type hints in SpaController.serveBuildOutput() - Fix return type in AuthenticationSuccessHandler.getTargetPathFromRequest() - Uncomment HMR redirect with correct internal port (5173) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move chart contents from helm/fullstack/ to helm/ (root level).
This aligns with previous chart structure and simplifies deployment.
Version updates:
- Chart version: 1.0.0 → 0.9.3
- App version: 1.0.0 → 0.9.3
- App image tag: fullstack-symfony-react:1.0.0 → 0.9.3
Naming:
- Chart name: fullstack-symfony-react → fullstack
- Resources use {{ .Release.Name }} dynamically
Example: helm install myapp ./helm creates resources prefixed with 'myapp'
helm install production ./helm creates resources prefixed with 'production'
This matches the old app/telemetry chart behavior where release name drove resource names.
Updated:
- helm/Chart.yaml - version 0.9.3, name shortened
- helm/values.yaml - image tag 0.9.3
- helm/README.md - installation examples with custom release names
- Removed helm/fullstack/ directory
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update Kubernetes deployment section: - Replace separate app + telemetry helm install with single unified chart - Update helm install command to use ./helm with release name parameter - Clarify all services are included in single deployment - Update access points (port 30080 for web, 30300 for Grafana) - Reference helm/README.md for production deployment guide Also improve infrastructure services documentation: - Clarify Adminer UI for database access - Add OpenTelemetry Collector ports - Improve formatting and clarity Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Issues fixed: 1. Move initContainers to spec level (was incorrectly at container level) 2. Add wait-for-db init container (worker needs database access) 3. Keep wait-for-rabbitmq init container (already correct intent) 4. Remove misplaced restartPolicy (Deployment doesn't use this) 5. Add missing OTEL_EXPORTER_OTLP_ENDPOINT environment variable Worker now: - Waits for both DB and RabbitMQ before starting - Has all required environment variables for messaging - Follows same pattern as app deployment Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create comprehensive ConfigMap as single source of truth for application configuration. New cm-app.yaml structure: - Environment: APP_ENV, APP_VERSION - Database: DATABASE_URL (with connection params) - Cache: REDIS_URL - Messaging: MESSENGER_TRANSPORT_DSN - Secrets: APP_SECRET, ADMIN_EMAIL, ADMIN_PASSWORD - Telemetry: APP_TELEMETRY, OTEL_PHP_AUTOLOAD_ENABLED, OTEL_TRACES_EXPORTER, OTEL_EXPORTER_OTLP_ENDPOINT Updated deployments (app, worker, init job): - All use 'envFrom: configMapRef' to pull base config - Only add service-specific env vars (OTEL_SERVICE_NAME: app/worker) - Removed hardcoded values and duplication - Single source of truth for all shared configuration Benefits: ✓ DRY principle - no duplication across deployments ✓ Easy to update all services at once ✓ Clear separation between shared config and service-specific vars ✓ Matches docker-compose structure more closely Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update cm-app.yaml to construct connection strings from values:
- REDIS_URL: now uses {{ .Values.cache.port }} (was hardcoded 6379)
- MESSENGER_TRANSPORT_DSN: now constructs from rabbitmq config
Uses: RABBITMQ_DEFAULT_USER, RABBITMQ_DEFAULT_PASS, .Values.rabbitmq.port
Remove redundant hardcoded values from values.yaml:
- Removed app.env.MESSENGER_TRANSPORT_DSN (now computed in ConfigMap)
- Removed app.env.APP_ENV and APP_DEBUG (set by global.environment in ConfigMap)
Result: All connection strings are now fully derived from values.yaml
- Change one value, all services use the new configuration
- Single source of truth for all infrastructure settings
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
In svc-lgtm.yaml, nodePort was at spec level (invalid), causing: W0410 13:54:36.550380 77395 warnings.go:70] unknown field "spec.nodePort" According to Kubernetes Service API spec, nodePort must be defined per port, not at the spec level. Move nodePort to the Grafana port (first port in array). Result: Helm install now runs without warnings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
In base-alpine stage, extensions were compiled but runtime libraries were being removed with build-deps, causing PHP startup warnings: - amqp: Unable to load (missing librabbitmq.so.4) - intl: Unable to load (missing libicuio.so) - zip: Unable to load (missing libzip.so) Fix: Install runtime dependencies permanently: - icu-libs (for intl extension) - libzip (for zip extension) - rabbitmq-c (for amqp extension, keeping .so files) These are kept in the final image while build-deps are still removed. Result: All PHP extensions now load successfully without warnings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Change from IfNotPresent to Always so Kubernetes picks up updated images when developing locally with Docker Desktop or similar environments. This ensures pod restarts will fetch the latest image rather than using cached images. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Change livenessProbe and readinessProbe from httpGet (which fails with 404 on /) to tcpSocket (checks if port 80 is listening). NGINX is a reverse proxy, not a static web server, so it returns 404 for / unless the backend app is working. TCP probe is more appropriate. Also update imagePullPolicy to Always for consistent image updates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add '|| true' to doctrine:migrations:migrate command so it doesn't fail when there are no migration files to run. This is expected in early-stage projects and shouldn't cause the job to fail. Also update imagePullPolicy to Always for consistency. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move NGINX from separate pod to sidecar container in app deployment. Both containers share emptyDir volume at /var/www/project so NGINX can: - Access static files from /dist/ and /assets/ - Proxy FastCGI requests to app:9000 on localhost Update svc-web to select app pods instead of web pods. Remove standalone deploy-web.yaml as web is now integrated into app pod. This matches the original architecture where NGINX and PHP run together, sharing the same filesystem and volume mounts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
In Kubernetes with emptyDir volumes, files from the Docker image are not automatically available to other containers. Add a copy-app-files init container that copies all files from the app image to the shared volume before NGINX and app containers start. This ensures NGINX can access the project files for serving static assets. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use 'cp -r /var/www/project/. /app-files/' instead of separate glob patterns. This properly copies all files including dotfiles like .env, .gitignore, etc. The previous command failed to copy .env causing Symfony to fail at startup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add full database initialization steps: 1. doctrine:database:create --if-not-exists (create DB if missing) 2. doctrine:schema:update --force (create/update schema) 3. doctrine:migrations:migrate (run any migrations) Previously the job only ran migrations, but migrations are empty in early development. Now the schema is properly created from the entity definitions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
In the sidecar architecture where NGINX and app containers are in the same pod, they communicate via localhost:9000 instead of the service name 'app:9000'. This fixes hanging requests where NGINX was trying to reach the service endpoint instead of the co-located app container. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The app service is no longer needed now that NGINX and PHP-FPM run as sidecars in the same pod. They communicate via localhost:9000, not through a Kubernetes service. The service was only used for external NGINX pods to reach the app container. Other pods (worker, init job) that need database/cache access still use their respective services (db, cache, rabbitmq). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rename service from 'app-fullstack-web' to 'app-fullstack-app' to match the deployment name. This provides consistent naming throughout: the deployment is 'app-fullstack-app' and the service is also 'app-fullstack-app'. The 'web' name was used when NGINX was a separate deployment. Now that it's integrated as a sidecar, the naming should match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Set OTEL_COLLECTOR_ADDRESS environment variable during Vite build to inject the LGTM collector endpoint into the built index.html. The React app will use http://localhost:30571/v1/logs for submitting traces to the LGTM service via its NodePort. This allows browser-based tracing when accessing the app via port-forward or NodePort. The collector address is baked into the built app and accessible to the browser when accessing the application. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove 'fullstack-' prefix from all resource names for cleaner, more minimal naming. Updated resources: - Deployments: app, worker (instead of fullstack-app, fullstack-worker) - Pods: db, cache, lgtm, rabbitmq (instead of fullstack-*) - ConfigMaps: app-config, nginx-config (instead of fullstack-app-config, fullstack-nginx-config) - Jobs: init (instead of fullstack-init) - Services: already using minimal names Changes include: - Updated _helpers.tpl to simplify fullname definition - Updated all templates to use component names directly - Verified with 'helm lint' - all checks pass Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Change service types from NodePort to ClusterIP since we now use Ingress controller for external access instead of direct NodePort exposure. Changes: - svc-app.yaml: Changed to ClusterIP, removed nodePort - svc-lgtm.yaml: Changed to ClusterIP, removed nodePort fields - values.yaml: Removed nginx.nodePort and observability.nodePorts config Services are now internal-only (ClusterIP). External access is provided by the Ingress controller via ingress-nginx on port 80. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Improved naming convention for template helper functions to be more generic and comprehensive. The prefix 'chart.' better describes what these helpers are (chart-level template utilities) rather than 'fullstack.' which is ambiguous (application name vs repo name). Changes: - fullstack.name → chart.name - fullstack.fullname → chart.fullname - fullstack.chart → chart.version - fullstack.labels → chart.labels - fullstack.selectorLabels → chart.selectorLabels Updated all templates and _helpers.tpl file accordingly. Chart lints successfully with no errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #6 +/- ##
============================================
- Coverage 42.97% 40.10% -2.88%
- Complexity 386 417 +31
============================================
Files 90 93 +3
Lines 1061 1137 +76
============================================
Hits 456 456
- Misses 605 681 +76 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.