Search by

tropikal-ai / connect-filament

tropikal-ai

TROPIKAL Connect integration for Laravel Filament.

Package info

github.com/tropikal-ai/connect-filament

pkg:composer/tropikal-ai/connect-filament

Statistics

Installs: 1 013

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 6

v0.2.17 2026-10-03 21:57 UTC

README

Laravel + Filament integration for TROPIKAL Connect.

tropikal-ai/connect-filament provides the Filament plugin, OAuth setup controller, encrypted installation model, Eloquent business-object discovery, explicit read/write/delete grants, signed resource API, audit logging, and optional public embed proxy endpoints. Protocol primitives live in tropikal-ai/connect.

Requirements

  • PHP 8.2 or newer
  • Laravel 11.28, 12, or 13
  • Filament 5
  • Composer 2

Install

composer require tropikal-ai/connect-filament
php artisan connect-filament:install
php artisan migrate

For clone-based development inside an app, add both local packages as path repositories:

{
    "repositories": [
        {
            "type": "path",
            "url": "shared/connect",
            "options": {
                "symlink": true,
                "versions": {
                    "tropikal-ai/connect": "0.1.0"
                }
            }
        },
        {
            "type": "path",
            "url": "shared/connect-filament",
            "options": {
                "symlink": true,
                "versions": {
                    "tropikal-ai/connect-filament": "0.2.0"
                }
            }
        }
    ],
    "require": {
        "tropikal-ai/connect-filament": "^0.2"
    },
    "minimum-stability": "dev",
    "prefer-stable": true
}

Filament Plugin

Register the plugin in your Filament panel provider:

use Filament\Panel;
use TropikalAI\ConnectFilament\ConnectFilamentPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->plugin(ConnectFilamentPlugin::make());
}

OAuth One-Click Setup

Configure endpoints for the environment. Public examples use placeholder hosts:

APP_URL=https://cms.example.com
CONNECT_FILAMENT_AUTHORIZATION_SERVER_URL=https://auth.example.com
CONNECT_FILAMENT_CONTROL_PLANE_URL=https://control.example.com

Open the Filament navigation item labeled TROPIKAL Connect and click Connect. The package starts OAuth authorization code with PKCE, validates callback state and exact redirect URI, exchanges the code server-side, stores credentials encrypted, and registers the installation with a safe payload.

OAuth PKCE is the only supported setup path. Administrators never paste connection ids, raw tokens, signing credentials, endpoint credentials, or other secrets into the UI. There is no token-paste setup path, no copied-secret setup path, and no browser-visible credential path.

Business Object Discovery

Discovery is enabled by default, but access is opt-in. Empty grants expose nothing.

The package discovers safe Eloquent business-object candidates, excludes framework/internal/auth/security models, removes secret-shaped fields, and shows three explicit grants in Filament:

  • Read
  • Write
  • Delete

Read grants create list/get capabilities. Write grants create create/update capabilities. Delete grants create a destructive delete capability that requires confirmation. List capabilities support page, per_page, limit, search, and exact filters for safe readable scalar fields such as slug or category.

Optional discovery configuration:

'discovery' => [
    'enabled' => true,
    'included_model_namespaces' => [
        'App\\Models\\',
    ],
    'excluded_model_classes' => [],
    'max_records_per_list_response' => 100,
],

After connecting, open TROPIKAL Connect in Filament and enable only the access needed for each discovered business object. Granted capabilities sync to the private control plane and can be used by website owner chat and automation runtimes through the same capability contract.

Public Chat Actions

A Website may expose a small, provider-owned action to its public chat without granting resource CRUD. Implement PublicChatCapabilityProvider for the versioned manifests and query/execute handlers. Optionally implement PublicChatActorResolver when an action requires the Website's logged-in user.

The package registers the interfaces, signs server-to-server requests, issues short-lived encrypted actor permits, validates manifests, and makes execution idempotent. Manifests declare separate model-facing proposal and canonical provider execution schemas; query and execute payloads are rejected at the signed boundary when they do not match. The Website still owns authorization and the domain transaction; the package contains no booking or customer-specific behavior. After deploying a changed manifest, synchronize the existing installation explicitly:

php artisan connect-filament:sync

Deployment fails unless App acknowledges the exact protocol, normalized manifest hash, and accepted action kinds. Public chat actions never fall back to discovered model writes or owner capabilities.

Resource Declaration Example

Discovery can be supplemented with explicit resources in config/connect-filament.php:

'resources' => [
    'research_posts' => [
        'label' => 'Research Posts',
        'model' => App\Models\ResearchPost::class,
        'identifier' => 'id',
        'fields' => [
            'title' => ['label' => 'Title', 'readable' => true, 'writable' => true],
            'status' => ['label' => 'Status', 'readable' => true, 'writable' => true],
            'published_at' => ['label' => 'Published at', 'readable' => true, 'writable' => false],
        ],
    ],
],

Only declared readable fields are returned. Only declared writable fields are accepted.

Publish risk and image fields

The same declaration is the component contract used by Website owner chat, Telegram, and other MCP clients. A component does not need a channel-specific handler. Declare its writable fields, operation risk, and any staged image field:

'resources' => [
    'news' => [
        'label' => 'News',
        'model' => App\Models\News::class,
        'identifier' => 'id',
        'operation_risks' => ['create' => 'publish', 'update' => 'publish'],
        'fields' => [
            'id' => ['readable' => true, 'writable' => false],
            'title' => ['readable' => true, 'writable' => true, 'required' => true, 'max' => 255],
            'content' => ['readable' => true, 'writable' => true, 'required' => true],
            'img_path' => [
                'readable' => true,
                'writable' => true,
                'required' => true,
                'type' => 'asset',
                'asset' => [
                    'disk' => 'public',
                    'directory' => 'news',
                    'mime_types' => ['image/jpeg', 'image/png', 'image/webp'],
                    'max_bytes' => 5 * 1024 * 1024,
                ],
            ],
        ],
    ],
],

An asset field is represented in the capability JSON Schema as a bounded tropikal-asset-ref. The caller first prepares an upload, sends the bytes to a short-lived one-time Website capability, and then supplies the opaque ref in a normal create or update action. The Website validates, decodes, re-encodes, scopes, and atomically consumes the staged image; binary data never enters an MCP JSON-RPC request.

For an owner-chat-enabled installation, require idempotency at the actual mutation boundary:

CONNECT_FILAMENT_REQUIRE_MUTATION_IDEMPOTENCY=true
CONNECT_FILAMENT_ASSET_TTL_SECONDS=900

Every create, update, delete, and named action then requires X-Tropikal-Idempotency-Key. Exact retries return the durable receipt and never repeat the mutation; reuse for a different request returns 409.

Security Model

  • Setup routes require authenticated Filament/admin access.
  • The OAuth callback validates state, PKCE, expiry, host, and exact redirect URI.
  • OAuth, redirect, site, and control-plane URLs must use HTTPS outside localhost development.
  • Refresh credentials, PKCE verifiers, and server signing credentials use Laravel encrypted casts.
  • Server-to-server calls use short-lived signed requests from tropikal-ai/connect.
  • Signatures cover method, path, normalized query string, timestamp, nonce, installation id, and body hash.
  • Nonces are claimed atomically through the Laravel cache.
  • Resource reads project declared fields only.
  • Resource lists support pagination, text search, and exact filters over safe readable scalar fields only.
  • Resource writes reject unknown or unsafe fields and return structured 400/422 responses for expected input problems.
  • Mutations can require durable Website-side idempotency receipts, so a lost response cannot repeat an already committed operation.
  • Staged image refs are installation/resource/field scoped, expiry bounded, one-time consumable, and decoded plus re-encoded before storage.
  • Delete grants are explicit destructive capabilities and require confirmation.
  • Eloquent discovery excludes secret-shaped fields before grants can be enabled.
  • Write grants do not expose delete.
  • Public browser payloads are checked recursively for secret-shaped keys.
  • No secrets are returned to browsers.

See docs/security/threat-model.md for the release-candidate threat model.

Troubleshooting

  • 403 on /tropikal-connect/oauth/connect: the current user is not authenticated for the configured setup middleware.
  • Callback validation failed: the authorization server callback URL must exactly match APP_URL plus the configured callback path, unless CONNECT_FILAMENT_OAUTH_REDIRECT_BASE_URL is set.
  • Refresh status asks for a reconnect: the stored OAuth refresh credential expired or was revoked. Use the Reconnect action, complete authorization, and refresh again.
  • Signed API 401: the signature, query string, body hash, timestamp, nonce, or installation id did not match.
  • A discovered model is missing: check included_model_namespaces, excluded_model_classes, and the model's table connection.
  • A field is missing from a business object: secret-shaped, hidden, guarded, relation, and unsafe fields are excluded by default.
  • A write returns 422: the input did not match the explicit writable field schema.

Private Server Boundary

Server and control-plane internals are intentionally absent from this package. Public examples use example.com endpoints only.

Typed owner resource actions

An explicitly configured action may declare handler (a container-resolved class implementing Contracts\OwnerResourceAction) and a closed input_schema. Its handler receives validated arguments and returns the affected models of that resource, at most200. Use this for domain operations such as reordering a selected set; the handler still owns row locks, complete expected-revision checks and its business rules. It must return every changed record for the site's audit.

The supported argument shapes are bounded scalar fields, lists with item schemas (up to200 items), closed nested objects, and bounded maps with a property-name pattern and value schema. Validation is strict about JSON scalar types, unknown keys, bounds and duplicate list entries when uniqueItems is declared. Inputs are limited to64KiB and nesting depth5. This reuses pure schema validation; public-chat actor authorization is not involved.

Only installations granted action:<name> discover and execute the typed action. The existing Connect dashboard has an Owner actions checkbox for each declared typed handler. Each checkbox grants or revokes only that action; it does not enable generic create, update, or delete. Read/field changes preserve the action choices, and stopping resource sharing revokes them. Legacy model method actions are not offered by these controls. The catalog exposes its argument schema and always requires owner confirmation; server handler names never leave the site. Execution uses the signed installation route /resources/{resource}/actions/{action}, requires an idempotency key and runs inside the existing durable mutation transaction. Responses and receipt replay apply current per-field grants to every returned record. Existing zero-argument record actions retain their original route and do not fall through to this endpoint.

Handler validation failures use the controller's authored422 projection; uniqueness conflicts use its409 projection; unexpected failures use a generic500 projection even when debug is enabled. Exceptions unwind the durable transaction, so failed handlers do not retain database changes, audit rows or receipts.

Private photographic source staging

A field can opt into an application-owned OwnerSourceValidator and a bounded asset.owner_actions list. Preparation then requires one of those exact declared, trusted action grants; generic create/update grants do not substitute. Upload rechecks connected installation/resource/field/action authorization, its bearer capability, expiry, declared size and SHA. The trusted validator admits a private local staged file without transforming it. Source bytes remain identical, including photographic ICC/metadata, until the application-owned image lifecycle prepares public derivatives. Private visibility, regular contained paths, full synchronized staging writes, SHA verification and atomic rename are required. Validator failures are redacted and unfinished temporary files are removed. A prepared row owns a stable private source filename under its asset reference, guarded by a native nonblocking lock. If the final database save fails, one verified original is conservatively retained; retry reuses those identical bytes instead of proliferating untracked UUID originals. Installation retention/reconciliation must include these prepared and committed rows (and expiry/grace), not delete bytes inline after a persistence failure. Private visibility is an adapter requirement; the installation must also positively bind and verify its physical source root outside its public server/static aliases. Existing fields without this explicit contract retain the default image sanitizer.

This boundary does not expand the existing upload byte budget (5MiB by default), make originals public, enable generic resource writes or replace application image admission, generation, retention and publication policy. Larger CMS uploads use the site's independently bounded owner workflow. Consume the staged reference through StagedAssetManager::resolveForMutation inside the durable owner-action transaction; its installation/resource/field/status/expiry checks still apply.