Security, Authentication & Data Protection
The Planovi Microservices implement defense-in-depth security layers to ensure API authorization, secure client-to-service communication, isolated tokenized access, and resilience against replay and caching attacks.
1. Authentication & Authorization Patterns
The microservices utilize three distinct authorization schemes depending on the caller identity and operational context:
flowchart TD
Req["Incoming HTTP Request"] --> RouteCheck{"Request Destination & Headers"}
RouteCheck -->|"API Request
(e.g., /api/create_offer.php)"| APISecret{"X-Secret-Key Header matches
DEALFLOW_API_SECRET?"}
APISecret -->|Valid| ExecuteAPI["Allow API Execution (200 / 201)"]
APISecret -->|"Invalid / Missing"| Reject401["Reject with 401 Unauthorized"]
RouteCheck -->|"Public Scheduler / Offer
(e.g., offer.php?id=XXX)"| IDSanitize["Sanitize ID Parameter
preg_replace('/[^a-zA-Z0-9_-]/')"]
IDSanitize --> CheckFile{"data/{id}.json exists?"}
CheckFile -->|Yes| RenderView["Render Dynamic Offer / Calendar View"]
CheckFile -->|No| NotFound["Display Safe Not Found View"]
RouteCheck -->|"Tokenized Onboarding
(e.g., offer.php?token=XXX)"| CleanToken["Sanitize Token
preg_replace('/[^a-f0-9]/')"]
CleanToken --> ExpiryCheck{"Token length >= 32 &
expires_at > now() (72h)?"}
ExpiryCheck -->|Active| RenderOnboarding["Render Member Onboarding Form"]
ExpiryCheck -->|Expired| TokenExpiredErr["Display Token Expired Notice"]
2. API Secret Key Validation (X-Secret-Key)
Internal endpoints like dealflow/api/create_offer.php and dealflow/api/save_dealflow.php are invoked programmatically by the Planovi Flutter App and Supabase Edge Functions. They require an HTTP request header:
POST /api/create_offer.php HTTP/1.1Host: dev-dealflow.planovi.appContent-Type: application/jsonX-Secret-Key: <DEALFLOW_API_SECRET>Verification Implementation
The endpoint safely extracts headers across various server environments (Nginx FastCGI, Apache, PHP-FPM) and verifies against the environment variable DEALFLOW_API_SECRET:
function getRequestHeaders() { if (function_exists('getallheaders')) return getallheaders(); $headers = []; foreach ($_SERVER as $key => $value) { if (substr($key, 0, 5) == 'HTTP_') { $header = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5))))); $headers[$header] = $value; } } return $headers;}
$headers = getRequestHeaders();$providedKey = $headers['X-Secret-Key'] ?? ($headers['x-secret-key'] ?? ($_SERVER['HTTP_X_SECRET_KEY'] ?? ''));
if ($providedKey !== SECRET_KEY || empty(SECRET_KEY)) { http_response_code(401); echo json_encode([ 'status' => 'error', 'error' => 'Unauthorized. Invalid or missing secret key in X-Secret-Key header.' ]); exit;}3. Google OAuth 2.0 Integration & Token Management
The Scheduler integrates with the Google Calendar API (v3) to synchronize booked slots with user Google Calendars.
sequenceDiagram
autonumber
actor Admin as Cooperative Admin
participant App as "Scheduler UI (auth_start.php)"
participant Google as Google Identity Platform
participant Callback as "Scheduler API (auth_callback.php)"
participant DataStore as "JSON Data Store (data/*.json)"
participant Booking as "Public Customer (api.php)"
Admin->>App: Click "Connect Google Calendar"
App->>Google: Redirect to OAuth Consent Screen (access_type=offline, prompt=consent)
Google->>Admin: Request Calendar Scope Permission
Admin->>Google: Grant Approval
Google->>Callback: Redirect with Authorization Code (?code=...)
Callback->>Google: Exchange Code for Access & Refresh Token
Google-->>Callback: Return Tokens (access_token, refresh_token, expires_in)
Callback->>DataStore: Persist Refresh Token into data/{id}.json
Note over Booking,Google: Subsequent Automated Bookings by Clients
Booking->>DataStore: Load data/{id}.json
Booking->>Google: fetchAccessTokenWithRefreshToken(refresh_token)
Google-->>Booking: Fresh Access Token
Booking->>Google: Insert Event (calendarId=primary)
Offline Access & Refresh Token Guarantees
In scheduler/backend/config_google.php, offline access is strictly configured:
setAccessType('offline'): Instructs Google to issue a long-lived Refresh Token.setPrompt('select_account consent'): Forces prompt on consent to ensure Google re-issues the refresh token even if the account was previously authorized.- Scopes:
Google_Service_Calendar::CALENDARandemail.
4. Anti-Caching & Data Freshness Controls
Because deal configurations, offers, and calendar appointments represent real-time commercial and scheduling data, aggressive anti-caching HTTP response headers are injected into all endpoints:
// Anti-Cache Enforcement across PHP endpointsheader("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");header("Cache-Control: post-check=0, pre-check=0", false);header("Pragma: no-cache");header("Expires: 0");This prevents edge caches (Cloudflare, Nginx micro-caches, Hostinger proxy layers, and browser memory caches) from serving stale appointment slots or outdated offer figures.
5. Input Sanitization & Path Traversal Prevention
When loading data records based on user query parameters (such as ?id=XYZ or ?token=XYZ), values are strictly sanitized to block directory traversal (../) and injection attacks:
// Prevent Path Traversal in Scheduler & Dealflow$cal_id = $_GET['id'] ?? 'default';$cal_id = preg_replace('/[^a-zA-Z0-9_-]/', '', $cal_id);$file_path = __DIR__ . "/../data/{$cal_id}.json";
// Token Mode: Only allow lowercase hex characters$rawToken = $_GET['token'] ?? '';$cleanToken = preg_replace('/[^a-f0-9]/', '', strtolower($rawToken));$tokenFile = __DIR__ . '/data/' . $cleanToken . '.json';Any characters outside alphanumeric and hyphens/underscores are stripped before file system resolution.