Security & Authentication Architecture
The Planovi Backend enforces a zero-trust, layered security architecture. All 35 Supabase Edge Functions implement strict origin checking, granular role-based authorization, and isolated secret management.
Authentication Modes
Edge Functions operate in two primary authorization contexts:
flowchart TD
Req["Incoming Client Request"] --> OriginCheck{"Preflight / CORS"}
OriginCheck -->|"OPTIONS"| Resp200["200 OK + CORS Headers"]
OriginCheck -->|"GET / POST"| AuthHeaderCheck{"Authorization Header Present?"}
AuthHeaderCheck -->|"No"| PublicCheck{"Is Endpoint Public?"}
PublicCheck -->|"Yes (e.g. Webhook)"| VerifySecret["Verify Webhook HMAC / Shared Secret"]
PublicCheck -->|"No"| Err401["401 Unauthorized"]
AuthHeaderCheck -->|"Bearer JWT"| TokenValidation{"supabase.auth.getUser"}
TokenValidation -->|"Valid User Token"| UserContext["User Context Execution: RLS Enforced"]
TokenValidation -->|"Service Role Token"| ServiceContext["Elevated Administrative Execution"]
TokenValidation -->|"Invalid Token"| FallbackExtract["Fallback JWT Claims Parser / 401"]
1. User Context (RLS Enforced)
Used by functions invoked directly by the Planovi Flutter App on behalf of an authenticated member:
- The client passes the standard
Authorization: Bearer <user_access_token>. - The function initializes a scoped Supabase client using the caller’s JWT:
const supabase = createClient(Deno.env.get('SUPABASE_URL')!,Deno.env.get('SUPABASE_ANON_KEY')!,{ global: { headers: { Authorization: req.headers.get('Authorization')! } } });
- Database queries executed by this client inherit the user’s PostgreSQL Row-Level Security (RLS) policies.
2. Service Role (Elevated Administration)
Used by automated background jobs, telemetry ingestion, cron runners, and administrative actions (e.g., jarvis, billing-calculator, ingest-telemetry):
- Initialized via
SUPABASE_SERVICE_ROLE_KEY. - Completely bypasses PostgreSQL RLS to execute cross-tenant aggregations, bulk billing runs, and hardware state updates.
CORS Middleware Configuration
All public-facing Edge Functions declare standard cross-origin headers to support both Flutter Web and Native clients:
export const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE',};
// Standard OPTIONS preflight handlerif (req.method === 'OPTIONS') { return new Response('ok', { headers: corsHeaders });}High-Performance Deno KV Token Caching
To communicate with third-party IoT hardware clouds (such as the InCharge converter cloud) without adding 200ms–500ms network roundtrips on every telemetry cycle, functions implement an edge-cached token strategy via Deno KV:
sequenceDiagram
autonumber
participant EF as "Edge Function (ingest-telemetry)"
participant KV as Deno KV Store
participant InCharge as InCharge Cloud API
EF->>KV: kv.get(["incharge", "token"])
alt Token Exists and (Expiry - Now) > 5 Minutes
KV-->>EF: Return cached token immediately (~1ms)
else Token Missing or Expiring Soon
KV-->>EF: Null or Expired
EF->>InCharge: POST /api/v1/auth/login { username, password }
InCharge-->>EF: { token: "...", expiresIn: 86400 }
EF->>KV: kv.set(["incharge", "token"], token, { expireIn: 86400 * 1000 })
EF->>KV: kv.set(["incharge", "expiry"], Date.now() + 86400 * 1000)
end
Key Technical Properties:
- Storage Key:
["incharge", "token"]and["incharge", "expiry"]. - Refresh Buffer:
5 * 60 * 1000(5 minutes). Re-authenticates proactively before token expiration. - Fallback Safety: Gracefully degrades in local environments if Deno KV is unconfigured, falling back to direct sign-in.
Secrets & Environment Isolation
Environment variables are never committed to source control and are securely injected into the Deno runtime environment:
| Variable Name | Classification | Usage |
|---|---|---|
SUPABASE_URL | Internal | Base API URL of the Supabase instance. |
SUPABASE_ANON_KEY | Public-safe | Anonymous key for client-scoped operations. |
SUPABASE_SERVICE_ROLE_KEY | Strict Secret | Administrative master key for RLS bypass. |
GEMINI_API_KEY | Strict Secret | Google Gemini API key for OCR and vision models. |
INCHARGE_USERNAME | Strict Secret | InCharge IoT converter integration credentials. |
INCHARGE_PASSWORD | Strict Secret | InCharge IoT converter integration credentials. |
SMTP_HOST / SMTP_KEY | Strict Secret | Outbound notification mail delivery credentials. |
Error Handling & HTTP Status Standards
| HTTP Status | Trigger Condition | Standard Payload Response |
|---|---|---|
200 OK | Successful execution | { "success": true, "data": { ... } } |
400 Bad Request | Missing required parameters or schema validation failure | { "error": "Missing required field: coop_id" } |
401 Unauthorized | Missing, invalid, or expired JWT bearer token | { "error": "Invalid or expired session token" } |
403 Forbidden | Caller lacks necessary role permissions | { "error": "Insufficient privileges for this action" } |
404 Not Found | Requested entity or declaration schema does not exist | { "error": "Record not found" } |
500 Server Error | Unhandled runtime exception or upstream API failure | { "error": "Internal server error: [Sanitized Message]" } |