Recommended releases

Download Released

Acuity Secure Link - A generic tokenised secure-link engine for Backdrop CMS: scoped, expiring, revocable access through a single URL.

Acuity Secure Link is a Backdrop CMS module that provides the plumbing for secure message delivery a "magic link" features: create a link, deliver the URL, and the recipient - no account required - gets exactly the access the link grants, until it expires, is revoked, or reaches its open limit. The module deliberately does nothing user-facing by itself: consumer modules register link types via a hook and provide the page shown when a valid token resolves. The companion acuity_secure_message module (secret/document delivery) is the first consumer; site-specific consumers such as document-portal share links plug into the same engine.

Beta Release Notes

As a beta release, the token engine, resolution endpoint, admin listing, and cron purge are fully functional and verified by CLI test harnesses plus live HTTPS checks (uniform 404s, flood lockout and recovery, security headers, burn-on-read cleanup). Before deploying to production, ensure your Backdrop CMS environment is running PHP 8.0+ and clear your system caches after installation to register the resolution route and admin menu items.

Features

  • Tokens are never stored: Each link is a 256-bit random token, base64url encoded (43 characters). Only its SHA-256 hash is stored - the working URL exists solely at creation time, so a database dump cannot be used to mint or reconstruct links.
  • Uniform 404 responses: Unknown, expired, revoked, spent, and open-limit-reached tokens are indistinguishable to the requester - no oracle reveals whether a link ever existed.
  • Flood control: The public s/{token} endpoint applies per-IP flood limits before any database lookup. Repeated bad tokens lock the requester out (verified: even valid links 404 during lockout). Thresholds are configurable.
  • Optional zero-knowledge payloads: A link's payload can be encrypted with a key derived from the token itself (HKDF). Because the token is not stored, the server cannot decrypt the payload at rest - only the holder of the URL can. Used by secure messages; access-grant consumers keep plain payloads the server can enforce.
  • Consumer plugin architecture: Register link types with hook_acuity_secure_link_type_info(); clean up per-link artifacts (files, records) with hook_acuity_secure_link_delete(), invoked on manual delete and cron purge alike.
  • Expiry, open limits, revocation: Arbitrary expiry timestamps (consumers offer their own presets), an optional maximum number of opens (link visits), and idempotent revocation with the audit row retained.
  • Opens are counted, not consumed: Each link visit (s/{token}) counts as one open; follow-on actions on the same link (revealing a secret, downloading an attachment) do not add to the count. Burn-on-read consumers can mark a link spent - it stops resolving but its audit row is kept, showing as Used, until an administrator deletes it.
  • Admin listing: Every link with its label, type, payload summary (encrypted payloads shown as encrypted), dates, open count, status, and Revoke / Delete operations at Admin → Configuration → Acuity Utils → Secure Links.
  • Automatic hygiene: Cron purges links that have been expired or revoked beyond a configurable grace period, invoking consumer cleanup for each.
  • Leak-resistant by default: Resolution responses send Referrer-Policy: no-referrer and X-Robots-Tag: noindex, nofollow, and are excluded from the anonymous page cache.
  • Full audit trail: watchdog entries for create, successful resolve, failed resolve, revoke, delete, and purge.
  • Backdrop Native: Built exclusively for Backdrop CMS using strict PHP 8.0+ standards and Backdrop APIs throughout.

Requirements

  • Backdrop CMS 1.x
  • PHP 8.0+ (While the code may technically function with PHP 7.4 at this time, we strictly require PHP 8.0+ and will not address issues related to older PHP versions.)
  • Companion module: Acuity Encrypt (provides the AES-256-GCM primitive for token-derived payload encryption)
  • A consumer module that registers at least one link type, e.g. Acuity Secure Message

Installation

Install this module using the official Backdrop CMS instructions at https://docs.backdropcms.org/documentation/extend-with-modules

Enable the module, then clear your system caches to register the s/{token} route.

Configuration

  1. Grant the create secure links permission to roles that may create links via consumer modules, and administer secure links to administrators. Neither is granted to any role by default.
  2. Review the link listing at Admin → Configuration → Acuity Utils → Secure Links - links can be revoked here at any time.
  3. Optionally adjust acuity_secure_link.settings config: flood_threshold (default 15 attempts), flood_window (default 3600 seconds), and purge_grace_days (default 30 - how long expired/revoked links remain visible for auditing before cron removes them).

Consumer API

// Register a link type:
function mymodule_acuity_secure_link_type_info(): array {
  return [
    'my_type' => [
      'label' => t('My link type'),
      'render callback' => 'mymodule_link_page',   // receives ($link, $payload)
      'file' => 'mymodule.pages.inc',
    ],
  ];
}

// Create → ['id' => int, 'url' => string]; the URL is shown ONCE.
$result = acuity_secure_link_create('my_type', ['nid' => 42], [
  'expires' => REQUEST_TIME + 86400,
  'max_views' => 0,                 // maximum opens (link visits); 0 = unlimited
  'label' => 'Site inspector',
  'encrypt_payload' => FALSE,       // TRUE = token-derived key, server-unreadable
]);

acuity_secure_link_resolve($token);        // validates + counts one open, or FALSE
acuity_secure_link_resolve($token, FALSE); // validates WITHOUT counting an open
acuity_secure_link_mark_spent($id);        // burn: stops resolving, audit row kept (shows "Used")
acuity_secure_link_revoke($id);            // audit row remains
acuity_secure_link_delete($id);            // permanent; fires the delete hook
acuity_secure_link_list('my_type');        // for consumer admin pages

Consumers can call acuity_secure_link_resolve() from their own access checks (for example a private-file download route that accepts either normal node access or a valid unexpired link).

Issues

Bugs and feature requests should be reported in the Issue Queue: https://github.com/albanycomputers/acuity_secure_link/issues

Current Maintainer(s)

Planned Features

The following are on the roadmap but not yet implemented:

  • Access-grant consumer verification suite: A small test consumer proving the grant pattern end-to-end (plain payload, consumer-defined expiry presets, resolve from a file-download access check).
  • Per-consumer flood budgets: Separate flood event names per link type, so abuse of one consumer's links cannot lock out another's.
  • Creation UI helpers: Shared form elements (expiry preset radios, label field) consumers can reuse instead of rebuilding.

Credits

License

This project is GPL v2 or later software. See the LICENSE.txt file in this directory for complete text.