Foundation Moodle local plugin for FastPix video integration. Owns the
gateway, asset cache, webhook ingestion, scheduled cleanup, and JWT signing
that the surface plugins (mod_fastpix, filter_fastpix, tinymce_fastpix)
consume.
BETA — production-grade. Release 1.0.0-dev,
$plugin->maturity = MATURITY_BETA. The codebase clears every gate
(PHPUnit 221/221, coverage gate ALL GREEN at architecture targets,
seven non-negotiables grep-clean). Tag v1.0.0 (MATURITY_STABLE) is
pending operational verification on a production FastPix tenant —
see STATUS.md for the full breakdown and the verification
queue.
The previous 2026-05-04 senior review's Tier 0/1/2/3 findings and the 2026-05-11 adversarial review's BLOCKERs, MAJORs, MINORs and NITs are all closed. See CHANGELOG.md for the per-release breakdown.
local_fastpix is the trusted boundary between Moodle and FastPix.io
(video CDN). It is the foundation plugin in a planned 4-plugin
integration:
| Plugin | Role |
|---|---|
local_fastpix |
Gateway, asset metadata, webhook ledger, JWT signing, cron |
mod_fastpix |
Activity module — student-facing video player |
filter_fastpix |
Filter — embeds FastPix video by playback ID |
tinymce_fastpix |
TinyMCE plugin — upload widget in the editor |
This plugin has no UI of its own beyond an admin settings page. It is
consumed via PHP namespaces and a small web-services surface
(local_fastpix_create_upload_session, local_fastpix_create_url_pull_session,
local_fastpix_get_upload_status).
The architecture, wire formats, capability model, and all 30 Definition-of-Done items live in .claude/docs/01-local-fastpix.md.
- Moodle 4.5 LTS or later
- PHP 8.1 or later
- A FastPix account with API credentials and a webhook signing secret
- A MUC backend that survives across PHP-FPM workers (Redis, Memcached, or the file store on a single-FPM dev install). The circuit breaker and rate-limiter rely on shared cache state.
Per Moodle Plugins Directory rules, no runtime Composer dependencies. The plugin vendors a single library:
firebase/php-jwtv6.10.0 atclasses/vendor/php-jwt/(six PHP files only, license MIT, SHA256 attestation inclasses/vendor/php-jwt/VENDOR.md).
-
Drop the plugin tree into your Moodle install at
local/fastpix/. From the Moodle root:cp -r path/to/local_fastpix local/fastpix -
Run the upgrade. From the Moodle root:
php admin/cli/upgrade.php --non-interactiveThe install hook at
db/install.phpruns once and seeds:webhook_secret_current— 64 hex chars fromrandom_bytes(32)user_hash_salt— 32 chars fromrandom_string(32), used to HMAC userids before sending to FastPix metadata- default feature flags (DRM off)
The local RS256 signing key for playback JWTs is not minted at install. It is bootstrapped lazily on first use by
credential_service::ensure_signing_key()once API credentials are saved. -
The install command prints the webhook URL admins must paste into the FastPix dashboard. It looks like:
https://your.moodle.example/local/fastpix/webhook.php
After install, log in as a site administrator and navigate to
Site administration → Server → FastPix. The page is gated by the
local/fastpix:configurecredentials capability, granted to managers by
default.
You will need a FastPix API key and API secret. Generate them in the FastPix dashboard under Settings → API Keys.
- API Key — plain text input.
- API Secret — masked input (
admin_setting_configpasswordunmask).
Storage disclosure. Both credentials are stored as plaintext rows in
Moodle's mdl_config_plugins table. The settings UI masks the secret
field, but the database does not encrypt it at rest. Operators should:
- treat database backups containing this table as sensitive material
- restrict read access to the Moodle DB user and DBA roles only
- avoid copying production credentials into dev or staging dumps without scrubbing
This is the standard Moodle pattern for configpasswordunmask settings;
the plugin does not introduce new exposure, but the disclosure matters
because a webhook signing key compromise lets an attacker forge events.
DRM is disabled by default. To enable:
- Tick Enable DRM on the settings page.
- Paste your FastPix DRM Configuration ID into DRM Configuration ID.
The plugin enforces a double gate at upload time:
feature_flag_service::drm_enabled() returns true only when both the
checkbox is on AND the configuration ID is non-empty. Either alone fails
closed — a DRM upload requested under a half-configured DRM setup raises
drm_not_configured and never hits the gateway.
The settings page displays a read-only webhook URL. Copy it into the FastPix dashboard's webhook configuration (next section).
In the FastPix dashboard:
-
Navigate to Settings → Webhooks (path may vary; consult FastPix documentation for the current UI).
-
Add a new webhook destination pointing at the URL displayed in Moodle's settings page (e.g.
https://your.moodle.example/local/fastpix/webhook.php). -
Set the signing secret to the value Moodle generated at install. Read it once with:
php -r 'define("CLI_SCRIPT", true); require "config.php"; echo get_config("local_fastpix", "webhook_secret_current"), "\n";'Run this from the Moodle root, as a user with read access to your Moodle config and DB.
-
Subscribe at minimum to:
video.media.created,video.media.ready,video.media.updated,video.media.failed,video.media.deleted.
The webhook signing secret is bootstrapped at install (64 hex chars from
random_bytes(32)) and is not editable through the admin UI. This is
intentional — exposing it in the UI invites typos that break verification
without an obvious failure mode, and rotation requires synchronizing with
the FastPix dashboard anyway.
To rotate, operators should:
- Generate a fresh 32-byte secret on the host:
openssl rand -hex 32. - Update both sides in close succession, either via a direct DB update
on
mdl_config_plugins(keywebhook_secret_current, pluginlocal_fastpix) or a one-shot CLI script that callsset_config('webhook_secret_current', $new, 'local_fastpix'). - Optionally populate
webhook_secret_previousandwebhook_secret_rotated_at(Unix seconds) to keep the previous secret live during the 30-minute rotation window the verifier honors. - Update the FastPix dashboard with the new value.
The verifier rejects any configured secret shorter than 32 bytes and
emits a webhook.secret_too_short log line on rejection — operators
see signature verification fail loudly rather than degrade to a weaker
check.
The plugin ships a small public health endpoint at:
https://your.moodle.example/local/fastpix/health.php
No authentication required. Per-IP rate-limited at 30 req/min so the endpoint can't be used as a probing oracle. Returns JSON:
{
"status": "ok" | "degraded" | "rate_limited" | "error",
"fastpix_reachable": true | false | null,
"latency_ms": <int>,
"timestamp": <unix-seconds>
}
HTTP status codes:
| Code | Meaning |
|---|---|
| 200 | FastPix probe succeeded |
| 503 | FastPix probe failed, or an unexpected upstream error |
| 429 | Per-IP rate limit hit; back off and retry |
The endpoint never returns 500. Any exception in the probe path is
caught and surfaced as a 503 with status: "error" so operators can
distinguish a hard upstream failure from a slow probe.
Suggested ops use:
- Wire it into your monitoring system (e.g. as a Pingdom / UptimeRobot / Prometheus blackbox check).
- Alert on either non-2xx for >2 consecutive checks, OR
fastpix_reachable=falsefor >5 minutes. - The
latency_msfield is the FastPix probe round-trip; useful as a proxy signal for FastPix-side degradation.
The 3-layer rule, the seven non-negotiables, the FastPix wire contract, the privacy provider, and the 30-checkbox Definition of Done live in the architecture document:
The plugin's agent-routing table, file-by-file review rules, and PR auto-reject list live under .claude/.
This is alpha. Outstanding items are tracked in STATUS.md under "What does not work / is not yet verified", broken into Tier 2 (documentation and ergonomics), Tier 3 (verification and CI), and Tier 4 (refactor and polish). Highlights as of the current commit:
- DNS-rebinding TOCTOU window between SSRF check and gateway fetch
(deferred from Tier 1; needs
CURLOPT_RESOLVEpinning through\core\http_client) - Moodle plugin checker not yet run
- End-to-end webhook delivery against real FastPix not yet exercised on this branch
Refer to STATUS.md for the authoritative list. This README deliberately does not duplicate it.
Run the test suite from the Moodle root:
vendor/bin/phpunit --testsuite=local_fastpix_testsuite
The current baseline is 132 tests / 200,332 assertions. The suite includes a 100K-key empirical collision test for cache-key hashing (SHA-256/32) that takes a few seconds; everything else runs fast.
GNU General Public License v3.0 or later, matching Moodle core. See
LICENSE in your Moodle install or
https://www.gnu.org/licenses/gpl-3.0.html.