# Codebase Concerns **Analysis Date:** 2026-05-20 ## Security Concerns ### CRITICAL: Firebase Service Account Key Committed to Storage - **Risk:** The file `src/storage/app/firebase/sides-b4abb-3604a7cf7584.json` is a Firebase service account credential file present in the repository. It contains `project_id: "sides-b4abb"` and full private key data. `src/.gitignore` does not exclude `storage/app/firebase/`. - **Files:** `src/storage/app/firebase/sides-b4abb-3604a7cf7584.json`, `src/app/Services/FcmService.php` (lines 17-19: reads this file at runtime via `env('FIREBASE_CREDENTIALS')`) - **Impact:** Anyone with repo access has full Firebase Admin SDK privileges (send notifications, access Firestore, etc.) - **Fix:** Move credentials to environment-only storage, add `storage/app/firebase/*.json` to `.gitignore`, rotate the exposed key. ### CRITICAL: SQL Injection via String Interpolation in Raw Queries - **Risk:** Multiple controllers build raw SQL queries by directly interpolating user input into SQL strings without parameterized bindings. - **Files:** - `src/app/Http/Controllers/RainfallController.php` line 46: `$stationCondition = " AND s.stationid = '{$stationFilter}'"` — `$stationFilter` comes directly from `$request->get('station')` and is concatenated into `DB::select()` on line 55. - `src/app/Http/Controllers/RainfallController.php` lines 85-103: `$dateFilter` and `$displayDate` interpolated into CAST expressions. - `src/app/Http/Controllers/WaterLevelController.php` line 30: `$stationCondition = " WHERE s.stationid = '{$stationFilter}' "` — same pattern. - `src/app/Http/Controllers/WaterLevelController.php` line 37: `$dateCondition = " AND w.datetime = '{$sqlDate}' "`. - **Impact:** An attacker can inject arbitrary SQL through the `station` or `date` query parameters, potentially extracting or modifying all data. - **Fix:** Replace all `DB::select()` with parameterized queries using `?` placeholders or named bindings, or use Eloquent/Query Builder with proper `->where()` calls. ### CRITICAL: Hardcoded Credentials in Autoscript - **Risk:** `autoscript/sidesdecode.py` contains hardcoded FTP and PostgreSQL credentials in plain text. - **Files:** `autoscript/sidesdecode.py` lines 22-31: - FTP: `ftp_username = "tck"`, `ftp_password = "tck6789"`, server `myvscada.com` - PostgreSQL: `pg_host = "192.168.0.211"`, `pg_user = "tck"`, `pg_password = "projectdev##1"` - **Impact:** Credentials are exposed in source control. Internal IP address leaks network topology. - **Fix:** Move all credentials to environment variables or a secrets manager. Add `autoscript/` to `.gitignore` or restructure to load config from `.env`. ### HIGH: API Routes Have No Authentication or Rate Limiting - **Risk:** All API routes in `src/routes/api.php` are completely unauthenticated. The API endpoints expose real-time station data, historical readings, notification history, and siren status without any auth check. - **Files:** `src/routes/api.php` lines 10-20 — no `middleware('auth')` or `middleware('throttle')` on any route. `src/bootstrap/app.php` line 11 — API routes registered without any middleware group. - **Impact:** Anyone can query all station data, and the `/api/alert` endpoint can be abused to send fraudulent push notifications to all users. - **Fix:** Add `Route::middleware(['auth:sanctum'])` to the API route group. At minimum, protect `/api/alert` with authentication. ### HIGH: API Login Endpoint Returns User Details Without Token - **Risk:** `Api/AuthController.php` `login()` validates credentials then returns user `id`, `name`, `email`, and `access_level` in a plain JSON response without issuing any authentication token or session. - **Files:** `src/app/Http/Controllers/Api/AuthController.php` lines 49-55 - **Impact:** The login endpoint leaks user info but provides no mechanism for subsequent authenticated requests. No token-based auth system exists for the API. - **Fix:** Implement Laravel Sanctum or Passport to issue API tokens on login. Remove password from the SELECT query (line 24). ### HIGH: Hardcoded Default Admin Password in Migration - **Risk:** Migration `2025_12_11_124201_add_default_user_to_users_table.php` seeds an admin user with email `admin@example.com` and password `password123`. - **Files:** `src/database/migrations/2025_12_11_124201_add_default_user_to_users_table.php` line 12-13 - **Impact:** If this migration runs in production, a trivially guessable admin account is created. There is no mechanism to force a password change. - **Fix:** Use database seeders instead of migrations for seed data. Generate a random password from env vars. Add forced password change on first login. ### MEDIUM: Admin Middleware Uses Loosely-Typed Access Level Check - **Risk:** `AdminMiddleware` checks `Auth::user()->access_level !== 1` using strict comparison but the database column is `integer` and the middleware does not verify the user model loaded correctly. - **Files:** `src/app/Http/Middleware/AdminMiddleware.php` line 26 - **Impact:** The access_level field is not constrained by any foreign key or enum — any integer value is accepted in `AdminController::storeUser()`. - **Fix:** Use a proper role/permission system (e.g., `spatie/laravel-permission`). At minimum, validate `access_level` is in `[1, 2]` range. ### MEDIUM: No Foreign Key Constraints on Data Tables - **Risk:** The `rainfall`, `waterlevel`, `notification`, and `siren` tables all have `stationid` columns but no foreign key constraints to `station.stationid`. Data integrity cannot be enforced at the database level. - **Files:** All migrations in `src/database/migrations/2025_11_*` - **Impact:** Orphaned records possible if stations are deleted. `AdminController::deleteStation()` (line 405) deletes a station without cleaning related data. - **Fix:** Add foreign key constraints with cascade delete to all child tables referencing `station.stationid`. ## Technical Debt ### No Eloquent Models for Domain Entities - **Issue:** The entire application uses `DB::table()` and raw `DB::select()` instead of Eloquent ORM. The only Eloquent model is `User`. Station, Rainfall, WaterLevel, Notification, and Siren have no models. - **Files:** All controllers in `src/app/Http/Controllers/` - **Impact:** No model-level validation, no relationships, no accessors/mutators, no model events. Code is harder to test and maintain. - **Fix:** Create Eloquent models for `Station`, `Rainfall`, `WaterLevel`, `Notification`, `Siren`. Define relationships and refactor controllers. ### Dashboard Query Duplicated Across Three Controllers - **Issue:** The same complex 4-table LEFT JOIN query (station + rainfall + waterlevel + siren) is copy-pasted in `MapController::getCurrentData()`, `AuthenticatedSessionController::create()`, and `Api\StationController::getCurrentData()`. - **Files:** - `src/app/Http/Controllers/MapController.php` lines 35-63 - `src/app/Http/Controllers/Auth/AuthenticatedSessionController.php` lines 28-56 - `src/app/Http/Controllers/Api/StationController.php` lines 13-43 - **Impact:** Any query change must be made in three places. Risk of divergence. - **Fix:** Extract to a shared repository/service class or a dedicated model scope. ### `AdminController` is a 427-line God Controller - **Issue:** `AdminController` handles both station management and user management — two distinct domains. Contains 8 methods for CRUD on 2 different entities. - **Files:** `src/app/Http/Controllers/AdminController.php` (427 lines) - **Impact:** Hard to maintain. Violates Single Responsibility Principle. - **Fix:** Split into `Admin\StationController` and `Admin\UserController`. ### `.env.example` Mismatch with Actual Configuration - **Issue:** `src/.env.example` is the default Laravel template with `DB_CONNECTION=sqlite`, but the application uses PostgreSQL via Docker. The Firebase env vars (`FIREBASE_PROJECT_ID`, `FIREBASE_CREDENTIALS`, `FCM_TOPIC_RAINFALL_WARNING`) are not documented in any `.env.example`. - **Files:** `src/.env.example` (65 lines, default Laravel template) - **Impact:** New developers cannot set up the project without reverse-engineering the code. - **Fix:** Update `.env.example` with actual PostgreSQL connection details and all custom env vars. ### Commented-Out Code Blocks - **Issue:** Several files contain large blocks of commented-out code suggesting incomplete or abandoned features. - **Files:** - `src/app/Http/Controllers/RainfallController.php` lines 250-263: commented-out alternative graph query - `autoscript/sidesdecode.py` lines 153-158, 489, 522-533: commented-out error handling and test functions - `src/routes/web.php` line 30: commented-out threshold graph route - `src/app/Services/FcmService.php` lines 27-30: commented-out alternative auth method - **Fix:** Remove dead code. Use version control for history. ## Performance Concerns ### N+1 Subqueries in Station Data Queries - **Issue:** The dashboard query uses correlated subqueries `(SELECT MAX(timestamp) FROM rainfall WHERE stationid = s.stationid)` for each of the 3 LEFT JOINs. For N stations, this results in ~3N additional subquery evaluations. - **Files:** - `src/app/Http/Controllers/MapController.php` lines 37-46 - `src/app/Http/Controllers/Api/StationController.php` lines 14-24 - `src/app/Http/Controllers/Auth/AuthenticatedSessionController.php` lines 30-39 - **Impact:** Query performance degrades linearly with station count. - **Fix:** Use window functions (`ROW_NUMBER() OVER PARTITION BY`) or pre-computed materialized views for latest readings. ### No Database Indexes on Query-Critical Columns - **Issue:** None of the migrations create indexes on columns used in WHERE, JOIN, or ORDER BY clauses. - **Missing indexes:** - `rainfall.stationid` and `rainfall.timestamp` — used in every rainfall query - `waterlevel.stationid` and `waterlevel.datetime` — used in every waterlevel query - `notification.stationid`, `notification.timestamp`, `notification.stationtype` — used in all notification queries - `siren.stationid` and `siren.active_time` — used in all siren queries - **Files:** `src/database/migrations/2025_11_06_*` and `2025_11_07_*` - **Impact:** Full table scans on every query. Performance will degrade significantly as data accumulates. - **Fix:** Add index migrations for all foreign key columns and frequently queried timestamp columns. ### No Caching for Dashboard/Map Data - **Issue:** The dashboard query (the heaviest query in the app) runs on every page load with no caching. The same data powers the home page, login page, and dashboard. - **Files:** `src/app/Http/Controllers/MapController.php`, `src/app/Http/Controllers/Auth/AuthenticatedSessionController.php` - **Impact:** Database hit on every request for data that changes at most every few minutes. - **Fix:** Cache dashboard data for 1-5 minutes using `Cache::remember()`. ### Heavy PDF Export Queries Without Pagination - **Issue:** PDF export endpoints (`exportHistoryRfPDF`, `exportHistoryWlPDF`, `exportHistorySirenPDF`) load entire unbounded result sets with `->get()`. - **Files:** - `src/app/Http/Controllers/NotificationController.php` lines 122-126, 143-147 - `src/app/Http/Controllers/SirenController.php` lines 67-74 - **Impact:** Memory exhaustion with large datasets. No date filtering or row limits. - **Fix:** Add date range filters and chunk processing for PDF generation. ## Maintainability Concerns ### Inconsistent Naming Conventions - **Issue:** Controller names mix PascalCase and camelCase. The `cctvController` uses lowercase class name violating PSR-1/PSR-12. - **Files:** - `src/app/Http/Controllers/cctvController.php` — class `cctvController` (should be `CctvController`) - `src/app/Http/Controllers/SirenController.php` method `SirenHistory()` — PascalCase method (should be `sirenHistory`) - `src/app/Http/Controllers/NotificationController.php` method `SirenNotification()` — same issue - **Fix:** Rename to follow PSR standards: `CctvController`, camelCase methods. ### Typo: "potrait" Instead of "portrait" in PDF Generation - **Issue:** Three PDF export methods pass `'potrait'` as the paper orientation. While DomPDF may not error, this is an invalid value that likely falls back to default. - **Files:** - `src/app/Http/Controllers/SirenController.php` line 77 - `src/app/Http/Controllers/NotificationController.php` lines 129, 150 - **Fix:** Change `'potrait'` to `'portrait'`. ### No Test Coverage for Custom Application Logic - **Issue:** Test files only contain Breeze defaults (auth tests). Zero tests cover custom controllers, API endpoints, or the data processing script. - **Files:** `src/tests/` — only default Laravel/Breeze tests exist - **Impact:** Any refactoring or fix risks introducing regressions with no safety net. - **Fix:** Add feature tests for all API endpoints, admin CRUD operations, and PDF export endpoints. ## Dependency Concerns ### `google/auth` Used Directly Instead of Official Firebase SDK - **Issue:** The project uses `google/auth` package directly with manual HTTP calls to FCM instead of the official `kreait/laravel-firebase` SDK. - **Files:** `src/app/Services/FcmService.php`, `src/composer.json` line 12: `"google/auth": "^1.49"` - **Impact:** More boilerplate, manual token management, no automatic retry or error handling. - **Fix:** Replace with `kreait/laravel-firebase` for cleaner integration. ### Docker Image Not Pinned, `composer:2.3` Outdated - **Issue:** `Dockerfile` copies Composer from `composer:2.3` image (line 62) which is significantly behind current stable. `postgres` image in `docker-compose.yml` has no version tag (line 26: `image: postgres`). - **Files:** `Dockerfile` line 62, `docker-compose.yml` line 26 - **Impact:** Unpredictable builds. Breaking changes when pulling latest postgres image. - **Fix:** Pin Composer to `composer:2` or latest. Pin Postgres to a specific major version (e.g., `postgres:16-alpine`). ## Infrastructure Concerns ### PostgreSQL Data Exposed on Port 5432 - **Issue:** `docker-compose.yml` maps Postgres port 5432 directly to the host. Both pgAdmin (5050) and Adminer (6060) are also exposed in what appears to be a production configuration. - **Files:** `docker-compose.yml` lines 34-35, 61, 74 - **Impact:** Database and management tools accessible from the network without additional protection. - **Fix:** Remove port mappings for production. Use Docker networks only. Adminer should not be in production compose. ### No Health Checks in Docker Services - **Issue:** None of the Docker services define `healthcheck` blocks. The `app` service uses `depends_on: postgres` without a health condition, so it may start before Postgres is ready. - **Files:** `docker-compose.yml` - **Fix:** Add health checks for `postgres` and configure `app` with `depends_on.postgres.condition: service_healthy`. ### `src/` Directory Volume-Mounted but Also COPY'd in Dockerfile - **Issue:** `Dockerfile` COPYs `src/` into the image (lines 72-75), but `docker-compose.yml` also mounts `./src:/var/www/html` as a volume (line 16). The COPY is wasted and creates confusion. - **Files:** `Dockerfile` lines 72-75, `docker-compose.yml` line 16 - **Fix:** Remove COPY from Dockerfile for development (volume mount handles it). Create a separate production Dockerfile that COPYs code. ## Observability ### No Structured Logging - **Issue:** The application uses Laravel's default file logging (`LOG_CHANNEL=stack`, `LOG_STACK=single`). No centralized logging, no log levels differentiated by environment. - **Files:** `src/.env.example` lines 18-21 - **Impact:** Logs are local to the container and lost on restart. No way to search or alert on errors. - **Fix:** Configure `LOG_CHANNEL=stderr` for Docker, or integrate with a log aggregation service. ### No Error Tracking Service - **Issue:** No error tracking integration (Sentry, Bugsnag, etc.) detected. Exceptions are caught locally and returned as flash messages in some controllers but silently swallowed in others. - **Files:** `src/app/Http/Controllers/AdminController.php` lines 216-218: `$e->getMessage()` returned to user. - **Impact:** Production errors invisible to developers. Raw exception messages may leak internal details to users. - **Fix:** Integrate an error tracking service. Never return raw `$e->getMessage()` to end users. ### No Monitoring or Alerting - **Issue:** No monitoring tools, uptime checks, or alerting configuration detected. The application has a `/up` health endpoint (from Laravel 12) but nothing monitors it. - **Impact:** Downtime or degraded performance goes undetected until users report it. - **Fix:** Set up uptime monitoring for the `/up` endpoint. Add APM for performance tracking. --- *Concerns audit: 2026-05-20*