Skip to content

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 handler
if (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 NameClassificationUsage
SUPABASE_URLInternalBase API URL of the Supabase instance.
SUPABASE_ANON_KEYPublic-safeAnonymous key for client-scoped operations.
SUPABASE_SERVICE_ROLE_KEYStrict SecretAdministrative master key for RLS bypass.
GEMINI_API_KEYStrict SecretGoogle Gemini API key for OCR and vision models.
INCHARGE_USERNAMEStrict SecretInCharge IoT converter integration credentials.
INCHARGE_PASSWORDStrict SecretInCharge IoT converter integration credentials.
SMTP_HOST / SMTP_KEYStrict SecretOutbound notification mail delivery credentials.

Error Handling & HTTP Status Standards

HTTP StatusTrigger ConditionStandard Payload Response
200 OKSuccessful execution{ "success": true, "data": { ... } }
400 Bad RequestMissing required parameters or schema validation failure{ "error": "Missing required field: coop_id" }
401 UnauthorizedMissing, invalid, or expired JWT bearer token{ "error": "Invalid or expired session token" }
403 ForbiddenCaller lacks necessary role permissions{ "error": "Insufficient privileges for this action" }
404 Not FoundRequested entity or declaration schema does not exist{ "error": "Record not found" }
500 Server ErrorUnhandled runtime exception or upstream API failure{ "error": "Internal server error: [Sanitized Message]" }