Skip to content
Wilkes-LibertyPublic

About

Gate access to private files with pluggable methods (short-lived signed URLs, authenticated access) and deliver them to any decoupled front end.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

292 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

File Gate

Gate access to private files with pluggable methods (short-lived signed URLs, authenticated access, …) and deliver them to any decoupled front end.

The problem it solves

Out of the box, Drupal grants a private-file download to anyone who can view the entity that references it. Because anonymous users can view published content, a private file attached to published media is effectively public to anyone who has the link — private:// only means "not guessable", not "gated".

File Gate closes that gap. It denies the normal /system/files path for any private file a gated field references, and delivers those files instead through its own endpoint, only after a pluggable gate method approves the request.

How it works

                    (1) editor uploads a file to a gated, private:// field
                         │
 visitor ──(2)──▶ your front end ──(3, server-to-server)──▶  POST /api/file-gate/mint
   ▲   passes the front end's gate         (file/media UUID + shared secret)
   │   (lead form, login, purchase…)                        │
   │                                                        ▼
   └─────(5) browser redeems ◀──(4)── { path, expires, ttl } (short-lived signed URL)
             GET /api/file-gate/download?f=…&exp=…&sig=…
                         │
                         ▼
             File Gate verifies the grant and streams the bytes.
             Meanwhile GET /system/files/… stays denied for the public.
  1. A private file/image field is marked gated (see Configuration).
  2. A visitor passes your gate — a lead form, a login, a purchase — entirely in your front end. File Gate does not care how you gate; it only mints and verifies.
  3. Your back end calls the mint endpoint (server-to-server) with the file or media UUID and the shared secret.
  4. File Gate returns a relative, short-lived, signed path.
  5. The visitor's browser redeems it at the download endpoint. File Gate verifies the signature (and any usage limit) and streams the file. The raw /system/files path is denied to the public throughout.

Features

  • Pluggable gate methods. A clean plugin type (GateMethod) decides how a request proves it passed the gate. Ships with Signed URL (HMAC), Authenticated access, Token (revocable per-grant links and pre-shared campaign tokens), Referrer lock (a signed URL restricted to an allowed origin), and One-time passcode (email-verified); optional submodules add File Gate Form (a coupled email / lead-capture form Drupal renders), File Gate Commerce (gate behind a Drupal Commerce purchase or a pluggable entitlement), and File Gate Assurance (hardware-backed, phishing-resistant PIV/CAC + FIDO2/WebAuthn gating over any OIDC IdP, with opt-in DPoP). Add your own in a few lines.
  • Front-end agnostic / headless-first. Mint over a server-to-server API; redeem in the browser. Nothing about React, Next.js, Vue, or a coupled Twig theme is assumed. Works for decoupled, coupled, and hybrid sites.
  • Per-field configuration. Mark any file/image field gated where you choose its storage; enabling gating forces and locks the private file system so a gated field can never silently store public, world-readable files.
  • Media-optional. Gate a Media source field, a plain file field, or any other private file field. The only hard dependency is core File.
  • Expiry & usage controls. Per-field TTL, an absolute availability window (available_until), and usage limits (max_uses, including one-time links). All are bound into the signature and cannot be altered by the client.
  • Fails closed. With no signing secret configured, minting returns 503 and every gated file is denied.
  • Constant-time verification. HMAC-SHA256 grants compared with hash_equals(); the secret lives in the environment, never in configuration.
  • Editors keep working. A Bypass file gate permission lets trusted staff download gated files normally through the admin UI.
  • Auditing & analytics. A dedicated logger channel records security events (denied downloads, failed mint auth, fail-closed refusals) and usage events (mints and deliveries). Optional soft integration with audit_chain for a hash-chained durable trail (docs/AUDIT.md). Audit rows identify files as file:<id> — never the original filename. Optional audit_require_keyed (off by default) refuses mint and download with HTTP 503 when a signed audit append fails.
  • Admin dashboard. Secret status, defaults, gate methods, gated fields, plus mint/delivery/denial tables from the file_gate dblog channel when Database Logging is enabled.
  • Secret rotation with dual keys, multi-field mint pin, forced identity mint, mint-time OIDC (A2), self-only WebAuthn enrollment UI — see CHANGELOG.md and docs/SECRET_ROTATION.md, docs/E2E-ASSURANCE.md.
  • IdP-first (Keycloak) architecture: docs/KEYCLOAK-UNIFIED-AUTH.md. Roadmap and scheduling are tracked internally rather than in this repository.
  • Optional identity-aware mint. Pass account (user UUID) or uid on mint so a grant cannot exceed that user's download rights, view rights on every referencing host (including a user host), view rights on the referencing field, and view rights on nested parents (getParentEntity(), e.g. paragraphs). A file with no resolvable host is refused.

Requirements

  • Base module: Drupal 10.6 / PHP 8.2, Drupal 11.3+ / PHP 8.3+, or Drupal 12. Drupal 11.4 uses core's FileReferenceResolver; older core uses a revision-aware backport with the same host and field access checks. The backport recomputes references because older core cannot invalidate the native resolver's cache tags.
  • Optional submodules retain their separately declared Drupal 11.4 floor. The supported-core workflow exercises the base module; the existing full suite covers optional integrations.
  • A configured private file system (file_private_path).
  • A signing secret, injected from the environment (below).

Installation

composer require drupal/file_gate
drush en file_gate

Configuration

1. Provide the signing secret (required)

Secrets are never stored in exported configuration. While none are set the module fails closed.

Legacy (single secret, whole gated corpus) — fine for one trusted mint backend:

$config['file_gate.settings']['download_secret'] = getenv('DRUPAL_FILE_GATE_SECRET');

Named secrets (field-scoped) — use when different front ends must not mint each other's files. Values in $settings (not $config); scopes in config (admin form or export):

// settings.php — values never export.
$settings['file_gate.secrets'] = [
  's_public' => getenv('FILE_GATE_SECRET_PUBLIC'),
  's_nda' => getenv('FILE_GATE_SECRET_NDA'),
];
// Scopes (exportable) — or set under Admin → File Gate "Named secret scopes":
//   s_public: node.field_whitepaper
//   s_nda: node.field_nda_pdf
$config['file_gate.settings']['secret_scopes'] = [
  's_public' => ['node.field_whitepaper'],
  's_nda' => ['node.field_nda_pdf'],
];

Mint with Basic auth username = secret id, password = value. Minted URLs include k=<id> so redemption uses the same key. Scope is checked at mint and again at download against the signed fld claim, or the field stored on the grant when fld is absent (narrowing a secret revokes outstanding grants). An unsigned body field is not a redeem pin. A named secret with a value but no scope can mint nothing and is reported on the status report. Keep secrets off the public network; only signatures reach the browser.

2. Gate a field

Edit any private file or image field. In its settings you will find "Gate access to these files". Enabling it:

  • forces the field's storage to the private file system and locks that control; and
  • lets you pick a gate method (default: Signed URL).

Gating is stored as third-party settings on the field storage, so it travels with your exported configuration.

Per-method options (for Signed URL, under the field's method_settings):

Setting Meaning
ttl Signed-URL lifetime in seconds (defaults to the global TTL).
available_until Absolute Unix timestamp; caps every grant's expiry (a "download available until X" window).
max_uses Maximum redemptions per minted URL (1 = one-time link).

Token method

The Token method (token) adds the one thing a bare signature cannot do: revoke a single live link without rotating the site secret (which would break every other link). It offers two shapes, chosen at redemption by whether the URL carries a signature:

  • Minted, revocable (per grant). mint self-issues a random token, records its SHA-256 hash, and binds that hash into the signed URL. Revoke a link by deleting its stored hash — via the revoke endpoint or by removing the key from the file_gate_tokens expirable key/value store. Because mint receives no caller input, a token is per-grant / independently revocable — attach recipient meaning (token → person) in your own back end.
  • Pre-shared campaign. Configure an allowlist of token hashes and hand out static ?token=<plaintext> links (no mint). Revoke by removing the hash from configuration. These links are static — no per-request expiry.

Per-field method_settings:

Setting Meaning
ttl, available_until, max_uses Same as Signed URL, applied to minted tokens.
tokens Array of SHA-256 hashes of pre-shared tokens (e.g. hash('sha256', $token)). Store hashes only — never the plaintext token, which travels only in the recipient's link. Leave empty to disable pre-shared mode.

Tokens are stored and configured only as hashes, so a store or config dump yields no usable credentials. Like Signed URL's usage counter, the revocation store is a fast key/value store, not a lock: a redemption whose use-increment races a manual revocation could admit one extra request. Adequate for lead-gen and distribution; not a hard licensing lock.

Referrer lock method

The Referrer lock method (referrer_lock) is a Signed URL that is additionally only redeemable from an allowed origin. It mints and validates the same signed grant (and inherits ttl, available_until, and max_uses), and at redemption also checks the request's Origin header — falling back to the origin of the Referer — against a per-field allowlist. The origin is checked first, so a request from a disallowed origin is denied before a usage-limited grant would spend one of its uses.

Hardening, not authorization. The Origin / Referer header is trivially spoofable by any non-browser client and is often stripped by privacy setups, so this is not an access boundary — the signature is. Use it to discourage a leaked link from working when embedded on another site, not to protect anything the signature alone should not already protect.

Per-field method_settings (in addition to all of Signed URL's):

Setting Meaning
allowed_origins List of allowed origins, e.g. https://app.example.com. Matched as scheme + host + (non-default) port. An empty list denies every request (fail closed) — configure at least one.
on_missing_referrer What to do when no parseable Origin/Referer is available: deny (default) or allow (tolerate privacy setups that strip the header, leaning on the signature alone).

Assurance method (PIV/CAC + FIDO2/WebAuthn)

The optional File Gate Assurance submodule (file_gate_assurance) adds the assurance method: a signed URL whose delivery also requires a hardware-backed, phishing-resistant NIST SP 800-63 assurance level proven at any OIDC IdP — PIV/CAC (HSPD-12 / FIPS 201) or FIDO2/WebAuthn — with opt-in DPoP (RFC 9449) sender-constraining. A field trusts a list of issuers (trusted_issuers), each with its own audience and accepted acr values; a token is matched to exactly one entry by iss, with no cross-matching and no fallback, and the legacy single-issuer settings behave as a one-entry list. It is federation (an asserted level), not File Gate acting as an AAL3 verifier. See modules/file_gate_assurance/README.md and the design note in docs/design/piv-cac-webauthn.md.

One-time passcode method

The One-time passcode method (otp) proves control of an email address: a trusted back end requests a code bound to (file, email) at POST /api/file-gate/otp (shared-secret auth, like mint), File Gate emails it, and the visitor exchanges the code at POST /api/file-gate/otp/session (JSON body: file, email, otp). That sets an HttpOnly FG_OTP cookie; the browser then downloads with GET /api/file-gate/download?f=… and no passcode in the URL. The code is single-use, TTL-limited, attempt-locked, and stored only as a hash. A step up from a bare token — verified in the moment — without an account.

Setting Meaning
ttl Code lifetime in seconds (default 600).
max_attempts Wrong-code tries before the code is locked out (default 5).
code_length Number of digits in the passcode (default 6).

Email is not a confidential channel. The passcode proves control of the address, not that the message is secret — anyone who can read the recipient's mail (or intercept it without transport encryption) can use the code within its window. Use otp to gate lead-gen / self-service documents, not to protect content a real secret should protect; keep the TTL short.

Form (email / lead capture) method

The optional File Gate Form submodule (file_gate_form) adds the form method: Drupal renders a lightweight email / lead-capture form at /file-gate/form/{file} and grants the download on submission (a per-session grant in the private tempstore, TTL-limited). Spam is bounded by a honeypot and a per-IP rate limit, and a LeadCapturedEvent lets the site persist submissions without File Gate storing PII. See modules/file_gate_form/README.md.

Commerce (purchase / entitlement) method

The optional File Gate Commerce submodule (file_gate_commerce) adds the commerce method: deliver only to a buyer / licensee, re-checked live on every download. The bundled checker grants on a completed Drupal Commerce order matching a configured SKU; override the file_gate_commerce.entitlement_checker service for licences or an external entitlement API. Access gating, not DRM. See modules/file_gate_commerce/README.md.

3. Global defaults

Visit Administration → Configuration → Media → File Gate (/admin/config/media/file-gate) to set the default TTL, content disposition (attachment/inline), and mint rate limiting, to check the secret status, and to see every gated field. Optional Refuse delivery if a signed audit write fails (audit_require_keyed, default off) makes mint and download call audit_chain logKeyed() and return HTTP 503 if that signed append fails. See docs/AUDIT.md.

API contract (for front ends)

Mint — POST /api/file-gate/mint

Server-to-server only. Authenticate with the shared secret as the HTTP Basic password (or the X-File-Gate-Secret header).

Request body (JSON):

{ "media": "<media-uuid>" }

or

{ "file": "<file-uuid>" }

Response 200:

{
  "path": "/api/file-gate/download?f=<file-uuid>&exp=1720800000&sig=<hmac>",
  "expires": 1720800000,
  "ttl": 120
}

path is root-relative and host-agnostic — prepend your public Drupal file origin. Errors: 503 (no secret configured, or audit_require_keyed is on and the signed audit write failed), 401 (bad/absent secret), 404 (unknown file/media), 409 (host media unpublished), 422 (file not gated / empty), 429 (rate limited), 400 (bad request).

Download — GET /api/file-gate/download?f=…&exp=…&sig=…

Redeemed by the visitor's browser. Returns the file stream (200) or 403 when the grant is missing, expired, tampered, or spent; 404 when the file is unknown or not gated; 503 when audit_require_keyed is on and the signed audit write failed (generic body, no file bytes).

For the Token method the browser also sends token (the plaintext token); a pre-shared campaign link sends only token (no exp/sig). For the OTP method the browser first POSTs email and otp in the JSON body to /api/file-gate/otp/session, then GETs the download with the FG_OTP cookie. The passcode is never a query parameter.

Revoke — POST /api/file-gate/revoke

Server-to-server, same shared-secret authentication as mint. Invalidates one minted grant without rotating the site secret. Pre-shared campaign tokens are revoked by removing their hash from the field's tokens configuration, not here.

Request body (JSON) — exactly one of:

{ "token": "<plaintext-token>" }
{ "jti": "<signed-url-jti>" }

token deletes a minted token-method hash (field-scoped when the stored field is known). jti spends a usage-limited signed_url grant and drops it from inventory; the secret must be allowed for the grant's stored field, and a named secret must match stored k.

A jti revoke is a kill mark: a redemption counter set to its maximum, stored with an expiry. The mark lasts until the grant's own expiry plus one hour, and never less than 30 days. An optional "ttl" (seconds) can lengthen it. A ttl shorter than the grant's remaining life is raised to it, because a mark that lapses first would make the revoked URL work again. Revoking the same grant again never shortens its mark. Bulk revoke (POST /api/file-gate/grants/revoke-bulk) follows the same rule.

Responses: 204 (revoked), 400 (no token/jti), 401 (bad/absent secret), 403 (credential not allowed for that grant's field / k), 404 (unknown or already-gone token or jti), 429 (rate limited), 503 (no secret configured).

OTP — POST /api/file-gate/otp

Server-to-server, same shared-secret authentication as mint. Issues a single-use passcode bound to a (file, email) pair and emails it to that address (File Gate sends the mail and stores only a hash of the code). The visitor then redeems by posting the code in the JSON body to /api/file-gate/otp/session and downloading with the FG_OTP cookie (GET /api/file-gate/download?f=…).

Request body (JSON):

{ "file": "<file-uuid>", "email": "person@example.com" }

(or "media": "<media-uuid>" in place of "file".)

Responses: 204 (code issued and emailed), 400 (invalid body or email), 401 (bad/absent secret), 404 (unknown file/media), 409 (host media unpublished), 422 (file not gated with the otp method), 429 (rate limited — per IP and per (file, email)), 503 (no secret configured).

Front-end integration sketch (any framework)

// Server-side (never expose the secret to the browser):
const res = await fetch(`${DRUPAL_INTERNAL_ORIGIN}/api/file-gate/mint`, {
  method: 'POST',
  headers: {
    'Authorization': 'Basic ' + Buffer.from(':' + process.env.DRUPAL_FILE_GATE_SECRET).toString('base64'),
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ media: mediaUuid }),
});
const { path } = await res.json();
// Hand the browser the public URL to redeem:
const downloadUrl = `${DRUPAL_PUBLIC_FILE_ORIGIN}${path}`;

The metadata the front end needs (that a gated document exists, its name, type, size) should be exposed via your API (JSON:API/GraphQL) without a directly resolvable file URL — the bytes are delivered only through the gated endpoint.

Optional MCP tools

file_gate_mcp exposes Tool API plugins so an operator or a governed agent can ask about the gate over MCP. It depends on Tool API and MCP Sentinel. The base module depends on neither. The submodule declares Drupal ^11.4 only. Tool API declares ^10.5 || ^11, so the submodule stays off Drupal 12.

Tool Operation Returns
file_gate_status read base-module runtime findings, whether a signing secret is configured, named secret ids and whether each has a field scope, every gated field with its method, scheme and a protected flag
file_gate_file_gate read For one file or media UUID: whether it is gated, by which field and method, and that the plain /system/files URL will not serve it
file_gate_grants_list read Live usage-limited grants for one gated field, newest first, at most 200
file_gate_metrics read Mints, deliveries, denials and authentication failures for 1 to 90 days
file_gate_grant_revoke write Revokes one grant by field and grant id

The lookup tool answers a question API clients hit often: an upload or an entity read returns a /system/files/… URL, and for a gated file that URL answers 403 for every account without bypass file gate. The tool says so, and says that a trusted front end has to mint a grant.

  • Grant use file gate mcp tools to the role your MCP credential uses. Revoke also needs revoke file gate grants via mcp. Both are restricted permissions.
  • MCP Sentinel's gates apply first: permission, source readiness, scope, IP policy and rate limit. A tool is listed only for an account that can run it.
  • No tool returns secret material, a file path, a URL, a grant token or a gate method's settings. Secrets appear by id only. A grant id identifies a grant for revocation; it cannot be redeemed.
  • The grant tools act with site-operator reach. The HTTP routes bind a named secret to the grants it minted; these tools list and revoke grants minted by any secret. Grant the permissions with that in mind.
  • Revoke is scoped by field: the field stored with the grant must match, so a grant id from another field is refused. It revokes one grant per call, keeps the kill mark for at least as long as the grant would have lived, and writes the same revoke audit entry as the HTTP route, with secret_id: mcp and the acting uid.
  • The grants list says whether a grant is bound to a subject. It does not return the stored subject hash, which is an unkeyed hash of a value the minting front end supplied.
  • A media UUID resolves only when the acting account may view that media. A file UUID resolves for any holder of the permission, because core denies view on every gated file entity; the answer reveals that the file exists.
  • A refusal from this module is one fixed message. Tool API and MCP Sentinel have their own messages for invalid input, denied access and rate limits. None of them relays an input value, and exception text is not logged.
  • Results are capped at 128 KiB, or at the profile's response-size cap when that is lower.
  • Installing the submodule publishes nothing by itself. Enable the tools in your site's MCP tool bridge configuration.

Not available as tools, by design: minting a download URL, serving bytes, issuing an OTP, bulk revoke, editing secrets or scopes, and switching gating on or off. A mint hands out a bearer URL and belongs to a trusted front end that knows who is asking.

Security model

  • Deny by default. The hook_file_download() implementation returns -1 (a hard veto that overrides core's permissive private-file access) for any gated file requested at /system/files, unless the account holds Bypass file gate. It never grants there — delivery happens only on the module's route.
  • Signed grants. sig = HMAC-SHA256(normalized_uri . "|" . canonical(claims), secret). Every claim (expiry, not-before, usage token/cap) is bound, so a client cannot change the file, the expiry, or the usage limit without invalidating the grant. Comparison is constant-time.
  • Fail closed. No secret ⇒ nothing validates and minting refuses (503).
  • No path disclosure. The signed URL carries the file UUID, not the private:// path; delivery streams BinaryFileResponse with Cache-Control: private, no-store.
  • Usage limits are approximate. The redemption counter is a fast key/value store, not a lock; a tight race could allow one extra redemption. Adequate for lead-gen and casual limits, not for hard licensing.

The trust boundary — read this before deploying

Mint authorizes nothing. It authenticates the caller with the shared secret and then mints whatever was asked for. It performs no entity access check and no field access check. The only content test anywhere in the path is that a media host must be published; minting by file UUID does not check even that.

So the secret's blast radius is the entire gated corpus. Anyone holding it can mint a working download URL for any gated file whose UUID they can guess or obtain, regardless of who they are or whether they could view the referencing entity.

That is a deliberate design — File Gate delegates the gate decision to a trusted back end that has already run its own (a lead form, a login, an entitlement check) — but it has two consequences worth stating plainly:

  • Never provision the secret into a public web tier. If a front-end container that serves anonymous traffic holds it, that tier holds a skeleton key to every gated file. Mint from a server-side route or a back-end service, not from code that ships to the browser or from an environment an attacker reaching the front end can read.
  • The secret is worth rotating like a credential, because it is one. It also doubles as the HMAC signing key, so rotating it invalidates outstanding grants — which is the point when you suspect exposure.

Mint trusts the secret holder. Identity-aware mint (account / uid, field require_identity_mint, or global require_acting_account) and field-scoped named secrets (secret_scopes) are opt-in constraints on that holder — they are not a default ACL. Without them, anyone who holds the secret can mint any gated file.

Gating requires the private file system

Gating only applies to files on the private file system. Public files are served straight off disk by the web server or a CDN and never reach Drupal, so there is no request to gate.

A field storage with file_gate.gated: true and a uri_scheme other than private claims to be gated and is not. The module refuses every write that would produce it:

  • The field edit form forces the private scheme when you enable gating.
  • An entity save (the entity API, a recipe, an update hook, a module's default configuration) throws GatedPublicSchemeException before anything is written. The message names the storage and says how to fix it.
  • A raw configuration write (drush config:set, a configuration tool that saves through the config factory) never loads the entity. Core has no event before such a write, so the module puts the previous value back and the command fails with the same exception.
  • A configuration import is rejected at validation, before any change.
  • The configuration schema carries the rule as the FileGateGatedFieldScheme constraint, so code that validates configuration gets a violation at third_party_settings.file_gate.gated.

All five use one rule, GatedFieldSchemeRule. A field type with no uri_scheme setting is not affected.

A site that is already in this state keeps working. The field loads, the edit form opens, and a save that leaves gated and uri_scheme as they were is allowed, so a core update, an unrelated configuration import or a module uninstall does not start failing. The status report shows the field as an error until it is fixed. Fix it by saving the field with the private scheme or by removing the gating. Moving it to another non-private scheme is refused.

Changing an existing field to the private scheme does not move files that are already stored publicly. They stay where they are, and stay readable, until they are re-uploaded or migrated.

Extending: writing a gate method

A gate method answers "has this request passed the gate for this file?"

namespace Drupal\my_module\Plugin\GateMethod;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\file\FileInterface;
use Drupal\file_gate\Attribute\GateMethod;
use Drupal\file_gate\GateMethodBase;
use Symfony\Component\HttpFoundation\Request;

#[GateMethod(
  id: 'my_method',
  label: new TranslatableMarkup('My method'),
  description: new TranslatableMarkup('…'),
)]
final class MyMethod extends GateMethodBase {

  public function grants(FileInterface $file, Request $request): bool {
    // Return TRUE only if the request positively proves it passed the gate.
  }

  public function mint(FileInterface $file): ?array {
    // Return query params to append to the download URL, or NULL if this
    // method decides access live (no pre-issued grant).
  }

}

Built-in methods

Five methods ship in the core module; three more ship in optional submodules (each noted below). All are complete — new methods are added by third parties or future submodules, not tracked as a roadmap here.

Method Status Notes
signed_url shipped HMAC signed URL; TTL, availability window, usage limits.
authenticated shipped Delivers to any logged-in Drupal user.
token shipped Revocable per-grant token and/or a pre-shared campaign allowlist.
form shipped Coupled email / lead-capture form (Drupal renders the gate). Ships in the File Gate Form submodule.
otp shipped One-time passcode emailed to a self-identified address; single-use, TTL-limited, attempt-locked.
referrer_lock shipped Signed URL that is only redeemable from an allowed origin/referrer (hardening, not authz).
assurance shipped Signed URL gated on a hardware-backed OIDC assurance (PIV/CAC + FIDO2/WebAuthn), with opt-in DPoP. Ships in the File Gate Assurance submodule.
commerce shipped Gate behind a purchase / entitlement (Drupal Commerce by default, or a pluggable external checker). Ships in the File Gate Commerce submodule.

Permissions

  • Administer File Gate — configure the module and view gated fields.
  • Bypass file gate — download gated files directly via /system/files (editors/operators). The administrator role holds it implicitly.

Logging

File Gate logs to its own file_gate channel: warning for security events (denied downloads, failed mint auth, fail-closed refusals) and info for usage events (mints, deliveries). Watch it via Reports → Recent log messages or your log aggregator.

Maintainers

License

GPL-2.0-or-later. See LICENSE.

About

Gate access to private files with pluggable methods (short-lived signed URLs, authenticated access) and deliver them to any decoupled front end.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages